#include "lib.h"
#include "array.h"
+#include "crc32.h"
#include "str.h"
#include "str-parse.h"
#include "read-full.h"
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) \
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);