}
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,
.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 ? "" :
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,
#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 =
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);