From 9b45e3b224bd10f2379ec2e38754a76eb53cbde6 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Wed, 22 Jul 2026 10:53:53 +0200 Subject: [PATCH] Compare configuration JSON semantically --- include/bongojson.h | 1 + src/apps/manager/manager.c | 23 ++++++++--- src/libs/json/CMakeLists.txt | 6 +++ src/libs/json/json.c | 59 +++++++++++++++++++++++++++++ src/libs/json/tests/equality-test.c | 56 +++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 src/libs/json/tests/equality-test.c diff --git a/include/bongojson.h b/include/bongojson.h index be446ea..f9e49b9 100644 --- a/include/bongojson.h +++ b/include/bongojson.h @@ -88,6 +88,7 @@ BongoJsonNode *BongoJsonNodeNewString(const char *val); BongoJsonNode *BongoJsonNodeNewStringGive(char *val); BongoJsonNode *BongoJsonNodeDup(BongoJsonNode *node); +BOOL BongoJsonNodeEqual(BongoJsonNode *left, BongoJsonNode *right); #define BongoJsonNodeAsObject(n) ((n)->value.objectVal) #define BongoJsonNodeAsArray(n) ((n)->value.arrayVal) diff --git a/src/apps/manager/manager.c b/src/apps/manager/manager.c index b395629..f398c7c 100644 --- a/src/apps/manager/manager.c +++ b/src/apps/manager/manager.c @@ -763,16 +763,21 @@ SyncConfigurationContent(const char *storeName, const char *path, const char *local) { char *stored = NULL; - char *canonicalStored = NULL; + BongoJsonNode *localRoot = NULL; BongoJsonNode *storedRoot = NULL; BOOL changed = TRUE; BOOL result = FALSE; + if (BongoJsonParseString(local, &localRoot) != BONGO_JSON_OK || !localRoot || + localRoot->type != BONGO_JSON_OBJECT) { + fprintf(stderr, + _("bongo-manager: cannot parse local configuration %s\n"), path); + goto done; + } if (NMAPReadConfigFile(storeName, &stored) && stored && BongoJsonParseString(stored, &storedRoot) == BONGO_JSON_OK && storedRoot && storedRoot->type == BONGO_JSON_OBJECT) { - canonicalStored = BongoJsonNodeToString(storedRoot); - changed = !canonicalStored || strcmp(local, canonicalStored); + changed = !BongoJsonNodeEqual(localRoot, storedRoot); } if (changed && !NMAPReplaceConfigFile(storeName, local, strlen(local))) { @@ -786,12 +791,12 @@ SyncConfigurationContent(const char *storeName, const char *path, result = TRUE; done: + if (localRoot) + BongoJsonNodeFree(localRoot); if (storedRoot) BongoJsonNodeFree(storedRoot); if (stored) MemFree(stored); - if (canonicalStored) - MemFree(canonicalStored); return result; } @@ -801,6 +806,7 @@ SyncConfigurationDocument(const char *storeName, const char *path) char *local = NULL; char *stored = NULL; char *canonicalStored = NULL; + BongoJsonNode *localRoot = NULL; BongoJsonNode *storedRoot = NULL; struct stat details; uint64_t storeModified = 0; @@ -808,6 +814,9 @@ SyncConfigurationDocument(const char *storeName, const char *path) if (!ReadConfigurationFile(path, &local, &details)) goto done; + if (BongoJsonParseString(local, &localRoot) != BONGO_JSON_OK || !localRoot || + localRoot->type != BONGO_JSON_OBJECT) + goto done; if (!NMAPReadConfigFile(storeName, &stored) || !stored) { result = SyncConfigurationContent(storeName, path, local); goto done; @@ -821,7 +830,7 @@ SyncConfigurationDocument(const char *storeName, const char *path) storeName); goto done; } - if (!strcmp(local, canonicalStored)) { + if (BongoJsonNodeEqual(localRoot, storedRoot)) { result = SetConfigurationFileTime(path, &details, storeModified); goto done; } @@ -840,6 +849,8 @@ SyncConfigurationDocument(const char *storeName, const char *path) } done: + if (localRoot) + BongoJsonNodeFree(localRoot); if (storedRoot) BongoJsonNodeFree(storedRoot); if (stored) diff --git a/src/libs/json/CMakeLists.txt b/src/libs/json/CMakeLists.txt index d3d44be..12b23c7 100644 --- a/src/libs/json/CMakeLists.txt +++ b/src/libs/json/CMakeLists.txt @@ -12,3 +12,9 @@ target_link_libraries(bongojson PRIVATE ) install(TARGETS bongojson DESTINATION ${LIB_INSTALL_DIR}) + +if(BUILD_TESTING) + add_executable(json-equality-test tests/equality-test.c) + target_link_libraries(json-equality-test PRIVATE bongojson) + add_test(NAME json-equality COMMAND json-equality-test) +endif() diff --git a/src/libs/json/json.c b/src/libs/json/json.c index 6f52acb..0bb6e38 100644 --- a/src/libs/json/json.c +++ b/src/libs/json/json.c @@ -287,6 +287,65 @@ BongoJsonNodeDup(BongoJsonNode *old) return node; } +BOOL +BongoJsonNodeEqual(BongoJsonNode *left, BongoJsonNode *right) +{ + unsigned int index; + + if (left == right) + return TRUE; + if (!left || !right || left->type != right->type) + return FALSE; + + switch (left->type) { + case BONGO_JSON_NULL: + return TRUE; + case BONGO_JSON_OBJECT: { + BongoJsonObject *leftObject = BongoJsonNodeAsObject(left); + BongoJsonObject *rightObject = BongoJsonNodeAsObject(right); + BongoJsonObjectIter iterator; + + if (BongoHashtableSize((BongoHashtable *)leftObject) != + BongoHashtableSize((BongoHashtable *)rightObject)) + return FALSE; + for (BongoJsonObjectIterFirst(leftObject, &iterator); + iterator.key != NULL; + BongoJsonObjectIterNext(leftObject, &iterator)) { + BongoJsonNode *rightValue = NULL; + + if (BongoJsonObjectGet(rightObject, iterator.key, &rightValue) != + BONGO_JSON_OK || + !BongoJsonNodeEqual(iterator.value, rightValue)) + return FALSE; + } + return TRUE; + } + case BONGO_JSON_ARRAY: { + GArray *leftArray = BongoJsonNodeAsArray(left); + GArray *rightArray = BongoJsonNodeAsArray(right); + + if (leftArray->len != rightArray->len) + return FALSE; + for (index = 0; index < leftArray->len; index++) { + if (!BongoJsonNodeEqual(BongoJsonArrayGet(leftArray, index), + BongoJsonArrayGet(rightArray, index))) + return FALSE; + } + return TRUE; + } + case BONGO_JSON_BOOL: + return BongoJsonNodeAsBool(left) == BongoJsonNodeAsBool(right); + case BONGO_JSON_DOUBLE: + return BongoJsonNodeAsDouble(left) == BongoJsonNodeAsDouble(right); + case BONGO_JSON_INT: + return BongoJsonNodeAsInt(left) == BongoJsonNodeAsInt(right); + case BONGO_JSON_STRING: + return strcmp(BongoJsonNodeAsString(left), + BongoJsonNodeAsString(right)) == 0; + } + return FALSE; +} + char * diff --git a/src/libs/json/tests/equality-test.c b/src/libs/json/tests/equality-test.c new file mode 100644 index 0000000..e53a920 --- /dev/null +++ b/src/libs/json/tests/equality-test.c @@ -0,0 +1,56 @@ +/**************************************************************************** + * + * Copyright (c) 2001 Novell, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact Novell, Inc. + * + ****************************************************************************/ + +#include + +#include + +static BongoJsonNode * +Parse(const char *content) +{ + BongoJsonNode *node = NULL; + + assert(BongoJsonParseString(content, &node) == BONGO_JSON_OK); + assert(node != NULL); + return node; +} + +int +main(void) +{ + BongoJsonNode *left = Parse( + "{\"enabled\":true,\"nested\":{\"port\":993,\"names\":[\"a\",\"b\"]}}"); + BongoJsonNode *reordered = Parse( + "{\"nested\":{\"names\":[\"a\",\"b\"],\"port\":993},\"enabled\":true}"); + BongoJsonNode *differentArray = Parse( + "{\"nested\":{\"names\":[\"b\",\"a\"],\"port\":993},\"enabled\":true}"); + BongoJsonNode *differentValue = Parse( + "{\"enabled\":false,\"nested\":{\"port\":993,\"names\":[\"a\",\"b\"]}}"); + + assert(BongoJsonNodeEqual(left, reordered)); + assert(BongoJsonNodeEqual(left, left)); + assert(!BongoJsonNodeEqual(left, differentArray)); + assert(!BongoJsonNodeEqual(left, differentValue)); + assert(!BongoJsonNodeEqual(left, NULL)); + + BongoJsonNodeFree(differentValue); + BongoJsonNodeFree(differentArray); + BongoJsonNodeFree(reordered); + BongoJsonNodeFree(left); + return 0; +}