From c25d7fdb0a30b4b67cd5c847c648b6f0209c13b8 Mon Sep 17 00:00:00 2001 From: Michal Bloch Date: Mon, 2 Mar 2020 14:11:27 +0100 Subject: [PATCH] libdlog: pid based limiter Change-Id: Iac3e9df7cb97f2ce9246e35db4e00708ff684b54 Signed-off-by: Michal Bloch --- Makefile.am | 21 ++++++-- include/logctl.h | 6 ++- include/loglimiter.h | 9 ++++ src/logctl/logctl.c | 138 +++++++++++++++++++++++++++++++++++++++--------- src/shared/loglimiter.c | 116 +++++++++++++++++++++++++++++++++++++--- src/tests/logctl.c | 30 +++++------ src/tests/pid_limiter.c | 74 ++++++++++++++++++++++++++ tests/dlog_test.in | 55 +++++++++++++++++++ 8 files changed, 397 insertions(+), 52 deletions(-) create mode 100644 src/tests/pid_limiter.c diff --git a/Makefile.am b/Makefile.am index bfb8ef7..f629132 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ libdlog_la_SOURCES = \ src/shared/logcommon.c \ src/shared/logconfig.c \ src/shared/parsers.c \ + src/shared/ptrs_list.c \ src/shared/queued_entry.c \ src/shared/translate_syslog.c \ src/libdlog/deduplicate.c \ @@ -172,6 +173,7 @@ dlogctl_SOURCES = \ src/shared/logcommon.c \ src/shared/logconfig.c \ src/shared/parsers.c \ + src/shared/ptrs_list.c \ src/shared/loglimiter.c \ src/logctl/logctl.c @@ -333,6 +335,7 @@ check_PROGRAMS = \ src/tests/metrics \ src/tests/hash_test \ src/tests/deduplicate_test \ + src/tests/pid_limiter \ src/tests/filters check_CFLAGS = $(AM_CFLAGS) -O0 -fprofile-arcs -DUNIT_TEST \ @@ -365,11 +368,11 @@ src_tests_test_common_pos_SOURCES = src/tests/test_common_pos.c src/shared/logco src_tests_test_common_pos_CFLAGS = $(check_CFLAGS) src_tests_test_common_pos_LDFLAGS = $(AM_LDFLAGS) -Wl,--wrap=sendmsg,--wrap=recvmsg,--wrap=writev,--wrap=read,--wrap=poll,--wrap=fcntl,--wrap=fcntl64,--wrap=malloc,--wrap=calloc,--wrap=connect,--wrap=socket,--wrap=open,--wrap=open64,--wrap=ioctl -src_tests_limiter_pos_SOURCES = src/tests/limiter_pos.c src/shared/loglimiter.c src/shared/logconfig.c src/shared/logcommon.c src/shared/parsers.c +src_tests_limiter_pos_SOURCES = src/tests/limiter_pos.c src/shared/loglimiter.c src/shared/logconfig.c src/shared/logcommon.c src/shared/parsers.c src/shared/ptrs_list.c src_tests_limiter_pos_CFLAGS = $(check_CFLAGS) src_tests_limiter_pos_LDFLAGS = $(AM_LDFLAGS) -Wl,--wrap=snprintf,--wrap=malloc,--wrap=time -src_tests_limiter_neg_SOURCES = src/tests/limiter_neg.c src/shared/loglimiter.c src/shared/logconfig.c src/shared/logcommon.c src/shared/parsers.c +src_tests_limiter_neg_SOURCES = src/tests/limiter_neg.c src/shared/loglimiter.c src/shared/logconfig.c src/shared/logcommon.c src/shared/parsers.c src/shared/ptrs_list.c src_tests_limiter_neg_CFLAGS = $(check_CFLAGS) src_tests_limiter_neg_LDFLAGS = $(AM_LDFLAGS) -Wl,--wrap=snprintf,--wrap=malloc,--wrap=time @@ -469,7 +472,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/parsers.c src/shared/logconfig.c src/shared/loglimiter.c src/shared/logcommon.c +src_tests_logctl_SOURCES = src/tests/logctl.c src/logctl/logctl.c src/shared/parsers.c src/shared/logconfig.c src/shared/loglimiter.c src/shared/logcommon.c src/shared/ptrs_list.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 @@ -587,12 +590,22 @@ src_tests_critical_log_SOURCES = src/tests/critical_log.c \ src/libdlog/deduplicate.c \ src/shared/hash.c \ src/libdlog/dynamic_config.c \ + src/shared/ptrs_list.c \ src/shared/logcommon.c \ src/shared/logconfig.c \ src/shared/parsers.c src_tests_critical_log_CFLAGS = $(check_CFLAGS) -pthread src_tests_critical_log_LDFLAGS = $(AM_LDFLAGS) -lpthread -Wl,--wrap=execv,--wrap=clock_gettime +src_tests_pid_limiter_SOURCES = src/tests/pid_limiter.c \ + src/shared/loglimiter.c \ + src/shared/logcommon.c \ + src/shared/logconfig.c \ + src/shared/parsers.c \ + src/shared/ptrs_list.c +src_tests_pid_limiter_CFLAGS = $(check_CFLAGS) -Wl,--wrap=getpid,--wrap=time +src_tests_pid_limiter_LDFLAGS = $(AM_LDFLAGS) + src_tests_filters_SOURCES = src/tests/filters.c src/shared/ptrs_list.c src/shared/logcommon.c src/shared/logprint.c src/shared/queued_entry_timestamp.c src_tests_filters_CFLAGS = $(check_CFLAGS) src_tests_filters_LDFLAGS = $(AM_LDFLAGS) @@ -609,7 +622,7 @@ src_tests_metrics_LDADD = libdlogutil.la src_tests_metrics_CFLAGS = $(check_CFLAGS) src_tests_metrics_LDFLAGS = $(AM_LDFLAGS) -src_tests_deduplicate_test_SOURCES = src/tests/deduplicate_test.c src/libdlog/log.c src/libdlog/deduplicate.c src/shared/hash.c src/shared/logcommon.c src/shared/loglimiter.c src/libdlog/dynamic_config.c src/shared/logconfig.c src/shared/parsers.c +src_tests_deduplicate_test_SOURCES = src/tests/deduplicate_test.c src/libdlog/log.c src/libdlog/deduplicate.c src/shared/hash.c src/shared/logcommon.c src/shared/loglimiter.c src/libdlog/dynamic_config.c src/shared/logconfig.c src/shared/parsers.c src/shared/ptrs_list.c src_tests_deduplicate_test_CFLAGS = $(check_CFLAGS) -pthread src_tests_deduplicate_test_LDFLAGS = $(AM_LDFLAGS) -lpthread -Wl,--wrap=clock_gettime,--wrap=log_config_read diff --git a/include/logctl.h b/include/logctl.h index 31154a1..b9c605b 100644 --- a/include/logctl.h +++ b/include/logctl.h @@ -10,21 +10,23 @@ struct log_config; struct parsed_params { enum action_t { ACTION_NONE = 0, - ACTION_GET, + ACTION_GET, // incl. getting PID ACTION_SET, // incl. setting to NULL (clearing) ACTION_DUMP, ACTION_CLEAR, // clears all rules + ACTION_SET_PID, ACTION_PLOG, } action; const char *tag; char prio; + int pid; const char *value; bool plog_enable; int plog_buffers_bitset; }; struct keys_values { - char **keys; + char **keys; // TODO: This should be const, the code already assumes that... const char **values; size_t n; }; diff --git a/include/loglimiter.h b/include/loglimiter.h index 240951d..7f58582 100644 --- a/include/loglimiter.h +++ b/include/loglimiter.h @@ -34,6 +34,7 @@ extern "C" { #include #include +#include struct rule; @@ -44,6 +45,11 @@ struct limiter_limits { int global; }; +struct pid_limit { + pid_t pid; + size_t limit; +}; + void __log_limiter_destroy(void); int __log_limiter_pass_log(const char* tag, int prio); @@ -56,6 +62,9 @@ void __log_limiter_update(const struct log_config *config); int __log_limiter_dump_rule(struct rule **, char *, const size_t); +// Note: result only valid until next __log_limiter_{update,destroy} +list_head __log_limiter_get_pid_limits(); + #ifdef __cplusplus } #endif diff --git a/src/logctl/logctl.c b/src/logctl/logctl.c index 27fdedb..efc819d 100644 --- a/src/logctl/logctl.c +++ b/src/logctl/logctl.c @@ -30,9 +30,10 @@ bool parse_options(int argc, const char **argv, struct parsed_params *params, in params->tag = "*"; params->prio = '\0'; + params->pid = 0; params->plog_buffers_bitset = 0; - bool use_global_action = true; // if neither tag not priority is specified + bool use_global_action = true; // if neither tag, pid nor priority is specified #define SET_ACTION(x) do { \ if (params->action != x && params->action != ACTION_NONE) \ @@ -43,15 +44,16 @@ bool parse_options(int argc, const char **argv, struct parsed_params *params, in 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'}, - {"enable" , no_argument, NULL, '\0'}, - {"disable" , no_argument, NULL, '\0'}, - {"clear" , no_argument, NULL, 'c'}, + {"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'}, + {"enable" , no_argument, NULL, 0}, + {"disable" , no_argument, NULL, 0}, + {"clear" , no_argument, NULL, 'c'}, + {"pid" , required_argument, NULL, 1}, {0} }; @@ -63,8 +65,16 @@ bool parse_options(int argc, const char **argv, struct parsed_params *params, in switch (opt) { case 0: SET_ACTION(ACTION_PLOG); - params->plog_enable = (long_index == 6); + params->plog_enable = (long_index == 6); // TODO: Why?! break; + case 1: { + const int value_num = atoi(optarg); + if (value_num <= 0) + goto failure; + params->pid = value_num; + use_global_action = false; + break; + } case 'b': { const int buf_id = log_id_by_name(optarg); if (buf_id == LOG_ID_INVALID) @@ -107,6 +117,9 @@ bool parse_options(int argc, const char **argv, struct parsed_params *params, in #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) goto failure; @@ -121,10 +134,15 @@ bool parse_options(int argc, const char **argv, struct parsed_params *params, in params->action = ACTION_DUMP; break; case ACTION_CLEAR: - if (!use_global_action) + 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; } @@ -138,9 +156,10 @@ failure: *exit_value = EXIT_FAILURE; print_help: - printf("Usage: %s [-t/--tag tag] [-p/--priority priority] (-g/--get | -s/--set value)\n" + 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" @@ -334,12 +353,17 @@ int handle_set(const struct parsed_params *params, const char *config_path, stru return copy_file_with_exceptions(config_path, &kv); } -int handle_clear(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) { - (void) params; (void) conf; - char *key = "limiter|"; // note: no '=' at the end, so as to match all rules - const char *value = NULL; // removal + + __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; struct keys_values kv = { .keys = &key, @@ -350,6 +374,22 @@ int handle_clear(const struct parsed_params *params, const char *config_path, st 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; + char *key[] = { "limiter|", "pidlimit|" }; // note: no '=' at the end, so as to match all rules + const char *value[] = { NULL, NULL }; // removal + + 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; @@ -411,6 +451,36 @@ static void print_limit(int limit, const char *tag, char prio, bool *shadowed, b *shadowed = true; } +static void get_pid_rule(void *prerule, void *prepid) +{ + struct pid_limit *rule = prerule; + assert(rule); + + assert(prepid); + pid_t pid = *(pid_t *)prepid; + + if (rule->pid != pid) + return; + + if (rule->limit == 0) + printf("Denied\n"); + else if (rule->limit == __LOG_LIMITER_LIMIT_MAX + 1) + printf("Unlimited\n"); + else + printf("%zu logs/min\n", rule->limit); +} + +static void get_pid_limits(const struct parsed_params *params, const char *config_path, struct log_config *conf) +{ + log_config_read_file(conf, config_path); // not an error on failure - config still valid if missing, static rules apply + __log_limiter_update(conf); + + pid_t pid_to_find = params->pid; + list_head pidrules = __log_limiter_get_pid_limits(); + if (pidrules) + list_foreach(pidrules, &pid_to_find, get_pid_rule); +} + static void get_limits(const struct parsed_params *params, const char *config_path, struct log_config *conf) { struct limiter_limits lims_static = __log_limiter_get_limits(params->tag, params->prio); @@ -500,7 +570,9 @@ int handle_get(const struct parsed_params *params, const char *config_path, stru return EXIT_FAILURE; } - if (params->prio == '\0') + if (params->pid != 0) + get_pid_limits(params, config_path, conf); + else if (params->prio == '\0') get_prio_limits(params, config_path, conf); else get_limits(params, config_path, conf); @@ -509,6 +581,19 @@ int handle_get(const struct parsed_params *params, const char *config_path, stru return EXIT_SUCCESS; } +void dump_pid_rule(void *prerule, void *_garbage) +{ + struct pid_limit *rule = prerule; + assert(rule); + + if (rule->limit == 0) + printf("Denied for %d\n", rule->pid); + else if (rule->limit == __LOG_LIMITER_LIMIT_MAX + 1) + printf("Unlimited for %d\n", rule->pid); + else + printf("%zu logs/min for %d\n", rule->limit, rule->pid); +} + int handle_dump(const struct parsed_params *params, const char *config_path, struct log_config *conf) { (void) params; @@ -532,6 +617,10 @@ int handle_dump(const struct parsed_params *params, const char *config_path, str printf("%s\n", buf); } while (r); + list_head pidrules = __log_limiter_get_pid_limits(); + if (pidrules) + list_foreach(pidrules, NULL, dump_pid_rule); + printf("\nLogging buffer status:\n"); const bool plog_default = log_config_get_boolean(conf, "plog", true); for (int i = 0; i < LOG_ID_MAX; ++i) { @@ -578,11 +667,12 @@ int main(int argc, const char **argv) } int (*handles[])(const struct parsed_params *, const char *, struct log_config *) = { - [ACTION_GET] = handle_get, - [ACTION_SET] = handle_set, - [ACTION_DUMP] = handle_dump, - [ACTION_PLOG] = handle_plog, - [ACTION_CLEAR] = handle_clear, + [ACTION_GET] = handle_get, + [ACTION_SET] = handle_set, + [ACTION_SET_PID] = handle_set_pid, + [ACTION_DUMP] = handle_dump, + [ACTION_PLOG] = handle_plog, + [ACTION_CLEAR] = handle_clear, }; exit_code = handles[params.action](¶ms, full_inotify_path, &conf); diff --git a/src/shared/loglimiter.c b/src/shared/loglimiter.c index b1fcf75..984e4c4 100644 --- a/src/shared/loglimiter.c +++ b/src/shared/loglimiter.c @@ -38,12 +38,11 @@ #include "loglimiter.h" #include "loglimiter-internal.h" #include +#include /* Some random big odd number to make hash more diverse */ #define HASH_MAGIC_THINGY 5237231 -#define TIME_FRAME 60 - typedef int (*hash_cmp_func_t)(struct rule*, struct rule*); typedef int (*hash_match_func_t)(struct rule*, unsigned, const char*, int); @@ -54,6 +53,8 @@ struct hashmap { void* bucket[]; }; +static list_head pid_rules = NULL; + static struct hashmap* rules_hashmap = NULL; /* Keep rules table as single-linked list */ @@ -63,6 +64,56 @@ static struct rule* original_rules_table = NULL; #define HASHMAP_MASK(hm) ((int)(hm->size - 1)) #define HASHMAP_LINEAR_PROBE_LEAP 1 +static struct pid_limit *cached_pid_rule = NULL; +static struct pid_limit no_limit_rule = { .pid = -1, .limit = SIZE_MAX }; +static size_t sent_by_me = 0; +static time_t last_pid_time = -1; +static time_t refresh_rate_s = -1; +static pid_t prev_pid = -1; +static bool block_by_pid() +{ + const pid_t my_pid = getpid(); + + /* We cache the rule relevant to our pid since + * our pid doesn't usually change. Fork exists + * though so we have to pay attention lest we + * use an obsolete value. */ + if (!cached_pid_rule || ((cached_pid_rule->pid != -1 || prev_pid != my_pid) && cached_pid_rule->pid != my_pid)) { + + /* Forking resets the counter (mostly so that launcher + * log usage does not get inherited by innocent apps, + * since forking is otherwise fairly rare as far as + * typical dlog clients go). */ + if (prev_pid != my_pid) + sent_by_me = 0; + + inline void find_my_rule(elem_value value, void *userdata) { + struct pid_limit *const p = (struct pid_limit *) value; + if (p->pid == my_pid) + cached_pid_rule = p; + } + + cached_pid_rule = &no_limit_rule; + list_foreach(pid_rules, NULL, find_my_rule); + } + + if (cached_pid_rule->limit > __LOG_LIMITER_LIMIT_MAX) + return false; + + prev_pid = my_pid; + if (++sent_by_me <= cached_pid_rule->limit) + return false; + + const time_t now = time(NULL); + if (now < last_pid_time + refresh_rate_s) + return true; + + last_pid_time = now; + sent_by_me = 0; + + return ++sent_by_me > cached_pid_rule->limit; +} + static void rules_destroy(struct rule **rlist) { assert(rlist); @@ -288,12 +339,19 @@ int __log_limiter_initialize(struct rule *rules_table) return 0; } -void __log_limiter_destroy(void) +static void destroy_hashmap_etc() { hashmap_destroy(&rules_hashmap); rules_destroy(&original_rules_table); } +void __log_limiter_destroy(void) +{ + cached_pid_rule = NULL; + list_clear_free_contents(&pid_rules); + destroy_hashmap_etc(); +} + struct rule* __log_limiter_add_rule(struct rule** rules_table, const char* tag, int prio, int limit) { assert(tag); @@ -355,6 +413,9 @@ struct limiter_limits __log_limiter_get_limits(const char *tag, int prio) */ int __log_limiter_pass_log(const char* tag, int prio) { + if (block_by_pid()) + return 0; + if (!rules_hashmap) return 1; @@ -382,7 +443,7 @@ int __log_limiter_pass_log(const char* tag, int prio) if (0 > now) return 1; - if (now - r->start <= TIME_FRAME) { + if (now - r->start <= refresh_rate_s) { if (r->hit >= 0) { if (r->hit < r->limit) { r->hit++; @@ -408,7 +469,7 @@ int __log_limiter_pass_log(const char* tag, int prio) * @param[in] value The entry value * @param[in,out] userdata A pointer to the rules table (pointer to struct rule *) */ -static void __config_iteration(const char* key, const char* value, void *userdata) +static void regular_limiter_iteration(const char* key, const char* value, void *userdata) { assert(key); assert(value); @@ -439,18 +500,51 @@ static void __config_iteration(const char* key, const char* value, void *userdat __log_limiter_add_rule(rules_table, limiter_tag, *(delimiter_pos + 1), limit); } +static void pid_limiter_iteration(const char* key, const char* value, void *userdata) +{ + if (strncmp(key, "pidlimit|", sizeof "pidlimit|" - 1)) + return; + + int pid = atoi(key + sizeof "pidlimit|" - 1); + + int limit; + if (strcmp(value, "deny") == 0) + limit = 0; + else if (strcmp(value, "allow") == 0) + limit = __LOG_LIMITER_LIMIT_MAX + 1; + else + limit = atoi(value); + + struct pid_limit *const p = malloc(sizeof *p); + if (!p) + return; + p->limit = limit; + p->pid = pid; + + list_add(&pid_rules, p); +} + +static void __config_iteration(const char* key, const char* value, void *userdata) +{ + regular_limiter_iteration(key, value, userdata); + pid_limiter_iteration(key, value, userdata); +} + int __log_limiter_create(const struct log_config *config) { assert(config); assert(!original_rules_table); assert(!rules_hashmap); + last_pid_time = time(NULL); + cached_pid_rule = NULL; log_config_foreach(config, __config_iteration, &original_rules_table); + refresh_rate_s = log_config_get_int(config, "refresh_rate_s", 60); const int r = __log_limiter_initialize(original_rules_table); if (r) { rules_destroy(&original_rules_table); - return 0; + return pid_rules != NULL; } for (struct rule *i = original_rules_table; i; i = i->prev) @@ -472,6 +566,9 @@ void __log_limiter_update(const struct log_config *config) r->type = RULE_STATIC; } + last_pid_time = time(NULL); + cached_pid_rule = NULL; + list_clear_free_contents(&pid_rules); log_config_foreach(config, __config_iteration, &rules_table); if (rules_hashmap) { @@ -488,7 +585,7 @@ void __log_limiter_update(const struct log_config *config) hashmap_destroy(&rules_hashmap); if (__log_limiter_initialize(rules_table) < 0) { - __log_limiter_destroy(); + destroy_hashmap_etc(); return; } @@ -534,3 +631,8 @@ int __log_limiter_dump_rule(struct rule **r, char *buf, const size_t size) *r = ruleptr->prev; return 0; } + +list_head __log_limiter_get_pid_limits() +{ + return pid_rules; +} diff --git a/src/tests/logctl.c b/src/tests/logctl.c index 5302b34..559bba4 100644 --- a/src/tests/logctl.c +++ b/src/tests/logctl.c @@ -353,21 +353,21 @@ void test_parse_options() const char *plog_err_args[] = { "x", "-g", "--buffer", "main" , NULL}; const char *set_err_args[] = { "x", "-s", "tizen" , NULL}; - static const struct expected_opts none_opts = { EXIT_SUCCESS, false, { ACTION_NONE , "*" , '\0', NULL, false, 0 } }; - static const struct expected_opts garbage_opts = { EXIT_SUCCESS, false, { ACTION_NONE , "*" , '\0', NULL, false, 0 } }; - static const struct expected_opts clear_opts = { EXIT_SUCCESS, true , { ACTION_SET , "tag_to_clear", '*', NULL, false, 0 } }; - static const struct expected_opts clr_all_opts = { EXIT_SUCCESS, true , { ACTION_CLEAR, "*" , '\0', NULL, false, 0 } }; - static const struct expected_opts set_opts = { EXIT_SUCCESS, true , { ACTION_SET , "*" , 'E', "15", false, 0 } }; - static const struct expected_opts get_opts = { EXIT_SUCCESS, true , { ACTION_GET , "*" , '*', NULL, false, 0 } }; - static const struct expected_opts dump_opts = { EXIT_SUCCESS, true , { ACTION_DUMP , "*" , '\0', NULL, false, 0 } }; - static const struct expected_opts enable_opts = { EXIT_SUCCESS, true , { ACTION_PLOG , "*" , '\0', NULL, true , 1 << LOG_ID_MAIN | 1 << LOG_ID_RADIO } }; - static const struct expected_opts disable_opts = { EXIT_SUCCESS, true , { ACTION_PLOG , "*" , '\0', NULL, false, 1 << LOG_ID_MAIN | 1 << LOG_ID_RADIO } }; - static const struct expected_opts help_opts = { EXIT_SUCCESS, false, { ACTION_NONE , "*" , '\0', NULL, false, 0 } }; - - static const struct expected_opts prio_err_opts = { EXIT_FAILURE, false, { ACTION_NONE , "*" , ']', NULL, false, 0 } }; - static const struct expected_opts buf_err_opts = { EXIT_FAILURE, false, { ACTION_NONE , "*" , '\0', NULL, false, 0 } }; - static const struct expected_opts plog_err_opts = { EXIT_FAILURE, false, { ACTION_GET , "*" , '\0', NULL, false, 1 << LOG_ID_MAIN } }; - static const struct expected_opts set_err_opts = { EXIT_FAILURE, false, { ACTION_NONE , "*" , '\0', NULL, false, 0 } }; + static const struct expected_opts none_opts = { EXIT_SUCCESS, false, { ACTION_NONE , "*" , '\0', -1, NULL, false, 0 } }; + static const struct expected_opts garbage_opts = { EXIT_SUCCESS, false, { ACTION_NONE , "*" , '\0', -1, NULL, false, 0 } }; + static const struct expected_opts clear_opts = { EXIT_SUCCESS, true , { ACTION_SET , "tag_to_clear", '*', -1, NULL, false, 0 } }; + static const struct expected_opts clr_all_opts = { EXIT_SUCCESS, true , { ACTION_CLEAR, "*" , '\0', -1, NULL, false, 0 } }; + static const struct expected_opts set_opts = { EXIT_SUCCESS, true , { ACTION_SET , "*" , 'E', -1, "15", false, 0 } }; + static const struct expected_opts get_opts = { EXIT_SUCCESS, true , { ACTION_GET , "*" , '*', -1, NULL, false, 0 } }; + static const struct expected_opts dump_opts = { EXIT_SUCCESS, true , { ACTION_DUMP , "*" , '\0', -1, NULL, false, 0 } }; + static const struct expected_opts enable_opts = { EXIT_SUCCESS, true , { ACTION_PLOG , "*" , '\0', -1, NULL, true , 1 << LOG_ID_MAIN | 1 << LOG_ID_RADIO } }; + static const struct expected_opts disable_opts = { EXIT_SUCCESS, true , { ACTION_PLOG , "*" , '\0', -1, NULL, false, 1 << LOG_ID_MAIN | 1 << LOG_ID_RADIO } }; + static const struct expected_opts help_opts = { EXIT_SUCCESS, false, { ACTION_NONE , "*" , '\0', -1, NULL, false, 0 } }; + + static const struct expected_opts prio_err_opts = { EXIT_FAILURE, false, { ACTION_NONE , "*" , ']', -1, NULL, false, 0 } }; + static const struct expected_opts buf_err_opts = { EXIT_FAILURE, false, { ACTION_NONE , "*" , '\0', -1, NULL, false, 0 } }; + static const struct expected_opts plog_err_opts = { EXIT_FAILURE, false, { ACTION_GET , "*" , '\0', -1, NULL, false, 1 << LOG_ID_MAIN } }; + static const struct expected_opts set_err_opts = { EXIT_FAILURE, false, { ACTION_NONE , "*" , '\0', -1, NULL, false, 0 } }; #define CHECK_OPTION_SET(TYPE) check_option_set(NELEMS(TYPE##_args) - 1, TYPE##_args, &(TYPE##_opts)) CHECK_OPTION_SET(none); diff --git a/src/tests/pid_limiter.c b/src/tests/pid_limiter.c new file mode 100644 index 0000000..69d457e --- /dev/null +++ b/src/tests/pid_limiter.c @@ -0,0 +1,74 @@ +// C +#include +#include + +// POSIX +#include + +// DLog +#include +#include +#include + +time_t time_ret = 0; +void advance_clock() +{ + time_ret += 61; +} +time_t __wrap_time(time_t *t) +{ + return time_ret; +} + +pid_t pid_ret = 0; +pid_t __wrap_getpid() +{ + return pid_ret; +} + +int main() +{ + struct log_config conf = {NULL, NULL}; + log_config_set(&conf, "pidlimit|77", "7"); + log_config_set(&conf, "pidlimit|88", "0"); + log_config_set(&conf, "pidlimit|55", "125"); + assert(__log_limiter_create(&conf)); + __log_limiter_update(&conf); + +#define PASS assert(1 == __log_limiter_pass_log("FOO", 'W')) +#define BLOCK assert(0 == __log_limiter_pass_log("FOO", 'W')) + + pid_ret = 77; + for (int i = 0; i < 7; ++i) PASS; + for (int i = 7; i < 100; ++i) BLOCK; + __log_limiter_update(&conf); // shouldn't have any effect + for (int i = 100; i < 200; ++i) BLOCK; + + advance_clock(); + + for (int i = 0; i < 7; ++i) PASS; + for (int i = 7; i < 100; ++i) BLOCK; + pid_ret = 55; // pid change emulates a fork + for (int i = 0; i < 125; ++i) PASS; + for (int i = 125; i < 333; ++i) BLOCK; + + advance_clock(); + + for (int i = 0; i < 125; ++i) PASS; + for (int i = 125; i < 666; ++i) BLOCK; + + log_config_remove(&conf, "pidlimit|55"); + __log_limiter_update(&conf); + advance_clock(); + + for (int i = 0; i < 1936; ++i) PASS; + pid_ret = 88; + for (int i = 0; i < 1939; ++i) BLOCK; // ¡No pasarán! + pid_ret = 99; + for (int i = 0; i < 1975; ++i) PASS; // Hemos pasado + +#undef BLOCK +#undef PASS + + __log_limiter_destroy(); +} diff --git a/tests/dlog_test.in b/tests/dlog_test.in index 4db1db4..caed8d4 100644 --- a/tests/dlog_test.in +++ b/tests/dlog_test.in @@ -8,6 +8,7 @@ LOG_DETAILS= #relevant pids default vals UTIL_PID=-1 +DLOGSEND_PID=-1 MT_TEST=-1 LOGGER=-1 @@ -686,6 +687,60 @@ if [ "$TEST_DYNAMIC_FILTERS" == "true" ]; then LOG_DETAILS="testing proper SMACK label for dynamic config file location" ORIG_FILTERS_DIR=$(cat $ORIGINAL_CONFIG_PATH | grep dynamic_config_path | awk -F "=" '{print $2}') [ $(ls -dZ $ORIG_FILTERS_DIR | awk -F " " '{print $1}') == "System::Shared" ] && ok || fail + + # This creates a process which will wait for SIGCONT and then spam logs. We know its PID, so we can add + # PID-based limits and see what happens. + sh -c 'kill -s STOP $$; exec dlogsend -b main -c 100 "Pid test"' & + DLOGSEND_PID=$! + while [[ $(cut -d ' ' -f 3 < /proc/$DLOGSEND_PID/stat) != "T" ]]; do :; done + dlogctl --pid $DLOGSEND_PID -s deny + kill -s CONT $DLOGSEND_PID + wait $DLOGSEND_PID + LOG_DETAILS="testing if PID limiting works (1/10)" + [[ $(dlogutil --pid $DLOGSEND_PID -d | wc -l) -eq 0 ]] && ok || fail + LOG_DETAILS="testing if PID limiting works (2/10)" + [[ $(dlogctl --pid $DLOGSEND_PID -g) == "Denied" ]] && ok || fail + LOG_DETAILS="testing if PID limiting works (3/10)" + dlogctl -g | grep -q "Denied for $DLOGSEND_PID" && ok || fail + + sh -c 'kill -s STOP $$; exec dlogsend -b main -c 100 "Pid test"' & + DLOGSEND_PID=$! + while [[ $(cut -d ' ' -f 3 < /proc/$DLOGSEND_PID/stat) != "T" ]]; do :; done + dlogctl --pid $DLOGSEND_PID -s 25 + kill -s CONT $DLOGSEND_PID + wait $DLOGSEND_PID + LOG_DETAILS="testing if PID limiting works (4/10)" + [[ $(dlogutil --pid $DLOGSEND_PID -d | wc -l) -eq 25 ]] && ok || fail + LOG_DETAILS="testing if PID limiting works (5/10)" + [[ $(dlogctl --pid $DLOGSEND_PID -g) == '25 logs/min' ]] && ok || fail + LOG_DETAILS="testing if PID limiting works (6/10)" + dlogctl -g | grep -q "25 logs/min for $DLOGSEND_PID" && ok || fail + + sh -c 'kill -s STOP $$; exec dlogsend -b main -c 100 "Pid test"' & + DLOGSEND_PID=$! + while [[ $(cut -d ' ' -f 3 < /proc/$DLOGSEND_PID/stat) != "T" ]]; do :; done + dlogctl --pid $DLOGSEND_PID -s allow + kill -s CONT $DLOGSEND_PID + wait $DLOGSEND_PID + LOG_DETAILS="testing if PID limiting works (7/10)" + [[ $(dlogutil --pid $DLOGSEND_PID -d | wc -l) -eq 100 ]] && ok || fail + LOG_DETAILS="testing if PID limiting works (8/10)" + [[ $(dlogctl --pid $DLOGSEND_PID -g) == 'Unlimited' ]] && ok || fail + LOG_DETAILS="testing if PID limiting works (9/10)" + dlogctl -g | grep -q "Unlimited for $DLOGSEND_PID" && ok || fail + + echo "refresh_rate_s=5" > "$RUNTIME_FILTERS_DIR/69-refresh-rate.conf" + sh -c 'kill -s STOP $$; exec dlogsend -b main -c 15 -d 1 "Pid test"' & + DLOGSEND_PID=$! + while [[ $(cut -d ' ' -f 3 < /proc/$DLOGSEND_PID/stat) != "T" ]]; do :; done + dlogctl --pid $DLOGSEND_PID -s 2 + kill -s CONT $DLOGSEND_PID + wait $DLOGSEND_PID + rm "$RUNTIME_FILTERS_DIR/69-refresh-rate.conf" + LOG_DETAILS="testing if PID limiting works (10/10)" + # In each 5 second period, 5 logs are sent, so there are 3 periods. + # 2 logs from each period are allowed. + [[ $(dlogutil --pid $DLOGSEND_PID -d | wc -l) -eq 6 ]] && ok || fail fi LOG_DETAILS="testing if libdlogutil clears the buffer correctly" -- 2.7.4