From 8ef1409ace7869c03870a4941f227eca2d81fdf2 Mon Sep 17 00:00:00 2001 From: Leonid Genkin Date: Mon, 7 Dec 2020 08:15:59 +0200 Subject: [PATCH] UCS/DATASTRUCT: Add allow_list data struct --- src/ucs/config/parser.c | 95 +++++++++++++++++++++++++++++++++-- src/ucs/config/parser.h | 11 ++++ src/ucs/config/types.h | 17 +++++++ test/gtest/ucs/test_config.cc | 63 ++++++++++++++++++----- 4 files changed, 169 insertions(+), 17 deletions(-) diff --git a/src/ucs/config/parser.c b/src/ucs/config/parser.c index 732e73be076..657432c21ef 100644 --- a/src/ucs/config/parser.c +++ b/src/ucs/config/parser.c @@ -25,6 +25,8 @@ /* width of titles in docstring */ #define UCS_CONFIG_PARSER_DOCSTR_WIDTH 10 +/* String literal for allow-list */ +#define UCS_CONFIG_PARSER_ALL "all" /* list of prefixes for a configuration variable, used to dump all possible * aliases. @@ -47,6 +49,7 @@ extern char **environ; UCS_LIST_HEAD(ucs_config_global_list); static khash_t(ucs_config_env_vars) ucs_config_parser_env_vars = {0}; static pthread_mutex_t ucs_config_parser_env_vars_hash_lock = PTHREAD_MUTEX_INITIALIZER; +static char ucs_config_parser_negate = '^'; const char *ucs_async_mode_names[] = { @@ -814,10 +817,14 @@ ucs_status_t ucs_config_clone_array(const void *src, void *dest, const void *arg ucs_status_t status; unsigned i; - dest_array->data = ucs_calloc(src_array->count, array->elem_size, - "config array"); - if (dest_array->data == NULL) { - return UCS_ERR_NO_MEMORY; + if (src_array->count > 0) { + dest_array->data = ucs_calloc(src_array->count, array->elem_size, + "config array"); + if (dest_array->data == NULL) { + return UCS_ERR_NO_MEMORY; + } + } else { + dest_array->data = NULL; } dest_array->count = src_array->count; @@ -855,6 +862,86 @@ void ucs_config_help_array(char *buf, size_t max, const void *arg) array->parser.help(buf + strlen(buf), max - strlen(buf), array->parser.arg); } +int ucs_config_sscanf_allow_list(const char *buf, void *dest, const void *arg) +{ + ucs_config_allow_list_t *field = dest; + unsigned offset = 0; + + if (buf[0] == ucs_config_parser_negate) { + field->mode = UCS_CONFIG_ALLOW_LIST_NEGATE; + offset++; + } else { + field->mode = UCS_CONFIG_ALLOW_LIST_ALLOW; + } + + if (!ucs_config_sscanf_array(&buf[offset], &field->array, arg)) { + return 0; + } + + if ((field->array.count >= 1) && + !strcmp(field->array.names[0], UCS_CONFIG_PARSER_ALL)) { + field->mode = UCS_CONFIG_ALLOW_LIST_ALLOW_ALL; + ucs_config_release_array(&field->array, arg); + if (field->array.count != 1) { + return 0; + } + + field->array.count = 0; + } + + return 1; +} + +int ucs_config_sprintf_allow_list(char *buf, size_t max, const void *src, + const void *arg) +{ + const ucs_config_allow_list_t *allow_list = src; + size_t offset = 0; + + if (allow_list->mode == UCS_CONFIG_ALLOW_LIST_ALLOW_ALL) { + snprintf(buf, max, UCS_CONFIG_PARSER_ALL); + return 1; + } + + if (allow_list->mode == UCS_CONFIG_ALLOW_LIST_NEGATE) { + buf[offset++] = ucs_config_parser_negate; + max--; + } + + return ucs_config_sprintf_array(&buf[offset], max, &allow_list->array, arg); +} + +ucs_status_t ucs_config_clone_allow_list(const void *src, void *dest, const void *arg) +{ + const ucs_config_allow_list_t *src_list = src; + ucs_config_allow_list_t *dest_list = dest; + + dest_list->mode = src_list->mode; + return ucs_config_clone_array(&src_list->array, &dest_list->array, arg); +} + +void ucs_config_release_allow_list(void *ptr, const void *arg) +{ + ucs_config_allow_list_t *allow_list = ptr; + + if (allow_list->mode == UCS_CONFIG_ALLOW_LIST_ALLOW_ALL) { + return; + } + + ucs_config_release_array(&allow_list->array, arg); +} + +void ucs_config_help_allow_list(char *buf, size_t max, const void *arg) +{ + const ucs_config_array_t *array = arg; + + snprintf( + buf, + max, "comma-separated list (use \"all\" for including \ + all items or \'^\' for negation) of: "); + array->parser.help(buf + strlen(buf), max - strlen(buf), array->parser.arg); +} + int ucs_config_sscanf_table(const char *buf, void *dest, const void *arg) { char *tokens; diff --git a/src/ucs/config/parser.h b/src/ucs/config/parser.h index 3c4659bce02..89bf8c4f244 100644 --- a/src/ucs/config/parser.h +++ b/src/ucs/config/parser.h @@ -203,6 +203,13 @@ ucs_status_t ucs_config_clone_array(const void *src, void *dest, const void *arg void ucs_config_release_array(void *ptr, const void *arg); void ucs_config_help_array(char *buf, size_t max, const void *arg); +int ucs_config_sscanf_allow_list(const char *buf, void *dest, const void *arg); +int ucs_config_sprintf_allow_list(char *buf, size_t max, const void *src, + const void *arg); +ucs_status_t ucs_config_clone_allow_list(const void *src, void *dest, const void *arg); +void ucs_config_release_allow_list(void *ptr, const void *arg); +void ucs_config_help_allow_list(char *buf, size_t max, const void *arg); + int ucs_config_sscanf_table(const char *buf, void *dest, const void *arg); ucs_status_t ucs_config_clone_table(const void *src, void *dest, const void *arg); void ucs_config_release_table(void *ptr, const void *arg); @@ -321,6 +328,10 @@ void ucs_config_help_generic(char *buf, size_t max, const void *arg); ucs_config_clone_array, ucs_config_release_array, \ ucs_config_help_array, &ucs_config_array_##a} +#define UCS_CONFIG_TYPE_ALLOW_LIST {ucs_config_sscanf_allow_list, ucs_config_sprintf_allow_list, \ + ucs_config_clone_allow_list, ucs_config_release_allow_list, \ + ucs_config_help_allow_list, &ucs_config_array_string} + #define UCS_CONFIG_TYPE_TABLE(t) {ucs_config_sscanf_table, NULL, \ ucs_config_clone_table, ucs_config_release_table, \ ucs_config_help_table, t} diff --git a/src/ucs/config/types.h b/src/ucs/config/types.h index 30699b21ed5..a23b0f30dd9 100644 --- a/src/ucs/config/types.h +++ b/src/ucs/config/types.h @@ -111,6 +111,23 @@ typedef enum { typedef UCS_CONFIG_STRING_ARRAY_FIELD(names) ucs_config_names_array_t; + +/** + * Enum for representing possible modes of an "allow-list" + */ +typedef enum { + UCS_CONFIG_ALLOW_LIST_ALLOW_ALL, /* Allow all possible options */ + UCS_CONFIG_ALLOW_LIST_ALLOW, /* Allow only the specified options */ + UCS_CONFIG_ALLOW_LIST_NEGATE /* Negate (forbid) the specified options */ +} ucs_config_allow_list_mode_t; + + +typedef struct { + ucs_config_names_array_t array; + ucs_config_allow_list_mode_t mode; +} ucs_config_allow_list_t; + + /** * @ingroup UCS_RESOURCE * BSD socket address specification. diff --git a/test/gtest/ucs/test_config.cc b/test/gtest/ucs/test_config.cc index 3bcf5e9e074..73a92d73621 100644 --- a/test/gtest/ucs/test_config.cc +++ b/test/gtest/ucs/test_config.cc @@ -91,6 +91,7 @@ typedef struct { ucs_time_t time_value; ucs_time_t time_auto; ucs_time_t time_inf; + ucs_config_allow_list_t allow_list; } car_opts_t; @@ -218,6 +219,9 @@ ucs_config_field_t car_opts_table[] = { {"TIME_INF", "inf", "Time value \"inf\"", ucs_offsetof(car_opts_t, time_inf), UCS_CONFIG_TYPE_TIME_UNITS}, + {"ALLOW_LIST", "all", "Allow-list: \"all\" OR \"val1,val2\" OR \"^val1,val2\"", + ucs_offsetof(car_opts_t, allow_list), UCS_CONFIG_TYPE_ALLOW_LIST}, + {NULL} }; @@ -414,6 +418,8 @@ UCS_TEST_F(test_config, parse_default) { EXPECT_EQ(ucs_time_from_sec(1.0), opts->time_value); EXPECT_EQ(UCS_TIME_AUTO, opts->time_auto); EXPECT_EQ(UCS_TIME_INFINITY, opts->time_inf); + EXPECT_EQ(UCS_CONFIG_ALLOW_LIST_ALLOW_ALL, opts->allow_list.mode); + EXPECT_EQ(0, opts->allow_list.array.count); } UCS_TEST_F(test_config, clone) { @@ -436,6 +442,7 @@ UCS_TEST_F(test_config, clone) { } EXPECT_EQ(COLOR_WHITE, (*opts_clone_ptr)->color); + EXPECT_EQ(UCS_CONFIG_ALLOW_LIST_ALLOW_ALL, (*opts_clone_ptr)->allow_list.mode); delete opts_clone_ptr; } @@ -540,27 +547,23 @@ UCS_TEST_F(test_config, unused) { UCS_TEST_F(test_config, dump) { /* aliases must not be counted here */ - test_config_print_opts(UCS_CONFIG_PRINT_CONFIG, 31u); + test_config_print_opts(UCS_CONFIG_PRINT_CONFIG, 32u); } UCS_TEST_F(test_config, dump_hidden) { /* aliases must be counted here */ - test_config_print_opts((UCS_CONFIG_PRINT_CONFIG | - UCS_CONFIG_PRINT_HIDDEN), - 38u); + test_config_print_opts(UCS_CONFIG_PRINT_CONFIG | UCS_CONFIG_PRINT_HIDDEN, 39u); } UCS_TEST_F(test_config, dump_hidden_check_alias_name) { /* aliases must be counted here */ - test_config_print_opts((UCS_CONFIG_PRINT_CONFIG | - UCS_CONFIG_PRINT_HIDDEN | - UCS_CONFIG_PRINT_DOC), - 38u); - - test_config_print_opts((UCS_CONFIG_PRINT_CONFIG | - UCS_CONFIG_PRINT_HIDDEN | - UCS_CONFIG_PRINT_DOC), - 38u, "TEST_"); + test_config_print_opts( + UCS_CONFIG_PRINT_CONFIG | UCS_CONFIG_PRINT_HIDDEN | UCS_CONFIG_PRINT_DOC, + 39u); + + test_config_print_opts( + UCS_CONFIG_PRINT_CONFIG | UCS_CONFIG_PRINT_HIDDEN | UCS_CONFIG_PRINT_DOC, + 39u, "TEST_"); } UCS_TEST_F(test_config, deprecated) { @@ -594,3 +597,37 @@ UCS_TEST_F(test_config, deprecated) { /* reset to not warn about unused env vars */ ucs_global_opts.warn_unused_env_vars = 0; } + +UCS_TEST_F(test_config, test_allow_list) { + const std::string allow_list = "UCX_ALLOW_LIST"; + + { + /* coverity[tainted_string_argument] */ + ucs::scoped_setenv env(allow_list.c_str(), "first,second"); + + car_opts opts(UCS_DEFAULT_ENV_PREFIX, NULL); + EXPECT_EQ(UCS_CONFIG_ALLOW_LIST_ALLOW, opts->allow_list.mode); + EXPECT_EQ(2, opts->allow_list.array.count); + EXPECT_EQ(std::string("first"), opts->allow_list.array.names[0]); + EXPECT_EQ(std::string("second"), opts->allow_list.array.names[1]); + } + + { + /* coverity[tainted_string_argument] */ + ucs::scoped_setenv env(allow_list.c_str(), "^first,second"); + + car_opts opts(UCS_DEFAULT_ENV_PREFIX, NULL); + EXPECT_EQ(UCS_CONFIG_ALLOW_LIST_NEGATE, opts->allow_list.mode); + EXPECT_EQ(2, opts->allow_list.array.count); + EXPECT_EQ(std::string("first"), opts->allow_list.array.names[0]); + EXPECT_EQ(std::string("second"), opts->allow_list.array.names[1]); + } +} + +UCS_TEST_F(test_config, test_allow_list_negative) +{ + ucs_config_allow_list_t field; + + EXPECT_EQ(ucs_config_sscanf_allow_list("all,all", &field, + &ucs_config_array_string), 0); +}