From: Timo Sirainen Date: Fri, 1 Mar 2024 12:38:00 +0000 (+0200) Subject: lib-settings: Add settings_hash() and settings_equal() X-Git-Tag: 2.4.1~964 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=29c7f88c2c089309f1117e3ca8f782d34333ee19;p=thirdparty%2Fdovecot%2Fcore.git lib-settings: Add settings_hash() and settings_equal() --- diff --git a/src/lib-settings/settings-parser.c b/src/lib-settings/settings-parser.c index b18e81523b..64d813c1e2 100644 --- a/src/lib-settings/settings-parser.c +++ b/src/lib-settings/settings-parser.c @@ -2,6 +2,7 @@ #include "lib.h" #include "array.h" +#include "crc32.h" #include "str.h" #include "str-parse.h" #include "read-full.h" @@ -915,6 +916,160 @@ bool settings_parser_check(struct setting_parser_context *ctx, pool_t pool, ctx->set_struct, error_r); } +unsigned int settings_hash(const struct setting_parser_info *info, + const void *set, const char *const *except_fields) +{ + unsigned int crc = 0; + + for (unsigned int i = 0; info->defines[i].key != NULL; i++) { + if (except_fields != NULL && + str_array_find(except_fields, info->defines[i].key)) + continue; + + const void *p = CONST_PTR_OFFSET(set, info->defines[i].offset); + switch (info->defines[i].type) { + case SET_BOOL: { + const bool *b = p; + crc = crc32_data_more(crc, b, sizeof(*b)); + break; + } + case SET_UINT: + case SET_UINT_OCT: + case SET_TIME: + case SET_TIME_MSECS: { + const unsigned int *i = p; + crc = crc32_data_more(crc, i, sizeof(*i)); + break; + } + case SET_SIZE: { + const uoff_t *s = p; + crc = crc32_data_more(crc, s, sizeof(*s)); + break; + } + case SET_IN_PORT: { + const in_port_t *port = p; + crc = crc32_data_more(crc, port, sizeof(*port)); + break; + } + case SET_STR: + case SET_STR_NOVARS: + case SET_ENUM: { + const char *const *str = p; + crc = crc32_str_more(crc, *str); + break; + } + case SET_FILE: { + const char *const *str = p; + const char *lf = strchr(*str, '\n'); + if (lf == NULL) + i_panic("Settings file value is missing LF"); + if (lf == *str) { + /* no filename - need to hash the content */ + crc = crc32_str_more(crc, *str + 1); + } else { + /* hashing the filename is enough */ + crc = crc32_data_more(crc, *str, lf - *str); + } + break; + } + case SET_STRLIST: + case SET_BOOLLIST: + case SET_FILTER_ARRAY: { + const ARRAY_TYPE(const_string) *list = p; + if (array_is_created(list)) { + const char *str; + array_foreach_elem(list, str) + crc = crc32_str_more(crc, str); + } + break; + } + case SET_ALIAS: + case SET_FILTER_NAME: + break; + } + } + return crc; +} + +bool settings_equal(const struct setting_parser_info *info, + const void *set1, const void *set2, + const char *const *except_fields) +{ + for (unsigned int i = 0; info->defines[i].key != NULL; i++) { + if (except_fields != NULL && + str_array_find(except_fields, info->defines[i].key)) + continue; + + const void *p1 = CONST_PTR_OFFSET(set1, info->defines[i].offset); + const void *p2 = CONST_PTR_OFFSET(set2, info->defines[i].offset); + switch (info->defines[i].type) { + case SET_BOOL: { + const bool *b1 = p1, *b2 = p2; + if (*b1 != *b2) + return FALSE; + break; + } + case SET_UINT: + case SET_UINT_OCT: + case SET_TIME: + case SET_TIME_MSECS: { + const unsigned int *i1 = p1, *i2 = p2; + if (*i1 != *i2) + return FALSE; + break; + } + case SET_SIZE: { + const uoff_t *s1 = p1, *s2 = p2; + if (*s1 != *s2) + return FALSE; + break; + } + case SET_IN_PORT: { + const in_port_t *port1 = p1, *port2 = p2; + if (*port1 != *port2) + return FALSE; + break; + } + case SET_STR: + case SET_STR_NOVARS: + case SET_ENUM: + case SET_FILE: { + const char *const *str1 = p1, *const *str2 = p2; + if (strcmp(*str1, *str2) != 0) + return FALSE; + break; + } + case SET_STRLIST: + case SET_BOOLLIST: + case SET_FILTER_ARRAY: { + const ARRAY_TYPE(const_string) *list1 = p1, *list2 = p2; + if (array_is_empty(list1)) { + if (!array_is_empty(list2)) + return FALSE; + break; + } + if (array_is_empty(list2)) + return FALSE; + + unsigned int i, count1, count2; + const char *const *str1 = array_get(list1, &count1); + const char *const *str2 = array_get(list2, &count2); + if (count1 != count2) + return FALSE; + for (i = 0; i < count1; i++) { + if (strcmp(str1[i], str2[i]) != 0) + return FALSE; + } + break; + } + case SET_ALIAS: + case SET_FILTER_NAME: + break; + } + } + return TRUE; +} + const char *settings_section_escape(const char *name) { #define CHAR_NEED_ESCAPE(c) \ diff --git a/src/lib-settings/settings-parser.h b/src/lib-settings/settings-parser.h index ac7730a3ae..1fe7c8dd91 100644 --- a/src/lib-settings/settings-parser.h +++ b/src/lib-settings/settings-parser.h @@ -264,6 +264,16 @@ bool settings_file_has_path(const char *value); const char *settings_file_get_value(pool_t pool, const struct settings_file *file); +/* Return hash of all the settings, except the specified fields + (NULL = no exceptions). */ +unsigned int settings_hash(const struct setting_parser_info *info, + const void *set, const char *const *except_fields); +/* Returns TRUE if the two settings structs are equal, except for the + specified fields (NULL = no exceptions). */ +bool settings_equal(const struct setting_parser_info *info, + const void *set1, const void *set2, + const char *const *except_fields); + /* Return section name escaped */ const char *settings_section_escape(const char *name); const char *settings_section_unescape(const char *name); diff --git a/src/lib-settings/test-settings-parser.c b/src/lib-settings/test-settings-parser.c index e4cb89c242..8791932e37 100644 --- a/src/lib-settings/test-settings-parser.c +++ b/src/lib-settings/test-settings-parser.c @@ -221,12 +221,173 @@ static void test_settings_section_escape(void) test_end(); } +struct test_settings { + bool b; + unsigned int i; + uoff_t size; + in_port_t port; + const char *str; + const char *file; + ARRAY_TYPE(const_string) strlist; +}; + +static void test_settings_hash_equals(void) +{ +#undef DEF +#define DEF(type, name) \ + SETTING_DEFINE_STRUCT_##type(#name, name, struct test_settings) + const struct setting_define defines[] = { + DEF(BOOL, b), + DEF(UINT, i), + DEF(SIZE, size), + DEF(IN_PORT, port), + DEF(STR, str), + DEF(FILE, file), + DEF(STRLIST, strlist), + + SETTING_DEFINE_LIST_END + }; + const struct setting_parser_info info = { + .defines = defines, + }; + struct test_settings set1 = { + .str = "", + .file = "\n", + }; + struct test_settings set2 = set1; + + test_begin("settings_hash() and settings_equal()"); + + test_assert(settings_equal(&info, &set1, &set2, NULL)); + + /* boolean */ + set1.b = TRUE; + test_assert(settings_hash(&info, &set1, NULL) != + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + set2.b = TRUE; + test_assert(settings_hash(&info, &set1, NULL) == + settings_hash(&info, &set2, NULL)); + test_assert(settings_equal(&info, &set1, &set2, NULL)); + + /* uint */ + set1.i = 1234567; + set2.i = 1234568; + test_assert(settings_hash(&info, &set1, NULL) != + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + set2.i = 1234567; + test_assert(settings_hash(&info, &set1, NULL) == + settings_hash(&info, &set2, NULL)); + test_assert(settings_equal(&info, &set1, &set2, NULL)); + + /* size */ + set1.size = 0x500000000ULL; + set2.size = 0x600000000ULL; + test_assert(settings_hash(&info, &set1, NULL) != + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + set2.size = 0x500000000ULL; + test_assert(settings_hash(&info, &set1, NULL) == + settings_hash(&info, &set2, NULL)); + test_assert(settings_equal(&info, &set1, &set2, NULL)); + + /* port */ + set1.port = 65535; + set2.port = 65534; + test_assert(settings_hash(&info, &set1, NULL) != + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + set2.port = 65535; + test_assert(settings_hash(&info, &set1, NULL) == + settings_hash(&info, &set2, NULL)); + test_assert(settings_equal(&info, &set1, &set2, NULL)); + + /* string */ + set1.str = "foo1"; + set2.str = "foo2"; + test_assert(settings_hash(&info, &set1, NULL) != + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + set2.str = "foo1"; + test_assert(settings_hash(&info, &set1, NULL) == + settings_hash(&info, &set2, NULL)); + test_assert(settings_equal(&info, &set1, &set2, NULL)); + + /* file with filename */ + set1.file = "fname\ncontent"; + set2.file = "fname2\ncontent"; + test_assert(settings_hash(&info, &set1, NULL) != + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + set2.file = "fname\ncontent-with-different"; + test_assert(settings_hash(&info, &set1, NULL) == + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + set2.file = "fname\ncontent"; + test_assert(settings_hash(&info, &set1, NULL) == + settings_hash(&info, &set2, NULL)); + test_assert(settings_equal(&info, &set1, &set2, NULL)); + + /* file without filename */ + set1.file = "\ncontent"; + set2.file = "\ncontent2"; + test_assert(settings_hash(&info, &set1, NULL) != + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + set2.file = "\ncontent"; + test_assert(settings_hash(&info, &set1, NULL) == + settings_hash(&info, &set2, NULL)); + test_assert(settings_equal(&info, &set1, &set2, NULL)); + + /* string list */ + const char *str; + t_array_init(&set1.strlist, 4); + str = "list1"; array_push_back(&set1.strlist, &str); + str = "list2"; array_push_back(&set1.strlist, &str); + str = "list3"; array_push_back(&set1.strlist, &str); + str = "list4"; array_push_back(&set1.strlist, &str); + t_array_init(&set2.strlist, 4); + str = "list1"; array_push_back(&set2.strlist, &str); + str = "list2"; array_push_back(&set2.strlist, &str); + test_assert(settings_hash(&info, &set1, NULL) != + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + str = "list3"; array_push_back(&set2.strlist, &str); + str = "list4"; array_push_back(&set2.strlist, &str); + test_assert(settings_hash(&info, &set1, NULL) == + settings_hash(&info, &set2, NULL)); + test_assert(settings_equal(&info, &set1, &set2, NULL)); + + /* test exceptions */ + const char *const except_fields1[] = { "b", NULL }; + set1.b = FALSE; + test_assert(settings_hash(&info, &set1, NULL) != + settings_hash(&info, &set2, NULL)); + test_assert(!settings_equal(&info, &set1, &set2, NULL)); + test_assert(settings_hash(&info, &set1, except_fields1) == + settings_hash(&info, &set2, except_fields1)); + test_assert(settings_equal(&info, &set1, &set2, except_fields1)); + + const char *const except_fields2[] = { "b", "i", NULL }; + set1.i = 3535; + test_assert(settings_hash(&info, &set1, except_fields1) != + settings_hash(&info, &set2, except_fields1)); + test_assert(!settings_equal(&info, &set1, &set2, except_fields1)); + test_assert(settings_hash(&info, &set1, except_fields2) == + settings_hash(&info, &set2, except_fields2)); + test_assert(settings_equal(&info, &set1, &set2, except_fields2)); + + test_end(); +} + int main(void) { static void (*const test_functions[])(void) = { test_settings_parser, test_settings_parse_boollist_string, test_settings_section_escape, + test_settings_hash_equals, NULL }; return test_run(test_functions);