src/shared/ptrs_list.c \
src/shared/hash.c \
src/shared/loglimiter.c \
+ src/logctl/logctl_functions.c \
src/logctl/logctl.c
bin_PROGRAMS += dlog_cleanup
src_tests_queued_entry_monotonic_pos_CFLAGS = $(check_CFLAGS) -DUSE_ANDROID_MONOTONIC
src_tests_queued_entry_monotonic_pos_LDFLAGS = $(AM_LDFLAGS)
-src_tests_logctl_SOURCES = src/tests/logctl.c src/logctl/logctl.c src/shared/logcommon.c src/shared/logconfig.c src/shared/parsers.c src/shared/buffer_traits.c
+src_tests_logctl_SOURCES = src/tests/logctl.c src/logctl/logctl_functions.c src/shared/logcommon.c src/shared/logconfig.c src/shared/parsers.c src/shared/buffer_traits.c
src_tests_logctl_CFLAGS = $(check_CFLAGS)
src_tests_logctl_LDFLAGS = $(AM_LDFLAGS) -Wl,--wrap=calloc,--wrap=asprintf,--wrap=open,--wrap=open64,--wrap=fdopen,--wrap=fdopen64,--wrap=mkstemp,--wrap=mkstemp64,--wrap=fchmod,--wrap=rename
-// C
-#include <assert.h>
-#include <ctype.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-// POSIX
-#include <getopt.h>
-#include <unistd.h>
-#include <sys/file.h>
-#include <sys/stat.h>
-
// DLog
#include <buffer_config.h>
#include <buffer_traits.h>
#include <dynamic_config.h>
#include <extra_sinks.h>
-#include <logconfig.h>
-#include <loglimiter.h>
-#include "log_compressed_storage.h"
-#include "fdi_pipe.h"
-#include "fdi_pipe_internal.h"
+#include "logctl_functions.h"
#include "logctl.h"
-#include "logpipe.h"
-
-int get_is_pipe(struct log_config *conf, bool *out)
-{
- const char *const backend = log_config_claim_backend(conf);
- if (!backend)
- return -ENOKEY;
-
- *out = strcmp(backend, "pipe") == 0;
- return 0;
-}
-
-bool parse_options(int argc, const char **argv, struct parsed_params *params, int *exit_value)
-{
- assert(argv);
- assert(params);
- assert(params->action == ACTION_NONE);
- assert(!params->value);
- assert(!params->tag);
- assert(exit_value);
-
- params->tag = "*";
- params->prio = '\0';
- params->pid = 0;
- params->plog_buffers_bitset = 0;
-
- bool use_global_action = true; // if neither tag, pid nor priority is specified
-
- /* skips a part of the help message */
- bool show_dynamic_conf_msg = true;
-
-#define SET_ACTION(x) do { \
- if (params->action != x && params->action != ACTION_NONE) \
- goto failure; \
- params->action = x; \
-} while (false)
-
- for (;;) {
-
- static const struct option long_options[] = {
- {"get" , no_argument, NULL, 'g'},
- {"set" , required_argument, NULL, 's'},
- {"tag" , required_argument, NULL, 't'},
- {"priority", required_argument, NULL, 'p'},
- {"help" , no_argument, NULL, 'h'},
- {"buffer" , required_argument, NULL, 'b'},
- {"clear" , no_argument, NULL, 'c'},
- {"enable" , no_argument, NULL, 0},
- {"disable" , no_argument, NULL, 1},
- {"pid" , required_argument, NULL, 2},
- {"enable-stdout", no_argument, NULL, 3},
- {"disable-stdout", no_argument, NULL, 4},
- {"compression-resize", required_argument, NULL, 5},
- {"compression-name", required_argument, NULL, 6},
- {0}
- };
-
- int long_index;
- int opt = getopt_long(argc, (char **) argv, "chb:gs:t:p:", long_options, &long_index);
- if (opt < 0)
- break;
-
- switch (opt) {
- case 0:
- SET_ACTION(ACTION_PLOG);
- params->plog_enable = true;
- break;
- case 1:
- SET_ACTION(ACTION_PLOG);
- params->plog_enable = false;
- break;
- case 2: {
- const int value_num = atoi(optarg);
- if (value_num <= 0)
- goto failure;
- params->pid = value_num;
- use_global_action = false;
- break;
- }
- case 3:
- SET_ACTION(ACTION_STDOUT);
- params->plog_enable = true;
- break;
- case 4:
- SET_ACTION(ACTION_STDOUT);
- params->plog_enable = false;
- break;
- case 5: {
- const int capacity = atoi(optarg);
- if (capacity <= 0)
- goto failure;
- params->capacity = capacity;
- SET_ACTION(ACTION_RESIZE);
- show_dynamic_conf_msg = false;
- break;
- } case 6: {
- params->storage_name = optarg;
- SET_ACTION(ACTION_RESIZE);
- show_dynamic_conf_msg = false;
- break;
- } case 'b': {
- const int buf_id = sink_id_by_name(optarg);
- if (buf_id == LOG_ID_INVALID)
- goto failure;
-
- params->plog_buffers_bitset |= 1 << buf_id;
- break;
- } case 'h':
- *exit_value = EXIT_SUCCESS;
- goto print_help;
- case 'g':
- SET_ACTION(ACTION_GET);
- break;
- case 'c':
- SET_ACTION(ACTION_CLEAR);
- params->value = NULL;
- break;
- case 's': {
- const int value_num = atoi(optarg);
- if (strcmp(optarg, "allow") && strcmp(optarg, "deny") && (value_num <= 0 || value_num >= __LOG_LIMITER_LIMIT_MAX))
- goto failure;
- params->value = optarg;
- SET_ACTION(ACTION_SET);
- break;
- } case 't': {
- params->tag = optarg;
- use_global_action = false;
- break;
- } case 'p':
- params->prio = toupper(*optarg);
- if (!strchr("DVIWEFS*", params->prio))
- goto failure;
- use_global_action = false;
- break;
-
- default:
- break;
- }
- }
-
-#undef SET_ACTION
-
- if (params->pid != 0 && (params->prio != '\0' || strcmp(params->tag, "*") != 0))
- goto failure;
-
- if (params->plog_buffers_bitset != 0 && params->action != ACTION_PLOG && params->action != ACTION_STDOUT)
- goto failure;
-
- if (params->action == ACTION_NONE) {
- *exit_value = EXIT_SUCCESS;
- goto print_help;
- }
-
- switch (params->action) {
- case ACTION_GET:
- if (use_global_action)
- params->action = ACTION_DUMP;
- break;
- case ACTION_CLEAR:
- if (params->pid != 0)
- params->action = ACTION_SET_PID;
- else if (!use_global_action)
- params->action = ACTION_SET;
- break;
- case ACTION_SET:
- if (params->pid != 0)
- params->action = ACTION_SET_PID;
- break;
- default:
- break;
- }
-
- if (params->action == ACTION_SET && params->prio == '\0')
- params->prio = '*';
-
- return true;
-
-failure:
- *exit_value = EXIT_FAILURE;
-
-print_help:
- printf("Usage: %s [-t/--tag tag] [-p/--priority priority] [--pid pid] (-g/--get | -s/--set value)\n"
- "\ttag: defaults to *\n"
- "\tpriority: F[atal], E[rror], W[arning], I[nfo], V[erbose], D[ebug] S[ilent] * (all, default)\n"
- "\tpid: integer larger than 0, mutually exclusive with --tag and --priority\n"
- "\t-g/--get: get current limit for given tag/priority; if neither given, dump the whole filterset\n"
- "\t-s/--set value: set a new limit to value (\"allow\", \"deny\", or number (0; %d) - allowed logs per minute)\n"
- "\t-c/--clear: clear the limit for given tag/priority; if neither given, clear the whole filterset\n"
- "Note: static filters are shadowed by dynamic ones but cannot be overridden at runtime\n\n"
- " %s [-b/--buffer bufname] [-b ...] (--enable | --disable)\n"
- "\tbufname: main system radio apps (default: all of them)\n"
- "\t-b/--buffer bufname: pick given buffer for plog control\n"
- "\t--enable/--disable: control platform logging for chosen buffers\n"
- "\t--enable-stdout/--disable-stdout: control stdout redirection for chosen buffers\n"
- "\t--compression-name: resizes given named compression buffer. Must be used with --compression-resize\n"
- "\t--compression-resize: sets compression buffer to given size, in bytes.\n"
- "\n"
- , argv[0]
- , __LOG_LIMITER_LIMIT_MAX
- , argv[0]
- );
-
- if (show_dynamic_conf_msg) {
- printf("NOTE: to use dlogctl, dynamic dlog config has to be enabled!\n"
- "Define the %s config entry to a *DIRECTORY* path.\n"
- "ALSO: filters won't work for the APPS buffer (whence most spam comes)\n"
- "unless you set `limiter_apply_to_all_buffers` in the config to 1!\n"
- "The filters are also currently per process; this may change in the future.\n\n"
- , DYNAMIC_CONFIG_CONF_KEY
- );
- }
-
- return false;
-}
-
-/**
- * @brief Copy file, except modify some lines
- * @details Copies a file linewise to another, except ensures lines starting with given prefixes have certain follow-up
- * @param[in] in Copy from this file
- * @param[in] out Copy into this file
- * @param[in] kv A set of key-value pairs, where lines which do not match any of the prefixes are copied verbatim, while
- * lines that do are ignored. For each key where the value is not NULL, a line with the key as the prefix
- * and the value as the suffix is then appended to the resulting file.
- *
- * @notes Usually the keys will contain a '=' at the end so as to only replace exact matches in a config file,
- * and keys without '=' at the end should usually have the value NULL to keep the config format valid
- *
- * @example Consider a file with the contents:
- * FOO=BAR
- * FOOOO=BAZ
- * ABC=XYZ
- * ABCABC=QQQ
- * ABC=ZYX
- *
- * And a set of kv pairs:
- * {"FOO", NULL}
- * {"ABC=", "YYY"}
- * {"QWER=", "ASDF"}
- *
- * Thus:
- * FOO matches both FOO=BAR and FOOOO=BAZ. Since the value is NULL, both these lines are removed.
- * ABC= matches both ABC=XYZ and ABC=ZYX, these lines are not copied. Instead, ABC=YYY is added
- * behind any lines that were not matched.
- * ABCABC is not matched by any kv pair, so it is copied verbatim.
- * The QWER= prefix is not matched by any line, so QWER=ASDF is appended to the end.
- *
- * The resulting file will therefore look like:
- * ABCABC=QQQ
- * ABC=YYY
- * QWER=ASDF
- *
- */
-int copy_except(FILE *in, FILE *out, const struct keys_values *kv)
-{
- __attribute__((cleanup(free_ptr))) size_t *const key_len = calloc(kv->n, sizeof *key_len);
- if (!key_len)
- return -ENOMEM;
-
- for (size_t i = 0; i < kv->n; ++i)
- key_len[i] = strlen(kv->keys[i]);
-
- char line[MAX_CONF_KEY_LEN + MAX_CONF_VAL_LEN + 2]; // +2 for delimiter and terminator: "K=V\0"
- while (fgets(line, sizeof line, in)) {
- size_t i = 0;
- while (i < kv->n && strncmp(line, kv->keys[i], key_len[i]))
- ++i;
- if (i == kv->n)
- fputs(line, out);
- }
-
- for (size_t i = 0; i < kv->n; ++i)
- if (kv->values[i])
- fprintf(out, "%s%s\n", kv->keys[i], kv->values[i]);
-
- return 0;
-}
-
-int copy_file_with_exceptions(const char *config_path, const struct keys_values *kv)
-{
- const int configfd = open(config_path, O_RDONLY | O_CREAT, 0644);
- if (configfd < 0) {
- ERR("configfd open failed: %m");
- return EXIT_FAILURE;
- }
-
- int r = flock(configfd, LOCK_EX);
- if (r < 0) {
- ERR("flock failed: %m");
- close(configfd);
- return EXIT_FAILURE;
- }
-
- __attribute__((cleanup(close_FILE))) FILE *const configfile = fdopen(configfd, "r");
- if (!configfile) {
- ERR("configfile fdopen failed: %m");
- close(configfd);
- return EXIT_FAILURE;
- }
-
- __attribute__((cleanup(free_ptr))) char *tempname = NULL;
- r = asprintf(&tempname, "%sXXXXXX", config_path);
- if (r < 0) {
- tempname = NULL;
- ERR("asprintf tempname failed: %m\n");
- return EXIT_FAILURE;
- }
-
- /* In older libc versions, umask(077) needed to be called
- * before mkstemp() to prevent a potential race condition
- * vulnerability. POSIX-2008 requires mkstemp() to create
- * the file with rights 600, but our static analysis tools
- * still complain about this and in this case it was safe
- * to just do this and silence them. In the general case,
- * doing this is unsafe as umask() is not thread-aware but
- * logctl is single-threaded anyway. */
- mode_t old = umask(077);
- const int tempfd = mkstemp(tempname);
- umask(old);
- if (tempfd < 0) {
- ERR("mkstemp failed %m\n");
- return EXIT_FAILURE;
- }
-
- __attribute__((cleanup(close_FILE))) FILE *const tempfile = fdopen(tempfd, "w");
- if (!tempfile) {
- ERR("tmpfile fdopen failed: %s %m\n", tempname);
- goto remove_temp;
- }
-
- if (fchmod(tempfd, 0644) < 0) {
- ERR("fchmod failed on %s %m\n", tempname);
- goto remove_temp;
- }
-
- if (copy_except(configfile, tempfile, kv) < 0) {
- ERR("copy_except failed\n");
- goto remove_temp;
- }
-
- if (rename(tempname, config_path) < 0) {
- ERR("rename failed: %m\n");
- goto remove_temp;
- }
-
- return EXIT_SUCCESS;
-
-remove_temp:
- unlink(tempname);
-
- return EXIT_FAILURE;
-}
-
-int handle_set(const struct parsed_params *params, const char *config_path, struct log_config *conf)
-{
- (void) conf;
- __attribute__((cleanup(free_ptr))) char *key;
- if (asprintf(&key, "limiter|%s|%c=", params->tag, params->prio) < 0) {
- key = NULL;
- ERR("asprintf key failed: %m\n");
- return EXIT_FAILURE;
- }
- const char *value = params->value;
-
- const struct keys_values kv = {
- .keys = (const char **)&key,
- .values = &value,
- .n = 1,
- };
-
- return copy_file_with_exceptions(config_path, &kv);
-}
-int handle_set_pid(const struct parsed_params *params, const char *config_path, struct log_config *conf)
-{
- (void) conf;
-
- __attribute__((cleanup(free_ptr))) char *key;
- if (asprintf(&key, "pidlimit|%d=", params->pid) < 0) {
- key = NULL;
- ERR("asprintf key failed: %m\n");
- return EXIT_FAILURE;
- }
- const char *value = params->value;
-
- const struct keys_values kv = {
- .keys = (const char **)&key,
- .values = &value,
- .n = 1,
- };
-
- return copy_file_with_exceptions(config_path, &kv);
-}
-
-int handle_clear(const struct parsed_params *params, const char *config_path, struct log_config *conf)
-{
- (void) params;
- (void) conf;
- const char *key[] = { "limiter|", "pidlimit|" }; // note: no '=' at the end, so as to match all rules
- const char *value[] = { NULL, NULL }; // removal
-
- const struct keys_values kv = {
- .keys = key,
- .values = value,
- .n = 2,
- };
-
- return copy_file_with_exceptions(config_path, &kv);
-}
-
-int handle_plog(const struct parsed_params *params, const char *config_path, struct log_config *conf)
-{
- (void) conf;
- const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << SINKS_MAX) - 1);
-
- int count = 0;
- for (int i = 0; i < SINKS_MAX; ++i)
- if (buffers_bitset & (1 << i))
- ++count;
- assert(count <= SINKS_MAX);
-
- const char *key_arr[SINKS_MAX];
- const char *value_arr[SINKS_MAX];
-
- const int BUF_SIZE = SINKS_MAX * 20;
- char buf[BUF_SIZE];
- int buf_pos = 0;
- int real_count = 0;
- for (int i = 0; i < SINKS_MAX; ++i) {
- if (!(buffers_bitset & (1 << i)))
- continue;
-
- const int r = snprintf(buf + buf_pos, BUF_SIZE - buf_pos, "enable_%s=", log_name_by_id((log_id_t)i));
- if (r < 0) {
- ERR("snprintf bufname %d failed: %m\n", i);
- return EXIT_FAILURE;
- }
- assert(r < BUF_SIZE - buf_pos);
- key_arr[real_count] = buf + buf_pos;
- buf_pos += r + 1; // Also go past NULL
-
- value_arr[real_count] = params->plog_enable ? "1" : "0";
- ++real_count;
- }
- assert(real_count == count);
-
- const struct keys_values kv = {
- .keys = key_arr,
- .values = value_arr,
- .n = count,
- };
-
- return copy_file_with_exceptions(config_path, &kv);
-}
-
-int handle_stdout_one_pipe(log_id_t id, struct log_config *conf, bool enabled)
-{
- assert(conf);
-
- char conf_key[MAX_CONF_KEY_LEN];
- snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
-
- const char *sock_path = log_config_get(conf, conf_key);
- if (!sock_path)
- return -ENOENT;
-
- __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
- if (sock_fd < 0)
- return sock_fd;
-
- char enabled_char = (char)enabled;
-
- int r = send_dlog_request(sock_fd, DLOG_REQ_GLOBAL_ENABLE_DISABLE_STDOUT, &enabled_char, sizeof(enabled_char));
- if (r < 0)
- return r;
-
- return 0;
-}
-
-int handle_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool enabled)
-{
- assert(conf);
-
- return -ENOTSUP;
-}
-
-int handle_stdout(const struct parsed_params *params, const char *config_path, struct log_config *conf)
-{
- assert(params);
- assert(conf);
- (void)config_path;
-
- const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << LOG_ID_MAX) - 1);
-
- bool is_pipe;
- int r = get_is_pipe(conf, &is_pipe);
- if (r < 0)
- return r;
-
- for (int i = 0; i < LOG_ID_MAX; ++i) {
- if (!(buffers_bitset & (1 << i)))
- continue;
-
- r = (is_pipe ? handle_stdout_one_pipe : handle_stdout_one_nonpipe)(i, conf, params->plog_enable);
- if (r < 0)
- return r;
- }
-
- return 0;
-}
-
-int handle_resize(const struct parsed_params *params, const char *config_path, struct log_config *conf)
-{
- assert(params);
- assert(conf);
- (void)config_path;
-
- const unsigned int capacity = params->capacity;
- int request = DLOG_REQ_CHANGE_COMPRESSION_SIZE;
-
- char conf_key[MAX_CONF_KEY_LEN];
- snprintf(conf_key, sizeof(conf_key), "main_ctl_sock");
- strtok(conf_key, "\n");
-
- const char *sock_path = log_config_get(conf, conf_key);
- if (!sock_path) {
- fprintf(stderr, "Couldn't get sock path\n");
- return -ENOENT;
- }
-
- __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
- if (sock_fd < 0) {
- fprintf(stderr, "Couldn't connect to the sock\n");
- return sock_fd;
- }
-
- size_t data_size = sizeof(unsigned int) + strlen(params->storage_name) + 1;
- if (data_size > MAX_LOGGER_REQUEST_LEN) {
- fprintf(stderr, "Request data is too big\n");
- return -ENOMEM;
- }
-
- __attribute__((cleanup(free_ptr))) char *data = malloc(data_size);
- if (!data) {
- fprintf(stderr, "Insufficient memory\n");
- return -ENOMEM;
- }
- memcpy(data, &capacity, sizeof(unsigned int));
- memcpy(data + sizeof(unsigned int), params->storage_name, strlen(params->storage_name) + 1);
-
- return send_dlog_request(sock_fd, request, (void *)data, data_size);
-}
-
-int get_stdout_one_pipe(log_id_t id, struct log_config *conf, bool *out_enabled)
-{
- assert(conf);
- assert(out_enabled);
-
- char conf_key[MAX_CONF_KEY_LEN];
- snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
-
- const char *sock_path = log_config_get(conf, conf_key);
- if (!sock_path)
- return -ENOENT;
-
- __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
- if (sock_fd < 0)
- return sock_fd;
-
- int r = send_dlog_request(sock_fd, DLOG_REQ_GET_STDOUT, NULL, 0);
- if (r < 0)
- return r;
-
- __attribute__((cleanup(free_ptr))) char *enabled_char = NULL;
- int recv_size;
- r = recv_dlog_reply(sock_fd, DLOG_REQ_GET_STDOUT, (void **)&enabled_char, &recv_size);
- if (r < 0)
- return r;
- if (recv_size != sizeof(char))
- return -EINVAL;
-
- *out_enabled = *enabled_char != '\0';
- return 0;
-}
-
-int get_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool *out_enabled)
-{
- assert(conf);
-
- return -ENOTSUP;
-}
-
-#ifndef UNIT_TEST
static void print_limit(int limit, const char *tag, char prio, bool *shadowed, bool dynamic)
{
const char *const shadow_str = *shadowed ? "[shadowed] " : "";
return exit_code;
}
-#endif // !UNIT_TEST
+
--- /dev/null
+#include <buffer_traits.h>
+#include <extra_sinks.h>
+#include <dynamic_config.h>
+#include "logctl_functions.h"
+#include "logctl.h"
+
+#include <ctype.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+int get_is_pipe(struct log_config *conf, bool *out)
+{
+ const char *const backend = log_config_claim_backend(conf);
+ if (!backend)
+ return -ENOKEY;
+
+ *out = strcmp(backend, "pipe") == 0;
+ return 0;
+}
+
+bool parse_options(int argc, const char **argv, struct parsed_params *params, int *exit_value)
+{
+ assert(argv);
+ assert(params);
+ assert(params->action == ACTION_NONE);
+ assert(!params->value);
+ assert(!params->tag);
+ assert(exit_value);
+
+ params->tag = "*";
+ params->prio = '\0';
+ params->pid = 0;
+ params->plog_buffers_bitset = 0;
+
+ bool use_global_action = true; // if neither tag, pid nor priority is specified
+
+ /* skips a part of the help message */
+ bool show_dynamic_conf_msg = true;
+
+#define SET_ACTION(x) do { \
+ if (params->action != x && params->action != ACTION_NONE) \
+ goto failure; \
+ params->action = x; \
+} while (false)
+
+ for (;;) {
+
+ static const struct option long_options[] = {
+ {"get" , no_argument, NULL, 'g'},
+ {"set" , required_argument, NULL, 's'},
+ {"tag" , required_argument, NULL, 't'},
+ {"priority", required_argument, NULL, 'p'},
+ {"help" , no_argument, NULL, 'h'},
+ {"buffer" , required_argument, NULL, 'b'},
+ {"clear" , no_argument, NULL, 'c'},
+ {"enable" , no_argument, NULL, 0},
+ {"disable" , no_argument, NULL, 1},
+ {"pid" , required_argument, NULL, 2},
+ {"enable-stdout", no_argument, NULL, 3},
+ {"disable-stdout", no_argument, NULL, 4},
+ {"compression-resize", required_argument, NULL, 5},
+ {"compression-name", required_argument, NULL, 6},
+ {0}
+ };
+
+ int long_index;
+ int opt = getopt_long(argc, (char **) argv, "chb:gs:t:p:", long_options, &long_index);
+ if (opt < 0)
+ break;
+
+ switch (opt) {
+ case 0:
+ SET_ACTION(ACTION_PLOG);
+ params->plog_enable = true;
+ break;
+ case 1:
+ SET_ACTION(ACTION_PLOG);
+ params->plog_enable = false;
+ break;
+ case 2: {
+ const int value_num = atoi(optarg);
+ if (value_num <= 0)
+ goto failure;
+ params->pid = value_num;
+ use_global_action = false;
+ break;
+ }
+ case 3:
+ SET_ACTION(ACTION_STDOUT);
+ params->plog_enable = true;
+ break;
+ case 4:
+ SET_ACTION(ACTION_STDOUT);
+ params->plog_enable = false;
+ break;
+ case 5: {
+ const int capacity = atoi(optarg);
+ if (capacity <= 0)
+ goto failure;
+ params->capacity = capacity;
+ SET_ACTION(ACTION_RESIZE);
+ show_dynamic_conf_msg = false;
+ break;
+ } case 6: {
+ params->storage_name = optarg;
+ SET_ACTION(ACTION_RESIZE);
+ show_dynamic_conf_msg = false;
+ break;
+ } case 'b': {
+ const int buf_id = sink_id_by_name(optarg);
+ if (buf_id == LOG_ID_INVALID)
+ goto failure;
+
+ params->plog_buffers_bitset |= 1 << buf_id;
+ break;
+ } case 'h':
+ *exit_value = EXIT_SUCCESS;
+ goto print_help;
+ case 'g':
+ SET_ACTION(ACTION_GET);
+ break;
+ case 'c':
+ SET_ACTION(ACTION_CLEAR);
+ params->value = NULL;
+ break;
+ case 's': {
+ const int value_num = atoi(optarg);
+ if (strcmp(optarg, "allow") && strcmp(optarg, "deny") && (value_num <= 0 || value_num >= __LOG_LIMITER_LIMIT_MAX))
+ goto failure;
+ params->value = optarg;
+ SET_ACTION(ACTION_SET);
+ break;
+ } case 't': {
+ params->tag = optarg;
+ use_global_action = false;
+ break;
+ } case 'p':
+ params->prio = toupper(*optarg);
+ if (!strchr("DVIWEFS*", params->prio))
+ goto failure;
+ use_global_action = false;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+#undef SET_ACTION
+
+ if (params->pid != 0 && (params->prio != '\0' || strcmp(params->tag, "*") != 0))
+ goto failure;
+
+ if (params->plog_buffers_bitset != 0 && params->action != ACTION_PLOG && params->action != ACTION_STDOUT)
+ goto failure;
+
+ if (params->action == ACTION_NONE) {
+ *exit_value = EXIT_SUCCESS;
+ goto print_help;
+ }
+
+ switch (params->action) {
+ case ACTION_GET:
+ if (use_global_action)
+ params->action = ACTION_DUMP;
+ break;
+ case ACTION_CLEAR:
+ if (params->pid != 0)
+ params->action = ACTION_SET_PID;
+ else if (!use_global_action)
+ params->action = ACTION_SET;
+ break;
+ case ACTION_SET:
+ if (params->pid != 0)
+ params->action = ACTION_SET_PID;
+ break;
+ default:
+ break;
+ }
+
+ if (params->action == ACTION_SET && params->prio == '\0')
+ params->prio = '*';
+
+ return true;
+
+failure:
+ *exit_value = EXIT_FAILURE;
+
+print_help:
+ printf("Usage: %s [-t/--tag tag] [-p/--priority priority] [--pid pid] (-g/--get | -s/--set value)\n"
+ "\ttag: defaults to *\n"
+ "\tpriority: F[atal], E[rror], W[arning], I[nfo], V[erbose], D[ebug] S[ilent] * (all, default)\n"
+ "\tpid: integer larger than 0, mutually exclusive with --tag and --priority\n"
+ "\t-g/--get: get current limit for given tag/priority; if neither given, dump the whole filterset\n"
+ "\t-s/--set value: set a new limit to value (\"allow\", \"deny\", or number (0; %d) - allowed logs per minute)\n"
+ "\t-c/--clear: clear the limit for given tag/priority; if neither given, clear the whole filterset\n"
+ "Note: static filters are shadowed by dynamic ones but cannot be overridden at runtime\n\n"
+ " %s [-b/--buffer bufname] [-b ...] (--enable | --disable)\n"
+ "\tbufname: main system radio apps (default: all of them)\n"
+ "\t-b/--buffer bufname: pick given buffer for plog control\n"
+ "\t--enable/--disable: control platform logging for chosen buffers\n"
+ "\t--enable-stdout/--disable-stdout: control stdout redirection for chosen buffers\n"
+ "\t--compression-name: resizes given named compression buffer. Must be used with --compression-resize\n"
+ "\t--compression-resize: sets compression buffer to given size, in bytes.\n"
+ "\n"
+ , argv[0]
+ , __LOG_LIMITER_LIMIT_MAX
+ , argv[0]
+ );
+
+ if (show_dynamic_conf_msg) {
+ printf("NOTE: to use dlogctl, dynamic dlog config has to be enabled!\n"
+ "Define the %s config entry to a *DIRECTORY* path.\n"
+ "ALSO: filters won't work for the APPS buffer (whence most spam comes)\n"
+ "unless you set `limiter_apply_to_all_buffers` in the config to 1!\n"
+ "The filters are also currently per process; this may change in the future.\n\n"
+ , DYNAMIC_CONFIG_CONF_KEY
+ );
+ }
+
+ return false;
+}
+
+/**
+ * @brief Copy file, except modify some lines
+ * @details Copies a file linewise to another, except ensures lines starting with given prefixes have certain follow-up
+ * @param[in] in Copy from this file
+ * @param[in] out Copy into this file
+ * @param[in] kv A set of key-value pairs, where lines which do not match any of the prefixes are copied verbatim, while
+ * lines that do are ignored. For each key where the value is not NULL, a line with the key as the prefix
+ * and the value as the suffix is then appended to the resulting file.
+ *
+ * @notes Usually the keys will contain a '=' at the end so as to only replace exact matches in a config file,
+ * and keys without '=' at the end should usually have the value NULL to keep the config format valid
+ *
+ * @example Consider a file with the contents:
+ * FOO=BAR
+ * FOOOO=BAZ
+ * ABC=XYZ
+ * ABCABC=QQQ
+ * ABC=ZYX
+ *
+ * And a set of kv pairs:
+ * {"FOO", NULL}
+ * {"ABC=", "YYY"}
+ * {"QWER=", "ASDF"}
+ *
+ * Thus:
+ * FOO matches both FOO=BAR and FOOOO=BAZ. Since the value is NULL, both these lines are removed.
+ * ABC= matches both ABC=XYZ and ABC=ZYX, these lines are not copied. Instead, ABC=YYY is added
+ * behind any lines that were not matched.
+ * ABCABC is not matched by any kv pair, so it is copied verbatim.
+ * The QWER= prefix is not matched by any line, so QWER=ASDF is appended to the end.
+ *
+ * The resulting file will therefore look like:
+ * ABCABC=QQQ
+ * ABC=YYY
+ * QWER=ASDF
+ *
+ */
+int copy_except(FILE *in, FILE *out, const struct keys_values *kv)
+{
+ __attribute__((cleanup(free_ptr))) size_t *const key_len = calloc(kv->n, sizeof *key_len);
+ if (!key_len)
+ return -ENOMEM;
+
+ for (size_t i = 0; i < kv->n; ++i)
+ key_len[i] = strlen(kv->keys[i]);
+
+ char line[MAX_CONF_KEY_LEN + MAX_CONF_VAL_LEN + 2]; // +2 for delimiter and terminator: "K=V\0"
+ while (fgets(line, sizeof line, in)) {
+ size_t i = 0;
+ while (i < kv->n && strncmp(line, kv->keys[i], key_len[i]))
+ ++i;
+ if (i == kv->n)
+ fputs(line, out);
+ }
+
+ for (size_t i = 0; i < kv->n; ++i)
+ if (kv->values[i])
+ fprintf(out, "%s%s\n", kv->keys[i], kv->values[i]);
+
+ return 0;
+}
+
+int copy_file_with_exceptions(const char *config_path, const struct keys_values *kv)
+{
+ const int configfd = open(config_path, O_RDONLY | O_CREAT, 0644);
+ if (configfd < 0) {
+ ERR("configfd open failed: %m");
+ return EXIT_FAILURE;
+ }
+
+ int r = flock(configfd, LOCK_EX);
+ if (r < 0) {
+ ERR("flock failed: %m");
+ close(configfd);
+ return EXIT_FAILURE;
+ }
+
+ __attribute__((cleanup(close_FILE))) FILE *const configfile = fdopen(configfd, "r");
+ if (!configfile) {
+ ERR("configfile fdopen failed: %m");
+ close(configfd);
+ return EXIT_FAILURE;
+ }
+
+ __attribute__((cleanup(free_ptr))) char *tempname = NULL;
+ r = asprintf(&tempname, "%sXXXXXX", config_path);
+ if (r < 0) {
+ tempname = NULL;
+ ERR("asprintf tempname failed: %m\n");
+ return EXIT_FAILURE;
+ }
+
+ /* In older libc versions, umask(077) needed to be called
+ * before mkstemp() to prevent a potential race condition
+ * vulnerability. POSIX-2008 requires mkstemp() to create
+ * the file with rights 600, but our static analysis tools
+ * still complain about this and in this case it was safe
+ * to just do this and silence them. In the general case,
+ * doing this is unsafe as umask() is not thread-aware but
+ * logctl is single-threaded anyway. */
+ mode_t old = umask(077);
+ const int tempfd = mkstemp(tempname);
+ umask(old);
+ if (tempfd < 0) {
+ ERR("mkstemp failed %m\n");
+ return EXIT_FAILURE;
+ }
+
+ __attribute__((cleanup(close_FILE))) FILE *const tempfile = fdopen(tempfd, "w");
+ if (!tempfile) {
+ ERR("tmpfile fdopen failed: %s %m\n", tempname);
+ goto remove_temp;
+ }
+
+ if (fchmod(tempfd, 0644) < 0) {
+ ERR("fchmod failed on %s %m\n", tempname);
+ goto remove_temp;
+ }
+
+ if (copy_except(configfile, tempfile, kv) < 0) {
+ ERR("copy_except failed\n");
+ goto remove_temp;
+ }
+
+ if (rename(tempname, config_path) < 0) {
+ ERR("rename failed: %m\n");
+ goto remove_temp;
+ }
+
+ return EXIT_SUCCESS;
+
+remove_temp:
+ unlink(tempname);
+
+ return EXIT_FAILURE;
+}
+
+int handle_set(const struct parsed_params *params, const char *config_path, struct log_config *conf)
+{
+ (void) conf;
+ __attribute__((cleanup(free_ptr))) char *key;
+ if (asprintf(&key, "limiter|%s|%c=", params->tag, params->prio) < 0) {
+ key = NULL;
+ ERR("asprintf key failed: %m\n");
+ return EXIT_FAILURE;
+ }
+ const char *value = params->value;
+
+ const struct keys_values kv = {
+ .keys = (const char **)&key,
+ .values = &value,
+ .n = 1,
+ };
+
+ return copy_file_with_exceptions(config_path, &kv);
+}
+
+int handle_set_pid(const struct parsed_params *params, const char *config_path, struct log_config *conf)
+{
+ (void) conf;
+
+ __attribute__((cleanup(free_ptr))) char *key;
+ if (asprintf(&key, "pidlimit|%d=", params->pid) < 0) {
+ key = NULL;
+ ERR("asprintf key failed: %m\n");
+ return EXIT_FAILURE;
+ }
+ const char *value = params->value;
+
+ const struct keys_values kv = {
+ .keys = (const char **)&key,
+ .values = &value,
+ .n = 1,
+ };
+
+ return copy_file_with_exceptions(config_path, &kv);
+}
+
+int handle_clear(const struct parsed_params *params, const char *config_path, struct log_config *conf)
+{
+ (void) params;
+ (void) conf;
+ const char *key[] = { "limiter|", "pidlimit|" }; // note: no '=' at the end, so as to match all rules
+ const char *value[] = { NULL, NULL }; // removal
+
+ const struct keys_values kv = {
+ .keys = key,
+ .values = value,
+ .n = 2,
+ };
+
+ return copy_file_with_exceptions(config_path, &kv);
+}
+
+int handle_plog(const struct parsed_params *params, const char *config_path, struct log_config *conf)
+{
+ (void) conf;
+ const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << SINKS_MAX) - 1);
+
+ int count = 0;
+ for (int i = 0; i < SINKS_MAX; ++i)
+ if (buffers_bitset & (1 << i))
+ ++count;
+ assert(count <= SINKS_MAX);
+
+ const char *key_arr[SINKS_MAX];
+ const char *value_arr[SINKS_MAX];
+
+ const int BUF_SIZE = SINKS_MAX * 20;
+ char buf[BUF_SIZE];
+ int buf_pos = 0;
+ int real_count = 0;
+ for (int i = 0; i < SINKS_MAX; ++i) {
+ if (!(buffers_bitset & (1 << i)))
+ continue;
+
+ const int r = snprintf(buf + buf_pos, BUF_SIZE - buf_pos, "enable_%s=", log_name_by_id((log_id_t)i));
+ if (r < 0) {
+ ERR("snprintf bufname %d failed: %m\n", i);
+ return EXIT_FAILURE;
+ }
+ assert(r < BUF_SIZE - buf_pos);
+ key_arr[real_count] = buf + buf_pos;
+ buf_pos += r + 1; // Also go past NULL
+
+ value_arr[real_count] = params->plog_enable ? "1" : "0";
+ ++real_count;
+ }
+ assert(real_count == count);
+
+ const struct keys_values kv = {
+ .keys = key_arr,
+ .values = value_arr,
+ .n = count,
+ };
+
+ return copy_file_with_exceptions(config_path, &kv);
+}
+
+int handle_stdout_one_pipe(log_id_t id, struct log_config *conf, bool enabled)
+{
+ assert(conf);
+
+ char conf_key[MAX_CONF_KEY_LEN];
+ snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
+
+ const char *sock_path = log_config_get(conf, conf_key);
+ if (!sock_path)
+ return -ENOENT;
+
+ __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
+ if (sock_fd < 0)
+ return sock_fd;
+
+ char enabled_char = (char)enabled;
+
+ int r = send_dlog_request(sock_fd, DLOG_REQ_GLOBAL_ENABLE_DISABLE_STDOUT, &enabled_char, sizeof(enabled_char));
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int handle_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool enabled)
+{
+ assert(conf);
+
+ return -ENOTSUP;
+}
+
+int handle_stdout(const struct parsed_params *params, const char *config_path, struct log_config *conf)
+{
+ assert(params);
+ assert(conf);
+ (void)config_path;
+
+ const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << LOG_ID_MAX) - 1);
+
+ bool is_pipe;
+ int r = get_is_pipe(conf, &is_pipe);
+ if (r < 0)
+ return r;
+
+ for (int i = 0; i < LOG_ID_MAX; ++i) {
+ if (!(buffers_bitset & (1 << i)))
+ continue;
+
+ r = (is_pipe ? handle_stdout_one_pipe : handle_stdout_one_nonpipe)(i, conf, params->plog_enable);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int handle_resize(const struct parsed_params *params, const char *config_path, struct log_config *conf)
+{
+ assert(params);
+ assert(conf);
+ (void)config_path;
+
+ const unsigned int capacity = params->capacity;
+ int request = DLOG_REQ_CHANGE_COMPRESSION_SIZE;
+
+ char conf_key[MAX_CONF_KEY_LEN];
+ snprintf(conf_key, sizeof(conf_key), "main_ctl_sock");
+ strtok(conf_key, "\n");
+
+ const char *sock_path = log_config_get(conf, conf_key);
+ if (!sock_path) {
+ fprintf(stderr, "Couldn't get sock path\n");
+ return -ENOENT;
+ }
+
+ __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
+ if (sock_fd < 0) {
+ fprintf(stderr, "Couldn't connect to the sock\n");
+ return sock_fd;
+ }
+
+ size_t data_size = sizeof(unsigned int) + strlen(params->storage_name) + 1;
+ if (data_size > MAX_LOGGER_REQUEST_LEN) {
+ fprintf(stderr, "Request data is too big\n");
+ return -ENOMEM;
+ }
+
+ __attribute__((cleanup(free_ptr))) char *data = malloc(data_size);
+ if (!data) {
+ fprintf(stderr, "Insufficient memory\n");
+ return -ENOMEM;
+ }
+ memcpy(data, &capacity, sizeof(unsigned int));
+ memcpy(data + sizeof(unsigned int), params->storage_name, strlen(params->storage_name) + 1);
+
+ return send_dlog_request(sock_fd, request, (void *)data, data_size);
+}
+
+int get_stdout_one_pipe(log_id_t id, struct log_config *conf, bool *out_enabled)
+{
+ assert(conf);
+ assert(out_enabled);
+
+ char conf_key[MAX_CONF_KEY_LEN];
+ snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
+
+ const char *sock_path = log_config_get(conf, conf_key);
+ if (!sock_path)
+ return -ENOENT;
+
+ __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
+ if (sock_fd < 0)
+ return sock_fd;
+
+ int r = send_dlog_request(sock_fd, DLOG_REQ_GET_STDOUT, NULL, 0);
+ if (r < 0)
+ return r;
+
+ __attribute__((cleanup(free_ptr))) char *enabled_char = NULL;
+ int recv_size;
+ r = recv_dlog_reply(sock_fd, DLOG_REQ_GET_STDOUT, (void **)&enabled_char, &recv_size);
+ if (r < 0)
+ return r;
+ if (recv_size != sizeof(char))
+ return -EINVAL;
+
+ *out_enabled = *enabled_char != '\0';
+ return 0;
+}
+
+int get_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool *out_enabled)
+{
+ assert(conf);
+
+ return -ENOTSUP;
+}
+
--- /dev/null
+#pragma once
+
+#include <stdio.h> // FILE
+#include "dlog-internal.h" // log_id_t
+
+struct log_config;
+struct parsed_params;
+struct keys_values;
+
+int get_is_pipe(struct log_config *conf, bool *out);
+bool parse_options(int argc, const char **argv, struct parsed_params *params, int *exit_value);
+int copy_except(FILE *in, FILE *out, const struct keys_values *kv);
+int copy_file_with_exceptions(const char *config_path, const struct keys_values *kv);
+int handle_set(const struct parsed_params *params, const char *config_path, struct log_config *conf);
+int handle_set_pid(const struct parsed_params *params, const char *config_path, struct log_config *conf);
+int handle_clear(const struct parsed_params *params, const char *config_path, struct log_config *conf);
+int handle_plog(const struct parsed_params *params, const char *config_path, struct log_config *conf);
+int handle_stdout_one_pipe(log_id_t id, struct log_config *conf, bool enabled);
+int handle_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool enabled);
+int handle_stdout(const struct parsed_params *params, const char *config_path, struct log_config *conf);
+int handle_resize(const struct parsed_params *params, const char *config_path, struct log_config *conf);
+int get_stdout_one_pipe(log_id_t id, struct log_config *conf, bool *out_enabled);
+int get_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool *out_enabled);
+