From: Stephan Bosch Date: Tue, 3 Sep 2024 00:10:48 +0000 (+0200) Subject: lib-settings: settings - Add support for sorting filter array fields X-Git-Tag: 2.4.1~639 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e26c6de1c9f6b11b90627fe4b1f0c65baa5bf05b;p=thirdparty%2Fdovecot%2Fcore.git lib-settings: settings - Add support for sorting filter array fields --- diff --git a/src/lib-settings/settings-parser.h b/src/lib-settings/settings-parser.h index 3c131701ce..176ff24279 100644 --- a/src/lib-settings/settings-parser.h +++ b/src/lib-settings/settings-parser.h @@ -55,7 +55,13 @@ enum setting_apply_flags { SETTING_APPLY_FLAG_NO_EXPAND = BIT(1), }; -#define SETTING_DEFINE_LIST_END { 0, 0, NULL, 0, NULL, NULL } +#define SETTING_DEFINE_LIST_END { 0, 0, NULL, 0, NULL, NULL, NULL } + +struct setting_filter_array_order { + const struct setting_parser_info *info; + const char *field_name; + bool reverse; +}; struct setting_define { enum setting_type type; @@ -64,6 +70,7 @@ struct setting_define { size_t offset; const char *filter_array_field_name; + const struct setting_filter_array_order *filter_array_order; const char *required_setting; }; diff --git a/src/lib-settings/settings.c b/src/lib-settings/settings.c index fee3935d31..79e65023bc 100644 --- a/src/lib-settings/settings.c +++ b/src/lib-settings/settings.c @@ -1891,7 +1891,7 @@ settings_instance_get(struct settings_apply_ctx *ctx, } static int -settings_get_full(struct event *event, +settings_get_real(struct event *event, const char *filter_key, const char *filter_value, const struct setting_parser_info *info, const struct settings_get_params *params, @@ -1977,11 +1977,8 @@ settings_get_full(struct event *event, .filter_name_required = filter_name_required, }; - int ret; - T_BEGIN { - ret = settings_instance_get(&ctx, source_filename, source_linenum, - set_r, error_r); - } T_END_PASS_STR_IF(ret < 0, error_r); + int ret = settings_instance_get(&ctx, source_filename, source_linenum, + set_r, error_r); if (ret < 0) { *error_r = t_strdup_printf("%s settings%s: %s", info->name, filter_name == NULL ? "" : @@ -1996,6 +1993,225 @@ settings_get_full(struct event *event, return ret; } +struct settings_filter_array_item { + union { + unsigned int uint; + const char *str; + } order; + const char *value; +}; + +static int +settings_filter_array_uint_cmp(const struct settings_filter_array_item *item1, + const struct settings_filter_array_item *item2) +{ + if (item1->order.uint < item2->order.uint) + return -1; + if (item1->order.uint > item2->order.uint) + return 1; + return 0; +} + +static int +settings_filter_array_str_cmp(const struct settings_filter_array_item *item1, + const struct settings_filter_array_item *item2) +{ + return strcmp(item1->order.str, item2->order.str); +} + +static int +settings_sort_filter_array(struct event *event, const char *field_name, + const struct setting_filter_array_order *order, + ARRAY_TYPE(const_string) *fvals_arr, + const struct settings_get_params *params, + const char *source_filename, + unsigned int source_linenum, const char **error_r) +{ + const char **fvals; + unsigned int fidx, count, i; + + if (!array_is_created(fvals_arr)) + return 0; + + /* Find definition index of order field */ + if (!setting_parser_info_find_key(order->info, order->field_name, + &fidx)) + i_panic("BUG: Order field %s for filter array %s in %s " + "not found", + order->field_name, field_name, order->info->name); + + /* Resolve alias */ + while (fidx > 0 && order->info->defines[fidx].type == SET_ALIAS) + fidx--; + + /* Determine type of field */ + enum { + _ITEM_TYPE_UINT, + _ITEM_TYPE_STR, + } item_type; + + const struct setting_define *order_def = &order->info->defines[fidx]; + switch (order_def->type) { + case SET_UINT: + case SET_UINT_OCT: + case SET_TIME: + case SET_TIME_MSECS: + item_type = _ITEM_TYPE_UINT; + break; + case SET_STR: + case SET_STR_NOVARS: + item_type = _ITEM_TYPE_STR; + break; + case SET_ALIAS: + i_panic("BUG: Order field %s for filter array %s in %s " + "is an invalid alias", + order->field_name, field_name, order->info->name); + default: + i_panic("BUG: Order field %s for filter array %s in %s " + "has a type unsuitable for defining an order", + order->field_name, field_name, order->info->name); + } + + /* Read all order fields from filter array items */ + ARRAY(struct settings_filter_array_item) items_arr; + + fvals = array_get_modifiable(fvals_arr, &count); + t_array_init(&items_arr, count); + for (i = 0; i < count; i++) { + const void *set; + int ret; + + T_BEGIN { + ret = settings_get_real(event, field_name, fvals[i], + order->info, params, + source_filename, source_linenum, + &set, error_r); + } T_END_PASS_STR_IF(ret < 0, error_r); + if (ret < 0) + return -1; + if (ret == 0) { + *error_r = t_strdup_printf( + "Filter %s=%s unexpectedly not found while sorting " + "(invalid userdb or -o override settings?)", + field_name, fvals[i]); + return -1; + } + + pool_t *pool_p = PTR_OFFSET(set, order->info->pool_offset1 - 1); + pool_t pool = *pool_p; + struct settings_filter_array_item *item; + + item = array_append_space(&items_arr); + item->value = fvals[i]; + + switch (item_type) { + case _ITEM_TYPE_UINT: + item->order.uint = *((unsigned int *) + PTR_OFFSET(set, order_def->offset)); + break; + case _ITEM_TYPE_STR: + item->order.str = *((const char **) + PTR_OFFSET(set, order_def->offset)); + item->order.str = t_strdup(item->order.str); + break; + default: + i_unreached(); + } + + pool_unref(&pool); + } + + /* Sort the filter array items based on order field */ + switch (item_type) { + case _ITEM_TYPE_UINT: + array_sort(&items_arr, settings_filter_array_uint_cmp); + break; + case _ITEM_TYPE_STR: + array_sort(&items_arr, settings_filter_array_str_cmp); + break; + default: + i_unreached(); + } + + /* Move the items in the settings */ + const struct settings_filter_array_item *items; + unsigned int items_count; + + items = array_get(&items_arr, &items_count); + i_assert(items_count == count); + for (i = 0; i < count; i++) { + if (!order->reverse) + fvals[i] = items[i].value; + else + fvals[i] = items[count - 1 - i].value; + } + return 0; +} + +static int +settings_get_full(struct event *event, + const char *filter_key, const char *filter_value, + const struct setting_parser_info *info, + const struct settings_get_params *params, + const char *source_filename, + unsigned int source_linenum, + const void **set_r, const char **error_r) +{ + const void *set; + int ret; + + *set_r = NULL; + T_BEGIN { + ret = settings_get_real(event, filter_key, filter_value, info, + params, source_filename, source_linenum, + &set, error_r); + } T_END_PASS_STR_IF(ret < 0, error_r); + if (ret <= 0) + return ret; + if ((params->flags & SETTINGS_GET_FLAG_SORT_FILTER_ARRAYS) == 0) { + /* Sorting filter arrays disabled */ + *set_r = set; + return 1; + } + + const struct setting_define *def = info->defines; + + /* Sort filter arrays when order is defined */ + for (def = info->defines; def->key != NULL; def++) { + int ret; + + if (def->type != SET_FILTER_ARRAY) { + /* Not a filter array */ + continue; + } + if (def->filter_array_order == NULL) { + /* No order defined */ + continue; + } + + ARRAY_TYPE(const_string) *fvals_arr = + PTR_OFFSET(set, def->offset); + i_assert(fvals_arr != NULL); + + /* Sort this array */ + T_BEGIN { + ret = settings_sort_filter_array( + event, def->key, def->filter_array_order, + fvals_arr, params, + source_filename, source_linenum, error_r); + } T_END_PASS_STR_IF(ret < 0, error_r); + if (ret < 0) { + pool_t *pool_p = PTR_OFFSET(set, info->pool_offset1 - 1); + pool_t pool = *pool_p; + pool_unref(&pool); + return -1; + } + } + + *set_r = set; + return 1; +} + #undef settings_get int settings_get(struct event *event, const struct setting_parser_info *info, diff --git a/src/lib-settings/settings.h b/src/lib-settings/settings.h index d514c3ac35..bac21f7a6e 100644 --- a/src/lib-settings/settings.h +++ b/src/lib-settings/settings.h @@ -48,6 +48,8 @@ enum settings_get_flags { /* For unit tests: Don't validate that settings struct keys match th binary config file. */ SETTINGS_GET_NO_KEY_VALIDATION = BIT(3), + /* Sort filter arrays with defined ordering */ + SETTINGS_GET_FLAG_SORT_FILTER_ARRAYS = BIT(4), }; struct settings_get_params { diff --git a/src/lib-settings/test-settings.c b/src/lib-settings/test-settings.c index 7d51bfa6af..321a4f5a4e 100644 --- a/src/lib-settings/test-settings.c +++ b/src/lib-settings/test-settings.c @@ -3,11 +3,16 @@ #include "lib.h" #include "array.h" #include "net.h" +#include "settings.h" #include "settings-legacy.h" #include "istream.h" #include "ostream.h" #include "test-common.h" +/* + * settings_read_nosection() + */ + #define TEST_SETTING_FILE ".test_settings.conf" static const char *config_contents = @@ -155,10 +160,263 @@ static void test_settings_read_nosection(void) test_end(); } +/* + * settings_get() + */ + +struct test2_fruit_settings { + pool_t pool; + + const char *name; + bool eat; + unsigned int preference; +}; + +struct test2_settings { + pool_t pool; + + const char *title; + ARRAY_TYPE(const_string) fruits; +}; + +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type("test2_fruit_"#name, name, \ + struct test2_fruit_settings) + +static const struct setting_define test2_fruit_setting_defines[] = { + DEF(STR, name), + DEF(BOOL, eat), + DEF(UINT, preference), + SETTING_DEFINE_LIST_END +}; + +static struct test2_fruit_settings test2_fruit_default_settings = { + .name = "", + .eat = FALSE, + .preference = UINT_MAX, +}; + +static const struct setting_parser_info test2_fruit_setting_parser_info = { + .name = "test2_fruit", + + .defines = test2_fruit_setting_defines, + .defaults = &test2_fruit_default_settings, + + .struct_size = sizeof(struct test2_fruit_settings), + .pool_offset1 = 1 + offsetof(struct test2_fruit_settings, pool), +}; + +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type("test2_"#name, name, struct test2_settings) + +static struct setting_define test2_setting_defines[] = { + DEF(STR, title), + + { .type = SET_FILTER_ARRAY, .key = "test2_fruit", + .offset = offsetof(struct test2_settings, fruits), + .filter_array_field_name = "test2_fruit_name" }, + SETTING_DEFINE_LIST_END +}; + +static struct test2_settings test2_default_settings = { + .title = "", + .fruits = ARRAY_INIT, +}; + +static const struct setting_parser_info test2_setting_parser_info = { + .name = "test2", + + .defines = test2_setting_defines, + .defaults = &test2_default_settings, + + .struct_size = sizeof(struct test2_settings), + .pool_offset1 = 1 + offsetof(struct test2_settings, pool), +}; + +static void test_settings_get_scenario( + const char *scenario_name, + const struct setting_filter_array_order *order, const char *result[]) +{ + static const char *const settings[] = { + "test2_title=Fruit Preferences", + "test2_fruit+=Orange", + "test2_fruit/Orange/name=Orange", // FIXME: why is this not applied implicitly? + "test2_fruit/Orange/eat=no", + "test2_fruit/Orange/preference=1", + "test2_fruit+=Apple", + "test2_fruit/Apple/name=Apple", + "test2_fruit/Apple/eat=yes", + "test2_fruit/Apple/preference=2", + "test2_fruit+=Grapefruit", + "test2_fruit/Grapefruit/name=Grapefruit", + "test2_fruit/Grapefruit/eat=no", + "test2_fruit/Grapefruit/preference=0", + "test2_fruit+=Mulberry", + "test2_fruit/Mulberry/name=Mulberry", + "test2_fruit/Mulberry/eat=yes", + "test2_fruit/Mulberry/preference=6", + "test2_fruit+=Lemon", + "test2_fruit/Lemon/name=Lemon", + "test2_fruit/Lemon/eat=yes", + "test2_fruit/Lemon/preference=4", + "test2_fruit+=Fig", + "test2_fruit/Fig/name=Fig", + "test2_fruit/Fig/eat=no", + "test2_fruit/Fig/preference=5", + "test2_fruit+=PineApple", + "test2_fruit/PineApple/name=PineApple", + "test2_fruit/PineApple/eat=yes", + "test2_fruit/PineApple/preference=3", + NULL + }; + const char *error = NULL; + pool_t pool; + static struct test2_settings *set; + static struct settings_root *set_root; + int ret; + + /* Modified like this only for testing; normally this is all const */ + test2_setting_defines[1].filter_array_order = order; + + test_begin(t_strdup_printf("settings_get - %s", scenario_name)); + + pool = pool_alloconly_create(MEMPOOL_GROWING"test2 pool", 2048); + + set_root = settings_root_init(); + for (unsigned int i = 0; settings[i] != NULL; i++) { + const char *key, *value; + t_split_key_value_eq(settings[i], &key, &value); + settings_root_override(set_root, key, value, + SETTINGS_OVERRIDE_TYPE_CODE); + } + struct event *event = event_create(NULL); + event_set_ptr(event, SETTINGS_EVENT_ROOT, set_root); + + ret = settings_get(event, &test2_setting_parser_info, + (order != NULL ? + SETTINGS_GET_FLAG_SORT_FILTER_ARRAYS : 0), + &set, &error); + test_assert(ret == 0); + test_assert(error == NULL); + if (error != NULL) + i_error("%s", error); + if (ret == 0) { + test_assert(array_is_created(&set->fruits)); + test_assert_strcmp(set->title, "Fruit Preferences"); + } + if (ret == 0 && array_is_created(&set->fruits)) { + unsigned int count, i; + const char *const *fruits = array_get(&set->fruits, &count); + for (i = 0; i < count; i++) { + struct test2_fruit_settings *fruit_set; + ret = settings_get_filter( + event, "test2_fruit", fruits[i], + &test2_fruit_setting_parser_info, 0, + &fruit_set, &error); + test_assert(ret == 0); + test_assert(error == NULL); + + test_assert_strcmp(fruit_set->name, result[i]); + + settings_free(fruit_set); + } + } + + settings_free(set); + settings_root_deinit(&set_root); + pool_unref(&pool); + event_unref(&event); + + test_end(); +} + +static void test_settings_get(void) +{ + static const struct setting_filter_array_order order_by_preference = { + .info = &test2_fruit_setting_parser_info, + .field_name = "test2_fruit_preference", + }; + static struct setting_filter_array_order order_by_name = { + .info = &test2_fruit_setting_parser_info, + .field_name = "test2_fruit_name", + }; + static const struct setting_filter_array_order + order_by_preference_reverse = { + .info = &test2_fruit_setting_parser_info, + .field_name = "test2_fruit_preference", + .reverse = TRUE, + }; + static struct setting_filter_array_order order_by_name_reverse = { + .info = &test2_fruit_setting_parser_info, + .field_name = "test2_fruit_name", + .reverse = TRUE, + }; + static const char *result_unsorted[] = { + "Orange", + "Apple", + "Grapefruit", + "Mulberry", + "Lemon", + "Fig", + "PineApple", + }; + static const char *result_sorted_preference[] = { + "Grapefruit", + "Orange", + "Apple", + "PineApple", + "Lemon", + "Fig", + "Mulberry", + }; + static const char *result_sorted_name[] = { + "Apple", + "Fig", + "Grapefruit", + "Lemon", + "Mulberry", + "Orange", + "PineApple", + }; + static const char *result_sorted_preference_reverse[] = { + "Mulberry", + "Fig", + "Lemon", + "PineApple", + "Apple", + "Orange", + "Grapefruit", + }; + static const char *result_sorted_name_reverse[] = { + "PineApple", + "Orange", + "Mulberry", + "Lemon", + "Grapefruit", + "Fig", + "Apple", + }; + + test_settings_get_scenario("not sorted", NULL, result_unsorted); + test_settings_get_scenario("sort by preference", &order_by_preference, + result_sorted_preference); + test_settings_get_scenario("sort by name", &order_by_name, + result_sorted_name); + test_settings_get_scenario("sort by preference (reverse)", + &order_by_preference_reverse, + result_sorted_preference_reverse); + test_settings_get_scenario("sort by name (reverse)", + &order_by_name_reverse, + result_sorted_name_reverse); +} + int main(void) { static void (*const test_functions[])(void) = { test_settings_read_nosection, + test_settings_get, NULL }; return test_run(test_functions);