From 2671873b6fa51f4bde42eb5aee6b2be80dc1d1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 18 Feb 2026 16:33:18 +0100 Subject: [PATCH] buildroot: Update the patches to reflect the upgraded buildroot --- ...add-mcast_flood_always-bridge-option.patch | 36 +- ...-accept-symbolic-names-also-for-show.patch | 12 +- ...0001-Implement-SchemaNode-actionRpcs.patch | 135 ++++++ .../4/0002-CI-pin-to-libyang-v3.patch | 38 ++ .../4/0003-wrap-lyd_validate_op.patch | 216 +++++++++ .../4/0004-port-to-libyang-v4.patch | 435 ++++++++++++++++++ .../0005-Add-SchemaNode-isOutput-method.patch | 77 ++++ ...006-adapt-to-changes-in-libyang-v4.2.patch | 364 +++++++++++++++ ...fault-when-listing-module-s-features.patch | 112 +++++ ...-all-users-of-group-wheel-to-connect.patch | 34 -- ...-all-users-of-group-wheel-to-connect.patch | 0 ...0001-src-bundle-enable-tftp-protocol.patch | 12 - ...src-main-add-optional-syslog-support.patch | 84 ---- ...ll-print-bootname-when-checking-slot.patch | 29 -- ...ning-banner-at-start-of-installation.patch | 24 - ...src-main-add-optional-syslog-support.patch | 33 +- .../6/0001-CI-pin-to-libyang-v3.patch | 47 ++ ...-fix-implicit-dependency-on-optional.patch | 33 ++ ...x-include-for-for-our-public-headers.patch | 31 ++ ...r-of-files-which-are-named-utils.hpp.patch | 82 ++++ ...Fix-a-different-function-return-type.patch | 60 +++ ...-raw-strings-and-macros-break-gcc-13.patch | 61 +++ .../6/0007-Port-to-libyang-v4.patch | 305 ++++++++++++ ...nOperGet-unconsistent-with-C-library.patch | 90 ++++ .../6/0009-Update-to-libyang-v4.2.patch | 121 +++++ 25 files changed, 2255 insertions(+), 216 deletions(-) rename patches/iproute2/{6.14.0 => 6.17.0}/0001-iplink_bridge-add-mcast_flood_always-bridge-option.patch (79%) rename patches/iproute2/{6.14.0 => 6.17.0}/0002-ipaddress-accept-symbolic-names-also-for-show.patch (72%) create mode 100644 patches/libyang-cpp/4/0001-Implement-SchemaNode-actionRpcs.patch create mode 100644 patches/libyang-cpp/4/0002-CI-pin-to-libyang-v3.patch create mode 100644 patches/libyang-cpp/4/0003-wrap-lyd_validate_op.patch create mode 100644 patches/libyang-cpp/4/0004-port-to-libyang-v4.patch create mode 100644 patches/libyang-cpp/4/0005-Add-SchemaNode-isOutput-method.patch create mode 100644 patches/libyang-cpp/4/0006-adapt-to-changes-in-libyang-v4.2.patch create mode 100644 patches/libyang-cpp/4/0007-don-t-segfault-when-listing-module-s-features.patch delete mode 100644 patches/lldpd/1.0.15/0001-lldpd-allow-all-users-of-group-wheel-to-connect.patch rename patches/lldpd/{1.0.18 => 1.0.20}/0001-lldpd-allow-all-users-of-group-wheel-to-connect.patch (100%) delete mode 100644 patches/rauc/1.11.3/0001-src-bundle-enable-tftp-protocol.patch delete mode 100644 patches/rauc/1.11.3/0002-src-main-add-optional-syslog-support.patch delete mode 100644 patches/rauc/1.11.3/0003-src-install-print-bootname-when-checking-slot.patch delete mode 100644 patches/rauc/1.11.3/0004-src-main-add-warning-banner-at-start-of-installation.patch rename patches/rauc/{1.13 => 1.15.1}/0001-src-main-add-optional-syslog-support.patch (76%) create mode 100644 patches/sysrepo-cpp/6/0001-CI-pin-to-libyang-v3.patch create mode 100644 patches/sysrepo-cpp/6/0002-fix-implicit-dependency-on-optional.patch create mode 100644 patches/sysrepo-cpp/6/0003-fix-include-for-for-our-public-headers.patch create mode 100644 patches/sysrepo-cpp/6/0004-reduce-number-of-files-which-are-named-utils.hpp.patch create mode 100644 patches/sysrepo-cpp/6/0005-Fix-a-different-function-return-type.patch create mode 100644 patches/sysrepo-cpp/6/0006-fix-raw-strings-and-macros-break-gcc-13.patch create mode 100644 patches/sysrepo-cpp/6/0007-Port-to-libyang-v4.patch create mode 100644 patches/sysrepo-cpp/6/0008-fix-onOperGet-unconsistent-with-C-library.patch create mode 100644 patches/sysrepo-cpp/6/0009-Update-to-libyang-v4.2.patch diff --git a/patches/iproute2/6.14.0/0001-iplink_bridge-add-mcast_flood_always-bridge-option.patch b/patches/iproute2/6.17.0/0001-iplink_bridge-add-mcast_flood_always-bridge-option.patch similarity index 79% rename from patches/iproute2/6.14.0/0001-iplink_bridge-add-mcast_flood_always-bridge-option.patch rename to patches/iproute2/6.17.0/0001-iplink_bridge-add-mcast_flood_always-bridge-option.patch index 19129f40..fc590511 100644 --- a/patches/iproute2/6.14.0/0001-iplink_bridge-add-mcast_flood_always-bridge-option.patch +++ b/patches/iproute2/6.17.0/0001-iplink_bridge-add-mcast_flood_always-bridge-option.patch @@ -1,8 +1,11 @@ -From 392735fcb41b303b6dbe3517fab9ba734ff2e288 Mon Sep 17 00:00:00 2001 +From f9008d2d58b215886961ae2ce90caba473207309 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 5 Mar 2024 09:41:46 +0100 Subject: [PATCH 1/2] iplink_bridge: add mcast_flood_always bridge option -Organization: Addiva Elektronik +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires - Break out boolopt handling to simplify parsing and setting - Add set/get support for mcast_flood_always @@ -10,6 +13,7 @@ Organization: Addiva Elektronik Signed-off-by: Joachim Wiberg Signed-off-by: Tobias Waldekranz +Signed-off-by: Mattias Walström --- include/uapi/linux/if_bridge.h | 1 + ip/iplink_bridge.c | 19 +++++++++++++++++++ @@ -17,22 +21,22 @@ Signed-off-by: Tobias Waldekranz 3 files changed, 32 insertions(+) diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h -index c206cf6..84ef423 100644 +index bed5e0c1..637ad93c 100644 --- a/include/uapi/linux/if_bridge.h +++ b/include/uapi/linux/if_bridge.h -@@ -830,6 +830,7 @@ enum br_boolopt_id { - BR_BOOLOPT_NO_LL_LEARN, +@@ -832,6 +832,7 @@ enum br_boolopt_id { BR_BOOLOPT_MCAST_VLAN_SNOOPING, BR_BOOLOPT_MST_ENABLE, + BR_BOOLOPT_MDB_OFFLOAD_FAIL_NOTIFICATION, + BR_BOOLOPT_MCAST_FLOOD_ALWAYS, BR_BOOLOPT_MAX }; diff --git a/ip/iplink_bridge.c b/ip/iplink_bridge.c -index 1fe8955..d4f2c80 100644 +index 76e69086..827a0716 100644 --- a/ip/iplink_bridge.c +++ b/ip/iplink_bridge.c -@@ -41,6 +41,7 @@ static void print_explain(FILE *f) +@@ -42,6 +42,7 @@ static void print_explain(FILE *f) " [ vlan_default_pvid VLAN_DEFAULT_PVID ]\n" " [ vlan_stats_enabled VLAN_STATS_ENABLED ]\n" " [ vlan_stats_per_port VLAN_STATS_PER_PORT ]\n" @@ -40,7 +44,7 @@ index 1fe8955..d4f2c80 100644 " [ mcast_snooping MULTICAST_SNOOPING ]\n" " [ mcast_vlan_snooping MULTICAST_VLAN_SNOOPING ]\n" " [ mcast_router MULTICAST_ROUTER ]\n" -@@ -245,6 +246,18 @@ static int bridge_parse_opt(struct link_util *lu, int argc, char **argv, +@@ -247,6 +248,18 @@ static int bridge_parse_opt(struct link_util *lu, int argc, char **argv, bm.optval |= mcvl_bit; else bm.optval &= ~mcvl_bit; @@ -59,7 +63,7 @@ index 1fe8955..d4f2c80 100644 } else if (matches(*argv, "mcast_query_use_ifaddr") == 0) { __u8 mcast_qui; -@@ -623,6 +636,7 @@ static void bridge_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) +@@ -638,6 +651,7 @@ static void bridge_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) __u32 mcvl_bit = 1 << BR_BOOLOPT_MCAST_VLAN_SNOOPING; __u32 no_ll_learn_bit = 1 << BR_BOOLOPT_NO_LL_LEARN; __u32 mst_bit = 1 << BR_BOOLOPT_MST_ENABLE; @@ -67,10 +71,10 @@ index 1fe8955..d4f2c80 100644 struct br_boolopt_multi *bm; bm = RTA_DATA(tb[IFLA_BR_MULTI_BOOLOPT]); -@@ -641,6 +655,11 @@ static void bridge_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) - "mst_enabled", - "mst_enabled %u ", - !!(bm->optval & mst_bit)); +@@ -661,6 +675,11 @@ static void bridge_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) + "mdb_offload_fail_notification", + "mdb_offload_fail_notification %u ", + !!(bm->optval & mofn_bit)); + if (bm->optmask & mcfl_bit) + print_uint(PRINT_ANY, + "mcast_flood_always", @@ -80,10 +84,10 @@ index 1fe8955..d4f2c80 100644 if (tb[IFLA_BR_MCAST_ROUTER]) diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in -index efb6248..2308835 100644 +index e3297c57..f2852adf 100644 --- a/man/man8/ip-link.8.in +++ b/man/man8/ip-link.8.in -@@ -1712,6 +1712,8 @@ the following additional arguments are supported: +@@ -1737,6 +1737,8 @@ the following additional arguments are supported: ] [ .BI vlan_stats_per_port " VLAN_STATS_PER_PORT " ] [ @@ -92,7 +96,7 @@ index efb6248..2308835 100644 .BI mcast_snooping " MULTICAST_SNOOPING " ] [ .BI mcast_vlan_snooping " MULTICAST_VLAN_SNOOPING " -@@ -1860,6 +1862,16 @@ or disable +@@ -1887,6 +1889,16 @@ or disable .RI ( VLAN_STATS_PER_PORT " == 0) " per-VLAN per-port stats accounting. Can be changed only when there are no port VLANs configured. diff --git a/patches/iproute2/6.14.0/0002-ipaddress-accept-symbolic-names-also-for-show.patch b/patches/iproute2/6.17.0/0002-ipaddress-accept-symbolic-names-also-for-show.patch similarity index 72% rename from patches/iproute2/6.14.0/0002-ipaddress-accept-symbolic-names-also-for-show.patch rename to patches/iproute2/6.17.0/0002-ipaddress-accept-symbolic-names-also-for-show.patch index eda95409..d6ff6d5a 100644 --- a/patches/iproute2/6.14.0/0002-ipaddress-accept-symbolic-names-also-for-show.patch +++ b/patches/iproute2/6.17.0/0002-ipaddress-accept-symbolic-names-also-for-show.patch @@ -1,8 +1,11 @@ -From a035696129b543952e8105c27689ed4f9312eb3b Mon Sep 17 00:00:00 2001 +From 2aac8bf90e25e33e022c850702d8581d7d6cba25 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 20 May 2024 21:35:11 +0200 Subject: [PATCH 2/2] ipaddress: accept symbolic names also for show -Organization: Addiva Elektronik +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires This is a follow-up to 709063e, which in turn was a follow-up to bdb8d85, to add support for 'ip addr show proto static', where @@ -10,15 +13,16 @@ bdb8d85, to add support for 'ip addr show proto static', where Signed-off-by: Joachim Wiberg Signed-off-by: Tobias Waldekranz +Signed-off-by: Mattias Walström --- ip/ipaddress.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ip/ipaddress.c b/ip/ipaddress.c -index 4e1f934f..437072f9 100644 +index 4d93a04a..1bff689e 100644 --- a/ip/ipaddress.c +++ b/ip/ipaddress.c -@@ -2224,7 +2224,7 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action) +@@ -2248,7 +2248,7 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action) __u8 proto; NEXT_ARG(); diff --git a/patches/libyang-cpp/4/0001-Implement-SchemaNode-actionRpcs.patch b/patches/libyang-cpp/4/0001-Implement-SchemaNode-actionRpcs.patch new file mode 100644 index 00000000..d7a78e08 --- /dev/null +++ b/patches/libyang-cpp/4/0001-Implement-SchemaNode-actionRpcs.patch @@ -0,0 +1,135 @@ +From 22b41e7ed8268b94ccc139138e2037c390a3a616 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= +Date: Thu, 10 Jul 2025 15:00:45 +0200 +Subject: [PATCH 1/7] Implement `SchemaNode::actionRpcs()` +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This function allows to access list of actions +within schema node. + +It is implemented same as `Module::actionRpcs` +to follow same approach, so there is no need to use +collections. + +Change-Id: Ie99908cfdd334433fd9ae6f1a909f196e61139fd +Signed-off-by: Jan Kundrát +Signed-off-by: Mattias Walström +--- + include/libyang-cpp/SchemaNode.hpp | 1 + + src/SchemaNode.cpp | 12 ++++++++++++ + tests/example_schema.hpp | 28 ++++++++++++++++++++++++++++ + tests/schema_node.cpp | 21 +++++++++++++++++++++ + 4 files changed, 62 insertions(+) + +diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp +index 0f1a4c4..9f49fd7 100644 +--- a/include/libyang-cpp/SchemaNode.hpp ++++ b/include/libyang-cpp/SchemaNode.hpp +@@ -79,6 +79,7 @@ public: + Collection siblings() const; + Collection immediateChildren() const; + std::vector extensionInstances() const; ++ std::vector actionRpcs() const; + + std::vector when() const; + +diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp +index 26b5099..ce24203 100644 +--- a/src/SchemaNode.cpp ++++ b/src/SchemaNode.cpp +@@ -117,6 +117,18 @@ Collection SchemaNode::immediateChildren() c + return c ? c->siblings() : Collection{nullptr, nullptr}; + } + ++/** ++ * @brief Returns a collection of action nodes (not RPC nodes) as SchemaNode ++ */ ++std::vector SchemaNode::actionRpcs() const ++{ ++ std::vector res; ++ for (auto action = reinterpret_cast(lysc_node_actions(m_node)); action; action = action->next) { ++ res.emplace_back(SchemaNode{action, m_ctx}); ++ } ++ return res; ++} ++ + /** + * Returns the YANG description of the node. + * +diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp +index 0d8acb9..02d3d74 100644 +--- a/tests/example_schema.hpp ++++ b/tests/example_schema.hpp +@@ -214,6 +214,34 @@ module example-schema { + } + + container bigTree { ++ action firstAction { ++ input { ++ leaf inputLeaf1 { ++ type string; ++ } ++ } ++ ++ output { ++ leaf outputLeaf1 { ++ type string; ++ } ++ } ++ } ++ ++ action secondAction { ++ input { ++ leaf inputLeaf2 { ++ type string; ++ } ++ } ++ ++ output { ++ leaf outputLeaf2 { ++ type string; ++ } ++ } ++ } ++ + container one { + leaf myLeaf { + type string; +diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp +index 0001377..65ef462 100644 +--- a/tests/schema_node.cpp ++++ b/tests/schema_node.cpp +@@ -544,6 +544,27 @@ TEST_CASE("SchemaNode") + REQUIRE(elem.extensionInstances()[2].argument() == "last-modified"); + } + ++ DOCTEST_SUBCASE("SchemaNode::actionRpcs") ++ { ++ DOCTEST_SUBCASE("no actions") ++ { ++ auto actions = ctx->findPath("/example-schema:presenceContainer").actionRpcs(); ++ REQUIRE(actions.size() == 0); ++ } ++ ++ DOCTEST_SUBCASE("two actions") ++ { ++ auto actions = ctx->findPath("/example-schema:bigTree").actionRpcs(); ++ REQUIRE(actions.size() == 2); ++ REQUIRE(actions[0].asActionRpc().name() == "firstAction"); ++ REQUIRE(actions[0].asActionRpc().input().child()->name() == "inputLeaf1"); ++ REQUIRE(actions[0].asActionRpc().output().child()->name() == "outputLeaf1"); ++ REQUIRE(actions[1].asActionRpc().name() == "secondAction"); ++ REQUIRE(actions[1].asActionRpc().input().child()->name() == "inputLeaf2"); ++ REQUIRE(actions[1].asActionRpc().output().child()->name() == "outputLeaf2"); ++ } ++ } ++ + DOCTEST_SUBCASE("SchemaNode::operator==") + { + auto a = ctx->findPath("/type_module:leafString"); +-- +2.43.0 + diff --git a/patches/libyang-cpp/4/0002-CI-pin-to-libyang-v3.patch b/patches/libyang-cpp/4/0002-CI-pin-to-libyang-v3.patch new file mode 100644 index 00000000..cc29661a --- /dev/null +++ b/patches/libyang-cpp/4/0002-CI-pin-to-libyang-v3.patch @@ -0,0 +1,38 @@ +From 1f8e502cf0308faa0336b1077308184de6a83ee0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Mon, 20 Oct 2025 11:45:06 +0200 +Subject: [PATCH 2/7] CI: pin to libyang v3 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: Idba085ad9c9c137874801f1d4089f8ee6bb390c7 +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index 7b92766..fd89549 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -4,13 +4,13 @@ + - f38-gcc-cover: + required-projects: + - name: github/CESNET/libyang +- override-checkout: devel ++ override-checkout: cesnet/2025-08-07 + - name: github/doctest/doctest + override-checkout: v2.3.6 + - f38-clang-asan-ubsan: + required-projects: &projects + - name: github/CESNET/libyang +- override-checkout: devel ++ override-checkout: cesnet/2025-08-07 + - name: github/doctest/doctest + override-checkout: v2.4.11 + - f38-clang-tsan: +-- +2.43.0 + diff --git a/patches/libyang-cpp/4/0003-wrap-lyd_validate_op.patch b/patches/libyang-cpp/4/0003-wrap-lyd_validate_op.patch new file mode 100644 index 00000000..edbcded5 --- /dev/null +++ b/patches/libyang-cpp/4/0003-wrap-lyd_validate_op.patch @@ -0,0 +1,216 @@ +From b27a65d5d5f6ffffbd2c26c1b5bea57a6de584d0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Mon, 20 Oct 2025 18:46:38 +0200 +Subject: [PATCH 3/7] wrap lyd_validate_op +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This patch wraps lyd_validate_op from libyang. Downstream users can now +validate operations (RPC input, replies, and notifications). + +Change-Id: Ib03070bbc3e1d0dccd6bf38eda82e944db1093b1 +Signed-off-by: Mattias Walström +--- + include/libyang-cpp/DataNode.hpp | 2 + + src/DataNode.cpp | 24 +++++++++++ + tests/context.cpp | 2 +- + tests/data_node.cpp | 72 ++++++++++++++++++++++++++++++++ + tests/example_schema.hpp | 22 ++++++++++ + 5 files changed, 121 insertions(+), 1 deletion(-) + +diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp +index 50d6c0e..e202a96 100644 +--- a/include/libyang-cpp/DataNode.hpp ++++ b/include/libyang-cpp/DataNode.hpp +@@ -59,6 +59,7 @@ template + void handleLyTreeOperation(DataNode* affectedNode, Operation operation, Siblings siblings, std::shared_ptr newRefs); + + LIBYANG_CPP_EXPORT void validateAll(std::optional& node, const std::optional& opts = std::nullopt); ++LIBYANG_CPP_EXPORT void validateOp(libyang::DataNode& input, const std::optional& opsTree, OperationType opType); + + LIBYANG_CPP_EXPORT Set findXPathAt( + const std::optional& contextNode, +@@ -147,6 +148,7 @@ public: + friend LIBYANG_CPP_EXPORT lyd_node* getRawNode(DataNode node); + + friend LIBYANG_CPP_EXPORT void validateAll(std::optional& node, const std::optional& opts); ++ friend LIBYANG_CPP_EXPORT void validateOp(libyang::DataNode& input, const std::optional& opsTree, OperationType opType); + friend LIBYANG_CPP_EXPORT Set findXPathAt(const std::optional& contextNode, const libyang::DataNode& forest, const std::string& xpath); + + bool operator==(const DataNode& node) const; +diff --git a/src/DataNode.cpp b/src/DataNode.cpp +index 7e87917..e28ef30 100644 +--- a/src/DataNode.cpp ++++ b/src/DataNode.cpp +@@ -1226,6 +1226,30 @@ void validateAll(std::optional& node, const std::optional& opsTree, OperationType opType) ++{ ++ if (opType == OperationType::RpcYang || opType == OperationType::RpcRestconf || opType == OperationType::RpcNetconf) { ++ opType = OperationType::RpcYang; ++ } else if (opType == OperationType::ReplyYang || opType == OperationType::ReplyRestconf || opType == OperationType::ReplyNetconf) { ++ opType = OperationType::ReplyYang; ++ } else if (opType == OperationType::NotificationYang || opType == OperationType::NotificationRestconf || opType == OperationType::NotificationNetconf) { ++ opType = OperationType::NotificationYang; ++ } else { ++ throw Error("validateOp: DataYang datatype is not supported"); ++ } ++ ++ auto ret = lyd_validate_op(input.m_node, opsTree ? opsTree->m_node : nullptr, utils::toOpType(opType), nullptr); ++ throwIfError(ret, "libyang:validateOp: lyd_validate_op failed"); ++} ++ + /** @short Find instances matching the provided XPath + * + * @param contextNode The node which serves as the "context node" for XPath evaluation. Use nullopt to start at root. +diff --git a/tests/context.cpp b/tests/context.cpp +index 6c5dde8..c0b7e09 100644 +--- a/tests/context.cpp ++++ b/tests/context.cpp +@@ -154,7 +154,7 @@ TEST_CASE("context") + { + auto mod = ctx->parseModule(example_schema, libyang::SchemaFormat::YANG); + auto rpcs = mod.actionRpcs(); +- REQUIRE(rpcs.size() == 1); ++ REQUIRE(rpcs.size() == 2); + REQUIRE(rpcs[0].module().name() == "example-schema"); + REQUIRE(rpcs[0].name() == "myRpc"); + +diff --git a/tests/data_node.cpp b/tests/data_node.cpp +index db5a28e..9215b12 100644 +--- a/tests/data_node.cpp ++++ b/tests/data_node.cpp +@@ -2430,6 +2430,78 @@ TEST_CASE("Data Node manipulation") + "Can't parse into operation data tree: LY_EVALID", libyang::Error); + } + } ++ ++ DOCTEST_SUBCASE("Validation") ++ { ++ DOCTEST_SUBCASE("Valid input") ++ { ++ std::string rpcInput; ++ std::string rpcPath; ++ std::string expected; ++ std::optional depTree; ++ ++ DOCTEST_SUBCASE("RPC") ++ { ++ rpcInput = R"({"example-schema:input": { "number": 42 } })"; ++ rpcPath = "/example-schema:rpc-with-choice"; ++ expected = R"({ ++ "example-schema:rpc-with-choice": { ++ "number": 42 ++ } ++} ++)"; ++ } ++ ++ DOCTEST_SUBCASE("Action") ++ { ++ rpcInput = R"({ "example-schema:input": { "friend": "Kuba" } })"; ++ rpcPath = "/example-schema:person[name='Franta']/poke-a-friend"; ++ expected = R"({ ++ "example-schema:poke-a-friend": { ++ "friend": "Kuba" ++ } ++} ++)"; ++ depTree = ctx.newPath("/example-schema:person[name='Kuba']"); ++ } ++ ++ auto [parent, rpcTree] = ctx.newPath2(rpcPath); ++ auto rpcOp = rpcTree->parseOp(rpcInput, dataTypeFor(rpcInput), libyang::OperationType::RpcRestconf); ++ ++ REQUIRE(!rpcOp.op); ++ REQUIRE(rpcOp.tree); ++ ++ libyang::validateOp(*rpcTree, depTree, libyang::OperationType::RpcRestconf); ++ REQUIRE(*rpcTree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == expected); ++ } ++ ++ DOCTEST_SUBCASE("Nodes in disjunctive cases defined together") ++ { ++ auto rpcInput = R"({ "example-schema:input": { "number": 42, "text": "The ultimate answer" } })"; ++ ++ auto rpcTree = ctx.newPath("/example-schema:rpc-with-choice"); ++ auto rpcOp = rpcTree.parseOp(rpcInput, dataTypeFor(rpcInput), libyang::OperationType::RpcRestconf); ++ ++ REQUIRE(!rpcOp.op); ++ REQUIRE(rpcOp.tree); ++ ++ REQUIRE_THROWS_WITH_AS(libyang::validateOp(rpcTree, std::nullopt, libyang::OperationType::RpcRestconf), "libyang:validateOp: lyd_validate_op failed: LY_EVALID", libyang::Error); ++ } ++ ++ DOCTEST_SUBCASE("Action without the leafref node") ++ { ++ auto rpcInput = R"({ "example-schema:input": { "friend": "Kuba" } })"; ++ auto rpcPath = "/example-schema:person[name='Franta']/poke-a-friend"; ++ ++ auto [parent, rpcTree] = ctx.newPath2(rpcPath); ++ auto rpcOp = rpcTree->parseOp(rpcInput, dataTypeFor(rpcInput), libyang::OperationType::RpcRestconf); ++ ++ REQUIRE(!rpcOp.op); ++ REQUIRE(rpcOp.tree); ++ ++ REQUIRE_THROWS_WITH_AS(libyang::validateOp(*rpcTree, std::nullopt, libyang::OperationType::RpcRestconf), "libyang:validateOp: lyd_validate_op failed: LY_EVALID", libyang::Error); ++ } ++ } + } + + DOCTEST_SUBCASE("comparing") { +diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp +index 02d3d74..acc6ecc 100644 +--- a/tests/example_schema.hpp ++++ b/tests/example_schema.hpp +@@ -108,6 +108,16 @@ module example-schema { + } + + action poke { } ++ ++ action poke-a-friend { ++ input { ++ leaf friend { ++ type leafref { ++ path '../../../person/name'; ++ } ++ } ++ } ++ } + } + + leaf bossPerson { +@@ -284,6 +294,18 @@ module example-schema { + } + } + } ++ rpc rpc-with-choice { ++ input { ++ choice the-impossible-choice { ++ leaf text { ++ type string; ++ } ++ leaf number { ++ type int32; ++ } ++ } ++ } ++ } + + anydata myData { + } +-- +2.43.0 + diff --git a/patches/libyang-cpp/4/0004-port-to-libyang-v4.patch b/patches/libyang-cpp/4/0004-port-to-libyang-v4.patch new file mode 100644 index 00000000..c1a95091 --- /dev/null +++ b/patches/libyang-cpp/4/0004-port-to-libyang-v4.patch @@ -0,0 +1,435 @@ +From 4c00c24dee6bbc694d62e7174b7ac14f8aa95f14 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 30 Oct 2025 21:16:13 +0100 +Subject: [PATCH 4/7] port to libyang v4 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Apart from the usual API/ABI changes, the default values of a `leaf` or +a `leaf-list` are no longer canonicalized. + +This used to be missing from libyang v4, but it was added recently. +Sill, chances are that the form which was used in the YANG model's +source code is actually *the* intended presentation, so let's skip +canonicalization altogether. + +Upstream has removed some flags and defines, so let's sync their list +with whatever is currently available in the `devel` branch. + +Change-Id: I52dcec88eeee003b45dd6cf400f4d6875abbcc05 +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 4 ++-- + CMakeLists.txt | 4 ++-- + README.md | 2 +- + include/libyang-cpp/Context.hpp | 6 +++++- + include/libyang-cpp/DataNode.hpp | 5 ++++- + include/libyang-cpp/Enum.hpp | 15 +++++++++++++-- + src/Context.cpp | 12 ++++++++++-- + src/DataNode.cpp | 11 +++++++++-- + src/SchemaNode.cpp | 6 +++--- + src/Type.cpp | 2 +- + src/utils/enum.hpp | 21 +++++++++++++++++++-- + tests/context.cpp | 32 +++++++++++++++++--------------- + tests/data_node.cpp | 2 +- + tests/schema_node.cpp | 4 ++-- + 14 files changed, 89 insertions(+), 37 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index fd89549..7b92766 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -4,13 +4,13 @@ + - f38-gcc-cover: + required-projects: + - name: github/CESNET/libyang +- override-checkout: cesnet/2025-08-07 ++ override-checkout: devel + - name: github/doctest/doctest + override-checkout: v2.3.6 + - f38-clang-asan-ubsan: + required-projects: &projects + - name: github/CESNET/libyang +- override-checkout: cesnet/2025-08-07 ++ override-checkout: devel + - name: github/doctest/doctest + override-checkout: v2.4.11 + - f38-clang-tsan: +diff --git a/CMakeLists.txt b/CMakeLists.txt +index c6db320..90f9aaa 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -20,7 +20,7 @@ add_custom_target(libyang-cpp-version-cmake + cmake/ProjectGitVersionRunner.cmake + ) + include(cmake/ProjectGitVersion.cmake) +-set(LIBYANG_CPP_PKG_VERSION "4") ++set(LIBYANG_CPP_PKG_VERSION "5") + prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION}) + + find_package(Doxygen) +@@ -28,7 +28,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D + option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON) + + find_package(PkgConfig REQUIRED) +-pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET) ++pkg_check_modules(LIBYANG REQUIRED libyang>=4.1.0 IMPORTED_TARGET) + + # FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency + find_package(date) +diff --git a/README.md b/README.md +index 8291e00..0cef2ff 100644 +--- a/README.md ++++ b/README.md +@@ -8,7 +8,7 @@ + Object lifetimes are managed automatically via RAII. + + ## Dependencies +-- [libyang v3](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*) ++- [libyang v4](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*) + - C++20 compiler (e.g., GCC 10.x+, clang 10+) + - CMake 3.19+ + - optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework +diff --git a/include/libyang-cpp/Context.hpp b/include/libyang-cpp/Context.hpp +index ca89063..e695914 100644 +--- a/include/libyang-cpp/Context.hpp ++++ b/include/libyang-cpp/Context.hpp +@@ -108,7 +108,11 @@ public: + std::optional getSubmodule(const std::string& name, const std::optional& revision) const; + void registerModuleCallback(std::function callback); + +- ParsedOp parseOp(const std::string& input, const DataFormat format, const OperationType opType) const; ++ ParsedOp parseOp( ++ const std::string& input, ++ const DataFormat format, ++ const OperationType opType, ++ const std::optional parseOpts = std::nullopt) const; + + DataNode newPath(const std::string& path, const std::optional& value = std::nullopt, const std::optional options = std::nullopt) const; + CreatedNodes newPath2(const std::string& path, const std::optional& value = std::nullopt, const std::optional options = std::nullopt) const; +diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp +index e202a96..35e5936 100644 +--- a/include/libyang-cpp/DataNode.hpp ++++ b/include/libyang-cpp/DataNode.hpp +@@ -123,7 +123,10 @@ public: + Collection siblings() const; + Collection immediateChildren() const; + +- ParsedOp parseOp(const std::string& input, const DataFormat format, const OperationType opType) const; ++ ParsedOp parseOp(const std::string& input, ++ const DataFormat format, ++ const OperationType opType, ++ const std::optional parseOpts = std::nullopt) const; + + void parseSubtree( + const std::string& data, +diff --git a/include/libyang-cpp/Enum.hpp b/include/libyang-cpp/Enum.hpp +index 85dcc83..32142f9 100644 +--- a/include/libyang-cpp/Enum.hpp ++++ b/include/libyang-cpp/Enum.hpp +@@ -136,6 +136,7 @@ enum class DuplicationOptions : uint32_t { + WithFlags = 0x08, + NoExt = 0x10, + WithPriv = 0x20, ++ NoLyds = 0x40, + }; + + enum class NodeType : uint16_t { +@@ -167,6 +168,13 @@ enum class ContextOptions : uint16_t { + PreferSearchDirs = 0x20, + SetPrivParsed = 0x40, + ExplicitCompile = 0x80, ++ EnableImpFeatures = 0x100, ++ CompileObsolete = 0x200, ++ LybHashes = 0x400, ++ LeafrefExtended = 0x800, ++ LeafrefLinking = 0x1000, ++ BuiltinPluginsOnly = 0x2000, ++ StaticPluginsOnly = 0x4000, + }; + + /** +@@ -203,7 +211,6 @@ enum class AnydataValueType : uint32_t { + String, + XML, + JSON, +- LYB + }; + + /** +@@ -252,6 +259,7 @@ enum class ValidationOptions { + MultiError = 0x0004, + Operational = 0x0008, + NoDefaults = 0x0010, ++ NotFinal = 0x0020, + }; + + /** +@@ -262,11 +270,14 @@ enum class ParseOptions { + Strict = 0x020000, + Opaque = 0x040000, + NoState = 0x080000, +- LybModUpdate = 0x100000, ++ LybSkipCtxCheck = 0x100000, + Ordered = 0x200000, + Subtree = 0x400000, /**< Do not use this one for parsing of data subtrees */ + WhenTrue = 0x800000, + NoNew = 0x1000000, ++ StoreOnly = 0x2010000, ++ JsonNull = 0x4000000, ++ JsonStringDataTypes = 0x8000000, + }; + + /** +diff --git a/src/Context.cpp b/src/Context.cpp +index fec2f27..f5bdc71 100644 +--- a/src/Context.cpp ++++ b/src/Context.cpp +@@ -239,7 +239,7 @@ std::optional Context::parseExtData( + * Note: to parse a NETCONF RPC reply, you MUST parse the original NETCONF RPC request (that is, you have to use + * this method with OperationType::RpcNetconf). + */ +-ParsedOp Context::parseOp(const std::string& input, const DataFormat format, const OperationType opType) const ++ParsedOp Context::parseOp(const std::string& input, const DataFormat format, const OperationType opType, const std::optional parseOpts) const + { + auto in = wrap_ly_in_new_memory(input); + +@@ -251,7 +251,15 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con + case OperationType::NotificationYang: { + lyd_node* op = nullptr; + lyd_node* tree = nullptr; +- auto err = lyd_parse_op(m_ctx.get(), nullptr, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, &op); ++ auto err = lyd_parse_op( ++ m_ctx.get(), ++ nullptr, ++ in.get(), ++ utils::toLydFormat(format), ++ utils::toOpType(opType), ++ parseOpts ? utils::toParseOptions(*parseOpts) : 0, ++ &tree, ++ &op); + + ParsedOp res; + res.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt; +diff --git a/src/DataNode.cpp b/src/DataNode.cpp +index e28ef30..edabf9b 100644 +--- a/src/DataNode.cpp ++++ b/src/DataNode.cpp +@@ -391,7 +391,7 @@ DataNodeAny DataNode::asAny() const + * + * Wraps `lyd_parse_op`. + */ +-ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, const OperationType opType) const ++ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, const OperationType opType, const std::optional parseOpts) const + { + auto in = wrap_ly_in_new_memory(input); + +@@ -401,7 +401,14 @@ ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, co + case OperationType::ReplyRestconf: { + lyd_node* op = nullptr; + lyd_node* tree = nullptr; +- auto err = lyd_parse_op(m_node->schema->module->ctx, m_node, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, nullptr); ++ auto err = lyd_parse_op(m_node->schema->module->ctx, ++ m_node, ++ in.get(), ++ utils::toLydFormat(format), ++ utils::toOpType(opType), ++ parseOpts ? utils::toParseOptions(*parseOpts) : 0, ++ &tree, ++ nullptr); + ParsedOp res{ + .tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt, + .op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt +diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp +index ce24203..2e0351a 100644 +--- a/src/SchemaNode.cpp ++++ b/src/SchemaNode.cpp +@@ -564,7 +564,7 @@ std::vector LeafList::defaultValuesStr() const + auto dflts = reinterpret_cast(m_node)->dflts; + std::vector res; + for (const auto& it : std::span(dflts, LY_ARRAY_COUNT(dflts))) { +- res.emplace_back(lyd_value_get_canonical(m_ctx.get(), it)); ++ res.emplace_back(it.str); + } + return res; + } +@@ -660,8 +660,8 @@ bool LeafList::isUserOrdered() const + std::optional Leaf::defaultValueStr() const + { + auto dflt = reinterpret_cast(m_node)->dflt; +- if (dflt) { +- return lyd_value_get_canonical(m_ctx.get(), dflt); ++ if (dflt.str) { ++ return std::string{dflt.str}; + } else { + return std::nullopt; + } +diff --git a/src/Type.cpp b/src/Type.cpp +index 01b2c4f..4d2db46 100644 +--- a/src/Type.cpp ++++ b/src/Type.cpp +@@ -240,7 +240,7 @@ std::optional Type::description() const + */ + std::string Type::internalPluginId() const + { +- return m_type->plugin->id; ++ return lysc_get_type_plugin(m_type->plugin_ref)->id; + } + + /** +diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp +index 5e02d24..0b2e2d7 100644 +--- a/src/utils/enum.hpp ++++ b/src/utils/enum.hpp +@@ -91,6 +91,7 @@ static_assert(LYD_DUP_WITH_FLAGS == toDuplicationOptions(DuplicationOptions::Wit + static_assert(LYD_DUP_WITH_PARENTS == toDuplicationOptions(DuplicationOptions::WithParents)); + static_assert(LYD_DUP_NO_EXT == toDuplicationOptions(DuplicationOptions::NoExt)); + static_assert(LYD_DUP_WITH_PRIV == toDuplicationOptions(DuplicationOptions::WithPriv)); ++static_assert(LYD_DUP_NO_LYDS == toDuplicationOptions(DuplicationOptions::NoLyds)); + + static_assert((LYD_DUP_NO_META | LYD_DUP_NO_EXT) == + toDuplicationOptions(DuplicationOptions::NoMeta | DuplicationOptions::NoExt)); +@@ -131,6 +132,13 @@ static_assert(toContextOptions(ContextOptions::DisableSearchCwd) == LY_CTX_DISAB + static_assert(toContextOptions(ContextOptions::PreferSearchDirs) == LY_CTX_PREFER_SEARCHDIRS); + static_assert(toContextOptions(ContextOptions::SetPrivParsed) == LY_CTX_SET_PRIV_PARSED); + static_assert(toContextOptions(ContextOptions::ExplicitCompile) == LY_CTX_EXPLICIT_COMPILE); ++static_assert(toContextOptions(ContextOptions::EnableImpFeatures) == LY_CTX_ENABLE_IMP_FEATURES); ++static_assert(toContextOptions(ContextOptions::CompileObsolete) == LY_CTX_COMPILE_OBSOLETE); ++static_assert(toContextOptions(ContextOptions::LybHashes) == LY_CTX_LYB_HASHES); ++static_assert(toContextOptions(ContextOptions::LeafrefExtended) == LY_CTX_LEAFREF_EXTENDED); ++static_assert(toContextOptions(ContextOptions::LeafrefLinking) == LY_CTX_LEAFREF_LINKING); ++static_assert(toContextOptions(ContextOptions::BuiltinPluginsOnly) == LY_CTX_BUILTIN_PLUGINS_ONLY); ++static_assert(toContextOptions(ContextOptions::StaticPluginsOnly) == LY_CTX_STATIC_PLUGINS_ONLY); + + constexpr uint16_t toLogOptions(const LogOptions options) + { +@@ -198,6 +206,10 @@ constexpr uint32_t toValidationOptions(const ValidationOptions opts) + + static_assert(toValidationOptions(ValidationOptions::NoState) == LYD_VALIDATE_NO_STATE); + static_assert(toValidationOptions(ValidationOptions::Present) == LYD_VALIDATE_PRESENT); ++static_assert(toValidationOptions(ValidationOptions::MultiError) == LYD_VALIDATE_MULTI_ERROR); ++static_assert(toValidationOptions(ValidationOptions::Operational) == LYD_VALIDATE_OPERATIONAL); ++static_assert(toValidationOptions(ValidationOptions::NoDefaults) == LYD_VALIDATE_NO_DEFAULTS); ++static_assert(toValidationOptions(ValidationOptions::NotFinal) == LYD_VALIDATE_NOT_FINAL); + + constexpr uint32_t toParseOptions(const ParseOptions opts) + { +@@ -208,8 +220,14 @@ static_assert(toParseOptions(ParseOptions::ParseOnly) == LYD_PARSE_ONLY); + static_assert(toParseOptions(ParseOptions::Strict) == LYD_PARSE_STRICT); + static_assert(toParseOptions(ParseOptions::Opaque) == LYD_PARSE_OPAQ); + static_assert(toParseOptions(ParseOptions::NoState) == LYD_PARSE_NO_STATE); +-static_assert(toParseOptions(ParseOptions::LybModUpdate) == LYD_PARSE_LYB_MOD_UPDATE); ++static_assert(toParseOptions(ParseOptions::LybSkipCtxCheck) == LYD_PARSE_LYB_SKIP_CTX_CHECK); + static_assert(toParseOptions(ParseOptions::Ordered) == LYD_PARSE_ORDERED); ++static_assert(toParseOptions(ParseOptions::Subtree) == LYD_PARSE_SUBTREE); ++static_assert(toParseOptions(ParseOptions::WhenTrue) == LYD_PARSE_WHEN_TRUE); ++static_assert(toParseOptions(ParseOptions::NoNew) == LYD_PARSE_NO_NEW); ++static_assert(toParseOptions(ParseOptions::StoreOnly) == LYD_PARSE_STORE_ONLY); ++static_assert(toParseOptions(ParseOptions::JsonNull) == LYD_PARSE_JSON_NULL); ++static_assert(toParseOptions(ParseOptions::JsonStringDataTypes) == LYD_PARSE_JSON_STRING_DATATYPES); + + constexpr lyd_type toOpType(const OperationType type) + { +@@ -242,7 +260,6 @@ static_assert(toAnydataValueType(AnydataValueType::DataTree) == LYD_ANYDATA_DATA + static_assert(toAnydataValueType(AnydataValueType::String) == LYD_ANYDATA_STRING); + static_assert(toAnydataValueType(AnydataValueType::XML) == LYD_ANYDATA_XML); + static_assert(toAnydataValueType(AnydataValueType::JSON) == LYD_ANYDATA_JSON); +-static_assert(toAnydataValueType(AnydataValueType::LYB) == LYD_ANYDATA_LYB); + + constexpr LYS_OUTFORMAT toLysOutFormat(const SchemaOutputFormat format) + { +diff --git a/tests/context.cpp b/tests/context.cpp +index c0b7e09..eaedebf 100644 +--- a/tests/context.cpp ++++ b/tests/context.cpp +@@ -359,25 +359,27 @@ TEST_CASE("context") + ctx->loadModule("mod1", std::nullopt, {}); + ctx->parseModule(valid_yang_model, libyang::SchemaFormat::YANG); + auto modules = ctx->modules(); +- REQUIRE(modules.size() == 8); ++ REQUIRE(modules.size() == 9); + REQUIRE(modules.at(0).name() == "ietf-yang-metadata"); + REQUIRE(modules.at(0).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-metadata"); + REQUIRE(modules.at(1).name() == "yang"); + REQUIRE(modules.at(1).ns() == "urn:ietf:params:xml:ns:yang:1"); +- REQUIRE(modules.at(2).name() == "ietf-inet-types"); +- REQUIRE(modules.at(2).ns() == "urn:ietf:params:xml:ns:yang:ietf-inet-types"); +- REQUIRE(modules.at(3).name() == "ietf-yang-types"); +- REQUIRE(modules.at(3).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-types"); +- REQUIRE(modules.at(4).name() == "ietf-yang-schema-mount"); +- REQUIRE(modules.at(4).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount"); +- REQUIRE(modules.at(5).name() == "ietf-yang-structure-ext"); +- REQUIRE(modules.at(5).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext"); +- REQUIRE(modules.at(6).name() == "mod1"); +- REQUIRE(modules.at(6).ns() == "http://example.com"); +- REQUIRE(*modules.at(6).revision() == "2021-11-15"); +- REQUIRE(modules.at(7).name() == "test"); ++ REQUIRE(modules.at(2).name() == "default"); ++ REQUIRE(modules.at(2).ns() == "urn:ietf:params:xml:ns:netconf:default:1.0"); ++ REQUIRE(modules.at(3).name() == "ietf-inet-types"); ++ REQUIRE(modules.at(3).ns() == "urn:ietf:params:xml:ns:yang:ietf-inet-types"); ++ REQUIRE(modules.at(4).name() == "ietf-yang-types"); ++ REQUIRE(modules.at(4).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-types"); ++ REQUIRE(modules.at(5).name() == "ietf-yang-schema-mount"); ++ REQUIRE(modules.at(5).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount"); ++ REQUIRE(modules.at(6).name() == "ietf-yang-structure-ext"); ++ REQUIRE(modules.at(6).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext"); ++ REQUIRE(modules.at(7).name() == "mod1"); + REQUIRE(modules.at(7).ns() == "http://example.com"); +- REQUIRE(modules.at(7).revision() == std::nullopt); ++ REQUIRE(*modules.at(7).revision() == "2021-11-15"); ++ REQUIRE(modules.at(8).name() == "test"); ++ REQUIRE(modules.at(8).ns() == "http://example.com"); ++ REQUIRE(modules.at(8).revision() == std::nullopt); + } + + DOCTEST_SUBCASE("Module comparison") +@@ -704,7 +706,7 @@ TEST_CASE("context") + + DOCTEST_SUBCASE("schema printing") + { +- std::optional ctx_pp{std::in_place, std::nullopt, libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::SetPrivParsed}; ++ std::optional ctx_pp{std::in_place, std::nullopt, libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::CompileObsolete}; + auto mod = ctx_pp->parseModule(type_module, libyang::SchemaFormat::YANG); + + REQUIRE(mod.printStr(libyang::SchemaOutputFormat::Tree) == R"(module: type_module +diff --git a/tests/data_node.cpp b/tests/data_node.cpp +index 9215b12..88ca5b9 100644 +--- a/tests/data_node.cpp ++++ b/tests/data_node.cpp +@@ -2426,7 +2426,7 @@ TEST_CASE("Data Node manipulation") + "WTF": "foo bar baz" + } + } +- )", libyang::DataFormat::JSON, libyang::OperationType::RpcRestconf), ++ )", libyang::DataFormat::JSON, libyang::OperationType::RpcRestconf, libyang::ParseOptions::Strict), + "Can't parse into operation data tree: LY_EVALID", libyang::Error); + } + } +diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp +index 65ef462..58152a4 100644 +--- a/tests/schema_node.cpp ++++ b/tests/schema_node.cpp +@@ -19,7 +19,7 @@ using namespace std::string_literals; + TEST_CASE("SchemaNode") + { + std::optional ctx{std::in_place, std::nullopt, +- libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd}; ++ libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::CompileObsolete}; + std::optional ctxWithParsed{std::in_place, std::nullopt, + libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd}; + ctx->parseModule(example_schema, libyang::SchemaFormat::YANG); +@@ -841,7 +841,7 @@ TEST_CASE("SchemaNode") + + DOCTEST_SUBCASE("LeafList::defaultValuesStr") + { +- REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector{"-1", "512", "1024", "2048"}); ++ REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector{"-1", "+512", "0x400", "04000"}); + REQUIRE(ctx->findPath("/type_module:leafListBasic").asLeafList().defaultValuesStr().size() == 0); + } + +-- +2.43.0 + diff --git a/patches/libyang-cpp/4/0005-Add-SchemaNode-isOutput-method.patch b/patches/libyang-cpp/4/0005-Add-SchemaNode-isOutput-method.patch new file mode 100644 index 00000000..17b2417d --- /dev/null +++ b/patches/libyang-cpp/4/0005-Add-SchemaNode-isOutput-method.patch @@ -0,0 +1,77 @@ +From 51d7457bd281e34bac92ff8231978766c7784657 Mon Sep 17 00:00:00 2001 +From: Martin Bohal +Date: Tue, 4 Nov 2025 14:26:05 +0100 +Subject: [PATCH 5/7] Add SchemaNode::isOutput() method +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This commit introduces the `isOutput()` method to the `SchemaNode` +class, which checks whether the schema node is part of an output +statement subtree. + +It complements an already existing `isInput()` method. + +Change-Id: I8fe75f2e607c0159d8295d685a59d301155ca92e +Signed-off-by: Mattias Walström +--- + include/libyang-cpp/SchemaNode.hpp | 1 + + src/SchemaNode.cpp | 10 ++++++++++ + tests/schema_node.cpp | 7 +++++++ + 3 files changed, 18 insertions(+) + +diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp +index 9f49fd7..db5a63e 100644 +--- a/include/libyang-cpp/SchemaNode.hpp ++++ b/include/libyang-cpp/SchemaNode.hpp +@@ -58,6 +58,7 @@ public: + Status status() const; + Config config() const; + bool isInput() const; ++ bool isOutput() const; + NodeType nodeType() const; + // It is possible to cast SchemaNode to another type via the following methods. The types are children classes of + // SchemaNode. No problems with slicing can occur, because these types are value-based and aren't constructible +diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp +index 2e0351a..9241b23 100644 +--- a/src/SchemaNode.cpp ++++ b/src/SchemaNode.cpp +@@ -193,6 +193,16 @@ bool SchemaNode::isInput() const + return m_node->flags & LYS_INPUT; + } + ++/** ++ * @brief Checks whether this node is inside a subtree of an output statement. ++ * ++ * Wraps `LYS_OUTPUT`. ++ */ ++bool SchemaNode::isOutput() const ++{ ++ return m_node->flags & LYS_OUTPUT; ++} ++ + /** + * Returns the node type of this node (e.g. leaf, container...). + * +diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp +index 58152a4..01c2f19 100644 +--- a/tests/schema_node.cpp ++++ b/tests/schema_node.cpp +@@ -135,6 +135,13 @@ TEST_CASE("SchemaNode") + REQUIRE(!ctx->findPath("/type_module:leafString").isInput()); + } + ++ DOCTEST_SUBCASE("SchemaNode::isOutput") ++ { ++ REQUIRE(ctx->findPath("/example-schema:myRpc/outputLeaf", libyang::InputOutputNodes::Output).isOutput()); ++ REQUIRE(!ctx->findPath("/example-schema:myRpc/inputLeaf").isOutput()); ++ REQUIRE(!ctx->findPath("/type_module:leafString").isOutput()); ++ } ++ + DOCTEST_SUBCASE("SchemaNode::module") + { + REQUIRE(ctx->findPath("/type_module:leafString").module().name() == "type_module"); +-- +2.43.0 + diff --git a/patches/libyang-cpp/4/0006-adapt-to-changes-in-libyang-v4.2.patch b/patches/libyang-cpp/4/0006-adapt-to-changes-in-libyang-v4.2.patch new file mode 100644 index 00000000..c16768a7 --- /dev/null +++ b/patches/libyang-cpp/4/0006-adapt-to-changes-in-libyang-v4.2.patch @@ -0,0 +1,364 @@ +From 38d5c10009df602594e1b9a53bac41111a9ba430 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 20 Nov 2025 15:32:03 +0100 +Subject: [PATCH 6/7] adapt to changes in libyang v4.2 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +I was *so* tempted to leave the C++ enums as-is, just to save us from +this needless sed exercise, but hey, let's not start confusing any +possible downstream consumers now that upstream has changed these +identifiers. + +Change-Id: I9b34585822b41be0ca1578051f1c0eb8588a0a51 +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 4 +-- + README.md | 2 +- + include/libyang-cpp/Enum.hpp | 4 +-- + src/utils/enum.hpp | 4 +-- + tests/context.cpp | 10 +++---- + tests/data_node.cpp | 58 ++++++++++++++++++------------------ + 6 files changed, 41 insertions(+), 41 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 90f9aaa..cbaf82a 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -20,7 +20,7 @@ add_custom_target(libyang-cpp-version-cmake + cmake/ProjectGitVersionRunner.cmake + ) + include(cmake/ProjectGitVersion.cmake) +-set(LIBYANG_CPP_PKG_VERSION "5") ++set(LIBYANG_CPP_PKG_VERSION "6") + prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION}) + + find_package(Doxygen) +@@ -28,7 +28,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D + option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON) + + find_package(PkgConfig REQUIRED) +-pkg_check_modules(LIBYANG REQUIRED libyang>=4.1.0 IMPORTED_TARGET) ++pkg_check_modules(LIBYANG REQUIRED libyang>=4.2.1 IMPORTED_TARGET) + + # FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency + find_package(date) +diff --git a/README.md b/README.md +index 0cef2ff..842573f 100644 +--- a/README.md ++++ b/README.md +@@ -8,7 +8,7 @@ + Object lifetimes are managed automatically via RAII. + + ## Dependencies +-- [libyang v4](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*) ++- [libyang v4.2+](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*) + - C++20 compiler (e.g., GCC 10.x+, clang 10+) + - CMake 3.19+ + - optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework +diff --git a/include/libyang-cpp/Enum.hpp b/include/libyang-cpp/Enum.hpp +index 32142f9..096e030 100644 +--- a/include/libyang-cpp/Enum.hpp ++++ b/include/libyang-cpp/Enum.hpp +@@ -67,9 +67,9 @@ enum class OperationType : uint32_t { + */ + enum class PrintFlags : uint32_t { + WithDefaultsExplicit = 0x00, +- WithSiblings = 0x01, ++ Siblings = 0x01, + Shrink = 0x02, +- KeepEmptyCont = 0x04, ++ EmptyContainers = 0x04, + WithDefaultsTrim = 0x10, + WithDefaultsAll = 0x20, + WithDefaultsAllTag = 0x40, +diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp +index 0b2e2d7..a8902ff 100644 +--- a/src/utils/enum.hpp ++++ b/src/utils/enum.hpp +@@ -33,7 +33,7 @@ constexpr uint32_t toPrintFlags(const PrintFlags flags) + } + // These tests ensure that I used the right numbers when defining my enum. + // TODO: add asserts for operator|(PrintFlags, PrintFlags) +-static_assert(LYD_PRINT_KEEPEMPTYCONT == toPrintFlags(PrintFlags::KeepEmptyCont)); ++static_assert(LYD_PRINT_EMPTY_CONT == toPrintFlags(PrintFlags::EmptyContainers)); + static_assert(LYD_PRINT_SHRINK == toPrintFlags(PrintFlags::Shrink)); + static_assert(LYD_PRINT_WD_ALL == toPrintFlags(PrintFlags::WithDefaultsAll)); + static_assert(LYD_PRINT_WD_ALL_TAG == toPrintFlags(PrintFlags::WithDefaultsAllTag)); +@@ -41,7 +41,7 @@ static_assert(LYD_PRINT_WD_EXPLICIT == toPrintFlags(PrintFlags::WithDefaultsExpl + static_assert(LYD_PRINT_WD_IMPL_TAG == toPrintFlags(PrintFlags::WithDefaultsImplicitTag)); + static_assert(LYD_PRINT_WD_MASK == toPrintFlags(PrintFlags::WithDefaultsMask)); + static_assert(LYD_PRINT_WD_TRIM == toPrintFlags(PrintFlags::WithDefaultsTrim)); +-static_assert(LYD_PRINT_WITHSIBLINGS == toPrintFlags(PrintFlags::WithSiblings)); ++static_assert(LYD_PRINT_SIBLINGS == toPrintFlags(PrintFlags::Siblings)); + + #ifndef _MSC_VER + // MSVC doesn't respect the underlying enum size +diff --git a/tests/context.cpp b/tests/context.cpp +index eaedebf..355860b 100644 +--- a/tests/context.cpp ++++ b/tests/context.cpp +@@ -502,7 +502,7 @@ TEST_CASE("context") + auto errorsNode = node->findXPath("/ietf-restconf:errors"); + REQUIRE(errorsNode.size() == 1); + REQUIRE(errorsNode.begin()->path() == "/ietf-restconf:errors"); +- REQUIRE(*errorsNode.begin()->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*errorsNode.begin()->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({ + "ietf-restconf:errors": { + "error": [ + { +@@ -589,7 +589,7 @@ TEST_CASE("context") + + auto firstValue = edits.begin()->findPath("value"); + REQUIRE(firstValue); +- REQUIRE(*firstValue->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*firstValue->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({ + "ietf-yang-patch:value": { + "example-schema:person": { + "name": "John" +@@ -597,7 +597,7 @@ TEST_CASE("context") + } + } + )"); +- REQUIRE(*firstValue->printStr(libyang::DataFormat::XML, libyang::PrintFlags::KeepEmptyCont) == R"( ++ REQUIRE(*firstValue->printStr(libyang::DataFormat::XML, libyang::PrintFlags::EmptyContainers) == R"( + + John + +@@ -607,8 +607,8 @@ TEST_CASE("context") + auto secondValueNode = (edits.begin() + 1)->findPath("value"); + REQUIRE(secondValueNode); + auto secondValue = std::get(secondValueNode->asAny().releaseValue().value()); +- REQUIRE(*secondValue.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == "{\n \"example-schema:dummy\": \"I am a dummy\"\n}\n"); +- REQUIRE(*secondValue.printStr(libyang::DataFormat::XML, libyang::PrintFlags::KeepEmptyCont) == "I am a dummy\n"); ++ REQUIRE(*secondValue.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == "{\n \"example-schema:dummy\": \"I am a dummy\"\n}\n"); ++ REQUIRE(*secondValue.printStr(libyang::DataFormat::XML, libyang::PrintFlags::EmptyContainers) == "I am a dummy\n"); + } + } + +diff --git a/tests/data_node.cpp b/tests/data_node.cpp +index 88ca5b9..3f874de 100644 +--- a/tests/data_node.cpp ++++ b/tests/data_node.cpp +@@ -137,7 +137,7 @@ TEST_CASE("Data Node manipulation") + DOCTEST_SUBCASE("Printing") + { + auto node = ctx.parseData(data, libyang::DataFormat::JSON); +- auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont); ++ auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers); + const auto expected = R"({ + "example-schema:leafInt32": 420, + "example-schema:first": { +@@ -158,7 +158,7 @@ TEST_CASE("Data Node manipulation") + REQUIRE(str == expected); + + auto emptyCont = ctx.newPath("/example-schema:first"); +- REQUIRE(emptyCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings) == std::nullopt); ++ REQUIRE(emptyCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Siblings) == std::nullopt); + } + + DOCTEST_SUBCASE("Overwriting a tree with a different tree") +@@ -504,7 +504,7 @@ TEST_CASE("Data Node manipulation") + { + auto node = std::optional{ctx.newPath("/example-schema:leafInt32", "420")}; + libyang::validateAll(node, libyang::ValidationOptions::NoState); +- auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont); ++ auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers); + REQUIRE(str == data); + } + +@@ -725,7 +725,7 @@ TEST_CASE("Data Node manipulation") + } + + // The original tree should still be accesible. +- node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings); ++ node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings); + } + } + +@@ -855,7 +855,7 @@ TEST_CASE("Data Node manipulation") + auto cont = ctx.newPath2("/example-schema2:contWithTwoNodes").createdNode; + data->unlinkWithSiblings(); + cont->insertChild(*data); +- REQUIRE(*cont->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == R"({ ++ REQUIRE(*cont->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == R"({ + "example-schema2:contWithTwoNodes": { + "one": 333, + "two": 666 +@@ -1501,9 +1501,9 @@ TEST_CASE("Data Node manipulation") + REQUIRE(std::holds_alternative(rawVal)); + auto retrieved = std::get(rawVal); + REQUIRE(retrieved.path() == "/key"); +- REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == R"|({"key":"value"})|"); +- REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == R"|(value)|"); + } + } +@@ -1525,9 +1525,9 @@ TEST_CASE("Data Node manipulation") + REQUIRE(std::holds_alternative(rawVal)); + auto retrieved = std::get(rawVal); + REQUIRE(retrieved.path() == "/something"); +- REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == R"|(lol)|"); +- REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == R"|({"something":"lol"})|"); + } + } +@@ -1572,9 +1572,9 @@ TEST_CASE("Data Node manipulation") + val = jsonAnyXmlNode.createdNode->asAny().releaseValue(); + } + +- REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == R"|({"example-schema:ax":[1,2,3]})|"s); +- REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == R"|()|"s + origJSON + ""); + } + +@@ -1603,9 +1603,9 @@ TEST_CASE("Data Node manipulation") + } + + REQUIRE(root); +- REQUIRE(*root->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*root->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == origXML); +- REQUIRE(*root->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*root->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == origJSON); + + auto node = root->findPath("/example-schema:ax"); +@@ -1665,9 +1665,9 @@ TEST_CASE("Data Node manipulation") + auto retrieved = std::get(*val); + val.reset(); + REQUIRE(retrieved.path() == "/a"); +- REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == origXML); +- REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) ++ REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings) + == origJSON); + } + } +@@ -1806,7 +1806,7 @@ TEST_CASE("Data Node manipulation") + + nodeX.parseSubtree(data, libyang::DataFormat::JSON, + libyang::ParseOptions::Strict | libyang::ParseOptions::NoState | libyang::ParseOptions::ParseOnly); +- REQUIRE(*nodeX.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == R"({ ++ REQUIRE(*nodeX.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == R"({ + "example-schema5:x": { + "x_b": { + "x_b_leaf": 666 +@@ -1948,7 +1948,7 @@ TEST_CASE("Data Node manipulation") + { + netconfDeletePresenceCont.newMeta(netconf, "operation", "delete"); + netconfDeletePresenceCont.newMeta(ietfOrigin, "origin", "ietf-origin:default"); +- REQUIRE(*netconfDeletePresenceCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings) ++ REQUIRE(*netconfDeletePresenceCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Siblings) + == R"()" "\n"); + } + +@@ -1957,7 +1957,7 @@ TEST_CASE("Data Node manipulation") + auto opaqueLeaf = ctx.newPath("/example-schema:leafInt32", std::nullopt, libyang::CreationOptions::Opaque); + REQUIRE_THROWS(opaqueLeaf.newMeta(netconf, "operation", "delete")); + opaqueLeaf.newAttrOpaqueJSON("ietf-netconf", "operation", "delete"); +- REQUIRE(*opaqueLeaf.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) ++ REQUIRE(*opaqueLeaf.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) + == R"({ + "example-schema:leafInt32": "", + "@example-schema:leafInt32": { +@@ -1976,7 +1976,7 @@ TEST_CASE("Data Node manipulation") + auto discard2 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", "sysrepo", "discard-items"}, libyang::JSON{"/example-schema:b"}); + REQUIRE(!!discard2); + discard1->insertSibling(*discard2); +- REQUIRE(*discard1->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) ++ REQUIRE(*discard1->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) + == R"({ + "sysrepo:discard-items": "/example-schema:a", + "sysrepo:discard-items": "/example-schema:b" +@@ -1991,7 +1991,7 @@ TEST_CASE("Data Node manipulation") + + auto leafInt16 = ctx.newPath("/example-schema:leafInt16", "666"); + leafInt16.insertSibling(*discard1); +- REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) ++ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) + == R"({ + "example-schema:leafInt16": 666, + "sysrepo:discard-items": "/example-schema:a", +@@ -2020,7 +2020,7 @@ TEST_CASE("Data Node manipulation") + + dummy.insertSibling(*discard3); + leafInt16.insertSibling(dummy); +- REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) ++ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) + == R"({ + "example-schema:dummy": "blah", + "example-schema:leafInt16": 666, +@@ -2112,8 +2112,8 @@ TEST_CASE("Data Node manipulation") + data->unlinkWithSiblings(); + out->insertChild(*data); + +- REQUIRE(*out->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == expectedJson); +- REQUIRE(*out->printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings) == expectedXml); ++ REQUIRE(*out->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == expectedJson); ++ REQUIRE(*out->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Siblings) == expectedXml); + } + + DOCTEST_SUBCASE("libyang internal metadata") +@@ -2148,7 +2148,7 @@ TEST_CASE("Data Node manipulation") + auto node = ctx.newExtPath(ext, "/ietf-restconf:errors", std::nullopt, std::nullopt); + REQUIRE(node); + REQUIRE(node->schema().name() == "errors"); +- REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({ + "ietf-restconf:errors": {} + } + )"); +@@ -2157,7 +2157,7 @@ TEST_CASE("Data Node manipulation") + REQUIRE(node->newPath("ietf-restconf:error[1]/error-tag", "invalid-attribute")); + REQUIRE(node->newExtPath(ext, "/ietf-restconf:errors/error[1]/error-message", "ahoj")); + REQUIRE_THROWS_WITH(node->newPath("ietf-restconf:error[1]/error-message", "duplicate create"), "Couldn't create a node with path 'ietf-restconf:error[1]/error-message': LY_EEXIST"); +- REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({ + "ietf-restconf:errors": { + "error": [ + { +@@ -2173,7 +2173,7 @@ TEST_CASE("Data Node manipulation") + REQUIRE(node->newExtPath(ext, "/ietf-restconf:errors/error[2]/error-type", "transport")); + REQUIRE(node->newExtPath(ext, "/ietf-restconf:errors/error[2]/error-tag", "invalid-attribute")); + REQUIRE(node->newPath("ietf-restconf:error[2]/error-message", "aaa")); +- REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({ + "ietf-restconf:errors": { + "error": [ + { +@@ -2397,8 +2397,8 @@ TEST_CASE("Data Node manipulation") + REQUIRE(response.tree->path() == "/example-schema:output"); + REQUIRE(response.tree->isOpaque()); + REQUIRE(!response.tree->child()); // nothing gets "parsed" here, the result is put into the tree that parseOp() operated on (!) +- CAPTURE(*response.tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont)); +- CAPTURE(*replyTree.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont)); ++ CAPTURE(*response.tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers)); ++ CAPTURE(*replyTree.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers)); + + node = replyTree.findPath("/example-schema:myRpc/outputLeaf", libyang::InputOutputNodes::Output); + REQUIRE(!!node); +@@ -2472,7 +2472,7 @@ TEST_CASE("Data Node manipulation") + REQUIRE(rpcOp.tree); + + libyang::validateOp(*rpcTree, depTree, libyang::OperationType::RpcRestconf); +- REQUIRE(*rpcTree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == expected); ++ REQUIRE(*rpcTree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == expected); + } + + DOCTEST_SUBCASE("Nodes in disjunctive cases defined together") +-- +2.43.0 + diff --git a/patches/libyang-cpp/4/0007-don-t-segfault-when-listing-module-s-features.patch b/patches/libyang-cpp/4/0007-don-t-segfault-when-listing-module-s-features.patch new file mode 100644 index 00000000..a05ee827 --- /dev/null +++ b/patches/libyang-cpp/4/0007-don-t-segfault-when-listing-module-s-features.patch @@ -0,0 +1,112 @@ +From af187d1bc27a7390b358d7ad3b9f8f44cabe41ad Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 20 Nov 2025 12:46:49 +0100 +Subject: [PATCH 7/7] don't segfault when listing module's features +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This is especially relevant when working with the "printed context" from +sysrepo, because that context has all the parsed info removed. As a +result, it is not possible to query a module's list of features. Let's +stop dereferencing a null pointer in that case. + +Bug: https://github.com/sysrepo/sysrepo/issues/3695 +Change-Id: I6258f580b4e5aa02cae2d60260b84593aefcf587 +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 1 + + include/libyang-cpp/Utils.hpp | 1 + + src/Module.cpp | 3 +++ + src/utils/exception.cpp | 5 +++++ + tests/context.cpp | 12 ++++++++++++ + 5 files changed, 22 insertions(+) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index cbaf82a..b9da66b 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -118,6 +118,7 @@ if(BUILD_TESTING) + endfunction() + + libyang_cpp_test(context) ++ target_link_libraries(test_context PkgConfig::LIBYANG) + libyang_cpp_test(data_node) + target_link_libraries(test_data_node PkgConfig::LIBYANG) + libyang_cpp_test(schema_node) +diff --git a/include/libyang-cpp/Utils.hpp b/include/libyang-cpp/Utils.hpp +index 934714d..428b265 100644 +--- a/include/libyang-cpp/Utils.hpp ++++ b/include/libyang-cpp/Utils.hpp +@@ -35,6 +35,7 @@ public: + class LIBYANG_CPP_EXPORT ParsedInfoUnavailable : public Error { + public: + explicit ParsedInfoUnavailable(); ++ explicit ParsedInfoUnavailable(const std::string& what); + }; + + /** +diff --git a/src/Module.cpp b/src/Module.cpp +index d6d4023..8eb8fb4 100644 +--- a/src/Module.cpp ++++ b/src/Module.cpp +@@ -155,6 +155,9 @@ void Module::setImplemented(const AllFeatures) + */ + std::vector Module::features() const + { ++ if (!m_module->parsed) { ++ throw ParsedInfoUnavailable{"Module::features: lys_module::parsed is not available"}; ++ } + std::vector res; + for (const auto& feature : std::span(m_module->parsed->features, LY_ARRAY_COUNT(m_module->parsed->features))) { + res.emplace_back(Feature{&feature, m_ctx}); +diff --git a/src/utils/exception.cpp b/src/utils/exception.cpp +index ecd2b85..a8d908b 100644 +--- a/src/utils/exception.cpp ++++ b/src/utils/exception.cpp +@@ -22,6 +22,11 @@ ParsedInfoUnavailable::ParsedInfoUnavailable() + { + } + ++ParsedInfoUnavailable::ParsedInfoUnavailable(const std::string& what) ++ : Error(what) ++{ ++} ++ + ErrorCode ErrorWithCode::code() const + { + return m_errCode; +diff --git a/tests/context.cpp b/tests/context.cpp +index 355860b..5fce0f4 100644 +--- a/tests/context.cpp ++++ b/tests/context.cpp +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include "example_schema.hpp" + #include "pretty_printers.hpp" + #include "test_vars.hpp" +@@ -333,6 +334,17 @@ TEST_CASE("context") + REQUIRE(enabledFeatures == expectedEnabledFeatures); + } + ++ DOCTEST_SUBCASE("printed context") ++ { ++ ctx->setSearchDir(TESTS_DIR / "yang"); ++ auto mod = ctx->loadModule("mod1", std::nullopt, {"*"}); ++ REQUIRE(mod.features().size() == 3); ++ ly_ctx_free_parsed(retrieveContext(*ctx)); ++ REQUIRE_THROWS_WITH_AS(mod.features(), ++ "Module::features: lys_module::parsed is not available", ++ libyang::ParsedInfoUnavailable); ++ } ++ + DOCTEST_SUBCASE("Module::setImplemented") + { + ctx->setSearchDir(TESTS_DIR / "yang"); +-- +2.43.0 + diff --git a/patches/lldpd/1.0.15/0001-lldpd-allow-all-users-of-group-wheel-to-connect.patch b/patches/lldpd/1.0.15/0001-lldpd-allow-all-users-of-group-wheel-to-connect.patch deleted file mode 100644 index 1198f40d..00000000 --- a/patches/lldpd/1.0.15/0001-lldpd-allow-all-users-of-group-wheel-to-connect.patch +++ /dev/null @@ -1,34 +0,0 @@ ---- a/src/daemon/lldpd.c -+++ b/src/daemon/lldpd.c -@@ -1581,6 +1581,8 @@ - struct group *group; - uid_t uid; - gid_t gid; -+#else -+ struct group *group; - #endif - - saved_argv = argv; -@@ -1780,6 +1782,8 @@ - if ((group = getgrnam(PRIVSEP_GROUP)) == NULL) - fatalx("main", "no " PRIVSEP_GROUP " group for privilege separation, please create it"); - gid = group->gr_gid; -+#else -+ group = getgrnam("wheel"); - #endif - - /* Create and setup socket */ -@@ -1815,6 +1819,13 @@ - S_IRUSR | S_IWUSR | S_IXUSR | - S_IRGRP | S_IWGRP | S_IXGRP) == -1) - log_warn("main", "unable to chmod control socket"); -+#else -+ if (group) { -+ if (chown(ctlname, -1, group->gr_gid)) -+ log_warn("main", "unable to chown control socket"); -+ else -+ chmod(ctlname, 0770); -+ } - #endif - - /* Create associated advisory lock file */ diff --git a/patches/lldpd/1.0.18/0001-lldpd-allow-all-users-of-group-wheel-to-connect.patch b/patches/lldpd/1.0.20/0001-lldpd-allow-all-users-of-group-wheel-to-connect.patch similarity index 100% rename from patches/lldpd/1.0.18/0001-lldpd-allow-all-users-of-group-wheel-to-connect.patch rename to patches/lldpd/1.0.20/0001-lldpd-allow-all-users-of-group-wheel-to-connect.patch diff --git a/patches/rauc/1.11.3/0001-src-bundle-enable-tftp-protocol.patch b/patches/rauc/1.11.3/0001-src-bundle-enable-tftp-protocol.patch deleted file mode 100644 index ac98f459..00000000 --- a/patches/rauc/1.11.3/0001-src-bundle-enable-tftp-protocol.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/src/bundle.c b/src/bundle.c -index 05ec358..d5888d4 100644 ---- a/src/bundle.c -+++ b/src/bundle.c -@@ -1313,6 +1313,7 @@ static gboolean is_remote_scheme(const gchar *scheme) - { - return (g_strcmp0(scheme, "http") == 0) || - (g_strcmp0(scheme, "https") == 0) || -+ (g_strcmp0(scheme, "tftp") == 0) || - (g_strcmp0(scheme, "sftp") == 0) || - (g_strcmp0(scheme, "ftp") == 0) || - (g_strcmp0(scheme, "ftps") == 0); diff --git a/patches/rauc/1.11.3/0002-src-main-add-optional-syslog-support.patch b/patches/rauc/1.11.3/0002-src-main-add-optional-syslog-support.patch deleted file mode 100644 index 0cbc00bd..00000000 --- a/patches/rauc/1.11.3/0002-src-main-add-optional-syslog-support.patch +++ /dev/null @@ -1,84 +0,0 @@ -diff --git a/src/main.c b/src/main.c -index 8e851b4..62d0bec 100644 ---- a/src/main.c -+++ b/src/main.c -@@ -9,6 +9,7 @@ - #include - #include - #include -+#include - #include - #include - -@@ -2183,6 +2184,38 @@ static gboolean unknown_start(int argc, char **argv) - return TRUE; - } - -+static int log_level(GLogLevelFlags level) -+{ -+ if (level & G_LOG_FLAG_FATAL) -+ return LOG_EMERG; -+ if (level & G_LOG_FLAG_RECURSION) -+ return LOG_ALERT; -+ if (level & G_LOG_LEVEL_CRITICAL) -+ return LOG_CRIT; -+ if (level & G_LOG_LEVEL_ERROR) -+ return LOG_ERR; -+ if (level & G_LOG_LEVEL_WARNING) -+ return LOG_WARNING; -+ if (level & G_LOG_LEVEL_MESSAGE) -+ return LOG_NOTICE; -+ if (level & G_LOG_LEVEL_INFO) -+ return LOG_INFO; -+ if (level & G_LOG_LEVEL_DEBUG) -+ return LOG_DEBUG; -+ -+ /* Fallback to INFO for unknown levels */ -+ return LOG_INFO; -+} -+ -+static void syslog_handler(const gchar *domain, GLogLevelFlags level, const gchar *message, gpointer arg) -+{ -+ /* unused */ -+ (void)domain; -+ (void)arg; -+ -+ syslog(log_level(level), "%s", message); -+} -+ - typedef enum { - UNKNOWN = 0, - INSTALL, -@@ -2370,7 +2403,7 @@ static void create_option_groups(void) - - static void cmdline_handler(int argc, char **argv) - { -- gboolean help = FALSE, debug = FALSE, version = FALSE; -+ gboolean help = FALSE, debug = FALSE, use_syslog = FALSE, version = FALSE; - g_autofree gchar *confpath = NULL, *keyring = NULL, *mount = NULL; - char *cmdarg = NULL; - g_autoptr(GOptionContext) context = NULL; -@@ -2383,6 +2416,7 @@ static void cmdline_handler(int argc, char **argv) - {"intermediate", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME_ARRAY, &intermediate, "intermediate CA file or PKCS#11 URL", "PEMFILE|PKCS11-URL"}, - {"mount", '\0', 0, G_OPTION_ARG_FILENAME, &mount, "mount prefix", "PATH"}, - {"debug", 'd', 0, G_OPTION_ARG_NONE, &debug, "enable debug output", NULL}, -+ {"syslog", 's', 0, G_OPTION_ARG_NONE, &use_syslog, "use syslog instead of stdout", NULL}, - {"version", '\0', 0, G_OPTION_ARG_NONE, &version, "display version", NULL}, - {"help", 'h', 0, G_OPTION_ARG_NONE, &help, "display help and exit", NULL}, - {0} -@@ -2500,6 +2534,15 @@ static void cmdline_handler(int argc, char **argv) - g_message("Debug log domains: '%s'", domains); - } - -+ if (use_syslog) { -+ GLogLevelFlags levels = G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION; -+ const char *ident = "rauc"; -+ -+ /* XXX: facility should be configurable */ -+ openlog(ident, LOG_PID | LOG_NOWAIT, LOG_LOCAL0); -+ g_log_set_handler(ident, levels, syslog_handler, NULL); -+ } -+ - /* get first parameter without dashes */ - for (gint i = 1; i <= argc; i++) { - if (argv[i] && !g_str_has_prefix(argv[i], "-")) { diff --git a/patches/rauc/1.11.3/0003-src-install-print-bootname-when-checking-slot.patch b/patches/rauc/1.11.3/0003-src-install-print-bootname-when-checking-slot.patch deleted file mode 100644 index e281302f..00000000 --- a/patches/rauc/1.11.3/0003-src-install-print-bootname-when-checking-slot.patch +++ /dev/null @@ -1,29 +0,0 @@ -This makes the output more user friendly on systems where the bootname -is the common way to refer to a slot. - -Example output: -... -40% Checking slot rootfs.1 (secondary) -46% Checking slot rootfs.1 (secondary) done. -... - -diff --git a/src/install.c b/src/install.c -index ea7c95b3..fb38bcab 100644 ---- a/src/install.c -+++ b/src/install.c -@@ -965,7 +965,11 @@ static gboolean handle_slot_install_plan(const RaucManifest *manifest, const RIm - - install_args_update(args, "Checking slot %s", plan->target_slot->name); - -- r_context_begin_step_weighted_formatted("check_slot", 0, 1, "Checking slot %s", plan->target_slot->name); -+ r_context_begin_step_weighted_formatted("check_slot", 0, 1, "Checking slot %s%s%s%s", -+ plan->target_slot->name, -+ plan->target_slot->bootname ? " (" : "", -+ plan->target_slot->bootname ? plan->target_slot->bootname : "", -+ plan->target_slot->bootname ? ")" : ""); - - r_slot_status_load(plan->target_slot); - slot_state = plan->target_slot->status; --- -2.34.1 - diff --git a/patches/rauc/1.11.3/0004-src-main-add-warning-banner-at-start-of-installation.patch b/patches/rauc/1.11.3/0004-src-main-add-warning-banner-at-start-of-installation.patch deleted file mode 100644 index 22fac750..00000000 --- a/patches/rauc/1.11.3/0004-src-main-add-warning-banner-at-start-of-installation.patch +++ /dev/null @@ -1,24 +0,0 @@ -Print "the standard" warning banner at start of installation. - -diff --git a/src/main.c b/src/main.c -index 8e851b47..bba1d012 100644 ---- a/src/main.c -+++ b/src/main.c -@@ -258,6 +258,14 @@ static gboolean install_start(int argc, char **argv) - if (access_args.http_headers) - args->access_args.http_headers = g_strdupv(access_args.http_headers); - -+ g_print("+------------------------------------------------------------------------------+\n"); -+ g_print("| |\n"); -+ g_print("| [!] Installation might take a while. DO NOT power off the device. |\n"); -+ g_print("| |\n"); -+ g_print("| Please wait while the upgrade completes. |\n"); -+ g_print("| |\n"); -+ g_print("+------------------------------------------------------------------------------+\n"); -+ - r_loop = g_main_loop_new(NULL, FALSE); - if (ENABLE_SERVICE) { - g_auto(GVariantDict) dict = G_VARIANT_DICT_INIT(NULL); --- -2.34.1 - diff --git a/patches/rauc/1.13/0001-src-main-add-optional-syslog-support.patch b/patches/rauc/1.15.1/0001-src-main-add-optional-syslog-support.patch similarity index 76% rename from patches/rauc/1.13/0001-src-main-add-optional-syslog-support.patch rename to patches/rauc/1.15.1/0001-src-main-add-optional-syslog-support.patch index ed85c89d..7fc53af4 100644 --- a/patches/rauc/1.13/0001-src-main-add-optional-syslog-support.patch +++ b/patches/rauc/1.15.1/0001-src-main-add-optional-syslog-support.patch @@ -1,8 +1,11 @@ -From c4e4ad9d69b3bc62ee53f8088d6a192d288c6645 Mon Sep 17 00:00:00 2001 +From 20b63b112df3092b51b8f1d2f9ef26c238fbc3dc Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 23 Nov 2023 18:49:36 +0100 -Subject: [PATCH 2/2] src/main: add optional syslog support -Organization: Addiva Elektronik +Subject: [PATCH] src/main: add optional syslog support +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires Instead of having to redirect (colored) logs to stdout/stderr, this patch adds support for logging directly to syslog with approximate @@ -13,14 +16,15 @@ logged to LOG_LOCAL0 facility. This should of course be configurable but is not at this stage. Signed-off-by: Joachim Wiberg +Signed-off-by: Mattias Walström --- src/main.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) - -diff -urN rauc-1.13.orig/src/main.c rauc-1.13/src/main.c ---- rauc-1.13.orig/src/main.c 2025-03-04 14:55:59.671534612 +0100 -+++ rauc-1.13/src/main.c 2025-03-04 14:57:13.022772424 +0100 +diff --git a/src/main.c b/src/main.c +index b3192a38..0ca5af21 100644 +--- a/src/main.c ++++ b/src/main.c @@ -10,6 +10,7 @@ #include #include @@ -29,7 +33,7 @@ diff -urN rauc-1.13.orig/src/main.c rauc-1.13/src/main.c #include #include -@@ -2460,6 +2461,38 @@ +@@ -2540,6 +2541,38 @@ static gboolean unknown_start(int argc, char **argv) return TRUE; } @@ -68,16 +72,16 @@ diff -urN rauc-1.13.orig/src/main.c rauc-1.13/src/main.c typedef enum { UNKNOWN = 0, INSTALL, -@@ -2676,7 +2709,7 @@ +@@ -2774,7 +2807,7 @@ static gboolean collect_config_values(const gchar *option_name, const gchar *val static void cmdline_handler(int argc, char **argv) { - gboolean help = FALSE, debug = FALSE, version = FALSE; -+ gboolean help = FALSE, debug = FALSE, use_syslog = FALSE, version = FALSE; - g_autofree gchar *confpath = NULL, *keyring = NULL, *mount = NULL; ++ gboolean help = FALSE, debug = FALSE, version = FALSE, use_syslog = FALSE; + g_autofree gchar *confpath = NULL, *mount = NULL; char *cmdarg = NULL; g_autoptr(GOptionContext) context = NULL; -@@ -2690,6 +2723,7 @@ +@@ -2789,6 +2822,7 @@ static void cmdline_handler(int argc, char **argv) {"intermediate", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME_ARRAY, &intermediate, "intermediate CA file or PKCS#11 URL", "PEMFILE|PKCS11-URL"}, {"mount", '\0', 0, G_OPTION_ARG_FILENAME, &mount, "mount prefix", "PATH"}, {"debug", 'd', 0, G_OPTION_ARG_NONE, &debug, "enable debug output", NULL}, @@ -85,7 +89,7 @@ diff -urN rauc-1.13.orig/src/main.c rauc-1.13/src/main.c {"version", '\0', 0, G_OPTION_ARG_NONE, &version, "display version", NULL}, {"help", 'h', 0, G_OPTION_ARG_NONE, &help, "display help and exit", NULL}, {0} -@@ -2816,6 +2850,15 @@ +@@ -2916,6 +2950,15 @@ static void cmdline_handler(int argc, char **argv) ); } @@ -101,3 +105,6 @@ diff -urN rauc-1.13.orig/src/main.c rauc-1.13/src/main.c /* get first parameter without dashes */ for (gint i = 1; i <= argc; i++) { if (argv[i] && !g_str_has_prefix(argv[i], "-")) { +-- +2.43.0 + diff --git a/patches/sysrepo-cpp/6/0001-CI-pin-to-libyang-v3.patch b/patches/sysrepo-cpp/6/0001-CI-pin-to-libyang-v3.patch new file mode 100644 index 00000000..91d49243 --- /dev/null +++ b/patches/sysrepo-cpp/6/0001-CI-pin-to-libyang-v3.patch @@ -0,0 +1,47 @@ +From ee0ce2ea31f3ecba4a4753a761df08f0698c2726 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Mon, 20 Oct 2025 11:47:26 +0200 +Subject: [PATCH 1/9] CI: pin to libyang v3 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8978 +Change-Id: I2521ad3a62d6197e757e636b88478deeb3bb5592 +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index 7ada6f7..cef174f 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -4,9 +4,9 @@ + - f38-gcc-cover: + required-projects: + - name: github/CESNET/libyang +- override-checkout: devel ++ override-checkout: cesnet/2025-07-08 + - name: github/sysrepo/sysrepo +- override-checkout: devel ++ override-checkout: cesnet/2025-06-02 + - name: github/doctest/doctest + override-checkout: v2.4.8 + - name: github/rollbear/trompeloeil +@@ -15,9 +15,9 @@ + - f38-clang-asan-ubsan: + required-projects: &projects + - name: github/CESNET/libyang +- override-checkout: devel ++ override-checkout: cesnet/2025-07-08 + - name: github/sysrepo/sysrepo +- override-checkout: devel ++ override-checkout: cesnet/2025-06-02 + - name: github/doctest/doctest + override-checkout: v2.4.11 + - name: github/rollbear/trompeloeil +-- +2.43.0 + diff --git a/patches/sysrepo-cpp/6/0002-fix-implicit-dependency-on-optional.patch b/patches/sysrepo-cpp/6/0002-fix-implicit-dependency-on-optional.patch new file mode 100644 index 00000000..36c1f4b6 --- /dev/null +++ b/patches/sysrepo-cpp/6/0002-fix-implicit-dependency-on-optional.patch @@ -0,0 +1,33 @@ +From e49463aba8b44b932fdd342d1c1d117e70414aba Mon Sep 17 00:00:00 2001 +From: Edoardo Bortolozzo +Date: Fri, 17 Oct 2025 18:08:47 +0200 +Subject: [PATCH 2/9] fix: implicit dependency on optional +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +chrono imports optional in gcc >=13. +added the explicit include to compile in gcc <=12. + +Change-Id: I18cc1a45ab522ee6738d4bc7080696cba8284903 +Signed-off-by: Mattias Walström +--- + include/sysrepo-cpp/Connection.hpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/include/sysrepo-cpp/Connection.hpp b/include/sysrepo-cpp/Connection.hpp +index bf72bcd..a469a2c 100644 +--- a/include/sysrepo-cpp/Connection.hpp ++++ b/include/sysrepo-cpp/Connection.hpp +@@ -7,6 +7,7 @@ + */ + #pragma once + ++#include + #include + #include + #include +-- +2.43.0 + diff --git a/patches/sysrepo-cpp/6/0003-fix-include-for-for-our-public-headers.patch b/patches/sysrepo-cpp/6/0003-fix-include-for-for-our-public-headers.patch new file mode 100644 index 00000000..64f9e970 --- /dev/null +++ b/patches/sysrepo-cpp/6/0003-fix-include-for-for-our-public-headers.patch @@ -0,0 +1,31 @@ +From b8e349aacae4c7063517a1bc652e9e93f90e28ba Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 24 Oct 2025 17:05:52 +0200 +Subject: [PATCH 3/9] fix #include for for our public headers +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: Idcbfe38348e3ca2e4331fc1fad542ae759b8d0f1 +Signed-off-by: Mattias Walström +--- + src/utils/utils.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp +index ff4a32c..c13b394 100644 +--- a/src/utils/utils.cpp ++++ b/src/utils/utils.cpp +@@ -7,7 +7,7 @@ + */ + + #include +-#include "sysrepo-cpp/utils/exception.hpp" ++#include + extern "C" { + #include + } +-- +2.43.0 + diff --git a/patches/sysrepo-cpp/6/0004-reduce-number-of-files-which-are-named-utils.hpp.patch b/patches/sysrepo-cpp/6/0004-reduce-number-of-files-which-are-named-utils.hpp.patch new file mode 100644 index 00000000..aad1e008 --- /dev/null +++ b/patches/sysrepo-cpp/6/0004-reduce-number-of-files-which-are-named-utils.hpp.patch @@ -0,0 +1,82 @@ +From 753a3e6eb346f2a9f6263b2d9fcd1c6dbb508247 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 24 Oct 2025 17:06:56 +0200 +Subject: [PATCH 4/9] reduce number of files which are named "utils.hpp" +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +We used to have three (one in publicheaders, one in src/utils/, one +below tests/). Let's cut that by one to increase our collective sanity. + +Change-Id: I9e3d93c65dffb29ba18b3ae325ddd7712a8be7a4 +Signed-off-by: Mattias Walström +--- + src/Connection.cpp | 2 +- + src/Session.cpp | 2 +- + src/Subscription.cpp | 2 +- + src/utils/{utils.hpp => misc.hpp} | 0 + src/utils/utils.cpp | 2 +- + 5 files changed, 4 insertions(+), 4 deletions(-) + rename src/utils/{utils.hpp => misc.hpp} (100%) + +diff --git a/src/Connection.cpp b/src/Connection.cpp +index 0297ebe..1717b9a 100644 +--- a/src/Connection.cpp ++++ b/src/Connection.cpp +@@ -13,7 +13,7 @@ extern "C" { + #include + #include "utils/enum.hpp" + #include "utils/exception.hpp" +-#include "utils/utils.hpp" ++#include "utils/misc.hpp" + + namespace sysrepo { + +diff --git a/src/Session.cpp b/src/Session.cpp +index dfe1044..994ce87 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -21,7 +21,7 @@ extern "C" { + #include + #include "utils/enum.hpp" + #include "utils/exception.hpp" +-#include "utils/utils.hpp" ++#include "utils/misc.hpp" + + using namespace std::string_literals; + namespace sysrepo { +diff --git a/src/Subscription.cpp b/src/Subscription.cpp +index 303b54c..5bb724e 100644 +--- a/src/Subscription.cpp ++++ b/src/Subscription.cpp +@@ -16,7 +16,7 @@ extern "C" { + } + #include "utils/enum.hpp" + #include "utils/exception.hpp" +-#include "utils/utils.hpp" ++#include "utils/misc.hpp" + + namespace sysrepo { + /** +diff --git a/src/utils/utils.hpp b/src/utils/misc.hpp +similarity index 100% +rename from src/utils/utils.hpp +rename to src/utils/misc.hpp +diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp +index c13b394..2e8d8b4 100644 +--- a/src/utils/utils.cpp ++++ b/src/utils/utils.cpp +@@ -12,7 +12,7 @@ extern "C" { + #include + } + #include "enum.hpp" +-#include "utils.hpp" ++#include "misc.hpp" + + namespace sysrepo { + /** +-- +2.43.0 + diff --git a/patches/sysrepo-cpp/6/0005-Fix-a-different-function-return-type.patch b/patches/sysrepo-cpp/6/0005-Fix-a-different-function-return-type.patch new file mode 100644 index 00000000..646c382f --- /dev/null +++ b/patches/sysrepo-cpp/6/0005-Fix-a-different-function-return-type.patch @@ -0,0 +1,60 @@ +From dc118efa44b13508e1af47d924ad02ce58c58cea Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 24 Oct 2025 17:18:49 +0200 +Subject: [PATCH 5/9] Fix a different function return type +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +The prototype that was specified in the header file did not match what +was used in the corresponding .cpp implementation. This has not been a +problem because that implementation .cpp file was not including the +actual header, so this was not detected. Then, at runtime, this was not +a problem due to the way how derived types are laid out in memory. + +Fixes: 6ed5442 Utilities for working with existing sysrepo:discard-items nodes +Change-Id: Ie4cf8c0e39e9c8486a6289c0328510ad6f7360fb +Signed-off-by: Mattias Walström +--- + src/utils/utils.cpp | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp +index 2e8d8b4..61d0c7f 100644 +--- a/src/utils/utils.cpp ++++ b/src/utils/utils.cpp +@@ -8,6 +8,7 @@ + + #include + #include ++#include + extern "C" { + #include + } +@@ -78,7 +79,7 @@ void checkNoThreadFlag(const SubscribeOptions opts, const std::optional findMatchingDiscard(libyang::DataNode root, const std::string& xpath) ++std::optional findMatchingDiscard(libyang::DataNode root, const std::string& xpath) + { + auto discard = root.firstOpaqueSibling(); + while (discard) { +@@ -97,11 +98,11 @@ std::optional findMatchingDiscard(libyang::DataNode root, con + /** + * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendants of this XPath + */ +-std::vector findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix) ++std::vector findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix) + { + auto withSlash = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '/') ? xpathPrefix : xpathPrefix + '/'; + auto withBracket = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '[') ? xpathPrefix : xpathPrefix + '['; +- std::vector res; ++ std::vector res; + auto discard = root.firstOpaqueSibling(); + while (discard) { + if (discard->name().matches("sysrepo", "discard-items")) { +-- +2.43.0 + diff --git a/patches/sysrepo-cpp/6/0006-fix-raw-strings-and-macros-break-gcc-13.patch b/patches/sysrepo-cpp/6/0006-fix-raw-strings-and-macros-break-gcc-13.patch new file mode 100644 index 00000000..6e9ab6bd --- /dev/null +++ b/patches/sysrepo-cpp/6/0006-fix-raw-strings-and-macros-break-gcc-13.patch @@ -0,0 +1,61 @@ +From b943faea3cb00c5e86f21710647a2ca370e41f33 Mon Sep 17 00:00:00 2001 +From: Edoardo Bortolozzo +Date: Fri, 24 Oct 2025 13:45:00 +0200 +Subject: [PATCH 6/9] fix: raw strings and macros break gcc < 13 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +see bug report: +https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55971 + +Change-Id: I04afc2b3bee86a7e4b038b277aad0e3a150a1940 +Signed-off-by: Mattias Walström +--- + tests/subscriptions-dynamic.cpp | 21 ++++++++++++++------- + 1 file changed, 14 insertions(+), 7 deletions(-) + +diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp +index 5ed173b..561df0c 100644 +--- a/tests/subscriptions-dynamic.cpp ++++ b/tests/subscriptions-dynamic.cpp +@@ -39,21 +39,28 @@ + REQUIRE(pipeStatus((SUBSCRIPTION).fd(), -1) == PipeStatus::DataReady); \ + (SUBSCRIPTION).processEvent(cbNotif); + +-#define SUBSCRIPTION_TERMINATED(SUBSCRIPTION) R"({ ++auto SUBSCRIPTION_TERMINATED(const auto& SUBSCRIPTION) ++{ ++ return R"({ + "ietf-subscribed-notifications:subscription-terminated": { +- "id": )" + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(, ++ "id": )" ++ + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(, + "reason": "no-such-subscription" + } + } +-)" ++)"; ++}; + +-#define REPLAY_COMPLETED(SUBSCRIPTION) R"({ ++auto REPLAY_COMPLETED(const auto& SUBSCRIPTION) ++{ ++ return R"({ + "ietf-subscribed-notifications:replay-completed": { +- "id": )" \ +- + std::to_string((SUBSCRIPTION).subscriptionId()) + R"( ++ "id": )" ++ + std::to_string((SUBSCRIPTION).subscriptionId()) + R"( + } + } +-)" ++)"; ++}; + + #define REQUIRE_YANG_PUSH_UPDATE(SUBSCRIPTION, NOTIFICATION) \ + TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate((SUBSCRIPTION).subscriptionId(), NOTIFICATION)).IN_SEQUENCE(seq); +-- +2.43.0 + diff --git a/patches/sysrepo-cpp/6/0007-Port-to-libyang-v4.patch b/patches/sysrepo-cpp/6/0007-Port-to-libyang-v4.patch new file mode 100644 index 00000000..22a28e44 --- /dev/null +++ b/patches/sysrepo-cpp/6/0007-Port-to-libyang-v4.patch @@ -0,0 +1,305 @@ +From 5fd8cf4fb8db1d65b6a20d2777bad22647e7f5e0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 23 Oct 2025 16:04:10 +0200 +Subject: [PATCH 7/9] Port to libyang v4 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +I'm rather disappointed with the upstream changes that implement that +shared libyang "printed context" mapped to a fixed address, I think it's +effectively a step backwards. Upstream is happy with their solution, so +here we are. + +Change-Id: I6b89422ec66bc694dc2c6ecd0d40aa1e7f76a741 +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8984 +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 8 +++--- + CMakeLists.txt | 6 ++--- + ci/build.sh | 4 +++ + include/sysrepo-cpp/Enum.hpp | 18 ++++++++++--- + include/sysrepo-cpp/utils/utils.hpp | 5 ++++ + src/Connection.cpp | 3 +-- + src/Session.cpp | 2 +- + src/utils/enum.hpp | 18 +++++++------ + src/utils/utils.cpp | 18 +++++++++++++ + tests/session.cpp | 39 +++++++++++++++++++++-------- + 10 files changed, 90 insertions(+), 31 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index cef174f..7ada6f7 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -4,9 +4,9 @@ + - f38-gcc-cover: + required-projects: + - name: github/CESNET/libyang +- override-checkout: cesnet/2025-07-08 ++ override-checkout: devel + - name: github/sysrepo/sysrepo +- override-checkout: cesnet/2025-06-02 ++ override-checkout: devel + - name: github/doctest/doctest + override-checkout: v2.4.8 + - name: github/rollbear/trompeloeil +@@ -15,9 +15,9 @@ + - f38-clang-asan-ubsan: + required-projects: &projects + - name: github/CESNET/libyang +- override-checkout: cesnet/2025-07-08 ++ override-checkout: devel + - name: github/sysrepo/sysrepo +- override-checkout: cesnet/2025-06-02 ++ override-checkout: devel + - name: github/doctest/doctest + override-checkout: v2.4.11 + - name: github/rollbear/trompeloeil +diff --git a/CMakeLists.txt b/CMakeLists.txt +index f2a9b09..49338dd 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -19,7 +19,7 @@ add_custom_target(sysrepo-cpp-version-cmake + cmake/ProjectGitVersionRunner.cmake + ) + include(cmake/ProjectGitVersion.cmake) +-set(SYSREPO_CPP_PKG_VERSION "6") ++set(SYSREPO_CPP_PKG_VERSION "7") + prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION}) + + find_package(Doxygen) +@@ -27,8 +27,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D + option(WITH_EXAMPLES "Build examples" ON) + + find_package(PkgConfig) +-pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET) +-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.7.4 IMPORTED_TARGET) ++pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=5 IMPORTED_TARGET) ++pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.4 IMPORTED_TARGET) + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +diff --git a/ci/build.sh b/ci/build.sh +index 785a811..67bc7d3 100755 +--- a/ci/build.sh ++++ b/ci/build.sh +@@ -47,6 +47,10 @@ if [[ $ZUUL_JOB_NAME =~ .*-tsan ]]; then + # Our TSAN does not have interceptors for a variety of "less common" functions such as pthread_mutex_clocklock. + # Disable all functions which are optional in sysrepo/libnetconf2/Netopeer2. + CMAKE_OPTIONS="${CMAKE_OPTIONS} -DHAVE_PTHREAD_MUTEX_TIMEDLOCK=OFF -DHAVE_PTHREAD_MUTEX_CLOCKLOCK=OFF -DHAVE_PTHREAD_RWLOCK_CLOCKRDLOCK=OFF -DHAVE_PTHREAD_RWLOCK_CLOCKWRLOCK=OFF -DHAVE_PTHREAD_COND_CLOCKWAIT=OFF" ++ ++ # This is currently broken on TSAN ("Failed to map the printed context (Operation not permitted)."). ++ # Even if it wasn't broken, I want at least one build in the matrix with this (mis)feature disabled. ++ CMAKE_OPTIONS="${CMAKE_OPTIONS} -DPRINTED_CONTEXT_ADDRESS=0" + fi + + if [[ $ZUUL_JOB_NAME =~ .*-cover.* ]]; then +diff --git a/include/sysrepo-cpp/Enum.hpp b/include/sysrepo-cpp/Enum.hpp +index 48467c6..505797a 100644 +--- a/include/sysrepo-cpp/Enum.hpp ++++ b/include/sysrepo-cpp/Enum.hpp +@@ -176,12 +176,24 @@ enum class NotificationType : uint32_t { + }; + + /** +- * Wraps `sr_conn_flag_e`. ++ * Wraps `sr_context_flag_t`. ++ */ ++enum class ContextFlags : uint32_t { ++ Default = 0x00, /**< SR_CTX_DEFAULT */ ++ NoPrinted = 0x01, /**< SR_CTX_NO_PRINTED */ ++ LibYangPrivParsed = 0x02, /**< SR_CTX_SET_PRIV_PARSED */ ++}; ++ ++constexpr ContextFlags operator|(const ContextFlags a, const ContextFlags b) ++{ ++ return implEnumBitOr(a, b); ++} ++ ++/** ++ * Wraps `sr_conn_flag_t`. + */ + enum class ConnectionFlags : uint32_t { + Default = 0x00, /**< SR_CONN_DEFAULT */ +- CacheRunning = 0x01, /**< SR_CONN_CACHE_RUNNING */ +- LibYangPrivParsed = 0x02, /**< SR_CONN_CTX_SET_PRIV_PARSED */ + }; + + constexpr ConnectionFlags operator|(const ConnectionFlags a, const ConnectionFlags b) +diff --git a/include/sysrepo-cpp/utils/utils.hpp b/include/sysrepo-cpp/utils/utils.hpp +index 64b1c2e..6f2446f 100644 +--- a/include/sysrepo-cpp/utils/utils.hpp ++++ b/include/sysrepo-cpp/utils/utils.hpp +@@ -12,6 +12,11 @@ + namespace sysrepo { + Session wrapUnmanagedSession(sr_session_ctx_s* session); + void setLogLevelStderr(const LogLevel); ++enum class GlobalContextEffect { ++ Lazy, /**< Set these flags the next time the shared context is applied */ ++ Immediate, /**< Apply immediately */ ++}; ++ContextFlags setGlobalContextOptions(const ContextFlags flags, const GlobalContextEffect when); + std::optional findMatchingDiscard(libyang::DataNode root, const std::string& xpath); + std::vector findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix); + void unlinkFromForest(std::optional& root, libyang::DataNode node); +diff --git a/src/Connection.cpp b/src/Connection.cpp +index 1717b9a..96e340c 100644 +--- a/src/Connection.cpp ++++ b/src/Connection.cpp +@@ -26,7 +26,7 @@ Connection::Connection(const ConnectionFlags options) + : ctx(nullptr) + { + sr_conn_ctx_t* ctx; +- auto res = sr_connect(static_cast(options), &ctx); ++ auto res = sr_connect(static_cast(options), &ctx); + + throwIfError(res, "Couldn't connect to sysrepo"); + this->ctx = std::shared_ptr(ctx, sr_disconnect); +@@ -96,5 +96,4 @@ uint32_t Connection::getId() const + { + return sr_get_cid(ctx.get()); + } +- + } +diff --git a/src/Session.cpp b/src/Session.cpp +index 994ce87..95b8f24 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -241,7 +241,7 @@ std::optional Session::operationalChanges(const std::optional + void Session::discardOperationalChanges(const std::optional& moduleName, std::chrono::milliseconds timeout) + { + SYSREPO_CPP_SESSION_MTX; +- auto res = sr_discard_oper_changes(nullptr, m_sess.get(), moduleName ? nullptr : moduleName->c_str(), timeout.count()); ++ auto res = sr_discard_oper_changes(m_sess.get(), moduleName ? nullptr : moduleName->c_str(), timeout.count()); + throwIfError(res, "Session::discardOoperationalChanges: Couldn't discard "s + (moduleName ? "for module \"" + *moduleName + "\"" : "globally"s), m_sess.get()); + } + +diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp +index 2d3a298..e917b21 100644 +--- a/src/utils/enum.hpp ++++ b/src/utils/enum.hpp +@@ -151,13 +151,16 @@ static_assert(toNotificationType(SR_EV_NOTIF_MODIFIED) == NotificationType::Modi + static_assert(toNotificationType(SR_EV_NOTIF_SUSPENDED) == NotificationType::Suspended); + static_assert(toNotificationType(SR_EV_NOTIF_RESUMED) == NotificationType::Resumed); + +-static_assert(std::is_same_v>); ++static_assert(std::is_same_v, std::underlying_type_t>); + static_assert(static_cast(SR_CONN_DEFAULT) == ConnectionFlags::Default); +-static_assert(static_cast(SR_CONN_CACHE_RUNNING) == ConnectionFlags::CacheRunning); +-static_assert(static_cast(SR_CONN_CTX_SET_PRIV_PARSED) == ConnectionFlags::LibYangPrivParsed); +-static_assert(static_cast(SR_CONN_CACHE_RUNNING | SR_CONN_CTX_SET_PRIV_PARSED) == (ConnectionFlags::CacheRunning | ConnectionFlags::LibYangPrivParsed)); + +-static_assert(std::is_same_v>); ++static_assert(std::is_same_v, std::underlying_type_t>); ++static_assert(static_cast(SR_CTX_DEFAULT) == ContextFlags::Default); ++static_assert(static_cast(SR_CTX_SET_PRIV_PARSED) == ContextFlags::LibYangPrivParsed); ++static_assert(static_cast(SR_CTX_NO_PRINTED) == ContextFlags::NoPrinted); ++static_assert(static_cast(SR_CTX_SET_PRIV_PARSED | SR_CTX_NO_PRINTED) == (ContextFlags::LibYangPrivParsed | ContextFlags::NoPrinted)); ++ ++// upstream project provides no dedicated type for these + static_assert(static_cast(SR_OPER_DEFAULT) == GetOptions::Default); + static_assert(static_cast(SR_OPER_NO_STATE) == GetOptions::OperNoState); + static_assert(static_cast(SR_OPER_NO_CONFIG) == GetOptions::OperNoConfig); +@@ -168,9 +171,10 @@ static_assert(static_cast(SR_OPER_NO_POLL_CACHED) == GetOptions::Ope + static_assert(static_cast(SR_OPER_NO_RUN_CACHED) == GetOptions::OperNoRunningCached); + static_assert(static_cast(SR_GET_NO_FILTER) == GetOptions::NoFilter); + +-constexpr sr_get_options_t toGetOptions(const GetOptions opts) ++// upstream project provides no dedicated type for these ++constexpr uint32_t toGetOptions(const GetOptions opts) + { +- return static_cast(opts); ++ return static_cast(opts); + } + + static_assert(static_cast(SRSN_YP_CHANGE_CREATE) == YangPushChange::Create); +diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp +index 61d0c7f..dbbc5ce 100644 +--- a/src/utils/utils.cpp ++++ b/src/utils/utils.cpp +@@ -13,6 +13,7 @@ extern "C" { + #include + } + #include "enum.hpp" ++#include "exception.hpp" + #include "misc.hpp" + + namespace sysrepo { +@@ -37,6 +38,23 @@ void setLogLevelStderr(const LogLevel level) + sr_log_stderr(toLogLevel(level)); + } + ++/** ++ * @brief Set global sysrepo-level context options ++ * ++ * Be advised of consequences of manipulating a shared global state, especially when using multiple connections. ++ * ++ * Wraps `sr_context_options`. ++ */ ++ContextFlags setGlobalContextOptions(const ContextFlags flags, const GlobalContextEffect when) ++{ ++ uint32_t old; ++ throwIfError( ++ sr_context_options(static_cast(flags), when == GlobalContextEffect::Immediate, &old), ++ "sr_context_options failed", ++ nullptr); ++ return static_cast(old); ++} ++ + std::timespec toTimespec(std::chrono::time_point tp) + { + // https://embeddedartistry.com/blog/2019/01/31/converting-between-timespec-stdchrono#-std-chrono-time_point-to-timespec- +diff --git a/tests/session.cpp b/tests/session.cpp +index 47b5631..a709a09 100644 +--- a/tests/session.cpp ++++ b/tests/session.cpp +@@ -627,17 +627,34 @@ TEST_CASE("session") + + DOCTEST_SUBCASE("libyang context flags") + { +- sess.setItem("/test_module:popelnice/s", "666"); +- REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "666"); +- // Parsed type info is not preserved by libyang unless its context is constructed with a flag, +- // and that flag is not used by sysrepo by default... +- REQUIRE_THROWS_AS(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length(), libyang::ParsedInfoUnavailable); +- +- // ...unless we pass that flag explicitly as a parameter to the connection. +- auto sess2 = sysrepo::Connection{sysrepo::ConnectionFlags::LibYangPrivParsed}.sessionStart(); +- sess2.setItem("/test_module:popelnice/s", "333"); +- REQUIRE(sess2.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "333"); +- REQUIRE(sess2.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length().parts[0].max == 10); ++ DOCTEST_SUBCASE("default") ++ { ++ sess.setItem("/test_module:popelnice/s", "666"); ++ REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "666"); ++ // Parsed type info is not preserved by libyang unless its context is constructed with a flag, ++ // and that flag is not used by sysrepo by default. ++ REQUIRE_THROWS_AS(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length(), libyang::ParsedInfoUnavailable); ++ ++ // pending changes hold the context lock, so we cannot change the global context options ++ REQUIRE_THROWS_WITH_AS( ++ sysrepo::setGlobalContextOptions( ++ sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, ++ sysrepo::GlobalContextEffect::Immediate), ++ "sr_context_options failed: SR_ERR_TIME_OUT", ++ sysrepo::ErrorWithCode); ++ } ++ ++ // Since this is actually a shared global state, changing it is "tricky". The best bet is to actually ++ // ensure that there are no other connections (which we cannot do here, we already have a connection opened ++ // from the common code at the very top of this test case), and especially no internal sysrepo-level locks ++ // on the shared libyang context. For example, no uncommited changes -- hence a new subcase. ++ DOCTEST_SUBCASE("Connection::setGlobalContextOptions LibYangPrivParsed") ++ { ++ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate); ++ sess.setItem("/test_module:popelnice/s", "333"); ++ REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "333"); ++ REQUIRE(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length().parts[0].max == 10); ++ } + } + + DOCTEST_SUBCASE("replay support") +-- +2.43.0 + diff --git a/patches/sysrepo-cpp/6/0008-fix-onOperGet-unconsistent-with-C-library.patch b/patches/sysrepo-cpp/6/0008-fix-onOperGet-unconsistent-with-C-library.patch new file mode 100644 index 00000000..d864533d --- /dev/null +++ b/patches/sysrepo-cpp/6/0008-fix-onOperGet-unconsistent-with-C-library.patch @@ -0,0 +1,90 @@ +From 6e9dbd8513ce75e51f2f714df69c08af5b556d1d Mon Sep 17 00:00:00 2001 +From: Edoardo Bortolozzo +Date: Tue, 28 Oct 2025 13:24:13 +0100 +Subject: [PATCH 8/9] fix: onOperGet unconsistent with C library. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Fixes: https://github.com/sysrepo/sysrepo-cpp/issues/29 +Change-Id: I5a21069889c59466df8e9457c3a316b58e2571bb +Signed-off-by: Mattias Walström +--- + include/sysrepo-cpp/Session.hpp | 2 +- + include/sysrepo-cpp/Subscription.hpp | 2 +- + src/Session.cpp | 4 ++-- + src/Subscription.cpp | 4 ++-- + 4 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index aae7068..3af4b10 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -127,7 +127,7 @@ public: + [[nodiscard]] Subscription onOperGet( + const std::string& moduleName, + OperGetCb cb, +- const std::optional& xpath = std::nullopt, ++ const std::string& path, + const SubscribeOptions opts = SubscribeOptions::Default, + ExceptionHandler handler = nullptr, + const std::optional& callbacks = std::nullopt); +diff --git a/include/sysrepo-cpp/Subscription.hpp b/include/sysrepo-cpp/Subscription.hpp +index c8f3ff8..14ecce4 100644 +--- a/include/sysrepo-cpp/Subscription.hpp ++++ b/include/sysrepo-cpp/Subscription.hpp +@@ -32,7 +32,7 @@ public: + Subscription& operator=(Subscription&&) noexcept; + + void onModuleChange(const std::string& moduleName, ModuleChangeCb cb, const std::optional& xpath = std::nullopt, uint32_t priority = 0, const SubscribeOptions opts = SubscribeOptions::Default); +- void onOperGet(const std::string& moduleName, OperGetCb cb, const std::optional& xpath, const SubscribeOptions opts = SubscribeOptions::Default); ++ void onOperGet(const std::string& moduleName, OperGetCb cb, const std::string& path, const SubscribeOptions opts = SubscribeOptions::Default); + void onRPCAction(const std::string& xpath, RpcActionCb cb, uint32_t priority = 0, const SubscribeOptions opts = SubscribeOptions::Default); + void onNotification( + const std::string& moduleName, +diff --git a/src/Session.cpp b/src/Session.cpp +index 95b8f24..6f0327d 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -505,14 +505,14 @@ Subscription Session::onModuleChange( + Subscription Session::onOperGet( + const std::string& moduleName, + OperGetCb cb, +- const std::optional& xpath, ++ const std::string& path, + const SubscribeOptions opts, + ExceptionHandler handler, + const std::optional& callbacks) + { + checkNoThreadFlag(opts, callbacks); + auto sub = Subscription{*this, handler, callbacks}; +- sub.onOperGet(moduleName, cb, xpath, opts); ++ sub.onOperGet(moduleName, cb, path, opts); + return sub; + } + +diff --git a/src/Subscription.cpp b/src/Subscription.cpp +index 5bb724e..dca1176 100644 +--- a/src/Subscription.cpp ++++ b/src/Subscription.cpp +@@ -215,14 +215,14 @@ void Subscription::onModuleChange(const std::string& moduleName, ModuleChangeCb + * @param xpath XPath that identifies which data this subscription is able to provide. + * @param opts Options further changing the behavior of this method. + */ +-void Subscription::onOperGet(const std::string& moduleName, OperGetCb cb, const std::optional& xpath, const SubscribeOptions opts) ++void Subscription::onOperGet(const std::string& moduleName, OperGetCb cb, const std::string& path, const SubscribeOptions opts) + { + SYSREPO_CPP_SESSION_MTX_OF(m_sess); + checkNoThreadFlag(opts, m_customEventLoopCbs); + + auto& privRef = m_operGetCbs.emplace_back(PrivData{cb, m_exceptionHandler.get()}); + sr_subscription_ctx_s* ctx = m_sub.get(); +- auto res = sr_oper_get_subscribe(m_sess.m_sess.get(), moduleName.c_str(), xpath ? xpath->c_str() : nullptr, operGetItemsCb, reinterpret_cast(&privRef), toSubscribeOptions(opts), &ctx); ++ auto res = sr_oper_get_subscribe(m_sess.m_sess.get(), moduleName.c_str(), path.c_str(), operGetItemsCb, reinterpret_cast(&privRef), toSubscribeOptions(opts), &ctx); + throwIfError(res, "Couldn't create operational get items subscription", m_sess.m_sess.get()); + + saveContext(ctx); +-- +2.43.0 + diff --git a/patches/sysrepo-cpp/6/0009-Update-to-libyang-v4.2.patch b/patches/sysrepo-cpp/6/0009-Update-to-libyang-v4.2.patch new file mode 100644 index 00000000..49489be0 --- /dev/null +++ b/patches/sysrepo-cpp/6/0009-Update-to-libyang-v4.2.patch @@ -0,0 +1,121 @@ +From 60d729e4791e5447cf0d969be6da9f8f1878af64 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 20 Nov 2025 15:38:04 +0100 +Subject: [PATCH 9/9] Update to libyang v4.2 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/9009 +Change-Id: Ia4c53dba0befa8dade38e728489edc6473aa5f4f +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 4 ++-- + tests/session.cpp | 14 +++++++------- + tests/subscriptions-dynamic.cpp | 4 ++-- + 3 files changed, 11 insertions(+), 11 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 49338dd..8234464 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -27,8 +27,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D + option(WITH_EXAMPLES "Build examples" ON) + + find_package(PkgConfig) +-pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=5 IMPORTED_TARGET) +-pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.4 IMPORTED_TARGET) ++pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=6 IMPORTED_TARGET) ++pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.8 IMPORTED_TARGET) + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +diff --git a/tests/session.cpp b/tests/session.cpp +index a709a09..0e92e65 100644 +--- a/tests/session.cpp ++++ b/tests/session.cpp +@@ -105,7 +105,7 @@ TEST_CASE("session") + + auto data = sess.getData("/test_module:popelnice", 0); + REQUIRE(data); +- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({ + "test_module:popelnice": { + "content": { + "trash": [ +@@ -126,14 +126,14 @@ TEST_CASE("session") + + data = sess.getData("/test_module:popelnice", 1); + REQUIRE(data); +- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({ + "test_module:popelnice": {} + } + )"); + + data = sess.getData("/test_module:popelnice", 2); + REQUIRE(data); +- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({ + "test_module:popelnice": { + "content": {} + } +@@ -143,7 +143,7 @@ TEST_CASE("session") + // If a list should be returned, its keys are always returned as well. + data = sess.getData("/test_module:popelnice", 3); + REQUIRE(data); +- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({ + "test_module:popelnice": { + "content": { + "trash": [ +@@ -161,7 +161,7 @@ TEST_CASE("session") + + data = sess.getData("/test_module:popelnice", 4); + REQUIRE(data); +- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ ++ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({ + "test_module:popelnice": { + "content": { + "trash": [ +@@ -497,7 +497,7 @@ TEST_CASE("session") + { + sess.switchDatastore(sysrepo::Datastore::FactoryDefault); + auto data = sess.getData("/*"); +- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == "{\n\n}\n"); ++ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == "{\n\n}\n"); + REQUIRE_THROWS_AS(sess.setItem(leaf, "123"), sysrepo::ErrorWithCode); + } + +@@ -622,7 +622,7 @@ TEST_CASE("session") + } + + // the original tree is not corrupted +- REQUIRE(*conf->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) != ""); ++ REQUIRE(*conf->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) != ""); + } + + DOCTEST_SUBCASE("libyang context flags") +diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp +index 561df0c..0d4ce7c 100644 +--- a/tests/subscriptions-dynamic.cpp ++++ b/tests/subscriptions-dynamic.cpp +@@ -140,12 +140,12 @@ TEST_CASE("Dynamic subscriptions") + const auto subId = std::get(idNode->asTerm().value()); + idNode->unlink(); + +- rec.recordYangPushUpdate(subId, tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)); ++ rec.recordYangPushUpdate(subId, tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)); + }; + + auto cbNotif = [&](const std::optional& tree, auto) { + REQUIRE(tree); +- rec.recordNotification(tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)); ++ rec.recordNotification(tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)); + }; + + // write some initial data +-- +2.43.0 +