From fff31f892284b7db833dfc360a0286eda063f62c Mon Sep 17 00:00:00 2001 From: Michal Bloch Date: Thu, 29 Aug 2024 14:01:15 +0200 Subject: [PATCH] Extract functions used in tests to a separate file Previously they were under an #ifdef macro, which some static analyzer complained about. Change-Id: I2ed323bb036267ce3ecfdde646dff63502353cd1 --- Makefile.am | 3 +- src/logctl/logctl.c | 613 +----------------------------------------- src/logctl/logctl_functions.c | 602 +++++++++++++++++++++++++++++++++++++++++ src/logctl/logctl_functions.h | 24 ++ 4 files changed, 630 insertions(+), 612 deletions(-) create mode 100644 src/logctl/logctl_functions.c create mode 100644 src/logctl/logctl_functions.h diff --git a/Makefile.am b/Makefile.am index 85735ab..7218dd7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -240,6 +240,7 @@ dlogctl_SOURCES = \ 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 @@ -723,7 +724,7 @@ src_tests_queued_entry_monotonic_pos_SOURCES = src/tests/queued_entry_pos.c src/ 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 diff --git a/src/logctl/logctl.c b/src/logctl/logctl.c index 15706ab..3c49af4 100644 --- a/src/logctl/logctl.c +++ b/src/logctl/logctl.c @@ -1,621 +1,12 @@ -// C -#include -#include -#include -#include -#include -#include - -// POSIX -#include -#include -#include -#include - // DLog #include #include #include #include -#include -#include -#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] " : ""; @@ -883,4 +274,4 @@ int main(int argc, const char **argv) return exit_code; } -#endif // !UNIT_TEST + diff --git a/src/logctl/logctl_functions.c b/src/logctl/logctl_functions.c new file mode 100644 index 0000000..40fe8f2 --- /dev/null +++ b/src/logctl/logctl_functions.c @@ -0,0 +1,602 @@ +#include +#include +#include +#include "logctl_functions.h" +#include "logctl.h" + +#include +#include +#include +#include +#include +#include + +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; +} + diff --git a/src/logctl/logctl_functions.h b/src/logctl/logctl_functions.h new file mode 100644 index 0000000..8c9ebad --- /dev/null +++ b/src/logctl/logctl_functions.h @@ -0,0 +1,24 @@ +#pragma once + +#include // 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); + -- 2.7.4