From 5cc73ab97cdcc68584da3d9a61d53394f478d367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 14 Jan 2025 15:43:24 +0100 Subject: [PATCH] Bump sysrepo, netopeer,libyang and libnetconf2 A lot of changes in sysrepo, required to add extra patches (bugfixes) to sysrepo, sysrepo-cpp and rousette. --- buildroot | 2 +- ...pport-for-RpcYang-in-Context-parseOp.patch | 82 + ...-when-lyd_validate_all-returns-error.patch | 53 + .../0003-remove-a-misleading-comment.patch | 38 + ...mprove-List-keys-not-to-use-std-move.patch | 35 + ...f-list-s-default-statement-available.patch | 124 ++ ...choice-and-case-statements-available.patch | 434 +++++ ...term-for-changing-the-value-for-a-te.patch | 129 ++ ...stopped-reporting-schema-mounts-node.patch | 46 + .../999-GERRIT-step-up-sysrepo-cpp.patch | 62 + ...handling-changes-in-upstream-sysrepo.patch | 73 + ...-message-when-sr_session_start-fails.patch | 35 + ...0003-We-don-t-support-sysrepo-v3-yet.patch | 73 + ...-don-t-use-module-as-a-variable-name.patch | 85 + .../0005-DRY-a-dummy-leaf-XPath.patch | 253 +++ ...ngle-place-to-define-package-version.patch | 42 + ...reak-Change-how-pushed-ops-data-work.patch | 342 ++++ ...as-copy-from-only-in-rpc-copy-config.patch | 10 +- ...ers-backwards-compat-with-older-mode.patch | 6 +- ...-add-NP-containers-to-push-oper-data.patch | 36 + .../0002-sysrepo-BUGFIX-wrong-param.patch | 30 + ...03-SOVERSION-bump-to-version-7.29.11.patch | 30 + .../0004-VERSION-bump-to-version-3.3.11.patch | 30 + ...enerate-notifications-on-module-chan.patch | 963 +++++++++++ ...06-SOVERSION-bump-to-version-7.29.12.patch | 30 + .../0007-VERSION-bump-to-version-3.4.0.patch | 32 + ...R-conn_remap_unlock-remove-last-hole.patch | 62 + ...ATE-sess-push-oper-data-module-cache.patch | 250 +++ ...ACTOR-formatting-and-missing-doxygen.patch | 39 + ...UGFIX-remap-READ-lock-is-enough-3496.patch | 50 + ...OPTIMIZE-avoid-long-ext-lock-holding.patch | 162 ++ ...se-operation-ID-instead-of-request-I.patch | 1501 +++++++++++++++++ ...14-SOVERSION-bump-to-version-7.29.13.patch | 30 + .../0015-VERSION-bump-to-version-3.4.1.patch | 30 + ...dd-support-for-running-in-foregroun.patch} | 22 +- ...E-to-return-any-error-to-sysrepocfg.patch} | 47 +- ...-Allow-to-copy-from-factory-default.patch} | 12 +- ...ysrepoctl-to-install-factory-config.patch} | 17 +- ...-new-log-level-SEC-for-audit-trails.patch} | 14 +- ...il-for-high-priority-system-changes.patch} | 20 +- ...r_shmsub_listen_thread-exit-process.patch} | 24 +- 41 files changed, 5285 insertions(+), 70 deletions(-) create mode 100644 package/libyang-cpp/0001-added-support-for-RpcYang-in-Context-parseOp.patch create mode 100644 package/libyang-cpp/0002-throw-when-lyd_validate_all-returns-error.patch create mode 100644 package/libyang-cpp/0003-remove-a-misleading-comment.patch create mode 100644 package/libyang-cpp/0004-schema-improve-List-keys-not-to-use-std-move.patch create mode 100644 package/libyang-cpp/0005-schema-make-leaf-list-s-default-statement-available.patch create mode 100644 package/libyang-cpp/0006-schema-Make-choice-and-case-statements-available.patch create mode 100644 package/libyang-cpp/0007-Wrap-lyd_change_term-for-changing-the-value-for-a-te.patch create mode 100644 package/libyang-cpp/0008-upstream-stopped-reporting-schema-mounts-node.patch create mode 100644 package/rousette/999-GERRIT-step-up-sysrepo-cpp.patch create mode 100644 package/sysrepo-cpp/0001-error-handling-changes-in-upstream-sysrepo.patch create mode 100644 package/sysrepo-cpp/0002-Fix-error-message-when-sr_session_start-fails.patch create mode 100644 package/sysrepo-cpp/0003-We-don-t-support-sysrepo-v3-yet.patch create mode 100644 package/sysrepo-cpp/0004-refactor-don-t-use-module-as-a-variable-name.patch create mode 100644 package/sysrepo-cpp/0005-DRY-a-dummy-leaf-XPath.patch create mode 100644 package/sysrepo-cpp/0006-build-a-single-place-to-define-package-version.patch create mode 100644 package/sysrepo-cpp/9999-GERRIT-API-ABI-break-Change-how-pushed-ops-data-work.patch rename patches/netopeer2/{2.2.31 => 2.2.35}/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch (73%) rename patches/netopeer2/{2.2.31 => 2.2.35}/0002-Disable-local-users-backwards-compat-with-older-mode.patch (93%) create mode 100644 patches/sysrepo/3.3.10/0001-modinfo-BUGFIX-add-NP-containers-to-push-oper-data.patch create mode 100644 patches/sysrepo/3.3.10/0002-sysrepo-BUGFIX-wrong-param.patch create mode 100644 patches/sysrepo/3.3.10/0003-SOVERSION-bump-to-version-7.29.11.patch create mode 100644 patches/sysrepo/3.3.10/0004-VERSION-bump-to-version-3.3.11.patch create mode 100644 patches/sysrepo/3.3.10/0005-sysrepo-UPDATE-generate-notifications-on-module-chan.patch create mode 100644 patches/sysrepo/3.3.10/0006-SOVERSION-bump-to-version-7.29.12.patch create mode 100644 patches/sysrepo/3.3.10/0007-VERSION-bump-to-version-3.4.0.patch create mode 100644 patches/sysrepo/3.3.10/0008-shmext-REFACTOR-conn_remap_unlock-remove-last-hole.patch create mode 100644 patches/sysrepo/3.3.10/0009-modinfo-UPDATE-sess-push-oper-data-module-cache.patch create mode 100644 patches/sysrepo/3.3.10/0010-modinfo-REFACTOR-formatting-and-missing-doxygen.patch create mode 100644 patches/sysrepo/3.3.10/0011-shm_ext-BUGFIX-remap-READ-lock-is-enough-3496.patch create mode 100644 patches/sysrepo/3.3.10/0012-modinfo-OPTIMIZE-avoid-long-ext-lock-holding.patch create mode 100644 patches/sysrepo/3.3.10/0013-shm-sub-UPDATE-use-operation-ID-instead-of-request-I.patch create mode 100644 patches/sysrepo/3.3.10/0014-SOVERSION-bump-to-version-7.29.13.patch create mode 100644 patches/sysrepo/3.3.10/0015-VERSION-bump-to-version-3.4.1.patch rename patches/sysrepo/{2.11.7/0001-sysrepo-plugind-add-support-for-running-in-foregroun.patch => 3.3.10/0016-sysrepo-plugind-add-support-for-running-in-foregroun.patch} (78%) rename patches/sysrepo/{2.11.7/0002-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch => 3.3.10/0017-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch} (79%) rename patches/sysrepo/{2.11.7/0003-Allow-to-copy-from-factory-default.patch => 3.3.10/0018-Allow-to-copy-from-factory-default.patch} (68%) rename patches/sysrepo/{2.11.7/0004-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch => 3.3.10/0019-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch} (92%) rename patches/sysrepo/{2.11.7/0005-Introduce-new-log-level-SEC-for-audit-trails.patch => 3.3.10/0020-Introduce-new-log-level-SEC-for-audit-trails.patch} (91%) rename patches/sysrepo/{2.11.7/0006-Add-audit-trail-for-high-priority-system-changes.patch => 3.3.10/0021-Add-audit-trail-for-high-priority-system-changes.patch} (79%) rename patches/sysrepo/{2.11.7/0007-On-error-in-sr_shmsub_listen_thread-exit-process.patch => 3.3.10/0022-On-error-in-sr_shmsub_listen_thread-exit-process.patch} (53%) diff --git a/buildroot b/buildroot index 8cf431f9..e5315dbc 160000 --- a/buildroot +++ b/buildroot @@ -1 +1 @@ -Subproject commit 8cf431f9fb8d08bb62f144593579eac711d14ef1 +Subproject commit e5315dbce8ffe32d64644568e381a00a04d20793 diff --git a/package/libyang-cpp/0001-added-support-for-RpcYang-in-Context-parseOp.patch b/package/libyang-cpp/0001-added-support-for-RpcYang-in-Context-parseOp.patch new file mode 100644 index 00000000..67129032 --- /dev/null +++ b/package/libyang-cpp/0001-added-support-for-RpcYang-in-Context-parseOp.patch @@ -0,0 +1,82 @@ +From d0f6422fee7a46fcb7445c88f499f61b3eb0ead0 Mon Sep 17 00:00:00 2001 +From: Adam Piecek +Date: Wed, 23 Oct 2024 14:37:09 +0200 +Subject: [PATCH 1/8] added support for RpcYang in Context::parseOp +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Change-Id: I25182ea2d042be1e6e4246e18aee260cc032e547 +Signed-off-by: Mattias Walström +--- + src/Context.cpp | 6 ++++-- + tests/context.cpp | 21 +++++++++++++++++++++ + 2 files changed, 25 insertions(+), 2 deletions(-) + +diff --git a/src/Context.cpp b/src/Context.cpp +index b844bd9..287f8c8 100644 +--- a/src/Context.cpp ++++ b/src/Context.cpp +@@ -221,7 +221,8 @@ std::optional Context::parseExtData( + * - a NETCONF RPC, + * - a NETCONF notification, + * - a RESTCONF notification, +- * - a YANG notification. ++ * - a YANG notification, ++ * - a YANG RPC. + * + * Parsing any of these requires just the schema (which is available through the Context), and the textual payload. + * All the other information are encoded in the textual payload as per the standard. +@@ -243,6 +244,7 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con + auto in = wrap_ly_in_new_memory(input); + + switch (opType) { ++ case OperationType::RpcYang: + case OperationType::RpcNetconf: + case OperationType::NotificationNetconf: + case OperationType::NotificationRestconf: +@@ -254,7 +256,7 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con + ParsedOp res; + res.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt; + +- if (opType == OperationType::NotificationYang) { ++ if ((opType == OperationType::NotificationYang) || (opType == OperationType::RpcYang)) { + res.op = op && tree ? std::optional{DataNode(op, res.tree->m_refs)} : std::nullopt; + } else { + res.op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt; +diff --git a/tests/context.cpp b/tests/context.cpp +index 71ae873..11019eb 100644 +--- a/tests/context.cpp ++++ b/tests/context.cpp +@@ -464,6 +464,27 @@ TEST_CASE("context") + REQUIRE(data->findPath("/example-schema:leafInt8")->asTerm().valueStr() == "-43"); + } + ++ DOCTEST_SUBCASE("Context::parseOp") ++ { ++ DOCTEST_SUBCASE("RPC") ++ { ++ ctx->parseModule(example_schema, libyang::SchemaFormat::YANG); ++ std::string dataJson = R"({"example-schema:myRpc":{"inputLeaf":"str"}})"; ++ auto pop = ctx->parseOp(dataJson, libyang::DataFormat::JSON, libyang::OperationType::RpcYang); ++ REQUIRE(pop.op->schema().name() == "myRpc"); ++ REQUIRE(pop.tree->findPath("/example-schema:myRpc/inputLeaf")->asTerm().valueStr() == "str"); ++ } ++ DOCTEST_SUBCASE("action") ++ { ++ ctx->parseModule(example_schema, libyang::SchemaFormat::YANG); ++ std::string datajson = R"({"example-schema:person":[{"name":"john", "poke":{}}]})"; ++ auto pop = ctx->parseOp(datajson, libyang::DataFormat::JSON, libyang::OperationType::RpcYang); ++ REQUIRE(pop.op->schema().name() == "poke"); ++ REQUIRE(pop.tree->findPath("/example-schema:person[name='john']/poke")->schema().nodeType() == libyang::NodeType::Action); ++ } ++ } ++ ++ + DOCTEST_SUBCASE("Context::parseExt") + { + ctx->setSearchDir(TESTS_DIR / "yang"); +-- +2.43.0 + diff --git a/package/libyang-cpp/0002-throw-when-lyd_validate_all-returns-error.patch b/package/libyang-cpp/0002-throw-when-lyd_validate_all-returns-error.patch new file mode 100644 index 00000000..c676b814 --- /dev/null +++ b/package/libyang-cpp/0002-throw-when-lyd_validate_all-returns-error.patch @@ -0,0 +1,53 @@ +From 7e015f3486bdbb54f1dcc2e2ce51102b1d623081 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Wed, 23 Oct 2024 12:52:24 +0200 +Subject: [PATCH 2/8] throw when lyd_validate_all returns error +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Bug: https://github.com/CESNET/libyang-cpp/issues/20 +Change-Id: I005a2f1b057978573a4046e7b4cc31d77e36fde3 +Signed-off-by: Mattias Walström +--- + src/DataNode.cpp | 4 +++- + tests/data_node.cpp | 7 +++++++ + 2 files changed, 10 insertions(+), 1 deletion(-) + +diff --git a/src/DataNode.cpp b/src/DataNode.cpp +index 51c86c8..2ef17f2 100644 +--- a/src/DataNode.cpp ++++ b/src/DataNode.cpp +@@ -1170,7 +1170,9 @@ void validateAll(std::optional& node, const std::optionalm_node : nullptr, nullptr, opts ? utils::toValidationOptions(*opts) : 0, nullptr); ++ auto ret = lyd_validate_all(node ? &node->m_node : nullptr, nullptr, opts ? utils::toValidationOptions(*opts) : 0, nullptr); ++ throwIfError(ret, "libyang:validateAll: lyd_validate_all failed"); ++ + if (!node->m_node) { + node = std::nullopt; + } +diff --git a/tests/data_node.cpp b/tests/data_node.cpp +index a23e4c2..8a2610e 100644 +--- a/tests/data_node.cpp ++++ b/tests/data_node.cpp +@@ -489,6 +489,13 @@ TEST_CASE("Data Node manipulation") + REQUIRE_THROWS_WITH_AS(libyang::validateAll(node, libyang::ValidationOptions::NoState), "validateAll: Node is not a unique reference", libyang::Error); + } + ++ DOCTEST_SUBCASE("validateAll throws on validation failure") ++ { ++ ctx.parseModule(type_module, libyang::SchemaFormat::YANG); ++ auto node = std::optional{ctx.newPath("/type_module:leafWithConfigFalse", "hi")}; ++ REQUIRE_THROWS_WITH_AS(libyang::validateAll(node, libyang::ValidationOptions::NoState), "libyang:validateAll: lyd_validate_all failed: LY_EVALID", libyang::ErrorWithCode); ++ } ++ + DOCTEST_SUBCASE("unlink") + { + auto root = ctx.parseData(data2, libyang::DataFormat::JSON); +-- +2.43.0 + diff --git a/package/libyang-cpp/0003-remove-a-misleading-comment.patch b/package/libyang-cpp/0003-remove-a-misleading-comment.patch new file mode 100644 index 00000000..d7768a25 --- /dev/null +++ b/package/libyang-cpp/0003-remove-a-misleading-comment.patch @@ -0,0 +1,38 @@ +From 490d8bb242d33213b948485f5b94c55e22cf86a6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 21 Nov 2024 11:32:44 +0100 +Subject: [PATCH 3/8] remove a misleading comment +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +The whole intention within action's input/output handling here was to +put some emphasis on the fact that we aren't tracking the input/output +nodes directly. However, looking at all the other classes this is a bit +redundant, we're using a pattern like this all the time. Just drop the +comment. + +Change-Id: Ibd9bf9f1e83c650dda3bc43ef48e61dd6d95da5a +Signed-off-by: Mattias Walström +--- + src/SchemaNode.cpp | 3 --- + 1 file changed, 3 deletions(-) + +diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp +index 81e938f..9934cea 100644 +--- a/src/SchemaNode.cpp ++++ b/src/SchemaNode.cpp +@@ -640,9 +640,6 @@ bool List::isUserOrdered() const + */ + ActionRpcInput ActionRpc::input() const + { +- // I need a lysc_node* for ActionRpcInput, but m_node->input is a lysp_node_action_inout. lysp_node_action_inout is +- // still just a lysc_node, so I'll just convert to lysc_node. +- // This is not very pretty, but I don't want to introduce another member for ActionRpcInput and ActionRpcOutput. + return ActionRpcInput{reinterpret_cast(&reinterpret_cast(m_node)->input), m_ctx}; + } + +-- +2.43.0 + diff --git a/package/libyang-cpp/0004-schema-improve-List-keys-not-to-use-std-move.patch b/package/libyang-cpp/0004-schema-improve-List-keys-not-to-use-std-move.patch new file mode 100644 index 00000000..f29d1c07 --- /dev/null +++ b/package/libyang-cpp/0004-schema-improve-List-keys-not-to-use-std-move.patch @@ -0,0 +1,35 @@ +From e1b17386cf61048d2fe27fffb3b763981a225f52 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= +Date: Wed, 27 Nov 2024 09:47:47 +0100 +Subject: [PATCH 4/8] schema: improve `List::keys()` not to use `std::move` +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +`List::keys()` used `std::move` while iterating over array of leafs. +This was solved without using `std::move`. + +Change-Id: I8cbf8780ecd8848e46c1de5d4123a08624536bba +Signed-off-by: Mattias Walström +--- + src/SchemaNode.cpp | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp +index 9934cea..20e2aff 100644 +--- a/src/SchemaNode.cpp ++++ b/src/SchemaNode.cpp +@@ -593,8 +593,7 @@ std::vector List::keys() const + LY_LIST_FOR(list->child, elem) + { + if (lysc_is_key(elem)) { +- Leaf leaf(elem, m_ctx); +- res.emplace_back(std::move(leaf)); ++ res.emplace_back(Leaf(elem, m_ctx)); + } + } + +-- +2.43.0 + diff --git a/package/libyang-cpp/0005-schema-make-leaf-list-s-default-statement-available.patch b/package/libyang-cpp/0005-schema-make-leaf-list-s-default-statement-available.patch new file mode 100644 index 00000000..dd5b0a8c --- /dev/null +++ b/package/libyang-cpp/0005-schema-make-leaf-list-s-default-statement-available.patch @@ -0,0 +1,124 @@ +From 1102ecdcafbc9206f59b383769687e418557838e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= +Date: Mon, 25 Nov 2024 15:54:02 +0100 +Subject: [PATCH 5/8] schema: make leaf-list's `default` statement available +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Make leaf-list's `default` statement available so that it can be +accessed if end-user requires reading schema nodes. + +`LeafList::defaultValuesStr()` returns array of canonized string default +values. + +Change-Id: Idc42cd877f1fd3d717d491d09c46b59492527bff +Signed-off-by: Mattias Walström +--- + include/libyang-cpp/SchemaNode.hpp | 1 + + src/SchemaNode.cpp | 17 +++++++++++++++++ + tests/context.cpp | 1 + + tests/example_schema.hpp | 8 ++++++++ + tests/schema_node.cpp | 7 +++++++ + 5 files changed, 34 insertions(+) + +diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp +index 83afc06..8ddf9be 100644 +--- a/include/libyang-cpp/SchemaNode.hpp ++++ b/include/libyang-cpp/SchemaNode.hpp +@@ -169,6 +169,7 @@ class LIBYANG_CPP_EXPORT LeafList : public SchemaNode { + public: + bool isMandatory() const; + types::Type valueType() const; ++ std::vector defaultValuesStr() const; + libyang::types::constraints::ListSize maxElements() const; + libyang::types::constraints::ListSize minElements() const; + std::optional units() const; +diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp +index 9934cea..95bc09b 100644 +--- a/src/SchemaNode.cpp ++++ b/src/SchemaNode.cpp +@@ -472,6 +472,23 @@ types::Type LeafList::valueType() const + return types::Type{reinterpret_cast(m_node)->type, typeParsed, m_ctx}; + } + ++/** ++ * @brief Retrieves the default string values for this leaf-list. ++ * ++ * @return The default values, or an empty vector if the leaf-list does not have default values. ++ * ++ * Wraps `lysc_node_leaflist::dflts`. ++ */ ++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)); ++ } ++ return res; ++} ++ + /** + * @brief Retrieves the units for this leaf. + * @return The units, or std::nullopt if no units are available. +diff --git a/tests/context.cpp b/tests/context.cpp +index 11019eb..902faf6 100644 +--- a/tests/context.cpp ++++ b/tests/context.cpp +@@ -733,6 +733,7 @@ TEST_CASE("context") + +--rw iid-valid? instance-identifier + +--rw iid-relaxed? instance-identifier + +--rw leafListBasic* string ++ +--rw leafListWithDefault* int32 + +--rw leafListWithMinMaxElements* int32 + +--rw leafListWithUnits* int32 + +--rw listBasic* [primary-key] +diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp +index c093f50..2861b1a 100644 +--- a/tests/example_schema.hpp ++++ b/tests/example_schema.hpp +@@ -525,6 +525,14 @@ module type_module { + ordered-by user; + } + ++ leaf-list leafListWithDefault { ++ type int32; ++ default -1; ++ default +512; ++ default 0x400; ++ default 04000; ++ } ++ + leaf-list leafListWithMinMaxElements { + type int32; + min-elements 1; +diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp +index a86900d..80c7407 100644 +--- a/tests/schema_node.cpp ++++ b/tests/schema_node.cpp +@@ -200,6 +200,7 @@ TEST_CASE("SchemaNode") + "/type_module:iid-valid", + "/type_module:iid-relaxed", + "/type_module:leafListBasic", ++ "/type_module:leafListWithDefault", + "/type_module:leafListWithMinMaxElements", + "/type_module:leafListWithUnits", + "/type_module:listBasic", +@@ -606,6 +607,12 @@ TEST_CASE("SchemaNode") + REQUIRE(!ctx->findPath("/type_module:leafListBasic").asLeafList().isMandatory()); + } + ++ DOCTEST_SUBCASE("LeafList::defaultValuesStr") ++ { ++ REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector{"-1", "512", "1024", "2048"}); ++ REQUIRE(ctx->findPath("/type_module:leafListBasic").asLeafList().defaultValuesStr().size() == 0); ++ } ++ + DOCTEST_SUBCASE("LeafList::maxElements") + { + REQUIRE(ctx->findPath("/type_module:leafListWithMinMaxElements").asLeafList().maxElements() == 5); +-- +2.43.0 + diff --git a/package/libyang-cpp/0006-schema-Make-choice-and-case-statements-available.patch b/package/libyang-cpp/0006-schema-Make-choice-and-case-statements-available.patch new file mode 100644 index 00000000..797db204 --- /dev/null +++ b/package/libyang-cpp/0006-schema-Make-choice-and-case-statements-available.patch @@ -0,0 +1,434 @@ +From 01f2633cef60495d5cafc4b4b1f25273b03ab3cd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= +Date: Tue, 22 Oct 2024 15:11:30 +0200 +Subject: [PATCH 6/8] schema: Make choice and case statements available +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Make choice and case statements available so that they can be accessed +if end-user requires reading schema nodes. + +By design, choice and case statements do not exist in data tree directly. +Only children of one case can be present in the data tree at one time. +That means that choice and case children are not instantiable, thus +`SchemaNode::immediateChildren` must be used (instead of +`SchemaNode::childInstantibles`) if end-user wants to access choice +and case substatements. + +Change-Id: Ib089672ad21dda8a0344895835d92d3432fcccb8 +Co-authored-by: Jan Kundrát +Signed-off-by: Mattias Walström +--- + include/libyang-cpp/SchemaNode.hpp | 34 +++++++++++++ + src/SchemaNode.cpp | 68 ++++++++++++++++++++++++++ + tests/context.cpp | 77 +++++++++++++++++++----------- + tests/example_schema.hpp | 60 +++++++++++++++++++++++ + tests/schema_node.cpp | 72 ++++++++++++++++++++++++++++ + 5 files changed, 284 insertions(+), 27 deletions(-) + +diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp +index 8ddf9be..0f1a4c4 100644 +--- a/include/libyang-cpp/SchemaNode.hpp ++++ b/include/libyang-cpp/SchemaNode.hpp +@@ -22,6 +22,8 @@ class AnyDataAnyXML; + class ActionRpc; + class ActionRpcInput; + class ActionRpcOutput; ++class Case; ++class Choice; + class Container; + class Leaf; + class LeafList; +@@ -62,6 +64,8 @@ public: + // drectly by the user. + // TODO: turn these into a templated `as<>` method. + AnyDataAnyXML asAnyDataAnyXML() const; ++ Case asCase() const; ++ Choice asChoice() const; + Container asContainer() const; + Leaf asLeaf() const; + LeafList asLeafList() const; +@@ -129,6 +133,36 @@ private: + using SchemaNode::SchemaNode; + }; + ++/** ++ * @brief Class representing a schema definition of a `case` node. ++ * ++ * Wraps `lysc_node_case`. ++ */ ++class LIBYANG_CPP_EXPORT Case : public SchemaNode { ++public: ++ friend SchemaNode; ++ friend Choice; ++ ++private: ++ using SchemaNode::SchemaNode; ++}; ++ ++/** ++ * @brief Class representing a schema definition of a `choice` node. ++ * ++ * Wraps `lysc_node_choice`. ++ */ ++class LIBYANG_CPP_EXPORT Choice : public SchemaNode { ++public: ++ bool isMandatory() const; ++ std::vector cases() const; ++ std::optional defaultCase() const; ++ friend SchemaNode; ++ ++private: ++ using SchemaNode::SchemaNode; ++}; ++ + /** + * @brief Class representing a schema definition of a `container` node. + */ +diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp +index bd20402..26b5099 100644 +--- a/src/SchemaNode.cpp ++++ b/src/SchemaNode.cpp +@@ -191,6 +191,32 @@ NodeType SchemaNode::nodeType() const + return utils::toNodeType(m_node->nodetype); + } + ++/** ++ * @brief Try to cast this SchemaNode to a Case node. ++ * @throws Error If this node is not a case. ++ */ ++Case SchemaNode::asCase() const ++{ ++ if (nodeType() != NodeType::Case) { ++ throw Error("Schema node is not a case: " + path()); ++ } ++ ++ return Case{m_node, m_ctx}; ++} ++ ++/** ++ * @brief Try to cast this SchemaNode to a Choice node. ++ * @throws Error If this node is not a choice. ++ */ ++Choice SchemaNode::asChoice() const ++{ ++ if (nodeType() != NodeType::Choice) { ++ throw Error("Schema node is not a choice: " + path()); ++ } ++ ++ return Choice{m_node, m_ctx}; ++} ++ + /** + * @brief Try to cast this SchemaNode to a Container node. + * @throws Error If this node is not a container. +@@ -401,6 +427,48 @@ bool AnyDataAnyXML::isMandatory() const + return m_node->flags & LYS_MAND_TRUE; + } + ++/** ++ * @brief Checks whether this choice is mandatory. ++ * ++ * Wraps flag `LYS_MAND_TRUE`. ++ */ ++bool Choice::isMandatory() const ++{ ++ return m_node->flags & LYS_MAND_TRUE; ++} ++ ++/** ++ * @brief Retrieves the list of cases for this choice. ++ * ++ * Wraps `lysc_node_choice::cases`. ++ */ ++std::vector Choice::cases() const ++{ ++ auto choice = reinterpret_cast(m_node); ++ auto cases = reinterpret_cast(choice->cases); ++ std::vector res; ++ lysc_node* elem; ++ LY_LIST_FOR(cases, elem) ++ { ++ res.emplace_back(Case(elem, m_ctx)); ++ } ++ return res; ++} ++ ++/** ++ * @brief Retrieves the default case for this choice. ++ * ++ * Wraps `lysc_node_choice::dflt`. ++ */ ++std::optional Choice::defaultCase() const ++{ ++ auto choice = reinterpret_cast(m_node); ++ if (!choice->dflt) { ++ return std::nullopt; ++ } ++ return Case{reinterpret_cast(choice->dflt), m_ctx}; ++} ++ + /** + * @brief Checks whether this container is mandatory. + * +diff --git a/tests/context.cpp b/tests/context.cpp +index 902faf6..5929b75 100644 +--- a/tests/context.cpp ++++ b/tests/context.cpp +@@ -709,33 +709,56 @@ TEST_CASE("context") + auto mod = ctx_pp->parseModule(type_module, libyang::SchemaFormat::YANG); + + REQUIRE(mod.printStr(libyang::SchemaOutputFormat::Tree) == R"(module: type_module +- +--rw anydataBasic? anydata +- +--rw anydataWithMandatoryChild anydata +- +--rw anyxmlBasic? anyxml +- +--rw anyxmlWithMandatoryChild anyxml +- +--rw leafBinary? binary +- +--rw leafBits? bits +- +--rw leafEnum? enumeration +- +--rw leafEnum2? enumeration +- +--rw leafNumber? int32 +- +--rw leafRef? -> /custom-prefix:listAdvancedWithOneKey/lol +- +--rw leafRefRelaxed? -> /custom-prefix:listAdvancedWithOneKey/lol +- +--rw leafString? string +- +--rw leafUnion? union +- +--rw meal? identityref +- +--ro leafWithConfigFalse? string +- +--rw leafWithDefaultValue? string +- +--rw leafWithDescription? string +- +--rw leafWithMandatoryTrue string +- x--rw leafWithStatusDeprecated? string +- o--rw leafWithStatusObsolete? string +- +--rw leafWithUnits? int32 +- +--rw iid-valid? instance-identifier +- +--rw iid-relaxed? instance-identifier +- +--rw leafListBasic* string +- +--rw leafListWithDefault* int32 +- +--rw leafListWithMinMaxElements* int32 +- +--rw leafListWithUnits* int32 ++ +--rw anydataBasic? anydata ++ +--rw anydataWithMandatoryChild anydata ++ +--rw anyxmlBasic? anyxml ++ +--rw anyxmlWithMandatoryChild anyxml ++ +--rw choiceBasicContainer ++ | +--rw (choiceBasic)? ++ | +--:(case1) ++ | | +--rw l? string ++ | | +--rw ll* string ++ | +--:(case2) ++ | +--rw l2? string ++ +--rw choiceWithMandatoryContainer ++ | +--rw (choiceWithMandatory) ++ | +--:(case3) ++ | | +--rw l3? string ++ | +--:(case4) ++ | +--rw l4? string ++ +--rw choiceWithDefaultContainer ++ | +--rw (choiceWithDefault)? ++ | +--:(case5) ++ | | +--rw l5? string ++ | +--:(case6) ++ | +--rw l6? string ++ +--rw implicitCaseContainer ++ | +--rw (implicitCase)? ++ | +--:(implicitLeaf) ++ | +--rw implicitLeaf? string ++ +--rw leafBinary? binary ++ +--rw leafBits? bits ++ +--rw leafEnum? enumeration ++ +--rw leafEnum2? enumeration ++ +--rw leafNumber? int32 ++ +--rw leafRef? -> /custom-prefix:listAdvancedWithOneKey/lol ++ +--rw leafRefRelaxed? -> /custom-prefix:listAdvancedWithOneKey/lol ++ +--rw leafString? string ++ +--rw leafUnion? union ++ +--rw meal? identityref ++ +--ro leafWithConfigFalse? string ++ +--rw leafWithDefaultValue? string ++ +--rw leafWithDescription? string ++ +--rw leafWithMandatoryTrue string ++ x--rw leafWithStatusDeprecated? string ++ o--rw leafWithStatusObsolete? string ++ +--rw leafWithUnits? int32 ++ +--rw iid-valid? instance-identifier ++ +--rw iid-relaxed? instance-identifier ++ +--rw leafListBasic* string ++ +--rw leafListWithDefault* int32 ++ +--rw leafListWithMinMaxElements* int32 ++ +--rw leafListWithUnits* int32 + +--rw listBasic* [primary-key] + | +--rw primary-key string + +--rw listAdvancedWithOneKey* [lol] +diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp +index 2861b1a..ae3b4de 100644 +--- a/tests/example_schema.hpp ++++ b/tests/example_schema.hpp +@@ -390,6 +390,66 @@ module type_module { + mandatory true; + } + ++ container choiceBasicContainer { ++ choice choiceBasic { ++ case case1 { ++ leaf l { ++ type string; ++ } ++ leaf-list ll { ++ type string; ++ ordered-by user; ++ } ++ } ++ case case2 { ++ leaf l2 { ++ type string; ++ } ++ } ++ } ++ } ++ ++ container choiceWithMandatoryContainer { ++ choice choiceWithMandatory { ++ mandatory true; ++ case case3 { ++ leaf l3 { ++ type string; ++ } ++ } ++ case case4 { ++ leaf l4 { ++ type string; ++ } ++ } ++ } ++ } ++ ++ container choiceWithDefaultContainer { ++ choice choiceWithDefault { ++ default case5; ++ case case5 { ++ leaf l5 { ++ type string; ++ } ++ } ++ case case6 { ++ leaf l6 { ++ type string; ++ } ++ } ++ } ++ } ++ ++ container implicitCaseContainer { ++ choice implicitCase { ++ leaf implicitLeaf { ++ type string; ++ } ++ } ++ } ++ ++ + leaf leafBinary { + type binary; + } +diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp +index 80c7407..8d74bd2 100644 +--- a/tests/schema_node.cpp ++++ b/tests/schema_node.cpp +@@ -58,6 +58,9 @@ TEST_CASE("SchemaNode") + ], + "type_module:anydataWithMandatoryChild": {"content": "test-string"}, + "type_module:anyxmlWithMandatoryChild": {"content": "test-string"}, ++ "type_module:choiceWithMandatoryContainer": { ++ "l4": "test-string" ++ }, + "type_module:containerWithMandatoryChild": { + "leafWithMandatoryTrue": "test-string" + }, +@@ -180,6 +183,10 @@ TEST_CASE("SchemaNode") + "/type_module:anydataWithMandatoryChild", + "/type_module:anyxmlBasic", + "/type_module:anyxmlWithMandatoryChild", ++ "/type_module:choiceBasicContainer", ++ "/type_module:choiceWithMandatoryContainer", ++ "/type_module:choiceWithDefaultContainer", ++ "/type_module:implicitCaseContainer", + "/type_module:leafBinary", + "/type_module:leafBits", + "/type_module:leafEnum", +@@ -417,6 +424,71 @@ TEST_CASE("SchemaNode") + REQUIRE(!ctx->findPath("/type_module:anyxmlBasic").asAnyDataAnyXML().isMandatory()); + } + ++ DOCTEST_SUBCASE("Choice and Case") ++ { ++ std::string xpath; ++ bool isMandatory = false; ++ std::optional defaultCase; ++ std::vector caseNames; ++ std::optional root; ++ ++ DOCTEST_SUBCASE("two cases with nothing fancy") ++ { ++ root = ctx->findPath("/type_module:choiceBasicContainer"); ++ caseNames = {"case1", "case2"}; ++ } ++ ++ DOCTEST_SUBCASE("mandatory choice") { ++ root = ctx->findPath("/type_module:choiceWithMandatoryContainer"); ++ isMandatory = true; ++ caseNames = {"case3", "case4"}; ++ } ++ ++ DOCTEST_SUBCASE("default choice") { ++ root = ctx->findPath("/type_module:choiceWithDefaultContainer"); ++ defaultCase = "case5"; ++ caseNames = {"case5", "case6"}; ++ } ++ ++ DOCTEST_SUBCASE("implicit case") { ++ root = ctx->findPath("/type_module:implicitCaseContainer"); ++ caseNames = {"implicitLeaf"}; ++ } ++ ++ // For testing purposes, we have each choice in its own container. As choice and case are not directly instantiable, ++ // we wrap them in a container to simplify the testing process. It allows us to simply address the choice by its ++ // container and then get the choice from it. It also prevents polluting the test schema with unnecessary nodes ++ // and isolates the choice from other nodes. ++ auto container = root->asContainer(); ++ auto choice = container.immediateChildren().begin()->asChoice(); ++ REQUIRE(choice.isMandatory() == isMandatory); ++ REQUIRE(!!choice.defaultCase() == !!defaultCase); ++ if (defaultCase) { ++ REQUIRE(choice.defaultCase()->name() == *defaultCase); ++ } ++ std::vector actualCaseNames; ++ for (const auto& case_ : choice.cases()) { ++ actualCaseNames.push_back(case_.name()); ++ } ++ REQUIRE(actualCaseNames == caseNames); ++ ++ // Also test child node access for one arbitrary choice/case combination ++ if (root->path() == "/type_module:choiceBasicContainer") { ++ REQUIRE(choice.cases().size() == 2); ++ auto case1 = choice.cases()[0]; ++ auto children = case1.immediateChildren(); ++ auto it = children.begin(); ++ REQUIRE(it->asLeaf().name() == "l"); ++ ++it; ++ REQUIRE(it->asLeafList().name() == "ll"); ++ ++ auto case2 = choice.cases()[1]; ++ children = case2.immediateChildren(); ++ it = children.begin(); ++ REQUIRE(it->asLeaf().name() == "l2"); ++ } ++ } ++ + DOCTEST_SUBCASE("Container::isMandatory") + { + REQUIRE(ctx->findPath("/type_module:containerWithMandatoryChild").asContainer().isMandatory()); +-- +2.43.0 + diff --git a/package/libyang-cpp/0007-Wrap-lyd_change_term-for-changing-the-value-for-a-te.patch b/package/libyang-cpp/0007-Wrap-lyd_change_term-for-changing-the-value-for-a-te.patch new file mode 100644 index 00000000..20903283 --- /dev/null +++ b/package/libyang-cpp/0007-Wrap-lyd_change_term-for-changing-the-value-for-a-te.patch @@ -0,0 +1,129 @@ +From a1acdc794facf8cbf113f73274ecebd5898c81a1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Tue, 17 Dec 2024 15:08:43 +0100 +Subject: [PATCH 7/8] Wrap lyd_change_term for changing the value for a + terminal node +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Previously, the code would require a newPath(..., +libyang::CreationOptions::Update), which is quite a mouthful. + +Change-Id: I8a908c0fdd3e48dda830819758522a511adedd3b +Signed-off-by: Mattias Walström +--- + include/libyang-cpp/DataNode.hpp | 8 ++++++ + src/DataNode.cpp | 21 ++++++++++++++++ + tests/data_node.cpp | 42 ++++++++++++++++++++++++++------ + 3 files changed, 63 insertions(+), 8 deletions(-) + +diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp +index 2211415..851681b 100644 +--- a/include/libyang-cpp/DataNode.hpp ++++ b/include/libyang-cpp/DataNode.hpp +@@ -212,6 +212,14 @@ public: + Value value() const; + types::Type valueType() const; + ++ /** @brief Was the value changed? */ ++ enum class ValueChange { ++ Changed, /**< Yes, this is an actual change of the stored value */ ++ ExplicitNonDefault, /**< It still holds the default value, but it's been set explicitly now */ ++ EqualValueNotChanged, /**< No change, the previous value is the same as the new one, and it isn't an implicit default */ ++ }; ++ ValueChange changeValue(const std::string value); ++ + private: + using DataNode::DataNode; + }; +diff --git a/src/DataNode.cpp b/src/DataNode.cpp +index 2ef17f2..84591e5 100644 +--- a/src/DataNode.cpp ++++ b/src/DataNode.cpp +@@ -903,6 +903,27 @@ types::Type DataNodeTerm::valueType() const + return impl(reinterpret_cast(m_node)->value); + } + ++/** @short Change the term's value ++ * ++ * Wraps `lyd_change_term`. ++ * */ ++DataNodeTerm::ValueChange DataNodeTerm::changeValue(const std::string value) ++{ ++ auto ret = lyd_change_term(m_node, value.c_str()); ++ ++ switch (ret) { ++ case LY_SUCCESS: ++ return ValueChange::Changed; ++ case LY_EEXIST: ++ return ValueChange::ExplicitNonDefault; ++ case LY_ENOT: ++ return ValueChange::EqualValueNotChanged; ++ default: ++ throwIfError(ret, "DataNodeTerm::changeValue failed"); ++ __builtin_unreachable(); ++ } ++} ++ + /** + * @brief Returns a collection for iterating depth-first over the subtree this instance points to. + * +diff --git a/tests/data_node.cpp b/tests/data_node.cpp +index 8a2610e..45fd6c1 100644 +--- a/tests/data_node.cpp ++++ b/tests/data_node.cpp +@@ -456,15 +456,41 @@ TEST_CASE("Data Node manipulation") + REQUIRE(node.hasDefaultValue()); + REQUIRE(node.isImplicitDefault()); + +- data->newPath("/example-schema3:leafWithDefault", "not-default-value", libyang::CreationOptions::Update); +- node = data->findPath("/example-schema3:leafWithDefault")->asTerm(); +- REQUIRE(!node.hasDefaultValue()); +- REQUIRE(!node.isImplicitDefault()); ++ DOCTEST_SUBCASE("newPath") ++ { ++ data->newPath("/example-schema3:leafWithDefault", "not-default-value", libyang::CreationOptions::Update); ++ node = data->findPath("/example-schema3:leafWithDefault")->asTerm(); ++ REQUIRE(!node.hasDefaultValue()); ++ REQUIRE(!node.isImplicitDefault()); + +- data->newPath("/example-schema3:leafWithDefault", "AHOJ", libyang::CreationOptions::Update); +- node = data->findPath("/example-schema3:leafWithDefault")->asTerm(); +- REQUIRE(node.hasDefaultValue()); +- REQUIRE(!node.isImplicitDefault()); ++ data->newPath("/example-schema3:leafWithDefault", "AHOJ", libyang::CreationOptions::Update); ++ node = data->findPath("/example-schema3:leafWithDefault")->asTerm(); ++ REQUIRE(node.hasDefaultValue()); ++ REQUIRE(!node.isImplicitDefault()); ++ } ++ ++ DOCTEST_SUBCASE("changing values") ++ { ++ auto node = data->findPath("/example-schema3:leafWithDefault"); ++ REQUIRE(!!node); ++ auto term = node->asTerm(); ++ ++ DOCTEST_SUBCASE("to an arbitrary value") { ++ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::Changed); ++ } ++ ++ DOCTEST_SUBCASE("from an implicit default to an explicit default") { ++ REQUIRE(term.changeValue("AHOJ") == libyang::DataNodeTerm::ValueChange::ExplicitNonDefault); ++ REQUIRE(term.changeValue("AHOJ") == libyang::DataNodeTerm::ValueChange::EqualValueNotChanged); ++ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::Changed); ++ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::EqualValueNotChanged); ++ } ++ ++ DOCTEST_SUBCASE("from an implicit default to something else") { ++ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::Changed); ++ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::EqualValueNotChanged); ++ } ++ } + } + + DOCTEST_SUBCASE("isTerm") +-- +2.43.0 + diff --git a/package/libyang-cpp/0008-upstream-stopped-reporting-schema-mounts-node.patch b/package/libyang-cpp/0008-upstream-stopped-reporting-schema-mounts-node.patch new file mode 100644 index 00000000..2219a568 --- /dev/null +++ b/package/libyang-cpp/0008-upstream-stopped-reporting-schema-mounts-node.patch @@ -0,0 +1,46 @@ +From 39c7530caa510144c17521278b721ba1e6d8ff40 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Thu, 9 Jan 2025 15:31:37 +0100 +Subject: [PATCH 8/8] upstream stopped reporting schema-mounts node +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Change-Id: I940769d38d56fcfda3e1408c92331fdb00c161e9 +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + tests/context.cpp | 3 +-- + 2 files changed, 2 insertions(+), 3 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 3d86809..732f52b 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -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.4.2 IMPORTED_TARGET) ++pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.8 IMPORTED_TARGET) + set(LIBYANG_CPP_PKG_VERSION "3") + + # FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency +diff --git a/tests/context.cpp b/tests/context.cpp +index 5929b75..9d38fea 100644 +--- a/tests/context.cpp ++++ b/tests/context.cpp +@@ -509,8 +509,7 @@ TEST_CASE("context") + "error-message": "hi" + } + ] +- }, +- "ietf-yang-schema-mount:schema-mounts": {} ++ } + } + )"); + } +-- +2.43.0 + diff --git a/package/rousette/999-GERRIT-step-up-sysrepo-cpp.patch b/package/rousette/999-GERRIT-step-up-sysrepo-cpp.patch new file mode 100644 index 00000000..7e260ad6 --- /dev/null +++ b/package/rousette/999-GERRIT-step-up-sysrepo-cpp.patch @@ -0,0 +1,62 @@ +From f7307481fc4ca167592acf925136e5f8a51e2150 Mon Sep 17 00:00:00 2001 +From: Jan Kundrát +Date: Wed, 18 Dec 2024 17:26:15 +0100 +Subject: [PATCH] Update dependencies + +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8134 +Change-Id: Ib170aa61600ffb9089460247f762ff5a947dd6c6 +--- + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 22bce32..5ae8062 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -74,7 +74,7 @@ + pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) + find_package(Boost REQUIRED CONFIG COMPONENTS system thread) + +-pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=3) ++pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=4) + pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3) + pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd) + pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam) +diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp +index 0932984..d37ab03 100644 +--- a/tests/restconf-writing.cpp ++++ b/tests/restconf-writing.cpp +@@ -772,7 +772,7 @@ + { + "error-type": "protocol", + "error-tag": "invalid-value", +- "error-message": "Session::applyChanges: Couldn't apply changes: SR_ERR_NOT_FOUND\u000A Node \"lst\" instance to insert next to not found. (SR_ERR_NOT_FOUND)\u000A Applying operation \"replace\" failed. (SR_ERR_NOT_FOUND)" ++ "error-message": "Session::applyChanges: Couldn't apply changes: SR_ERR_NOT_FOUND\u000A Node \"lst\" instance to insert next to not found. (SR_ERR_NOT_FOUND)" + } + ] + } +@@ -855,7 +855,7 @@ + { + "error-type": "protocol", + "error-tag": "invalid-value", +- "error-message": "Session::applyChanges: Couldn't apply changes: SR_ERR_NOT_FOUND\u000A Node \"ll\" instance to insert next to not found. (SR_ERR_NOT_FOUND)\u000A Applying operation \"replace\" failed. (SR_ERR_NOT_FOUND)" ++ "error-message": "Session::applyChanges: Couldn't apply changes: SR_ERR_NOT_FOUND\u000A Node \"ll\" instance to insert next to not found. (SR_ERR_NOT_FOUND)" + } + ] + } +@@ -1560,7 +1560,7 @@ + { + "error-type": "protocol", + "error-tag": "invalid-value", +- "error-message": "Session::applyChanges: Couldn't apply changes: SR_ERR_NOT_FOUND\u000A Node \"lst\" instance to insert next to not found. (SR_ERR_NOT_FOUND)\u000A Applying operation \"create\" failed. (SR_ERR_NOT_FOUND)" ++ "error-message": "Session::applyChanges: Couldn't apply changes: SR_ERR_NOT_FOUND\u000A Node \"lst\" instance to insert next to not found. (SR_ERR_NOT_FOUND)" + } + ] + } +@@ -1626,7 +1626,7 @@ + { + "error-type": "protocol", + "error-tag": "invalid-value", +- "error-message": "Session::applyChanges: Couldn't apply changes: SR_ERR_NOT_FOUND\u000A Node \"ll\" instance to insert next to not found. (SR_ERR_NOT_FOUND)\u000A Applying operation \"create\" failed. (SR_ERR_NOT_FOUND)" ++ "error-message": "Session::applyChanges: Couldn't apply changes: SR_ERR_NOT_FOUND\u000A Node \"ll\" instance to insert next to not found. (SR_ERR_NOT_FOUND)" + } + ] + } diff --git a/package/sysrepo-cpp/0001-error-handling-changes-in-upstream-sysrepo.patch b/package/sysrepo-cpp/0001-error-handling-changes-in-upstream-sysrepo.patch new file mode 100644 index 00000000..0c62b486 --- /dev/null +++ b/package/sysrepo-cpp/0001-error-handling-changes-in-upstream-sysrepo.patch @@ -0,0 +1,73 @@ +From 23fa270a747c35d65aeeba691599ed6b21b501f0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Tue, 29 Oct 2024 15:15:52 +0100 +Subject: [PATCH 1/6] error handling changes in upstream sysrepo +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Upstream commit 396e331e4634a417420a71ad723567b42d75c443 removed these +extra error entries. + +Change-Id: Ifeda21194db7b7f7fdae8b6ae13c1e2d1f6b8d3d +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + tests/subscriptions.cpp | 16 ++++++---------- + 2 files changed, 7 insertions(+), 11 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index eb5f900..1eb625f 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -28,7 +28,7 @@ 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>=2.11.7 IMPORTED_TARGET) ++pkg_check_modules(SYSREPO REQUIRED sysrepo>=2.12.0 IMPORTED_TARGET) + set(SYSREPO_CPP_PKG_VERSION "3") + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) +diff --git a/tests/subscriptions.cpp b/tests/subscriptions.cpp +index 0e14fc0..36fd61c 100644 +--- a/tests/subscriptions.cpp ++++ b/tests/subscriptions.cpp +@@ -494,11 +494,11 @@ TEST_CASE("subscriptions") + sess.applyChanges(); + } catch (const sysrepo::ErrorWithCode&) { + auto errors = sess.getErrors(); +- REQUIRE(errors.size() == 2); +- REQUIRE(errors.at(0).errorMessage == message); +- REQUIRE(errors.at(0).code == sysrepo::ErrorCode::OperationFailed); +- REQUIRE(errors.at(1).errorMessage == "User callback failed."); +- REQUIRE(errors.at(1).code == sysrepo::ErrorCode::CallbackFailed); ++ REQUIRE(errors.size() == 1); ++ REQUIRE(errors.at(0) == sysrepo::ErrorInfo{ ++ .code = sysrepo::ErrorCode::OperationFailed, ++ .errorMessage = message ++ }); + } + + // The callback does not fail the second time. +@@ -548,15 +548,11 @@ TEST_CASE("subscriptions") + sess.setItem("/test_module:leafInt32", "123"); + REQUIRE_THROWS_AS(sess.applyChanges(), sysrepo::ErrorWithCode); + auto errors = sess.getErrors(); +- REQUIRE(errors.size() == 2); ++ REQUIRE(errors.size() == 1); + REQUIRE(errors.at(0) == sysrepo::ErrorInfo{ + .code = sysrepo::ErrorCode::OperationFailed, + .errorMessage = "Test callback failure.", + }); +- REQUIRE(errors.at(1) == sysrepo::ErrorInfo{ +- .code = sysrepo::ErrorCode::CallbackFailed, +- .errorMessage = "User callback failed." +- }); + auto ncErrors = sess.getNetconfErrors(); + REQUIRE(ncErrors.size() == 1); + REQUIRE(ncErrors.front() == errToSet); +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0002-Fix-error-message-when-sr_session_start-fails.patch b/package/sysrepo-cpp/0002-Fix-error-message-when-sr_session_start-fails.patch new file mode 100644 index 00000000..c59a87b1 --- /dev/null +++ b/package/sysrepo-cpp/0002-Fix-error-message-when-sr_session_start-fails.patch @@ -0,0 +1,35 @@ +From a73ffe1a5bbab51a67cf56dd2864c71a29c6685b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 21 Nov 2024 11:18:25 +0100 +Subject: [PATCH 2/6] Fix error message when sr_session_start fails +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Thanks to Jason Patterson for sending a bugreport; its investigation led +to discovering this bug. + +See-also: https://github.com/CESNET/rousette/issues/15 +Change-Id: I082987d60380f860e08f26b436d0f9db4a231bec +Signed-off-by: Mattias Walström +--- + src/Connection.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/Connection.cpp b/src/Connection.cpp +index de65d64..716fd1a 100644 +--- a/src/Connection.cpp ++++ b/src/Connection.cpp +@@ -53,7 +53,7 @@ Session Connection::sessionStart(sysrepo::Datastore datastore) + sr_session_ctx_t* sess; + auto res = sr_session_start(ctx.get(), toDatastore(datastore), &sess); + +- throwIfError(res, "Couldn't connect to sysrepo"); ++ throwIfError(res, "Couldn't start sysrepo session"); + return Session{sess, ctx}; + } + +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0003-We-don-t-support-sysrepo-v3-yet.patch b/package/sysrepo-cpp/0003-We-don-t-support-sysrepo-v3-yet.patch new file mode 100644 index 00000000..3611021d --- /dev/null +++ b/package/sysrepo-cpp/0003-We-don-t-support-sysrepo-v3-yet.patch @@ -0,0 +1,73 @@ +From 552a366990fe1fe2cdd8d155fa5cecb3ff3fbc13 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Wed, 4 Dec 2024 15:51:41 +0100 +Subject: [PATCH 3/6] We don't support sysrepo v3 yet +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +That version introduced some backward-incompatible changes to the way +how operational data are treated, especially in presence of "discards" +of this data. It's a breaking behavior, and before we release a fix, +let's pin the version to something which still works. The latest sysrepo +semi-release is v 2.12.0, which is unfortunately not tagged. + +Change-Id: I3673de9893d6e806926767a72b1ef83ee62bd610 +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 4 ++-- + CMakeLists.txt | 2 +- + README.md | 1 + + 3 files changed, 4 insertions(+), 3 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index dc87c6f..c3aea22 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -6,7 +6,7 @@ + - name: github/CESNET/libyang + override-checkout: devel + - name: github/sysrepo/sysrepo +- override-checkout: devel ++ override-checkout: cesnet/2024-10-before-oper-changes + - name: github/onqtam/doctest + override-checkout: v2.4.8 + - name: github/rollbear/trompeloeil +@@ -17,7 +17,7 @@ + - name: github/CESNET/libyang + override-checkout: devel + - name: github/sysrepo/sysrepo +- override-checkout: devel ++ override-checkout: cesnet/2024-10-before-oper-changes + - name: github/onqtam/doctest + override-checkout: v2.4.11 + - name: github/rollbear/trompeloeil +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 1eb625f..119bdd7 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -28,7 +28,7 @@ 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>=2.12.0 IMPORTED_TARGET) ++pkg_check_modules(SYSREPO REQUIRED sysrepo>=2.12.0 sysrepo<3 IMPORTED_TARGET) + set(SYSREPO_CPP_PKG_VERSION "3") + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) +diff --git a/README.md b/README.md +index 27dc748..e14b772 100644 +--- a/README.md ++++ b/README.md +@@ -9,6 +9,7 @@ It uses RAII for automatic memory management. + + ## Dependencies + - [sysrepo](https://github.com/sysrepo/sysrepo) - the `devel` branch (even for the `master` branch of *sysrepo-cpp*) ++ - we temporarily require pre-v3 sysrepo which introduced backward-incompatible changes to operational data handling + - [libyang-cpp](https://github.com/CESNET/libyang-cpp) - C++ bindings for *libyang* + - C++20 compiler (e.g., GCC 10.x+, clang 10+) + - CMake 3.19+ +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0004-refactor-don-t-use-module-as-a-variable-name.patch b/package/sysrepo-cpp/0004-refactor-don-t-use-module-as-a-variable-name.patch new file mode 100644 index 00000000..bb1323b7 --- /dev/null +++ b/package/sysrepo-cpp/0004-refactor-don-t-use-module-as-a-variable-name.patch @@ -0,0 +1,85 @@ +From f2db30721d909d127645721f4149de0cd36d2b1c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Tue, 17 Dec 2024 11:41:10 +0100 +Subject: [PATCH 4/6] refactor: don't use `module` as a variable name +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +...to unify stuff with all other occurrences, and to not use that magic +identifier. + +Change-Id: I8c38777a925271e7659560d0380a7f7968f4cfa7 +Signed-off-by: Mattias Walström +--- + include/sysrepo-cpp/Session.hpp | 4 ++-- + src/Session.cpp | 12 ++++++------ + 2 files changed, 8 insertions(+), 8 deletions(-) + +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index 7da11cb..477e604 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -95,7 +95,7 @@ public: + void copyConfig(const Datastore source, const std::optional& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + libyang::DataNode sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + void sendNotification(libyang::DataNode notification, const Wait wait, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); +- void replaceConfig(std::optional config, const std::optional& module = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); ++ void replaceConfig(std::optional config, const std::optional& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + + void setNacmUser(const std::string& user); + [[nodiscard]] Subscription initNacm( +@@ -172,7 +172,7 @@ public: + * + * Wraps `sr_lock`. + */ +- explicit Lock(Session session, std::optional module = std::nullopt, std::optional timeout = std::nullopt); ++ explicit Lock(Session session, std::optional moduleName = std::nullopt, std::optional timeout = std::nullopt); + /** @brief Release the lock + * + * Wraps `sr_unlock`. +diff --git a/src/Session.cpp b/src/Session.cpp +index 2706e1c..94a0fad 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -331,17 +331,17 @@ void Session::sendNotification(libyang::DataNode notification, const Wait wait, + * Wraps `sr_replace_config`. + * + * @param config Libyang tree to use as a complete datastore content, or nullopt +- * @param module If provided, a module name to limit the operation to ++ * @param moduleName If provided, a module name to limit the operation to + * @param timeout Optional timeout to wait for + */ +-void Session::replaceConfig(std::optional config, const std::optional& module, std::chrono::milliseconds timeout) ++void Session::replaceConfig(std::optional config, const std::optional& moduleName, std::chrono::milliseconds timeout) + { + std::optional thrashable; + if (config) { + thrashable = config->duplicateWithSiblings(libyang::DuplicationOptions::Recursive | libyang::DuplicationOptions::WithParents); + } + auto res = sr_replace_config( +- m_sess.get(), module ? module->c_str() : nullptr, ++ m_sess.get(), moduleName ? moduleName->c_str() : nullptr, + config ? libyang::releaseRawNode(*thrashable) : nullptr, + timeout.count()); + throwIfError(res, "sr_replace_config failed", m_sess.get()); +@@ -723,12 +723,12 @@ uint32_t Session::getId() const + return sr_session_get_id(m_sess.get()); + } + +-Lock::Lock(Session session, std::optional module, std::optional timeout) ++Lock::Lock(Session session, std::optional moduleName, std::optional timeout) + : m_session(session) + , m_lockedDs(m_session.activeDatastore()) +- , m_module(module) ++ , m_module(moduleName) + { +- auto res = sr_lock(getRawSession(m_session), module ? module->c_str() : nullptr, timeout ? timeout->count() : 0); ++ auto res = sr_lock(getRawSession(m_session), moduleName ? moduleName->c_str() : nullptr, timeout ? timeout->count() : 0); + throwIfError(res, "Cannot lock session", getRawSession(m_session)); + } + +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0005-DRY-a-dummy-leaf-XPath.patch b/package/sysrepo-cpp/0005-DRY-a-dummy-leaf-XPath.patch new file mode 100644 index 00000000..e7b7505a --- /dev/null +++ b/package/sysrepo-cpp/0005-DRY-a-dummy-leaf-XPath.patch @@ -0,0 +1,253 @@ +From a923f490350dc61b9f3e6000c06c1d7950a78a61 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Wed, 18 Dec 2024 16:12:14 +0100 +Subject: [PATCH 5/6] DRY: a dummy leaf XPath +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Change-Id: I47fca61d27380af07b2327f3ee5b984a8a8afb66 +Signed-off-by: Mattias Walström +--- + tests/session.cpp | 81 ++++++++++++++++++++++++----------------------- + 1 file changed, 41 insertions(+), 40 deletions(-) + +diff --git a/tests/session.cpp b/tests/session.cpp +index 5b384bb..f762f10 100644 +--- a/tests/session.cpp ++++ b/tests/session.cpp +@@ -20,6 +20,7 @@ TEST_CASE("session") + std::optional conn{std::in_place}; + auto sess = conn->sessionStart(); + sess.copyConfig(sysrepo::Datastore::Startup); ++ const auto leaf = "/test_module:leafInt32"s; + + DOCTEST_SUBCASE("Session should be still valid even after the Connection class gets freed") + { +@@ -29,39 +30,39 @@ TEST_CASE("session") + + DOCTEST_SUBCASE("Session lifetime is prolonged with data from getData") + { +- sess.setItem("/test_module:leafInt32", "123"); ++ sess.setItem(leaf, "123"); + sess.applyChanges(); +- auto data = sysrepo::Connection{}.sessionStart().getData("/test_module:leafInt32"); ++ auto data = sysrepo::Connection{}.sessionStart().getData(leaf); + REQUIRE(data->asTerm().valueStr() == "123"); + } + + DOCTEST_SUBCASE("basic data manipulation") + { +- auto data = sess.getData("/test_module:leafInt32"); ++ auto data = sess.getData(leaf); + REQUIRE(!data); + +- sess.setItem("/test_module:leafInt32", "123"); ++ sess.setItem(leaf, "123"); + sess.applyChanges(); +- data = sess.getData("/test_module:leafInt32"); ++ data = sess.getData(leaf); + REQUIRE(data->asTerm().valueStr() == "123"); +- auto node = sess.getOneNode("/test_module:leafInt32"); ++ auto node = sess.getOneNode(leaf); + REQUIRE(node.asTerm().valueStr() == "123"); + +- sess.setItem("/test_module:leafInt32", "420"); ++ sess.setItem(leaf, "420"); + sess.applyChanges(); +- data = sess.getData("/test_module:leafInt32"); ++ data = sess.getData(leaf); + REQUIRE(data->asTerm().valueStr() == "420"); + +- sess.deleteItem("/test_module:leafInt32"); ++ sess.deleteItem(leaf); + sess.applyChanges(); +- data = sess.getData("/test_module:leafInt32"); ++ data = sess.getData(leaf); + REQUIRE(!data); + +- sess.setItem("/test_module:leafInt32", "123"); ++ sess.setItem(leaf, "123"); + sess.discardChanges(); +- data = sess.getData("/test_module:leafInt32"); ++ data = sess.getData(leaf); + REQUIRE(!data); +- REQUIRE_THROWS_WITH_AS(sess.getOneNode("/test_module:leafInt32"), ++ REQUIRE_THROWS_WITH_AS(sess.getOneNode(leaf), + "Session::getOneNode: Couldn't get '/test_module:leafInt32': SR_ERR_NOT_FOUND", + sysrepo::ErrorWithCode); + +@@ -175,7 +176,7 @@ TEST_CASE("session") + { + sess.switchDatastore(sysrepo::Datastore::Operational); + sess.setItem("/test_module:stateLeaf", "42"); +- sess.setItem("/test_module:leafInt32", "1"); ++ sess.setItem(leaf, "1"); + sess.applyChanges(); + + DOCTEST_SUBCASE("Default options") +@@ -183,7 +184,7 @@ TEST_CASE("session") + auto data = sess.getData("/test_module:*"); + REQUIRE(data); + REQUIRE(data->findPath("/test_module:stateLeaf")); +- REQUIRE(data->findPath("/test_module:leafInt32")); ++ REQUIRE(data->findPath(leaf)); + } + + DOCTEST_SUBCASE("No state data") +@@ -191,7 +192,7 @@ TEST_CASE("session") + auto data = sess.getData("/test_module:*", 0, sysrepo::GetOptions::OperNoState); + REQUIRE(data); + REQUIRE(!data->findPath("/test_module:stateLeaf")); +- REQUIRE(data->findPath("/test_module:leafInt32")); ++ REQUIRE(data->findPath(leaf)); + } + } + +@@ -213,42 +214,42 @@ TEST_CASE("session") + DOCTEST_SUBCASE("Session::deleteOperItem") + { + // Set some arbitrary leaf. +- sess.setItem("/test_module:leafInt32", "123"); ++ sess.setItem(leaf, "123"); + sess.applyChanges(); + + // The leaf is accesible from the running datastore. +- REQUIRE(sess.getData("/test_module:leafInt32")->asTerm().valueStr() == "123"); ++ REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123"); + + // The leaf is NOT accesible from the operational datastore without a subscription. + sess.switchDatastore(sysrepo::Datastore::Operational); +- REQUIRE(!sess.getData("/test_module:leafInt32")); ++ REQUIRE(!sess.getData(leaf)); + + // When we create a subscription, the leaf is accesible from the operational datastore. + sess.switchDatastore(sysrepo::Datastore::Running); + auto sub = sess.onModuleChange("test_module", [] (auto, auto, auto, auto, auto, auto) { return sysrepo::ErrorCode::Ok; }); + sess.switchDatastore(sysrepo::Datastore::Operational); +- REQUIRE(sess.getData("/test_module:leafInt32")->asTerm().valueStr() == "123"); ++ REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123"); + + // After using deleteItem, the leaf is no longer accesible from the operational datastore. +- sess.deleteItem("/test_module:leafInt32"); ++ sess.deleteItem(leaf); + sess.applyChanges(); +- REQUIRE(!sess.getData("/test_module:leafInt32")); ++ REQUIRE(!sess.getData(leaf)); + + // Using discardItems makes the leaf visible again (in the operational datastore). +- sess.discardItems("/test_module:leafInt32"); ++ sess.discardItems(leaf); + sess.applyChanges(); +- REQUIRE(sess.getData("/test_module:leafInt32")->asTerm().valueStr() == "123"); ++ REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123"); + } + + DOCTEST_SUBCASE("edit batch") + { +- auto data = sess.getData("/test_module:leafInt32"); ++ auto data = sess.getData(leaf); + REQUIRE(!data); + +- auto batch = sess.getContext().newPath("/test_module:leafInt32", "1230"); ++ auto batch = sess.getContext().newPath(leaf, "1230"); + sess.editBatch(batch, sysrepo::DefaultOperation::Merge); + sess.applyChanges(); +- data = sess.getData("/test_module:leafInt32"); ++ data = sess.getData(leaf); + REQUIRE(data->asTerm().valueStr() == "1230"); + } + +@@ -307,8 +308,8 @@ TEST_CASE("session") + DOCTEST_SUBCASE("Session::getPendingChanges") + { + REQUIRE(sess.getPendingChanges() == std::nullopt); +- sess.setItem("/test_module:leafInt32", "123"); +- REQUIRE(sess.getPendingChanges().value().findPath("/test_module:leafInt32")->asTerm().valueStr() == "123"); ++ sess.setItem(leaf, "123"); ++ REQUIRE(sess.getPendingChanges().value().findPath(leaf)->asTerm().valueStr() == "123"); + + DOCTEST_SUBCASE("apply") + { +@@ -328,7 +329,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_THROWS_AS(sess.setItem("/test_module:leafInt32", "123"), sysrepo::ErrorWithCode); ++ REQUIRE_THROWS_AS(sess.setItem(leaf, "123"), sysrepo::ErrorWithCode); + } + + DOCTEST_SUBCASE("session IDs") +@@ -391,9 +392,9 @@ TEST_CASE("session") + + DOCTEST_SUBCASE("replace config") + { +- REQUIRE(!sess.getData("/test_module:leafInt32")); ++ REQUIRE(!sess.getData(leaf)); + // some "reasonable data" for two modules +- sess.setItem("/test_module:leafInt32", "666"); ++ sess.setItem(leaf, "666"); + sess.setItem("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='foo']", ""); + sess.applyChanges(); + +@@ -401,16 +402,16 @@ TEST_CASE("session") + REQUIRE(!!conf); + + // override a single leaf +- REQUIRE(sess.getOneNode("/test_module:leafInt32").asTerm().valueStr() == "666"); +- sess.setItem("/test_module:leafInt32", "123"); ++ REQUIRE(sess.getOneNode(leaf).asTerm().valueStr() == "666"); ++ sess.setItem(leaf, "123"); + sess.setItem("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='bar']", ""); + sess.applyChanges(); +- REQUIRE(sess.getOneNode("/test_module:leafInt32").asTerm().valueStr() == "123"); ++ REQUIRE(sess.getOneNode(leaf).asTerm().valueStr() == "123"); + + DOCTEST_SUBCASE("this module empty config") + { + sess.replaceConfig(std::nullopt, "test_module"); +- REQUIRE(!sess.getData("/test_module:leafInt32")); ++ REQUIRE(!sess.getData(leaf)); + REQUIRE(sess.getOneNode("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='foo']").asTerm().valueStr() == "foo"); + REQUIRE(sess.getOneNode("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='bar']").asTerm().valueStr() == "bar"); + } +@@ -418,7 +419,7 @@ TEST_CASE("session") + DOCTEST_SUBCASE("this module") + { + sess.replaceConfig(conf, "test_module"); +- REQUIRE(sess.getOneNode("/test_module:leafInt32").asTerm().valueStr() == "666"); ++ REQUIRE(sess.getOneNode(leaf).asTerm().valueStr() == "666"); + REQUIRE(sess.getOneNode("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='foo']").asTerm().valueStr() == "foo"); + REQUIRE(sess.getOneNode("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='bar']").asTerm().valueStr() == "bar"); + } +@@ -426,7 +427,7 @@ TEST_CASE("session") + DOCTEST_SUBCASE("other module") + { + sess.replaceConfig(std::nullopt, "ietf-netconf-acm"); +- REQUIRE(sess.getOneNode("/test_module:leafInt32").asTerm().valueStr() == "123"); ++ REQUIRE(sess.getOneNode(leaf).asTerm().valueStr() == "123"); + REQUIRE(!sess.getData("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='foo']")); + REQUIRE(!sess.getData("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='bar']")); + } +@@ -434,7 +435,7 @@ TEST_CASE("session") + DOCTEST_SUBCASE("entire datastore empty config") + { + sess.replaceConfig(std::nullopt); +- REQUIRE(!sess.getData("/test_module:leafInt32")); ++ REQUIRE(!sess.getData(leaf)); + REQUIRE(!sess.getData("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='foo']")); + REQUIRE(!sess.getData("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='bar']")); + } +@@ -442,7 +443,7 @@ TEST_CASE("session") + DOCTEST_SUBCASE("entire datastore") + { + sess.replaceConfig(conf); +- REQUIRE(sess.getOneNode("/test_module:leafInt32").asTerm().valueStr() == "666"); ++ REQUIRE(sess.getOneNode(leaf).asTerm().valueStr() == "666"); + REQUIRE(sess.getOneNode("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='foo']").asTerm().valueStr() == "foo"); + REQUIRE(!sess.getData("/ietf-netconf-acm:nacm/groups/group[name='ahoj']/user-name[.='bar']")); + } +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0006-build-a-single-place-to-define-package-version.patch b/package/sysrepo-cpp/0006-build-a-single-place-to-define-package-version.patch new file mode 100644 index 00000000..a26bc92d --- /dev/null +++ b/package/sysrepo-cpp/0006-build-a-single-place-to-define-package-version.patch @@ -0,0 +1,42 @@ +From 80e9659b41ea1de2e20d62f94319fe5af26adcee Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Wed, 18 Dec 2024 17:57:36 +0100 +Subject: [PATCH 6/6] build: a single place to define package version +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Don't ask me how I found out that I need this :). + +Change-Id: I2cd7397895ed4852f852e99b97543dde76eaff8f +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 119bdd7..68ade53 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -20,7 +20,8 @@ add_custom_target(sysrepo-cpp-version-cmake + cmake/ProjectGitVersionRunner.cmake + ) + include(cmake/ProjectGitVersion.cmake) +-prepare_git_version(SYSREPO_CPP_VERSION "3") ++set(SYSREPO_CPP_PKG_VERSION "3") ++prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION}) + + find_package(Doxygen) + option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${DOXYGEN_FOUND}) +@@ -29,7 +30,6 @@ 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>=2.12.0 sysrepo<3 IMPORTED_TARGET) +-set(SYSREPO_CPP_PKG_VERSION "3") + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +-- +2.43.0 + diff --git a/package/sysrepo-cpp/9999-GERRIT-API-ABI-break-Change-how-pushed-ops-data-work.patch b/package/sysrepo-cpp/9999-GERRIT-API-ABI-break-Change-how-pushed-ops-data-work.patch new file mode 100644 index 00000000..753c9a15 --- /dev/null +++ b/package/sysrepo-cpp/9999-GERRIT-API-ABI-break-Change-how-pushed-ops-data-work.patch @@ -0,0 +1,342 @@ +From b34fb200812f9e6bc72e80bf46bd006ebc06a9da Mon Sep 17 00:00:00 2001 +From: Jan Kundrát +Date: Wed, 18 Dec 2024 17:16:55 +0100 +Subject: [PATCH] API/ABI break: Change how pushed ops data work + +Upstream has changed the way how pushed operational data are stored and +managed. From now on, each session has a separate edit, these edits are +applied on top of each other using a given priority (priority setting is +not implemented in the C++ wrapper at this time, patches welcome). Each +session can retrieve and manipulate the stored edit. + +The key difference is that these edits are now per-session. This has a +wide-ranging impact, and the way how previously set nodes are "undone" +has changed. Unfortunately, upstream refused to rename the relevant +functions due to "API stability concerns", so we're taking care of +avoiding the possible confusion at the C++ layer: + +- `Session::dropForeignOperationalContent` (aka `sr_discard_items`), + which is now used to ensure that matching content from "previous + sources" is discarded. These "previous sources" are either content of + `running`, or stuff that was stored/pushed by other sessions with + lower priority. It *cannot* be used to remove stuff that was + previously pushed by the current session. + +- `Session::discardOperationalChanges` (aka `sr_discard_oper_changes`) + discards previously pushed content from *this session*. + +- It is now possible to fully manage the edit (which incrementally + builds the `operational` DS) of the current session. Use + `Session::operationalChanges()` to retrieve a full libyang::DataNode + forest, modify it in whichever ways are needed, and store it back via + `Session::editBatch(..., sysrepo::DefaultOperation::Replace)` followed + by `Session::applyChanges()`. + +Change-Id: Iba05a88411fd4c47c03d8c2b48cb7aadfd5dcd2a +--- + +diff --git a/.zuul.yaml b/.zuul.yaml +index c3aea22..dc87c6f 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -6,7 +6,7 @@ + - name: github/CESNET/libyang + override-checkout: devel + - name: github/sysrepo/sysrepo +- override-checkout: cesnet/2024-10-before-oper-changes ++ override-checkout: devel + - name: github/onqtam/doctest + override-checkout: v2.4.8 + - name: github/rollbear/trompeloeil +@@ -17,7 +17,7 @@ + - name: github/CESNET/libyang + override-checkout: devel + - name: github/sysrepo/sysrepo +- override-checkout: cesnet/2024-10-before-oper-changes ++ override-checkout: devel + - name: github/onqtam/doctest + override-checkout: v2.4.11 + - name: github/rollbear/trompeloeil +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 68ade53..cc4dba9 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -20,7 +20,7 @@ + cmake/ProjectGitVersionRunner.cmake + ) + include(cmake/ProjectGitVersion.cmake) +-set(SYSREPO_CPP_PKG_VERSION "3") ++set(SYSREPO_CPP_PKG_VERSION "4") + prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION}) + + find_package(Doxygen) +@@ -29,7 +29,7 @@ + + find_package(PkgConfig) + pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET) +-pkg_check_modules(SYSREPO REQUIRED sysrepo>=2.12.0 sysrepo<3 IMPORTED_TARGET) ++pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.0 IMPORTED_TARGET) + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +diff --git a/README.md b/README.md +index e14b772..27dc748 100644 +--- a/README.md ++++ b/README.md +@@ -9,7 +9,6 @@ + + ## Dependencies + - [sysrepo](https://github.com/sysrepo/sysrepo) - the `devel` branch (even for the `master` branch of *sysrepo-cpp*) +- - we temporarily require pre-v3 sysrepo which introduced backward-incompatible changes to operational data handling + - [libyang-cpp](https://github.com/CESNET/libyang-cpp) - C++ bindings for *libyang* + - C++20 compiler (e.g., GCC 10.x+, clang 10+) + - CMake 3.19+ +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index 477e604..238f480 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -85,13 +85,15 @@ + void setItem(const std::string& path, const std::optional& value, const EditOptions opts = sysrepo::EditOptions::Default); + void editBatch(libyang::DataNode edit, const DefaultOperation op); + void deleteItem(const std::string& path, const EditOptions opts = sysrepo::EditOptions::Default); +- void discardItems(const std::optional& xpath); + void moveItem(const std::string& path, const MovePosition move, const std::optional& keys_or_value, const std::optional& origin = std::nullopt, const EditOptions opts = sysrepo::EditOptions::Default); + std::optional getData(const std::string& path, int maxDepth = 0, const GetOptions opts = sysrepo::GetOptions::Default, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) const; + libyang::DataNode getOneNode(const std::string& path, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) const; + std::optional getPendingChanges() const; + void applyChanges(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); +- void discardChanges(); ++ void discardChanges(const std::optional& xpath = std::nullopt); ++ std::optional operationalChanges(const std::optional& moduleName = std::nullopt) const; ++ void discardOperationalChanges(const std::optional& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); ++ void dropForeignOperationalContent(const std::optional& xpath); + void copyConfig(const Datastore source, const std::optional& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + libyang::DataNode sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + void sendNotification(libyang::DataNode notification, const Wait wait, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); +diff --git a/src/Session.cpp b/src/Session.cpp +index 94a0fad..634e450 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -23,6 +23,26 @@ + + using namespace std::string_literals; + namespace sysrepo { ++ ++namespace { ++libyang::DataNode wrapSrData(std::shared_ptr sess, sr_data_t* data) ++{ ++ // Since the lyd_node came from sysrepo and it is wrapped in a sr_data_t, we have to postpone calling the ++ // sr_release_data() until after we're "done" with the libyang::DataNode. ++ // ++ // Normally, sr_release_data() would free the lyd_data as well. However, it is possible that the user wants to ++ // manipulate the data tree (think unlink()) in a way which might have needed to overwrite the tree->data pointer. ++ // Just delegate all the freeing to the C++ wrapper around lyd_data. The sysrepo library doesn't care about this. ++ auto tree = std::exchange(data->tree, nullptr); ++ ++ // Use wrapRawNode, not wrapUnmanagedRawNode because we want to let the C++ wrapper manage memory. ++ // Note: We're capturing the session inside the lambda. ++ return libyang::wrapRawNode(tree, std::shared_ptr(data, [extend_session_lifetime = sess] (sr_data_t* data) { ++ sr_release_data(data); ++ })); ++} ++} ++ + /** + * Wraps a pointer to sr_session_ctx_s and manages the lifetime of it. Also extends the lifetime of the connection + * specified by the `conn` argument. +@@ -119,20 +139,61 @@ + } + + /** +- * Prepare to discard nodes matching the specified xpath (or all if not set) previously set by the session connection. +- * Usable only for sysrepo::Datastore::Operational. The changes are applied only after calling Session::applyChanges. ++ * Prepare to drop "earlier content" from other sources in the operational DS for nodes matching the specified XPath ++ * ++ * The "earlier content" might come from the `running` datastore, or be pushed into the `operational` DS from ++ * another session, with a lower priority. This function prepares a special node into the current session's ++ * stored edit which effectively discards any matching content from previous, lower-priority sources. ++ * ++ * This function cannot be used to remove an edit which was pushed via the current session. To do that, ++ * use discardChanges(), or retrieve the stored edit and manipulate its libyang data tree. ++ * ++ * The changes are applied only after calling Session::applyChanges. + * + * Wraps `sr_discard_items`. + * + * @param xpath Expression filtering the nodes to discard, nullopt for all nodes. + */ +-void Session::discardItems(const std::optional& xpath) ++void Session::dropForeignOperationalContent(const std::optional& xpath) + { + auto res = sr_discard_items(m_sess.get(), xpath ? xpath->c_str() : nullptr); + + throwIfError(res, "Session::discardItems: Can't discard "s + (xpath ? "'"s + *xpath + "'" : "all nodes"s), m_sess.get()); + } + ++/** @short Get a copy of the stored push-operational data for this session ++ * ++ * To modify the stored push operational data, modify this tree in-place and pass it to editBatch() ++ * with the "replace" operation. ++ * ++ * Wraps `sr_get_oper_changes`. ++ */ ++std::optional Session::operationalChanges(const std::optional& moduleName) const ++{ ++ sr_data_t* data; ++ auto res = sr_get_oper_changes(m_sess.get(), moduleName ? moduleName->c_str() : nullptr, &data); ++ ++ throwIfError(res, "Session::operationalChanges: Couldn't retrieve data'"s + (moduleName ? " for \"" + *moduleName + '"' : ""s), m_sess.get()); ++ ++ if (!data) { ++ return std::nullopt; ++ } ++ ++ return wrapSrData(m_sess, data); ++ ++} ++ ++/** @short Discard push operational changes of a module for this session ++ * ++ * Wraps `sr_discard_oper_changes`. ++ * ++ * */ ++void Session::discardOperationalChanges(const std::optional& moduleName, std::chrono::milliseconds timeout) ++{ ++ auto res = sr_discard_oper_changes(nullptr, 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()); ++} ++ + /** + * Moves item (a list or a leaf-list) specified by `path`. + * @param path Node to move. +@@ -155,25 +216,6 @@ + throwIfError(res, "Session::moveItem: Can't move '"s + path + "'", m_sess.get()); + } + +-namespace { +-libyang::DataNode wrapSrData(std::shared_ptr sess, sr_data_t* data) +-{ +- // Since the lyd_node came from sysrepo and it is wrapped in a sr_data_t, we have to postpone calling the +- // sr_release_data() until after we're "done" with the libyang::DataNode. +- // +- // Normally, sr_release_data() would free the lyd_data as well. However, it is possible that the user wants to +- // manipulate the data tree (think unlink()) in a way which might have needed to overwrite the tree->data pointer. +- // Just delegate all the freeing to the C++ wrapper around lyd_data. The sysrepo library doesn't care about this. +- auto tree = std::exchange(data->tree, nullptr); +- +- // Use wrapRawNode, not wrapUnmanagedRawNode because we want to let the C++ wrapper manage memory. +- // Note: We're capturing the session inside the lambda. +- return libyang::wrapRawNode(tree, std::shared_ptr(data, [extend_session_lifetime = sess] (sr_data_t* data) { +- sr_release_data(data); +- })); +-} +-} +- + /** + * @brief Returns a tree which contains all nodes that match the provided XPath. + * +@@ -263,13 +305,15 @@ + } + + /** +- * Discards changes made in this Session. ++ * Discards changes made earlier in this Session, optionally only below a given XPath + * +- * Wraps `sr_discard_changes`. ++ * The changes are applied only after calling Session::applyChanges. ++ * ++ * Wraps `sr_discard_changes_xpath`. + */ +-void Session::discardChanges() ++void Session::discardChanges(const std::optional& xpath) + { +- auto res = sr_discard_changes(m_sess.get()); ++ auto res = sr_discard_changes_xpath(m_sess.get(), xpath ? xpath->c_str() : nullptr); + + throwIfError(res, "Session::discardChanges: Couldn't discard changes", m_sess.get()); + } +diff --git a/tests/session.cpp b/tests/session.cpp +index f762f10..2637f67 100644 +--- a/tests/session.cpp ++++ b/tests/session.cpp +@@ -211,7 +211,7 @@ + } + } + +- DOCTEST_SUBCASE("Session::deleteOperItem") ++ DOCTEST_SUBCASE("push operational data and deleting stuff") + { + // Set some arbitrary leaf. + sess.setItem(leaf, "123"); +@@ -230,15 +230,51 @@ + sess.switchDatastore(sysrepo::Datastore::Operational); + REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123"); + +- // After using deleteItem, the leaf is no longer accesible from the operational datastore. +- sess.deleteItem(leaf); +- sess.applyChanges(); +- REQUIRE(!sess.getData(leaf)); ++ DOCTEST_SUBCASE("discardOperationalChanges") ++ { ++ // apply a change which makes the leaf disappear ++ sess.dropForeignOperationalContent(leaf); ++ REQUIRE(!!sess.getData(leaf)); ++ sess.applyChanges(); ++ REQUIRE(!sess.getData(leaf)); + +- // Using discardItems makes the leaf visible again (in the operational datastore). +- sess.discardItems(leaf); +- sess.applyChanges(); +- REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123"); ++ // Using discardOperationalChanges makes the leaf visible again (in the operational datastore). ++ // Also, no need to applyChanges(). ++ sess.discardOperationalChanges("test_module"); ++ REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123"); ++ } ++ ++ DOCTEST_SUBCASE("direct edit of a libyang::DataNode") ++ { ++ // at first, set the leaf to some random value ++ sess.setItem(leaf, "456"); ++ sess.applyChanges(); ++ REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "456"); ++ ++ // change the edit in-place ++ auto pushed = sess.operationalChanges(); ++ REQUIRE(pushed->path() == leaf); ++ pushed->asTerm().changeValue("666"); ++ sess.editBatch(*pushed, sysrepo::DefaultOperation::Replace); ++ sess.applyChanges(); ++ REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "666"); ++ ++ // Remove that previous edit in-place. Since the new edit cannot be empty, set some other leaf. ++ pushed = sess.operationalChanges(); ++ auto another = "/test_module:popelnice/s"s; ++ pushed->newPath(another, "xxx"); ++ pushed = *pushed->findPath(another); ++ pushed->findPath(leaf)->unlink(); ++ // "the edit" for sysrepo must refer to a top-level node ++ while (pushed->parent()) { ++ pushed = *pushed->parent(); ++ } ++ REQUIRE(!pushed->findPath(leaf)); ++ REQUIRE(!!pushed->findPath(another)); ++ sess.editBatch(*pushed, sysrepo::DefaultOperation::Replace); ++ sess.applyChanges(); ++ REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123"); ++ } + } + + DOCTEST_SUBCASE("edit batch") +@@ -321,6 +357,11 @@ + sess.discardChanges(); + } + ++ DOCTEST_SUBCASE("discard XPath") ++ { ++ sess.discardChanges(leaf); ++ } ++ + REQUIRE(sess.getPendingChanges() == std::nullopt); + } + diff --git a/patches/netopeer2/2.2.31/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch b/patches/netopeer2/2.2.35/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch similarity index 73% rename from patches/netopeer2/2.2.31/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch rename to patches/netopeer2/2.2.35/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch index f0c70952..eeb366c1 100644 --- a/patches/netopeer2/2.2.31/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch +++ b/patches/netopeer2/2.2.35/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch @@ -1,19 +1,23 @@ -From 063f9b1696a4570aa61f02eb443d6752cab11c0c Mon Sep 17 00:00:00 2001 +From 2950046cd17bf9296a0b70b8f6b38114f6985864 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 22 Jun 2023 10:24:57 +0200 Subject: [PATCH 1/3] Allow 'factory' as copy-from (only) in rpc copy-config +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit Organization: Addiva Elektronik Signed-off-by: Joachim Wiberg +Signed-off-by: Mattias Walström --- src/netconf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/netconf.c b/src/netconf.c -index e0b790f..8ae4160 100644 +index f1cc6b3..fdd7297 100644 --- a/src/netconf.c +++ b/src/netconf.c -@@ -361,6 +361,8 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con +@@ -369,6 +369,8 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con ds = SR_DS_STARTUP; } else if (!strcmp(nodeset->dnodes[0]->schema->name, "candidate")) { ds = SR_DS_CANDIDATE; diff --git a/patches/netopeer2/2.2.31/0002-Disable-local-users-backwards-compat-with-older-mode.patch b/patches/netopeer2/2.2.35/0002-Disable-local-users-backwards-compat-with-older-mode.patch similarity index 93% rename from patches/netopeer2/2.2.31/0002-Disable-local-users-backwards-compat-with-older-mode.patch rename to patches/netopeer2/2.2.35/0002-Disable-local-users-backwards-compat-with-older-mode.patch index 974b2628..8aaf43e8 100644 --- a/patches/netopeer2/2.2.31/0002-Disable-local-users-backwards-compat-with-older-mode.patch +++ b/patches/netopeer2/2.2.35/0002-Disable-local-users-backwards-compat-with-older-mode.patch @@ -1,4 +1,4 @@ -From 26e36c7db9ebf11bf057a53e0c16238bda14792c Mon Sep 17 00:00:00 2001 +From d2e4e60838761e2bdd02d651b8e0bea47cc2dcb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 2 Jul 2024 14:56:15 +0200 Subject: [PATCH 2/3] Disable local users (backwards compat with older model) @@ -32,10 +32,10 @@ index 25e531b..13234e1 100644 "ietf-tls-server@2023-12-28.yang -e server-ident-x509-cert -e client-auth-supported -e client-auth-x509-cert" "ietf-netconf-server@2023-12-28.yang -e ssh-listen -e tls-listen -e ssh-call-home -e tls-call-home -e central-netconf-server-supported" diff --git a/scripts/merge_config.sh b/scripts/merge_config.sh -index 4b476d6..e588369 100755 +index eddada7..61af617 100755 --- a/scripts/merge_config.sh +++ b/scripts/merge_config.sh -@@ -81,9 +81,6 @@ CONFIG=" diff --git a/patches/sysrepo/3.3.10/0001-modinfo-BUGFIX-add-NP-containers-to-push-oper-data.patch b/patches/sysrepo/3.3.10/0001-modinfo-BUGFIX-add-NP-containers-to-push-oper-data.patch new file mode 100644 index 00000000..8c9b19ca --- /dev/null +++ b/patches/sysrepo/3.3.10/0001-modinfo-BUGFIX-add-NP-containers-to-push-oper-data.patch @@ -0,0 +1,36 @@ +From 2137bc0d614ad0aeabc37fd0041d05bc341a9007 Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Fri, 10 Jan 2025 08:55:38 +0100 +Subject: [PATCH 01/22] modinfo BUGFIX add NP containers to push oper data +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +... so that oper get subscriptions for the +containers are called. + +Signed-off-by: Mattias Walström +--- + src/modinfo.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/modinfo.c b/src/modinfo.c +index a58717f8..14d0b2ae 100644 +--- a/src/modinfo.c ++++ b/src/modinfo.c +@@ -1459,6 +1459,11 @@ sr_module_oper_data_update(struct sr_mod_info_mod_s *mod, const char *orig_name, + if ((err_info = sr_module_oper_data_load(mod, conn, 0, NULL, data))) { + return err_info; + } ++ ++ /* add any missing NP containers in the data */ ++ if ((err_info = sr_lyd_new_implicit_module(data, mod->ly_mod, LYD_IMPLICIT_NO_DEFAULTS, NULL))) { ++ return err_info; ++ } + } + + if (get_oper_opts & SR_OPER_NO_SUBS) { +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0002-sysrepo-BUGFIX-wrong-param.patch b/patches/sysrepo/3.3.10/0002-sysrepo-BUGFIX-wrong-param.patch new file mode 100644 index 00000000..fe2dbe6e --- /dev/null +++ b/patches/sysrepo/3.3.10/0002-sysrepo-BUGFIX-wrong-param.patch @@ -0,0 +1,30 @@ +From d4cded05509b63083283ba3a9104b27663c7eb39 Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Fri, 10 Jan 2025 08:56:29 +0100 +Subject: [PATCH 02/22] sysrepo BUGFIX wrong param +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Signed-off-by: Mattias Walström +--- + src/sysrepo.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/sysrepo.c b/src/sysrepo.c +index 844e11a5..7c96243c 100644 +--- a/src/sysrepo.c ++++ b/src/sysrepo.c +@@ -3578,7 +3578,7 @@ sr_delete_item(sr_session_ctx_t *session, const char *path, const sr_edit_option + /* just delete the selected node */ + node = NULL; + if (session->dt[session->ds].edit->tree && +- (err_info = sr_lyd_find_path(session->dt[session->ds].edit->tree, path, 1, &node))) { ++ (err_info = sr_lyd_find_path(session->dt[session->ds].edit->tree, path, 0, &node))) { + goto cleanup; + } + if (node) { +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0003-SOVERSION-bump-to-version-7.29.11.patch b/patches/sysrepo/3.3.10/0003-SOVERSION-bump-to-version-7.29.11.patch new file mode 100644 index 00000000..8b069626 --- /dev/null +++ b/patches/sysrepo/3.3.10/0003-SOVERSION-bump-to-version-7.29.11.patch @@ -0,0 +1,30 @@ +From 526da167043615ab9a0a8d36d4dd4a92644a0f58 Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Fri, 10 Jan 2025 08:56:54 +0100 +Subject: [PATCH 03/22] SOVERSION bump to version 7.29.11 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 84990130..4ecfe4d9 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -62,7 +62,7 @@ set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_ + # with backward compatible change and micro version is connected with any internal change of the library. + set(SYSREPO_MAJOR_SOVERSION 7) + set(SYSREPO_MINOR_SOVERSION 29) +-set(SYSREPO_MICRO_SOVERSION 10) ++set(SYSREPO_MICRO_SOVERSION 11) + set(SYSREPO_SOVERSION_FULL ${SYSREPO_MAJOR_SOVERSION}.${SYSREPO_MINOR_SOVERSION}.${SYSREPO_MICRO_SOVERSION}) + set(SYSREPO_SOVERSION ${SYSREPO_MAJOR_SOVERSION}) + +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0004-VERSION-bump-to-version-3.3.11.patch b/patches/sysrepo/3.3.10/0004-VERSION-bump-to-version-3.3.11.patch new file mode 100644 index 00000000..a6acde1e --- /dev/null +++ b/patches/sysrepo/3.3.10/0004-VERSION-bump-to-version-3.3.11.patch @@ -0,0 +1,30 @@ +From 880e15c69312a471c85a8f125e5c0ade3a93dcb1 Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Fri, 10 Jan 2025 08:57:04 +0100 +Subject: [PATCH 04/22] VERSION bump to version 3.3.11 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 4ecfe4d9..9f3cfde0 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -54,7 +54,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1) + # micro version is changed with a set of small changes or bugfixes anywhere in the project. + set(SYSREPO_MAJOR_VERSION 3) + set(SYSREPO_MINOR_VERSION 3) +-set(SYSREPO_MICRO_VERSION 10) ++set(SYSREPO_MICRO_VERSION 11) + set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_MICRO_VERSION}) + + # Version of the library +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0005-sysrepo-UPDATE-generate-notifications-on-module-chan.patch b/patches/sysrepo/3.3.10/0005-sysrepo-UPDATE-generate-notifications-on-module-chan.patch new file mode 100644 index 00000000..c8caff85 --- /dev/null +++ b/patches/sysrepo/3.3.10/0005-sysrepo-UPDATE-generate-notifications-on-module-chan.patch @@ -0,0 +1,963 @@ +From 31af3459bd8ea24228e2b13193176af6c34c289e Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Mon, 13 Jan 2025 11:19:30 +0100 +Subject: [PATCH 05/22] sysrepo UPDATE generate notifications on module changes +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Signed-off-by: Mattias Walström +--- + modules/sysrepo-notifications@2025-01-10.yang | 97 ++++++ + modules/sysrepo_notifications_yang.h | 208 ++++++++++++ + src/common.c | 295 +++++++++++++++++- + src/common.h | 42 ++- + src/lyd_mods.c | 4 + + src/modinfo.c | 2 +- + src/replay.c | 42 +-- + src/replay.h | 10 +- + src/sysrepo.c | 20 +- + tests/test_oper_pull.c | 6 + + 10 files changed, 693 insertions(+), 33 deletions(-) + create mode 100644 modules/sysrepo-notifications@2025-01-10.yang + create mode 100644 modules/sysrepo_notifications_yang.h + +diff --git a/modules/sysrepo-notifications@2025-01-10.yang b/modules/sysrepo-notifications@2025-01-10.yang +new file mode 100644 +index 00000000..73df4353 +--- /dev/null ++++ b/modules/sysrepo-notifications@2025-01-10.yang +@@ -0,0 +1,97 @@ ++module sysrepo-notifications { ++ namespace "http://www.sysrepo.org/yang/sysrepo-notifications"; ++ prefix srn; ++ ++ yang-version 1.1; ++ ++ import ietf-yang-library { ++ prefix yanglib; ++ } ++ ++ organization ++ "CESNET"; ++ ++ contact ++ "Author: Michal Vasko ++ "; ++ ++ description ++ "Sysrepo YANG notifications for various events."; ++ ++ revision "2025-01-10" { ++ description ++ "Initial revision."; ++ } ++ ++ typedef module-revision { ++ type union { ++ type yanglib:revision-identifier; ++ type string { ++ length "0"; ++ } ++ } ++ description ++ "The YANG module revision date. ++ A zero-length string is used if no revision statement ++ is present in the YANG module."; ++ } ++ ++ notification module-change { ++ description ++ "YANG module change occurred."; ++ ++ leaf name { ++ type string; ++ mandatory true; ++ description ++ "Name of the changed module."; ++ } ++ ++ leaf revision { ++ type module-revision; ++ mandatory true; ++ description ++ "Revision of the module."; ++ } ++ ++ leaf change { ++ type enumeration { ++ enum installed { ++ description ++ "New module was installed."; ++ } ++ enum uninstalled { ++ description ++ "Module was uninstalled."; ++ } ++ enum updated { ++ description ++ "Module was updated to a newer revision."; ++ } ++ enum feature-enabled { ++ description ++ "Feature was enabled in a module."; ++ } ++ enum feature-disabled { ++ description ++ "Feature was disabled in a module."; ++ } ++ } ++ mandatory true; ++ description ++ "Type of the module change."; ++ } ++ ++ leaf old-revision { ++ when "../change = 'updated'"; ++ type module-revision; ++ mandatory true; ++ } ++ ++ leaf feature-name { ++ when "../change = ('feature-enabled' or 'feature-disabled')"; ++ type string; ++ mandatory true; ++ } ++ } ++} +diff --git a/modules/sysrepo_notifications_yang.h b/modules/sysrepo_notifications_yang.h +new file mode 100644 +index 00000000..8f99c518 +--- /dev/null ++++ b/modules/sysrepo_notifications_yang.h +@@ -0,0 +1,208 @@ ++char sysrepo_notifications_yang[] = { ++ 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x73, 0x79, 0x73, 0x72, 0x65, ++ 0x70, 0x6f, 0x2d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, ++ 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6e, ++ 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x22, 0x68, 0x74, ++ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x79, 0x73, ++ 0x72, 0x65, 0x70, 0x6f, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x79, 0x61, 0x6e, ++ 0x67, 0x2f, 0x73, 0x79, 0x73, 0x72, 0x65, 0x70, 0x6f, 0x2d, 0x6e, 0x6f, ++ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, ++ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, ++ 0x20, 0x73, 0x72, 0x6e, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x79, ++ 0x61, 0x6e, 0x67, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, ++ 0x31, 0x2e, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, ++ 0x70, 0x6f, 0x72, 0x74, 0x20, 0x69, 0x65, 0x74, 0x66, 0x2d, 0x79, 0x61, ++ 0x6e, 0x67, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x20, 0x7b, ++ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, ++ 0x66, 0x69, 0x78, 0x20, 0x79, 0x61, 0x6e, 0x67, 0x6c, 0x69, 0x62, 0x3b, ++ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, ++ 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, ++ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x45, ++ 0x53, 0x4e, 0x45, 0x54, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, ++ 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x3a, ++ 0x20, 0x4d, 0x69, 0x63, 0x68, 0x61, 0x6c, 0x20, 0x56, 0x61, 0x73, 0x6b, ++ 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x6d, 0x76, 0x61, 0x73, ++ 0x6b, 0x6f, 0x40, 0x63, 0x65, 0x73, 0x6e, 0x65, 0x74, 0x2e, 0x63, 0x7a, ++ 0x3e, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, ++ 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x79, 0x73, 0x72, 0x65, 0x70, ++ 0x6f, 0x20, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, ++ 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x66, 0x6f, 0x72, ++ 0x20, 0x76, 0x61, 0x72, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x65, 0x76, 0x65, ++ 0x6e, 0x74, 0x73, 0x2e, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, ++ 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x22, 0x32, 0x30, ++ 0x32, 0x35, 0x2d, 0x30, 0x31, 0x2d, 0x31, 0x30, 0x22, 0x20, 0x7b, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, ++ 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, 0x69, ++ 0x74, 0x69, 0x61, 0x6c, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, ++ 0x6e, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x64, 0x65, 0x66, 0x20, ++ 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2d, 0x72, 0x65, 0x76, 0x69, 0x73, ++ 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x6f, 0x6e, ++ 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x79, 0x61, 0x6e, 0x67, ++ 0x6c, 0x69, 0x62, 0x3a, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, ++ 0x2d, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x3b, ++ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, ++ 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x6e, 0x67, 0x74, ++ 0x68, 0x20, 0x22, 0x30, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, ++ 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x59, 0x41, 0x4e, 0x47, ++ 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x76, 0x69, ++ 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x0a, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x41, 0x20, 0x7a, 0x65, 0x72, 0x6f, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 0x74, ++ 0x68, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, ++ 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x20, 0x72, ++ 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x74, 0x61, 0x74, ++ 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, ++ 0x65, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, ++ 0x20, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, ++ 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, ++ 0x20, 0x20, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, ++ 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2d, 0x63, ++ 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, ++ 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x22, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x6d, 0x6f, ++ 0x64, 0x75, 0x6c, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, ++ 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, ++ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, ++ 0x66, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, ++ 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, ++ 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, 0x75, 0x65, ++ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, ++ 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4e, 0x61, 0x6d, 0x65, 0x20, ++ 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, ++ 0x65, 0x64, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, ++ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66, ++ 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x74, 0x79, 0x70, 0x65, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2d, ++ 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, ++ 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, 0x75, 0x65, ++ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, ++ 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x52, 0x65, 0x76, 0x69, 0x73, ++ 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, ++ 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x20, 0x63, 0x68, 0x61, ++ 0x6e, 0x67, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, ++ 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, ++ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x69, 0x6e, ++ 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, ++ 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4e, 0x65, 0x77, 0x20, 0x6d, ++ 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x69, 0x6e, ++ 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, ++ 0x6d, 0x20, 0x75, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, ++ 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x22, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, ++ 0x75, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x2e, ++ 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, ++ 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x22, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, ++ 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, ++ 0x20, 0x6e, 0x65, 0x77, 0x65, 0x72, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, ++ 0x69, 0x6f, 0x6e, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, ++ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x66, 0x65, ++ 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, ++ 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x22, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x77, 0x61, 0x73, ++ 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, ++ 0x61, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, ++ 0x75, 0x6d, 0x20, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x64, ++ 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, ++ 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x46, 0x65, 0x61, 0x74, 0x75, ++ 0x72, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, 0x69, 0x73, 0x61, 0x62, ++ 0x6c, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x6f, 0x64, ++ 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, ++ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x6d, 0x61, 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, ++ 0x20, 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, ++ 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, ++ 0x54, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, ++ 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, ++ 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x6c, 0x65, 0x61, 0x66, 0x20, 0x6f, 0x6c, 0x64, 0x2d, 0x72, 0x65, 0x76, ++ 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x65, 0x6e, ++ 0x20, 0x22, 0x2e, 0x2e, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, ++ 0x3d, 0x20, 0x27, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x27, 0x22, ++ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, ++ 0x65, 0x2d, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x6d, 0x61, 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, ++ 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, ++ 0x65, 0x61, 0x66, 0x20, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, ++ 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, ++ 0x22, 0x2e, 0x2e, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x3d, ++ 0x20, 0x28, 0x27, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x65, ++ 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x27, 0x20, 0x6f, 0x72, 0x20, 0x27, ++ 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x64, 0x69, 0x73, 0x61, ++ 0x62, 0x6c, 0x65, 0x64, 0x27, 0x29, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, ++ 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20, ++ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, ++ 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, 0x75, 0x65, ++ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, ++ 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x00 ++}; +diff --git a/src/common.c b/src/common.c +index 10a379c0..72f3fc93 100644 +--- a/src/common.c ++++ b/src/common.c +@@ -55,10 +55,12 @@ + #include "modinfo.h" + #include "plugins_datastore.h" + #include "plugins_notification.h" ++#include "replay.h" + #include "shm_ext.h" + #include "shm_main.h" + #include "shm_mod.h" + #include "shm_sub.h" ++#include "subscr.h" + #include "sysrepo.h" + + #define SR_IS_YANG_ID_CHAR(c) (isalpha(c) || isdigit(c) || ((c) == '_') || ((c) == '-') || ((c) == '.')) +@@ -965,9 +967,9 @@ sr_module_default_mode(const struct lys_module *ly_mod) + if (!strcmp(ly_mod->name, "sysrepo")) { + return SR_INTMOD_MAIN_FILE_PERM; + } else if (sr_is_module_internal(ly_mod)) { +- if (!strcmp(ly_mod->name, "sysrepo-plugind") || !strcmp(ly_mod->name, "ietf-yang-schema-mount") || +- !strcmp(ly_mod->name, "ietf-yang-library") || !strcmp(ly_mod->name, "ietf-netconf-notifications") || +- !strcmp(ly_mod->name, "ietf-netconf")) { ++ if (!strcmp(ly_mod->name, "sysrepo-plugind") || !strcmp(ly_mod->name, "sysrepo-notifications") || ++ !strcmp(ly_mod->name, "ietf-yang-schema-mount") || !strcmp(ly_mod->name, "ietf-yang-library") || ++ !strcmp(ly_mod->name, "ietf-netconf-notifications") || !strcmp(ly_mod->name, "ietf-netconf")) { + return SR_INTMOD_WITHDATA_FILE_PERM; + } else if (!strcmp(ly_mod->name, "ietf-netconf-acm") || !strcmp(ly_mod->name, "sysrepo-monitoring")) { + return SR_INTMOD_NACM_SRMON_FILE_PERM; +@@ -5551,3 +5553,290 @@ sr_is_prod_env(void) + } + return sr_prod_env; + } ++ ++/** ++ * @brief Check whether a 'sysrepo-notifications' notification even need to be generated. ++ * ++ * @param[in] conn Connection to use. ++ * @param[in] shm_mod SHM mod. ++ * @param[out] subs_or_replay Set if there are any subscribers or replay is enabled for the module. ++ * @return err_info, NULL on success. ++ */ ++static sr_error_info_t * ++sr_generate_notif_has_subs_or_replay(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, int *subs_or_replay) ++{ ++ sr_error_info_t *err_info = NULL; ++ sr_mod_notif_sub_t *notif_subs; ++ uint32_t notif_sub_count; ++ ++ /* EXT READ LOCK */ ++ if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_READ, 0, __func__))) { ++ goto cleanup; ++ } ++ ++ /* get subscriber count */ ++ err_info = sr_notif_find_subscriber(conn, "sysrepo-notifications", ¬if_subs, ¬if_sub_count, NULL); ++ ++ /* EXT READ UNLOCK */ ++ sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 0, __func__); ++ ++ if (err_info) { ++ goto cleanup; ++ } ++ ++ /* check replay support and subscribers */ ++ if (!shm_mod->replay_supp && !notif_sub_count) { ++ /* no subscribers and replay off */ ++ *subs_or_replay = 0; ++ } else { ++ *subs_or_replay = 1; ++ } ++ ++cleanup: ++ return err_info; ++} ++ ++/** ++ * @brief Send a generated notification. ++ * ++ * @param[in] conn Connection to use. ++ * @param[in] shm_mod SHM mod. ++ * @param[in] notif Notification to send. ++ * @return err_info, NULL on success. ++ */ ++static sr_error_info_t * ++sr_generate_notif_send(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, const struct lyd_node *notif) ++{ ++ sr_error_info_t *err_info = NULL; ++ struct timespec notif_ts_mono, notif_ts_real; ++ ++ /* NOTIF SUB READ LOCK */ ++ if ((err_info = sr_rwlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid, ++ __func__, NULL, NULL))) { ++ goto cleanup; ++ } ++ ++ /* remember when the notification was generated */ ++ sr_timeouttime_get(¬if_ts_mono, 0); ++ sr_realtime_get(¬if_ts_real); ++ ++ /* send the notification (non-validated, must be valid) */ ++ err_info = sr_shmsub_notif_notify(conn, notif, notif_ts_mono, notif_ts_real, NULL, NULL, 0, 0); ++ ++ /* NOTIF SUB READ UNLOCK */ ++ sr_rwunlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid, __func__); ++ ++ if (err_info) { ++ goto cleanup; ++ } ++ ++ /* store the notification for a replay */ ++ if ((err_info = sr_replay_store(conn, NULL, notif, notif_ts_real))) { ++ goto cleanup; ++ } ++ ++cleanup: ++ return err_info; ++} ++ ++void ++sr_generate_notif_module_change_installed(sr_conn_ctx_t *conn, sr_int_install_mod_t *new_mods, uint32_t new_mod_count) ++{ ++ sr_error_info_t *err_info = NULL; ++ struct lyd_node *notif = NULL; ++ sr_mod_t *shm_mod; ++ uint32_t i; ++ int subs_or_replay; ++ ++ /* get this module */ ++ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications"); ++ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup); ++ ++ /* check whether to even generate any notifications */ ++ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) { ++ goto cleanup; ++ } ++ ++ for (i = 0; i < new_mod_count; ++i) { ++ /* generate the notifcation */ ++ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL, ++ ¬if))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "name", new_mods[i].ly_mod->name))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", new_mods[i].ly_mod->revision))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "change", "installed"))) { ++ goto cleanup; ++ } ++ ++ /* send it */ ++ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) { ++ goto cleanup; ++ } ++ ++ lyd_free_siblings(notif); ++ notif = NULL; ++ } ++ ++cleanup: ++ lyd_free_siblings(notif); ++ sr_errinfo_free(&err_info); ++} ++ ++void ++sr_generate_notif_module_change_uninstalled(sr_conn_ctx_t *conn, struct ly_set *mod_set) ++{ ++ sr_error_info_t *err_info = NULL; ++ struct lyd_node *notif = NULL; ++ const struct lys_module *ly_mod; ++ sr_mod_t *shm_mod; ++ uint32_t i; ++ int subs_or_replay; ++ ++ /* get this module */ ++ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications"); ++ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup); ++ ++ /* check whether to even generate any notifications */ ++ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) { ++ goto cleanup; ++ } ++ ++ for (i = 0; i < mod_set->count; ++i) { ++ ly_mod = mod_set->objs[i]; ++ ++ /* generate the notifcation */ ++ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL, ++ ¬if))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "name", ly_mod->name))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", ly_mod->revision))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "change", "uninstalled"))) { ++ goto cleanup; ++ } ++ ++ /* send it */ ++ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) { ++ goto cleanup; ++ } ++ ++ lyd_free_siblings(notif); ++ notif = NULL; ++ } ++ ++cleanup: ++ lyd_free_siblings(notif); ++ sr_errinfo_free(&err_info); ++} ++ ++void ++sr_generate_notif_module_change_updated(sr_conn_ctx_t *conn, struct ly_set *old_mod_set, struct ly_set *upd_mod_set) ++{ ++ sr_error_info_t *err_info = NULL; ++ struct lyd_node *notif = NULL; ++ const struct lys_module *ly_mod; ++ sr_mod_t *shm_mod; ++ uint32_t i; ++ int subs_or_replay; ++ ++ /* get this module */ ++ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications"); ++ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup); ++ ++ /* check whether to even generate any notifications */ ++ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) { ++ goto cleanup; ++ } ++ ++ for (i = 0; i < upd_mod_set->count; ++i) { ++ ly_mod = upd_mod_set->objs[i]; ++ ++ /* generate the notifcation */ ++ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL, ++ ¬if))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "name", ly_mod->name))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", ly_mod->revision))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "change", "updated"))) { ++ goto cleanup; ++ } ++ ++ ly_mod = old_mod_set->objs[i]; ++ if ((err_info = sr_lyd_new_term(notif, NULL, "old-revision", ly_mod->revision))) { ++ goto cleanup; ++ } ++ ++ /* send it */ ++ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) { ++ goto cleanup; ++ } ++ ++ lyd_free_siblings(notif); ++ notif = NULL; ++ } ++ ++cleanup: ++ lyd_free_siblings(notif); ++ sr_errinfo_free(&err_info); ++} ++ ++void ++sr_generate_notif_module_change_feature(sr_conn_ctx_t *conn, const struct lys_module *ly_mod, const char *feature_name, ++ int enabled) ++{ ++ sr_error_info_t *err_info = NULL; ++ struct lyd_node *notif = NULL; ++ sr_mod_t *shm_mod; ++ int subs_or_replay; ++ ++ /* get this module */ ++ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications"); ++ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup); ++ ++ /* check whether to even generate any notifications */ ++ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) { ++ goto cleanup; ++ } ++ ++ /* generate the notifcation */ ++ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL, ++ ¬if))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "name", ly_mod->name))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", ly_mod->revision))) { ++ goto cleanup; ++ } ++ if ((err_info = sr_lyd_new_term(notif, NULL, "change", enabled ? "feature-enabled" : "feature-disabled"))) { ++ goto cleanup; ++ } ++ ++ if ((err_info = sr_lyd_new_term(notif, NULL, "feature-name", feature_name))) { ++ goto cleanup; ++ } ++ ++ /* send it */ ++ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) { ++ goto cleanup; ++ } ++ ++cleanup: ++ lyd_free_siblings(notif); ++ sr_errinfo_free(&err_info); ++} +diff --git a/src/common.h b/src/common.h +index 99304bd7..9fc675d4 100644 +--- a/src/common.h ++++ b/src/common.h +@@ -4,8 +4,8 @@ + * @brief common routines header + * + * @copyright +- * Copyright (c) 2018 - 2023 Deutsche Telekom AG. +- * Copyright (c) 2018 - 2023 CESNET, z.s.p.o. ++ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. ++ * Copyright (c) 2018 - 2025 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. +@@ -1371,4 +1371,42 @@ sr_error_info_t *sr_conn_info(sr_cid_t **cids, pid_t **pids, uint32_t *count, sr + */ + int sr_is_prod_env(void); + ++/** ++ * @brief Generate the 'module-change' notification for newly installed modules. ++ * ++ * @param[in] conn Connection to use. ++ * @param[in] new_mods Array of new modules. ++ * @param[in] new_mod_count Count of @p new_mods. ++ */ ++void sr_generate_notif_module_change_installed(sr_conn_ctx_t *conn, sr_int_install_mod_t *new_mods, ++ uint32_t new_mod_count); ++ ++/** ++ * @brief Generate the 'module-change' notification for uninstalled modules. ++ * ++ * @param[in] conn Connection to use. ++ * @param[in] mod_set Set of uninstalled modules. ++ */ ++void sr_generate_notif_module_change_uninstalled(sr_conn_ctx_t *conn, struct ly_set *mod_set); ++ ++/** ++ * @brief Generate the 'module-change' notification for updated modules. ++ * ++ * @param[in] conn Connection to use. ++ * @param[in] old_mod_set Set of old modules. ++ * @param[in] upd_mod_set Set of updated modules. ++ */ ++void sr_generate_notif_module_change_updated(sr_conn_ctx_t *conn, struct ly_set *old_mod_set, struct ly_set *upd_mod_set); ++ ++/** ++ * @brief Generate the 'module-change' notification for a module feature change. ++ * ++ * @param[in] conn Connection to use. ++ * @param[in] ly_mod Changed module. ++ * @param[in] feature_name Changed feature name. ++ * @param[in] enabled Whether the feature was enabled or disabled. ++ */ ++void sr_generate_notif_module_change_feature(sr_conn_ctx_t *conn, const struct lys_module *ly_mod, ++ const char *feature_name, int enabled); ++ + #endif /* _COMMON_H */ +diff --git a/src/lyd_mods.c b/src/lyd_mods.c +index 256f292e..77f8cd7b 100644 +--- a/src/lyd_mods.c ++++ b/src/lyd_mods.c +@@ -65,6 +65,7 @@ + #include "../modules/ietf_netconf_yang.h" + #include "../modules/ietf_origin_yang.h" + #include "../modules/sysrepo_monitoring_yang.h" ++#include "../modules/sysrepo_notifications_yang.h" + #include "../modules/sysrepo_plugind_yang.h" + + /** +@@ -1000,6 +1001,9 @@ sr_lydmods_create(sr_conn_ctx_t *conn, struct ly_ctx *ly_ctx, struct lyd_node ** + /* install sysrepo-plugind */ + SR_INSTALL_INT_MOD(ly_ctx, sysrepo_plugind_yang, 0, new_mods, new_mod_count); + ++ /* install sysrepo-notifications */ ++ SR_INSTALL_INT_MOD(ly_ctx, sysrepo_notifications_yang, 0, new_mods, new_mod_count); ++ + /* install ietf-netconf (implemented dependency) and ietf-netconf-with-defaults */ + SR_INSTALL_INT_MOD(ly_ctx, ietf_netconf_yang, 1, new_mods, new_mod_count); + SR_INSTALL_INT_MOD(ly_ctx, ietf_netconf_with_defaults_yang, 0, new_mods, new_mod_count); +diff --git a/src/modinfo.c b/src/modinfo.c +index 14d0b2ae..7414121e 100644 +--- a/src/modinfo.c ++++ b/src/modinfo.c +@@ -3824,7 +3824,7 @@ sr_modinfo_generate_config_change_notif(struct sr_mod_info_s *mod_info, sr_sessi + } + + /* store the notification for a replay */ +- if ((err_info = sr_replay_store(session, notif, notif_ts_real))) { ++ if ((err_info = sr_replay_store(mod_info->conn, session, notif, notif_ts_real))) { + goto cleanup; + } + +diff --git a/src/replay.c b/src/replay.c +index c31043de..00e63eec 100644 +--- a/src/replay.c ++++ b/src/replay.c +@@ -127,7 +127,7 @@ cleanup: + } + + sr_error_info_t * +-sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct timespec notif_ts) ++sr_replay_store(sr_conn_ctx_t *conn, sr_session_ctx_t *sess, const struct lyd_node *notif, struct timespec notif_ts) + { + sr_error_info_t *err_info = NULL; + sr_mod_t *shm_mod; +@@ -146,7 +146,7 @@ sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct tim + SR_CHECK_INT_RET(notif_op->schema->nodetype != LYS_NOTIF, err_info); + + /* find SHM mod for replay lock and check if replay is even supported */ +- shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(sess->conn), ly_mod->name); ++ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), ly_mod->name); + SR_CHECK_INT_RET(!shm_mod, err_info); + + if (!shm_mod->replay_supp) { +@@ -154,32 +154,34 @@ sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct tim + return NULL; + } + +- /* MUTEX LOCK */ +- sr_timeouttime_get(&timeout_ts, SR_NOTIF_BUF_LOCK_TIMEOUT); +- if ((r = pthread_mutex_clocklock(&sess->notif_buf.lock.mutex, COMPAT_CLOCK_ID, &timeout_ts))) { +- SR_ERRINFO_LOCK(&err_info, __func__, r); +- return err_info; +- } ++ if (sess) { ++ /* MUTEX LOCK */ ++ sr_timeouttime_get(&timeout_ts, SR_NOTIF_BUF_LOCK_TIMEOUT); ++ if ((r = pthread_mutex_clocklock(&sess->notif_buf.lock.mutex, COMPAT_CLOCK_ID, &timeout_ts))) { ++ SR_ERRINFO_LOCK(&err_info, __func__, r); ++ return err_info; ++ } + +- if (sess->notif_buf.thread_running) { +- /* store the notification in the buffer */ +- has_buf = 1; +- err_info = sr_notif_buf_store(sess, notif, notif_ts); ++ if (sess->notif_buf.thread_running) { ++ /* store the notification in the buffer */ ++ has_buf = 1; ++ err_info = sr_notif_buf_store(sess, notif, notif_ts); + +- /* broadcast condition */ +- sr_cond_broadcast(&sess->notif_buf.lock.cond); +- } ++ /* broadcast condition */ ++ sr_cond_broadcast(&sess->notif_buf.lock.cond); ++ } + +- /* MUTEX UNLOCK */ +- pthread_mutex_unlock(&sess->notif_buf.lock.mutex); ++ /* MUTEX UNLOCK */ ++ pthread_mutex_unlock(&sess->notif_buf.lock.mutex); + +- if (err_info) { +- return err_info; ++ if (err_info) { ++ return err_info; ++ } + } + + if (!has_buf) { + /* write the notification to a replay file */ +- if ((err_info = sr_notif_write(sess->conn, shm_mod, notif, notif_ts))) { ++ if ((err_info = sr_notif_write(conn, shm_mod, notif, notif_ts))) { + return err_info; + } + } +diff --git a/src/replay.h b/src/replay.h +index ecf0cf25..7f3a851a 100644 +--- a/src/replay.h ++++ b/src/replay.h +@@ -4,8 +4,8 @@ + * @brief header for notification replay routines + * + * @copyright +- * Copyright (c) 2018 - 2021 Deutsche Telekom AG. +- * Copyright (c) 2018 - 2021 CESNET, z.s.p.o. ++ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. ++ * Copyright (c) 2018 - 2025 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. +@@ -26,12 +26,14 @@ + /** + * @brief Store a notification for replay. + * +- * @param[in] sess Session to use. ++ * @param[in] conn Connection to use. ++ * @param[in] sess Session to use, if available. + * @param[in] notif Notification to store. + * @param[in] notif_ts Notification timestamp to store. + * @return err_info, NULL on success. + */ +-sr_error_info_t *sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct timespec notif_ts); ++sr_error_info_t *sr_replay_store(sr_conn_ctx_t *conn, sr_session_ctx_t *sess, const struct lyd_node *notif, ++ struct timespec notif_ts); + + /** + * @brief Notification buffer thread. +diff --git a/src/sysrepo.c b/src/sysrepo.c +index 7c96243c..614b5454 100644 +--- a/src/sysrepo.c ++++ b/src/sysrepo.c +@@ -4,8 +4,8 @@ + * @brief sysrepo API routines + * + * @copyright +- * Copyright (c) 2018 - 2024 Deutsche Telekom AG. +- * Copyright (c) 2018 - 2024 CESNET, z.s.p.o. ++ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. ++ * Copyright (c) 2018 - 2025 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. +@@ -1541,6 +1541,9 @@ _sr_install_modules(sr_conn_ctx_t *conn, const char *search_dirs, const char *da + SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx); + sr_conn_ctx_switch(conn, &new_ctx, &old_ctx); + ++ /* send the notification */ ++ sr_generate_notif_module_change_installed(conn, *new_mods, *new_mod_count); ++ + goto cleanup; + + error: +@@ -1859,6 +1862,9 @@ sr_remove_modules(sr_conn_ctx_t *conn, const char **module_names, int force) + SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx); + sr_conn_ctx_switch(conn, &new_ctx, &old_ctx); + ++ /* send the notification */ ++ sr_generate_notif_module_change_uninstalled(conn, &mod_set); ++ + cleanup: + sr_lycc_update_data_clear(&data_info); + lyd_free_siblings(sr_mods); +@@ -2104,6 +2110,9 @@ sr_update_modules(sr_conn_ctx_t *conn, const char **schema_paths, const char *se + SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx); + sr_conn_ctx_switch(conn, &new_ctx, &old_ctx); + ++ /* send the notification */ ++ sr_generate_notif_module_change_updated(conn, &old_mod_set, &upd_mod_set); ++ + cleanup: + sr_lycc_update_data_clear(&data_info); + lyd_free_siblings(sr_mods); +@@ -2606,6 +2615,9 @@ sr_change_module_feature(sr_conn_ctx_t *conn, const char *module_name, const cha + SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx); + sr_conn_ctx_switch(conn, &new_ctx, &old_ctx); + ++ /* send the notification */ ++ sr_generate_notif_module_change_feature(conn, ly_mod, feature_name, enable); ++ + cleanup: + sr_lycc_update_data_clear(&data_info); + lyd_free_siblings(sr_mods); +@@ -2735,6 +2747,8 @@ sr_is_module_internal(const struct lys_module *ly_mod) + return 1; + } else if (!strcmp(ly_mod->name, "sysrepo-plugind")) { + return 1; ++ } else if (!strcmp(ly_mod->name, "sysrepo-notifications")) { ++ return 1; + } else if (!strcmp(ly_mod->name, "ietf-netconf-acm")) { + return 1; + } +@@ -7407,7 +7421,7 @@ sr_notif_send_tree(sr_session_ctx_t *session, struct lyd_node *notif, uint32_t t + } + + /* store the notification for a replay */ +- if ((err_info = sr_replay_store(session, notif_top, notif_ts_real))) { ++ if ((err_info = sr_replay_store(session->conn, session, notif_top, notif_ts_real))) { + goto cleanup; + } + +diff --git a/tests/test_oper_pull.c b/tests/test_oper_pull.c +index c9654faa..91ba526e 100644 +--- a/tests/test_oper_pull.c ++++ b/tests/test_oper_pull.c +@@ -406,6 +406,9 @@ test_sr_mon(void **state) + " \n" + " \n" + " \n" ++ " sysrepo-notifications\n" ++ " \n" ++ " \n" + " ietf-netconf\n" + " \n" + " \n" +@@ -669,6 +672,9 @@ test_sr_mon(void **state) + " \n" + " \n" + " \n" ++ " sysrepo-notifications\n" ++ " \n" ++ " \n" + " ietf-netconf\n" + " \n" + " \n" +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0006-SOVERSION-bump-to-version-7.29.12.patch b/patches/sysrepo/3.3.10/0006-SOVERSION-bump-to-version-7.29.12.patch new file mode 100644 index 00000000..d77e621c --- /dev/null +++ b/patches/sysrepo/3.3.10/0006-SOVERSION-bump-to-version-7.29.12.patch @@ -0,0 +1,30 @@ +From 05ef34960277dca49e1dda26eb2f4ffd5f1ba354 Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Mon, 13 Jan 2025 11:19:51 +0100 +Subject: [PATCH 06/22] SOVERSION bump to version 7.29.12 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 9f3cfde0..fcb49a24 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -62,7 +62,7 @@ set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_ + # with backward compatible change and micro version is connected with any internal change of the library. + set(SYSREPO_MAJOR_SOVERSION 7) + set(SYSREPO_MINOR_SOVERSION 29) +-set(SYSREPO_MICRO_SOVERSION 11) ++set(SYSREPO_MICRO_SOVERSION 12) + set(SYSREPO_SOVERSION_FULL ${SYSREPO_MAJOR_SOVERSION}.${SYSREPO_MINOR_SOVERSION}.${SYSREPO_MICRO_SOVERSION}) + set(SYSREPO_SOVERSION ${SYSREPO_MAJOR_SOVERSION}) + +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0007-VERSION-bump-to-version-3.4.0.patch b/patches/sysrepo/3.3.10/0007-VERSION-bump-to-version-3.4.0.patch new file mode 100644 index 00000000..719a9aad --- /dev/null +++ b/patches/sysrepo/3.3.10/0007-VERSION-bump-to-version-3.4.0.patch @@ -0,0 +1,32 @@ +From 74f5fa11043e579b6c007a2ff1f5f1d880637b92 Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Mon, 13 Jan 2025 11:20:00 +0100 +Subject: [PATCH 07/22] VERSION bump to version 3.4.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index fcb49a24..7d49cedb 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -53,8 +53,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1) + # minor version changes with added functionality (new tool, functionality of the tool or library, ...) and + # micro version is changed with a set of small changes or bugfixes anywhere in the project. + set(SYSREPO_MAJOR_VERSION 3) +-set(SYSREPO_MINOR_VERSION 3) +-set(SYSREPO_MICRO_VERSION 11) ++set(SYSREPO_MINOR_VERSION 4) ++set(SYSREPO_MICRO_VERSION 0) + set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_MICRO_VERSION}) + + # Version of the library +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0008-shmext-REFACTOR-conn_remap_unlock-remove-last-hole.patch b/patches/sysrepo/3.3.10/0008-shmext-REFACTOR-conn_remap_unlock-remove-last-hole.patch new file mode 100644 index 00000000..50f3eb8b --- /dev/null +++ b/patches/sysrepo/3.3.10/0008-shmext-REFACTOR-conn_remap_unlock-remove-last-hole.patch @@ -0,0 +1,62 @@ +From a0e02874aaf24a9e94c501ac95c5b9f55c316500 Mon Sep 17 00:00:00 2001 +From: IrfanMohammad +Date: Mon, 13 Jan 2025 08:19:04 +0000 +Subject: [PATCH 08/22] shmext REFACTOR conn_remap_unlock remove last hole +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +The logic in sr_shmext_conn_remap_unlock can be cleaner. + +A call to sr_file_get_size() can be avoided since we already know it +as `conn->ext_shm.size`. + +Also cleanup the somewhat complex cast + arithmetic in a if condition. + +Signed-off-by: Mattias Walström +--- + src/shm_ext.c | 16 +++++++--------- + 1 file changed, 7 insertions(+), 9 deletions(-) + +diff --git a/src/shm_ext.c b/src/shm_ext.c +index f4103837..78930469 100644 +--- a/src/shm_ext.c ++++ b/src/shm_ext.c +@@ -131,8 +131,8 @@ sr_shmext_conn_remap_unlock(sr_conn_ctx_t *conn, sr_lock_mode_t mode, int ext_lo + { + sr_error_info_t *err_info = NULL; + sr_ext_hole_t *iter, *last = NULL; +- uint32_t last_size; +- size_t shm_file_size = 0; ++ uint32_t new_size; ++ char *last_hole_end; + + /* make ext SHM smaller if there is a memory hole at its end */ + if (((mode == SR_LOCK_WRITE) || (mode == SR_LOCK_WRITE_URGE)) && ext_lock) { +@@ -140,17 +140,15 @@ sr_shmext_conn_remap_unlock(sr_conn_ctx_t *conn, sr_lock_mode_t mode, int ext_lo + last = iter; + } + +- if (last && ((uint32_t)((char *)last - conn->ext_shm.addr) + last->size == conn->ext_shm.size)) { +- if ((err_info = sr_file_get_size(conn->ext_shm.fd, &shm_file_size))) { +- goto cleanup_unlock; +- } ++ /* cast `last` as a char* for correct pointer arithmetic. */ ++ last_hole_end = last ? ((char *)last + last->size) : NULL; + ++ if (last_hole_end == conn->ext_shm.addr + conn->ext_shm.size) { + /* remove the hole */ +- last_size = last->size; ++ new_size = conn->ext_shm.size - last->size; + sr_ext_hole_del(SR_CONN_EXT_SHM(conn), last); +- + /* remap (and truncate) ext SHM */ +- if ((err_info = sr_shm_remap(&conn->ext_shm, shm_file_size - last_size))) { ++ if ((err_info = sr_shm_remap(&conn->ext_shm, new_size))) { + goto cleanup_unlock; + } + } +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0009-modinfo-UPDATE-sess-push-oper-data-module-cache.patch b/patches/sysrepo/3.3.10/0009-modinfo-UPDATE-sess-push-oper-data-module-cache.patch new file mode 100644 index 00000000..843ba76c --- /dev/null +++ b/patches/sysrepo/3.3.10/0009-modinfo-UPDATE-sess-push-oper-data-module-cache.patch @@ -0,0 +1,250 @@ +From e0f1e2480a3f69e291deed06a2626ed9b4df1662 Mon Sep 17 00:00:00 2001 +From: IrfanMohammad +Date: Mon, 13 Jan 2025 13:46:28 +0000 +Subject: [PATCH 09/22] modinfo UPDATE sess push oper data module cache +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Maintain a list of modules whose push oper data were modified +by the session. + +Acquiring the global ext_lock is significantly more expensive. + +Previously this was implemented for connections in +3b0165d656709b5f0cd3e8debc2e55c76c73663a + +But in sysrepo v3.x, oper push data is tied to the session and the logic +needs to be re-introduced. + +Signed-off-by: Mattias Walström +--- + src/common_types.h | 7 +++- + src/modinfo.c | 93 ++++++++++++++++++++++++++-------------------- + src/sysrepo.c | 14 ++++++- + 3 files changed, 71 insertions(+), 43 deletions(-) + +diff --git a/src/common_types.h b/src/common_types.h +index 915d9196..3ce27ad4 100644 +--- a/src/common_types.h ++++ b/src/common_types.h +@@ -180,7 +180,12 @@ struct sr_session_ctx_s { + char *user; /**< Session (system) user. */ + char *nacm_user; /**< Optional NACM user. If set, NACM is applied. */ + sr_error_info_t *err_info; /**< Session error information. */ +- int push_oper_data; /**< Whether the session has stored any push operational data. */ ++ ++ struct sr_oper_push_cache_s { ++ char *name; /**< Module name whose push oper data were ever modified by this session. */ ++ int has_data; /**< Flag if there are any actual data currently. */ ++ } *oper_push_mods; ++ uint32_t oper_push_mod_count; /**< Count of modules with modified push oper data by this session. */ + + char *orig_name; /**< Originator name used for all events sent on this session. */ + void *orig_data; /**< Originator data used for all events sent on this session. */ +diff --git a/src/modinfo.c b/src/modinfo.c +index 7414121e..d5c27672 100644 +--- a/src/modinfo.c ++++ b/src/modinfo.c +@@ -322,42 +322,26 @@ sr_error_info_t * + sr_modinfo_collect_oper_sess(sr_session_ctx_t *sess, const struct lys_module *ly_mod, struct sr_mod_info_s *mod_info) + { + sr_error_info_t *err_info = NULL; +- sr_mod_shm_t *mod_shm; +- sr_mod_t *shm_mod; +- sr_mod_oper_push_t *oper_push; + const struct lys_module *ly_mod2; +- uint32_t i, j; +- +- mod_shm = SR_CONN_MOD_SHM(sess->conn); +- +- /* EXT READ LOCK */ +- if ((err_info = sr_shmext_conn_remap_lock(sess->conn, SR_LOCK_READ, 1, __func__))) { +- return err_info; +- } +- +- /* go through all the SHM modules */ +- for (i = 0; i < mod_shm->mod_count; ++i) { +- shm_mod = SR_SHM_MOD_IDX(mod_shm, i); ++ uint32_t i; + +- if (ly_mod && strcmp(ly_mod->name, ((char *)mod_shm) + shm_mod->name)) { ++ /* add only the cached modules */ ++ for (i = 0; i < sess->oper_push_mod_count; ++i) { ++ if (ly_mod && strcmp(ly_mod->name, sess->oper_push_mods[i].name)) { + continue; + } + +- /* go through the sessions with oper data */ +- for (j = 0; j < shm_mod->oper_push_data_count; ++j) { +- oper_push = &((sr_mod_oper_push_t *)(sess->conn->ext_shm.addr + shm_mod->oper_push_data))[j]; +- if ((oper_push->sid == sess->sid) && oper_push->has_data) { +- break; +- } ++ if (!sess->oper_push_mods[i].has_data) { ++ continue; + } + +- if (j < shm_mod->oper_push_data_count) { +- /* remember the module */ +- ly_mod2 = ly_ctx_get_module_implemented(sess->conn->ly_ctx, ((char *)mod_shm) + shm_mod->name); +- SR_CHECK_INT_GOTO(!ly_mod2, err_info, cleanup_unlock); +- if ((err_info = sr_modinfo_add(ly_mod2, NULL, 0, 0, mod_info))) { +- goto cleanup_unlock; +- } ++ ly_mod2 = ly_ctx_get_module_implemented(sess->conn->ly_ctx, sess->oper_push_mods[i].name); ++ if (!ly_mod2) { ++ /* could have been removed */ ++ continue; ++ } ++ if ((err_info = sr_modinfo_add(ly_mod2, NULL, 0, 0, mod_info))) { ++ return err_info; + } + + if (ly_mod) { +@@ -365,11 +349,7 @@ sr_modinfo_collect_oper_sess(sr_session_ctx_t *sess, const struct lys_module *ly + } + } + +-cleanup_unlock: +- /* EXT READ UNLOCK */ +- sr_shmext_conn_remap_unlock(sess->conn, SR_LOCK_READ, 1, __func__); +- +- return err_info; ++ return NULL; + } + + sr_error_info_t * +@@ -2664,7 +2644,7 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m + struct lyd_node *mod_data = NULL; + const char **xpaths; + uint32_t xpath_count; +- int modified, has_data; ++ int modified, has_data = 0; + char *orig_name = NULL; + void *orig_data = NULL; + +@@ -2674,8 +2654,8 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m + if ((mod_info->ds == SR_DS_OPERATIONAL) && (mod_info->ds2 == SR_DS_OPERATIONAL)) { + assert(sess); + +- /* check whether this session even has any push oper data for this module */ +- if ((err_info = sr_shmext_oper_push_get(conn, mod->shm_mod, mod->ly_mod->name, sess->sid, NULL, &has_data, ++ /* check whether this session even has any push oper data and next push data for this module */ ++ if (sess->oper_push_mod_count && (err_info = sr_shmext_oper_push_get(conn, mod->shm_mod, mod->ly_mod->name, sess->sid, NULL, &has_data, + SR_LOCK_READ))) { + return err_info; + } +@@ -2735,7 +2715,7 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m + /* no cached data or unusable */ + + if ((mod_info->ds == SR_DS_OPERATIONAL) && (mod_info->ds2 == SR_DS_RUNNING)) { +- /* we need the whole runnign DS to avoid not getting parents of oper pull subscriptions and so considering ++ /* we need the whole running DS to avoid not getting parents of oper pull subscriptions and so considering + * them incorrectly as non-existent */ + xpaths = NULL; + xpath_count = 0; +@@ -3839,6 +3819,35 @@ cleanup: + return err_info; + } + ++static ++sr_error_info_t * ++sr_modinfo_push_oper_mod_update_sess(sr_session_ctx_t *sess, const char *mod_name, int has_data) ++{ ++ sr_error_info_t *err_info = NULL; ++ uint32_t i; ++ void *mem; ++ ++ for (i = 0; i < sess->oper_push_mod_count; ++i) { ++ if (!strcmp(sess->oper_push_mods[i].name, mod_name)) { ++ /* already added, update has_data */ ++ sess->oper_push_mods[i].has_data = has_data; ++ return NULL; ++ } ++ } ++ ++ /* add new module */ ++ mem = realloc(sess->oper_push_mods, (i + 1) * sizeof *(sess->oper_push_mods)); ++ SR_CHECK_MEM_GOTO(!mem, err_info, cleanup); ++ sess->oper_push_mods = mem; ++ ++ sess->oper_push_mods[i].name = strdup(mod_name); ++ SR_CHECK_MEM_GOTO(!sess->oper_push_mods[i].name, err_info, cleanup); ++ sess->oper_push_mods[i].has_data = has_data; ++ ++sess->oper_push_mod_count; ++cleanup: ++ return err_info; ++} ++ + sr_error_info_t * + sr_modinfo_data_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, int shmmod_session_del) + { +@@ -3911,12 +3920,14 @@ sr_modinfo_data_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, + } else { + /* stored oper data, update info in mod/ext SHM */ + if ((err_info = sr_shmext_oper_push_update(mod_info->conn, mod->shm_mod, mod->ly_mod->name, +- sid, 0, mod_data ? 1 : 0, SR_LOCK_WRITE))) { ++ sid, 0, !!mod_data, SR_LOCK_WRITE))) { + goto cleanup; + } + +- /* remember to discard the push oper data on session stop */ +- session->push_oper_data = 1; ++ /* cache the modified module in the session */ ++ if ((err_info = sr_modinfo_push_oper_mod_update_sess(session, mod->ly_mod->name, !!mod_data))) { ++ goto cleanup; ++ } + } + } + } +diff --git a/src/sysrepo.c b/src/sysrepo.c +index 614b5454..797de6aa 100644 +--- a/src/sysrepo.c ++++ b/src/sysrepo.c +@@ -713,6 +713,7 @@ _sr_session_stop(sr_session_ctx_t *session) + { + sr_error_info_t *err_info = NULL, *tmp_err; + sr_datastore_t ds; ++ uint32_t i; + + /* subscriptions need to be freed before, with a WRITE lock */ + assert(!session->subscription_count && !session->subscriptions); +@@ -721,7 +722,7 @@ _sr_session_stop(sr_session_ctx_t *session) + tmp_err = sr_session_notif_buf_stop(session); + sr_errinfo_merge(&err_info, tmp_err); + +- if (session->push_oper_data) { ++ if (session->oper_push_mod_count) { + /* free any stored operational data and the SHM ext push oper data entries */ + _sr_discard_oper_changes(session, NULL, 1, 0); + } +@@ -755,6 +756,13 @@ _sr_session_stop(sr_session_ctx_t *session) + sr_release_data(session->dt[ds].edit); + lyd_free_all(session->dt[ds].diff); + } ++ ++ /* free any push oper module names */ ++ for (i = 0; i < session->oper_push_mod_count; ++i) { ++ free(session->oper_push_mods[i].name); ++ } ++ free(session->oper_push_mods); ++ + sr_rwlock_destroy(&session->notif_buf.lock); + free(session); + +@@ -4708,6 +4716,10 @@ sr_get_oper_changes(sr_session_ctx_t *session, const char *module_name, sr_data_ + + *data = NULL; + ++ if (!session->oper_push_mod_count) { ++ return sr_api_ret(session, err_info); ++ } ++ + SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL); + + /* CONTEXT LOCK */ +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0010-modinfo-REFACTOR-formatting-and-missing-doxygen.patch b/patches/sysrepo/3.3.10/0010-modinfo-REFACTOR-formatting-and-missing-doxygen.patch new file mode 100644 index 00000000..f60f5b3a --- /dev/null +++ b/patches/sysrepo/3.3.10/0010-modinfo-REFACTOR-formatting-and-missing-doxygen.patch @@ -0,0 +1,39 @@ +From fef86228fcb8636d53faf43b36afd92b7161162b Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Tue, 14 Jan 2025 08:53:59 +0100 +Subject: [PATCH 10/22] modinfo REFACTOR formatting and missing doxygen +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Signed-off-by: Mattias Walström +--- + src/modinfo.c | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/modinfo.c b/src/modinfo.c +index d5c27672..ff6ffc40 100644 +--- a/src/modinfo.c ++++ b/src/modinfo.c +@@ -3819,8 +3819,15 @@ cleanup: + return err_info; + } + +-static +-sr_error_info_t * ++/** ++ * @brief Update push oper mod data in a session, add a new module if not yet present. ++ * ++ * @param[in] sess Session to update. ++ * @param[in] mod_name Module name. ++ * @param[in] has_data Flag to store. ++ * @return err_info, NULL on success. ++ */ ++static sr_error_info_t * + sr_modinfo_push_oper_mod_update_sess(sr_session_ctx_t *sess, const char *mod_name, int has_data) + { + sr_error_info_t *err_info = NULL; +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0011-shm_ext-BUGFIX-remap-READ-lock-is-enough-3496.patch b/patches/sysrepo/3.3.10/0011-shm_ext-BUGFIX-remap-READ-lock-is-enough-3496.patch new file mode 100644 index 00000000..86e01c51 --- /dev/null +++ b/patches/sysrepo/3.3.10/0011-shm_ext-BUGFIX-remap-READ-lock-is-enough-3496.patch @@ -0,0 +1,50 @@ +From a593e1c993ae4dd7aa2942aa046633c8aaeb5ee4 Mon Sep 17 00:00:00 2001 +From: Irfan +Date: Tue, 14 Jan 2025 08:59:44 +0100 +Subject: [PATCH 11/22] shm_ext BUGFIX remap READ lock is enough (#3496) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +`sr_shmext_rpc_sub_remove_dead()` calls `sr_shmext_rpc_sub_stop()` +which doesn't need a WRITE `ext_remap_lock`. + +sr_shmext_recover_sub_rpc() already only acquire a READ lock. + +Subscriptions can only be removed, which does not remap the SHM. + +Signed-off-by: Mattias Walström +--- + src/shm_ext.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/shm_ext.c b/src/shm_ext.c +index 78930469..917a049a 100644 +--- a/src/shm_ext.c ++++ b/src/shm_ext.c +@@ -1476,8 +1476,8 @@ sr_shmext_rpc_sub_remove_dead(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_co + sr_mod_rpc_sub_t *shm_sub; + char *path = NULL; + +- /* EXT WRITE LOCK */ +- if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_WRITE, 1, __func__))) { ++ /* EXT READ LOCK */ ++ if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_READ, 1, __func__))) { + sr_errinfo_free(&err_info); + return; + } +@@ -1502,8 +1502,8 @@ sr_shmext_rpc_sub_remove_dead(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_co + path = NULL; + } + +- /* EXT WRITE UNLOCK */ +- sr_shmext_conn_remap_unlock(conn, SR_LOCK_WRITE, 1, __func__); ++ /* EXT READ UNLOCK */ ++ sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__); + } + + sr_error_info_t * +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0012-modinfo-OPTIMIZE-avoid-long-ext-lock-holding.patch b/patches/sysrepo/3.3.10/0012-modinfo-OPTIMIZE-avoid-long-ext-lock-holding.patch new file mode 100644 index 00000000..c04f2132 --- /dev/null +++ b/patches/sysrepo/3.3.10/0012-modinfo-OPTIMIZE-avoid-long-ext-lock-holding.patch @@ -0,0 +1,162 @@ +From ce908d743bc9f49d5986c748dba71be194b7df89 Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Tue, 14 Jan 2025 09:57:49 +0100 +Subject: [PATCH 12/22] modinfo OPTIMIZE avoid long ext lock holding +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Fixes #3497 + +Signed-off-by: Mattias Walström +--- + src/modinfo.c | 77 +++++++++++++++++++++++++++++++-------------------- + 1 file changed, 47 insertions(+), 30 deletions(-) + +diff --git a/src/modinfo.c b/src/modinfo.c +index ff6ffc40..f969f363 100644 +--- a/src/modinfo.c ++++ b/src/modinfo.c +@@ -4,8 +4,8 @@ + * @brief routines for working with modinfo structure + * + * @copyright +- * Copyright (c) 2018 - 2023 Deutsche Telekom AG. +- * Copyright (c) 2018 - 2023 CESNET, z.s.p.o. ++ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. ++ * Copyright (c) 2018 - 2025 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. +@@ -1254,49 +1254,69 @@ sr_module_oper_data_load(struct sr_mod_info_mod_s *mod, sr_conn_ctx_t *conn, uin + struct lyd_node **mod_oper_data, struct lyd_node **data) + { + sr_error_info_t *err_info = NULL; +- sr_mod_oper_push_t *oper_push; ++ sr_mod_oper_push_t *oper_push_dup = NULL, *oper_push_ext; + struct lyd_node *mod_data = NULL, *next, *node; + const struct lys_module *ly_mod; + const char *xpath; + struct ly_set *set; +- uint32_t i, j, merge_opts; +- int last_sid = 0; ++ uint32_t i, j, merge_opts, oper_push_count = 0; ++ int last_sid = 0, dead_cid = 0; + + /* EXT READ LOCK */ + if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_READ, 1, __func__))) { +- return err_info; ++ goto cleanup; + } + +- oper_push = (sr_mod_oper_push_t *)(conn->ext_shm.addr + mod->shm_mod->oper_push_data); +- for (i = 0; i < mod->shm_mod->oper_push_data_count; ++i) { +- if (!sr_conn_is_alive(oper_push[i].cid)) { +- /* EXT READ UNLOCK */ +- sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__); +- +- /* recover oper push data of all dead connections */ +- if ((err_info = sr_shmmod_del_module_oper_data(conn, mod->ly_mod, &mod->state, mod->shm_mod, 1))) { +- goto cleanup; ++ if (mod->shm_mod->oper_push_data_count) { ++ /* make a local copy of the array, with only alive connections */ ++ oper_push_dup = malloc(mod->shm_mod->oper_push_data_count * sizeof *oper_push_dup); ++ if (!oper_push_dup) { ++ SR_ERRINFO_MEM(&err_info); ++ } else { ++ oper_push_ext = (sr_mod_oper_push_t *)(conn->ext_shm.addr + mod->shm_mod->oper_push_data); ++ for (i = 0; i < mod->shm_mod->oper_push_data_count; ++i) { ++ if (!sr_conn_is_alive(oper_push_ext[i].cid)) { ++ /* remember to remove the dead connections */ ++ dead_cid = 1; ++ } else { ++ oper_push_dup[oper_push_count] = oper_push_ext[i]; ++ ++oper_push_count; ++ } + } ++ } ++ } ++ ++ /* EXT READ UNLOCK */ ++ sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__); ++ ++ if (err_info) { ++ goto cleanup; ++ } + +- /* call the function again */ +- err_info = sr_module_oper_data_load(mod, conn, sid, mod_oper_data, data); ++ if (dead_cid) { ++ /* recover oper push data of all dead connections */ ++ if ((err_info = sr_shmmod_del_module_oper_data(conn, mod->ly_mod, &mod->state, mod->shm_mod, 1))) { + goto cleanup; +- } else if (!oper_push[i].has_data) { ++ } ++ } ++ ++ for (i = 0; i < oper_push_count; ++i) { ++ if (!oper_push_dup[i].has_data) { + /* no push oper data */ + continue; + } + + /* push data entries are ordered */ +- assert(!i || (oper_push[i].order > oper_push[i - 1].order)); ++ assert(!i || (oper_push_dup[i].order > oper_push_dup[i - 1].order)); + +- if (oper_push[i].sid == sid) { ++ if (oper_push_dup[i].sid == sid) { + last_sid = 1; + } + if (!last_sid || !mod_oper_data) { + /* load push oper data for the session */ +- if ((err_info = sr_module_file_data_append(mod->ly_mod, mod->ds_handle, SR_DS_OPERATIONAL, oper_push[i].cid, +- oper_push[i].sid, NULL, 0, &mod_data))) { +- goto cleanup_unlock; ++ if ((err_info = sr_module_file_data_append(mod->ly_mod, mod->ds_handle, SR_DS_OPERATIONAL, ++ oper_push_dup[i].cid, oper_push_dup[i].sid, NULL, 0, &mod_data))) { ++ goto cleanup; + } + } else { + use_mod_oper_data: +@@ -1324,12 +1344,12 @@ use_mod_oper_data: + + /* select the nodes to remove */ + if ((err_info = sr_lyd_find_xpath(*data, xpath, &set))) { +- goto cleanup_unlock; ++ goto cleanup; + } + + /* get rid of all redundant results that are descendants of another result */ + if ((err_info = sr_xpath_set_filter_subtrees(set))) { +- goto cleanup_unlock; ++ goto cleanup; + } + + /* free all the selected subtrees */ +@@ -1346,7 +1366,7 @@ use_mod_oper_data: + merge_opts = LYD_MERGE_WITH_FLAGS; + } + if ((err_info = sr_lyd_merge_module(data, mod_data, mod->ly_mod, sr_oper_data_merge_cb, NULL, merge_opts))) { +- goto cleanup_unlock; ++ goto cleanup; + } + mod_data = NULL; + +@@ -1362,11 +1382,8 @@ use_mod_oper_data: + goto use_mod_oper_data; + } + +-cleanup_unlock: +- /* EXT READ UNLOCK */ +- sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__); +- + cleanup: ++ free(oper_push_dup); + lyd_free_siblings(mod_data); + return err_info; + } +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0013-shm-sub-UPDATE-use-operation-ID-instead-of-request-I.patch b/patches/sysrepo/3.3.10/0013-shm-sub-UPDATE-use-operation-ID-instead-of-request-I.patch new file mode 100644 index 00000000..da68f425 --- /dev/null +++ b/patches/sysrepo/3.3.10/0013-shm-sub-UPDATE-use-operation-ID-instead-of-request-I.patch @@ -0,0 +1,1501 @@ +From c1e13d121ee8f865f36c22a7765aff56b1229c5a Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Tue, 14 Jan 2025 13:35:28 +0100 +Subject: [PATCH 13/22] shm sub UPDATE use operation ID instead of request ID +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +... which can be used to connect various callback +calls that are part of a single operation. + +Signed-off-by: Mattias Walström +--- + src/common.c | 8 +- + src/common.h | 7 +- + src/modinfo.c | 27 +++--- + src/modinfo.h | 1 + + src/shm_main.c | 1 + + src/shm_sub.c | 202 ++++++++++++++++++++++------------------- + src/shm_sub.h | 27 +++--- + src/shm_types.h | 4 +- + src/sysrepo.c | 72 +++++++-------- + src/sysrepo_types.h | 17 ++-- + tests/test_oper_pull.c | 3 +- + 11 files changed, 202 insertions(+), 167 deletions(-) + +diff --git a/src/common.c b/src/common.c +index 72f3fc93..af607557 100644 +--- a/src/common.c ++++ b/src/common.c +@@ -2890,7 +2890,7 @@ sr_conn_ext_data_update(sr_conn_ctx_t *conn) + struct lyd_node *yl_data = NULL, *new_ext_data = NULL; + + /* init mod info for cleanup */ +- SR_MODINFO_INIT(mi, conn, SR_DS_OPERATIONAL, SR_DS_RUNNING); ++ SR_MODINFO_INIT(mi, conn, SR_DS_OPERATIONAL, SR_DS_RUNNING, 0); + + /* manually get ietf-yang-schema-mount operational data but avoid recursive call of this function */ + ly_mod = ly_ctx_get_module_implemented(conn->ly_ctx, "ietf-yang-schema-mount"); +@@ -5609,6 +5609,7 @@ sr_generate_notif_send(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, const struct lyd_ + { + sr_error_info_t *err_info = NULL; + struct timespec notif_ts_mono, notif_ts_real; ++ uint32_t operation_id; + + /* NOTIF SUB READ LOCK */ + if ((err_info = sr_rwlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid, +@@ -5620,8 +5621,11 @@ sr_generate_notif_send(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, const struct lyd_ + sr_timeouttime_get(¬if_ts_mono, 0); + sr_realtime_get(¬if_ts_real); + ++ /* generate a new operation ID */ ++ operation_id = ATOMIC_INC_RELAXED(SR_CONN_MAIN_SHM(conn)->new_operation_id); ++ + /* send the notification (non-validated, must be valid) */ +- err_info = sr_shmsub_notif_notify(conn, notif, notif_ts_mono, notif_ts_real, NULL, NULL, 0, 0); ++ err_info = sr_shmsub_notif_notify(conn, notif, notif_ts_mono, notif_ts_real, NULL, NULL, operation_id, 0, 0); + + /* NOTIF SUB READ UNLOCK */ + sr_rwunlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid, __func__); +diff --git a/src/common.h b/src/common.h +index 9fc675d4..f6bc9128 100644 +--- a/src/common.h ++++ b/src/common.h +@@ -189,7 +189,12 @@ extern const sr_module_ds_t sr_module_ds_disabled_run; + #define SR_SHM_INITIALIZER {.fd = -1, .size = 0, .addr = NULL} + + /** initializer of mod_info structure */ +-#define SR_MODINFO_INIT(mi, c, d, d2) memset(&(mi), 0, sizeof (mi)); (mi).ds = (d); (mi).ds2 = (d2); (mi).conn = (c) ++#define SR_MODINFO_INIT(mi, c, d, d2, op_id) \ ++ memset(&(mi), 0, sizeof (mi)); \ ++ (mi).ds = (d); \ ++ (mi).ds2 = (d2); \ ++ (mi).conn = (c); \ ++ (mi).operation_id = (op_id) ? (op_id) : ATOMIC_INC_RELAXED(SR_CONN_MAIN_SHM(c)->new_operation_id) + + /** + * @brief Internal information about a module to be installed. +diff --git a/src/modinfo.c b/src/modinfo.c +index f969f363..d002bbdb 100644 +--- a/src/modinfo.c ++++ b/src/modinfo.c +@@ -1053,6 +1053,7 @@ cleanup: + * @param[in] req_xpath_count Count of @p request_xpaths. + * @param[in] orig_name Event originator name. + * @param[in] orig_data Event originator data. ++ * @param[in] operation_id Operation ID. + * @param[in] shm_subs Subscription array. + * @param[in] idx1 Index of the subscription array from where to read subscriptions with the same XPath. + * @param[in] parent Data parent required for the subscription, NULL if top-level. +@@ -1063,9 +1064,9 @@ cleanup: + */ + static sr_error_info_t * + sr_xpath_oper_data_get(struct sr_mod_info_mod_s *mod, const char *xpath, const char **request_xpaths, +- uint32_t req_xpath_count, const char *orig_name, const void *orig_data, sr_mod_oper_get_sub_t *shm_subs, +- uint32_t idx1, const struct lyd_node *parent, uint32_t timeout_ms, sr_conn_ctx_t *conn, +- struct lyd_node **oper_data) ++ uint32_t req_xpath_count, const char *orig_name, const void *orig_data, uint32_t operation_id, ++ sr_mod_oper_get_sub_t *shm_subs, uint32_t idx1, const struct lyd_node *parent, uint32_t timeout_ms, ++ sr_conn_ctx_t *conn, struct lyd_node **oper_data) + { + sr_error_info_t *err_info = NULL, *cb_err_info = NULL; + struct lyd_node *parent_dup = NULL, *last_parent; +@@ -1108,8 +1109,8 @@ sr_xpath_oper_data_get(struct sr_mod_info_mod_s *mod, const char *xpath, const c + request_xpath = (req_xpath_count == 1) ? request_xpaths[0] : NULL; + + /* get data from client */ +- if ((err_info = sr_shmsub_oper_get_notify(mod, xpath, request_xpath, parent_dup, orig_name, orig_data, shm_subs, +- idx1, timeout_ms, conn, oper_data, &cb_err_info))) { ++ if ((err_info = sr_shmsub_oper_get_notify(mod, xpath, request_xpath, parent_dup, orig_name, orig_data, operation_id, ++ shm_subs, idx1, timeout_ms, conn, oper_data, &cb_err_info))) { + sr_errinfo_merge(&err_info, cb_err_info); + goto cleanup; + } +@@ -1431,6 +1432,7 @@ cleanup: + * @param[in] oper_mode Current lock mode of @p mod for ::SR_DS_OPERATIONAL. + * @param[in] orig_name Event originator name. + * @param[in] orig_data Event originator data. ++ * @param[in] operation_id Operation ID. + * @param[in] conn Connection to use. + * @param[in] timeout_ms Operational callback timeout in milliseconds. + * @param[in] get_oper_opts Get oper data options. +@@ -1438,8 +1440,9 @@ cleanup: + * @return err_info, NULL on success. + */ + static sr_error_info_t * +-sr_module_oper_data_update(struct sr_mod_info_mod_s *mod, const char *orig_name, const void *orig_data, sr_conn_ctx_t *conn, +- uint32_t timeout_ms, sr_get_oper_flag_t get_oper_opts, struct lyd_node **data) ++sr_module_oper_data_update(struct sr_mod_info_mod_s *mod, const char *orig_name, const void *orig_data, ++ uint32_t operation_id, sr_conn_ctx_t *conn, uint32_t timeout_ms, sr_get_oper_flag_t get_oper_opts, ++ struct lyd_node **data) + { + sr_error_info_t *err_info = NULL; + sr_mod_oper_get_sub_t *shm_subs; +@@ -1554,7 +1557,7 @@ sr_module_oper_data_update(struct sr_mod_info_mod_s *mod, const char *orig_name, + for (j = 0; j < set->count; ++j) { + /* get oper data from the client */ + if ((err_info = sr_xpath_oper_data_get(mod, sub_xpath, request_xpaths, req_xpath_count, orig_name, +- orig_data, shm_subs, i, set->dnodes[j], timeout_ms, conn, &oper_data))) { ++ orig_data, operation_id, shm_subs, i, set->dnodes[j], timeout_ms, conn, &oper_data))) { + goto cleanup_opergetsub_ext_unlock; + } + +@@ -1574,7 +1577,7 @@ next_iter: + } else { + /* top-level data */ + if ((err_info = sr_xpath_oper_data_get(mod, sub_xpath, request_xpaths, req_xpath_count, orig_name, +- orig_data, shm_subs, i, NULL, timeout_ms, conn, &oper_data))) { ++ orig_data, operation_id, shm_subs, i, NULL, timeout_ms, conn, &oper_data))) { + goto cleanup_opergetsub_ext_unlock; + } + +@@ -2774,8 +2777,8 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m + } + + /* append any operational data provided by clients */ +- if ((err_info = sr_module_oper_data_update(mod, orig_name, orig_data, conn, timeout_ms, get_oper_opts, +- &mod_info->data))) { ++ if ((err_info = sr_module_oper_data_update(mod, orig_name, orig_data, mod_info->operation_id, conn, timeout_ms, ++ get_oper_opts, &mod_info->data))) { + return err_info; + } + } +@@ -3811,7 +3814,7 @@ sr_modinfo_generate_config_change_notif(struct sr_mod_info_s *mod_info, sr_sessi + + /* send the notification (non-validated, if everything works correctly it must be valid) */ + err_info = sr_shmsub_notif_notify(mod_info->conn, notif, notif_ts_mono, notif_ts_real, session->orig_name, +- session->orig_data, 0, 0); ++ session->orig_data, mod_info->operation_id, 0, 0); + + /* NOTIF SUB READ UNLOCK */ + sr_rwunlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, mod_info->conn->cid, __func__); +diff --git a/src/modinfo.h b/src/modinfo.h +index 1b82300e..b8108f8e 100644 +--- a/src/modinfo.h ++++ b/src/modinfo.h +@@ -50,6 +50,7 @@ struct sr_mod_info_s { + struct lyd_node *data; /**< Data tree. */ + int data_cached; /**< Whether the data are actually cached. */ + sr_conn_ctx_t *conn; /**< Associated connection. */ ++ uint32_t operation_id; /**< ID of the current operation for all the callbacks. */ + + struct sr_mod_info_mod_s { + sr_mod_t *shm_mod; /**< Module SHM structure. */ +diff --git a/src/shm_main.c b/src/shm_main.c +index 4b273b49..fdcb567b 100644 +--- a/src/shm_main.c ++++ b/src/shm_main.c +@@ -509,6 +509,7 @@ sr_shmmain_open(sr_shm_t *shm, int *created) + ATOMIC_STORE_RELAXED(main_shm->new_sr_sid, 1); + ATOMIC_STORE_RELAXED(main_shm->new_sub_id, 1); + ATOMIC_STORE_RELAXED(main_shm->new_evpipe_num, 1); ++ ATOMIC_STORE_RELAXED(main_shm->new_operation_id, 1); + strncpy(main_shm->repo_path, sr_get_repo_path(), sizeof main_shm->repo_path - 1); + + /* remove leftover event pipes */ +diff --git a/src/shm_sub.c b/src/shm_sub.c +index babeaa0c..fd6f105b 100644 +--- a/src/shm_sub.c ++++ b/src/shm_sub.c +@@ -4,8 +4,8 @@ + * @brief subscription SHM routines + * + * @copyright +- * Copyright (c) 2018 - 2023 Deutsche Telekom AG. +- * Copyright (c) 2018 - 2023 CESNET, z.s.p.o. ++ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. ++ * Copyright (c) 2018 - 2025 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. +@@ -451,6 +451,7 @@ event_handled: + * ::SR_SUB_EV_ERROR - an answer is expected and SHM will be further accessed so do not clear any events. + * @param[in] clear_ev_on_err Whether to clear the current event if error/timeout occurs or leave it be. + * @param[in] cid Connection ID. ++ * @param[in] operation_id Operation ID. + * @param[in] shm_data_sub Opened sub data SHM. + * @param[in] timeout_abs Absolute timeout for the event to be handled. + * @param[out] lock_lost Set if the WRITE lock was released, possible only if err_info is returned. +@@ -459,8 +460,8 @@ event_handled: + */ + static sr_error_info_t * + _sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t event, uint32_t request_id, sr_sub_event_t expected_ev, +- int clear_ev_on_err, sr_cid_t cid, sr_shm_t *shm_data_sub, struct timespec *timeout_abs, int *lock_lost, +- sr_error_info_t **cb_err_info) ++ int clear_ev_on_err, sr_cid_t cid, uint32_t operation_id, sr_shm_t *shm_data_sub, struct timespec *timeout_abs, ++ int *lock_lost, sr_error_info_t **cb_err_info) + { + sr_error_info_t *err_info = NULL; + sr_error_t err_code; +@@ -509,14 +510,14 @@ _sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t event, uint32_t + if (!(err_info = sr_sub_rwlock(&sub_shm->lock, &timeout_abs2, SR_LOCK_WRITE, cid, __func__, NULL, NULL, 1))) { + /* event timeout */ + sr_errinfo_new(cb_err_info, SR_ERR_TIME_OUT, "EV ORIGIN: SHM event \"%s\" ID %" PRIu32 " processing timed out.", +- sr_ev2str(event), request_id); ++ sr_ev2str(event), operation_id); + write_lock = 1; + } + } else { + /* other error - not ETIMEDOUT or shm event has changed incorrectly */ + if (event != last_event) { + SR_LOG_WRN("EV ORIGIN: SHM event \"%s\" ID %" PRIu32 " changed to \"%s\" unexpectedly", +- sr_ev2str(event), request_id, sr_ev2str(last_event)); ++ sr_ev2str(event), operation_id, sr_ev2str(last_event)); + + } + SR_ERRINFO_COND(&err_info, __func__, ret); +@@ -650,6 +651,7 @@ event_handled: + * ::SR_SUB_EV_ERROR - an answer is expected and SHM will be further accessed so do not clear any events. + * @param[in] clear_ev_on_err Whether to clear the current event if error/timeout occurs or leave it be. + * @param[in] cid Connection ID. ++ * @param[in] operation_id Operation ID. + * @param[in] shm_data_sub Opened sub data SHM. + * @param[in] timeout_ms Timeout in milliseconds. + * @param[out] lock_lost Set if the WRITE lock was released, possible only if err_info is returned. +@@ -658,7 +660,7 @@ event_handled: + */ + static sr_error_info_t * + sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t expected_ev, int clear_ev_on_err, sr_cid_t cid, +- sr_shm_t *shm_data_sub, uint32_t timeout_ms, int *lock_lost, sr_error_info_t **cb_err_info) ++ uint32_t operation_id, sr_shm_t *shm_data_sub, uint32_t timeout_ms, int *lock_lost, sr_error_info_t **cb_err_info) + { + sr_sub_event_t event; + uint32_t request_id; +@@ -671,8 +673,8 @@ sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t expected_ev, int + /* compute the timeout */ + sr_timeouttime_get(&timeout_abs, timeout_ms); + +- return _sr_shmsub_notify_wait_wr(sub_shm, event, request_id, expected_ev, clear_ev_on_err, cid, shm_data_sub, +- &timeout_abs, lock_lost, cb_err_info); ++ return _sr_shmsub_notify_wait_wr(sub_shm, event, request_id, expected_ev, clear_ev_on_err, cid, operation_id, ++ shm_data_sub, &timeout_abs, lock_lost, cb_err_info); + } + + /** +@@ -693,12 +695,13 @@ sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t expected_ev, int + * ::SR_SUB_EV_ERROR - an answer is expected and SHM will be further accessed so do not clear any events. + * @param[in] clear_ev_on_err Whether to clear the current event if error/timeout occurs or leave it be. + * @param[in] cid Connection ID. ++ * @param[in] operation_id Operation ID. + * @param[in] timeout_ms Timeout in milliseconds. + * @return err_info, NULL on success. + */ + static sr_error_info_t * + sr_shmsub_notify_many_wait_wr(struct sr_shmsub_many_info_s *notify_subs, uint32_t notify_size, uint32_t notify_count, +- sr_sub_event_t expected_ev, int clear_ev_on_err, sr_cid_t cid, uint32_t timeout_ms) ++ sr_sub_event_t expected_ev, int clear_ev_on_err, sr_cid_t cid, uint32_t operation_id, uint32_t timeout_ms) + { + sr_error_info_t *err_info = NULL, *tmp_err; + struct sr_shmsub_many_info_s *nsub; +@@ -756,7 +759,7 @@ sr_shmsub_notify_many_wait_wr(struct sr_shmsub_many_info_s *notify_subs, uint32_ + + /* wait for an event change */ + tmp_err = _sr_shmsub_notify_wait_wr(nsub->sub_shm, nsub->event, nsub->request_id, expected_ev, clear_ev_on_err, +- cid, &nsub->shm_data_sub, &timeout_abs, &lock_lost, &nsub->cb_err_info); ++ cid, operation_id, &nsub->shm_data_sub, &timeout_abs, &lock_lost, &nsub->cb_err_info); + if (tmp_err) { + if (lock_lost) { + /* WRITE lock lost */ +@@ -785,6 +788,7 @@ sr_shmsub_notify_many_wait_wr(struct sr_shmsub_many_info_s *notify_subs, uint32_ + * @param[in] orig_name Originator name. + * @param[in] orig_data Originator data. + * @param[in] subscriber_count Subscriber count. ++ * @param[in] operation_id Operation ID. + * @param[in] shm_data_sub Opened sub data SHM. + * @param[in] xpath Optional XPath written into sub data SHM. + * @param[in] data Optional data written into sub data SHM. +@@ -795,7 +799,8 @@ sr_shmsub_notify_many_wait_wr(struct sr_shmsub_many_info_s *notify_subs, uint32_ + static sr_error_info_t * + sr_shmsub_notify_write_event(sr_sub_shm_t *sub_shm, sr_cid_t orig_cid, uint32_t request_id, uint32_t priority, + sr_sub_event_t event, const char *orig_name, const void *orig_data, uint32_t subscriber_count, +- sr_shm_t *shm_data_sub, const char *xpath, const char *data, uint32_t data_len, const char *event_desc) ++ uint32_t operation_id, sr_shm_t *shm_data_sub, const char *xpath, const char *data, uint32_t data_len, ++ const char *event_desc) + { + sr_error_info_t *err_info = NULL; + char *shm_data_ptr = NULL; +@@ -817,6 +822,7 @@ sr_shmsub_notify_write_event(sr_sub_shm_t *sub_shm, sr_cid_t orig_cid, uint32_t + ATOMIC_STORE_RELAXED(sub_shm->event, event); + ATOMIC_STORE_RELAXED(sub_shm->priority, priority); + sub_shm->subscriber_count = subscriber_count; ++ sub_shm->operation_id = operation_id; + + /* remap if needed */ + if (xpath || data_len) { +@@ -848,7 +854,7 @@ sr_shmsub_notify_write_event(sr_sub_shm_t *sub_shm, sr_cid_t orig_cid, uint32_t + + if (event && event_desc) { + SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " for %" PRIu32 " subscribers published.", +- event_desc, sr_ev2str(event), request_id, priority, subscriber_count); ++ event_desc, sr_ev2str(event), operation_id, priority, subscriber_count); + } + return NULL; + } +@@ -1354,8 +1360,8 @@ sr_shmsub_change_notify_update(struct sr_mod_info_s *mod_info, const char *orig_ + mod->request_id = ++sub_shm->request_id; + } + if ((err_info = sr_shmsub_notify_write_event(sub_shm, cid, mod->request_id, cur_priority, +- SR_SUB_EV_UPDATE, orig_name, orig_data, subscriber_count, &shm_data_sub, NULL, diff_lyb, +- diff_lyb_len, mod->ly_mod->name))) { ++ SR_SUB_EV_UPDATE, orig_name, orig_data, subscriber_count, mod_info->operation_id, &shm_data_sub, ++ NULL, diff_lyb, diff_lyb_len, mod->ly_mod->name))) { + goto cleanup_wrunlock; + } + +@@ -1370,8 +1376,8 @@ sr_shmsub_change_notify_update(struct sr_mod_info_s *mod_info, const char *orig_ + } + + /* wait until the event is processed */ +- if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_ERROR, 0, cid, &shm_data_sub, timeout_ms, +- &lock_lost, cb_err_info))) { ++ if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_ERROR, 0, cid, mod_info->operation_id, ++ &shm_data_sub, timeout_ms, &lock_lost, cb_err_info))) { + if (lock_lost) { + goto cleanup; + } else { +@@ -1382,11 +1388,12 @@ sr_shmsub_change_notify_update(struct sr_mod_info_s *mod_info, const char *orig_ + if (*cb_err_info) { + /* failed callback or timeout */ + SR_LOG_WRN("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " failed (%s).", mod->ly_mod->name, +- sr_ev2str(SR_SUB_EV_UPDATE), mod->request_id, cur_priority, sr_strerror((*cb_err_info)->err[0].err_code)); ++ sr_ev2str(SR_SUB_EV_UPDATE), mod_info->operation_id, cur_priority, ++ sr_strerror((*cb_err_info)->err[0].err_code)); + goto cleanup_wrunlock; + } else { + SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", mod->ly_mod->name, +- sr_ev2str(SR_SUB_EV_UPDATE), mod->request_id, cur_priority); ++ sr_ev2str(SR_SUB_EV_UPDATE), mod_info->operation_id, cur_priority); + } + + assert(sub_shm->event == SR_SUB_EV_SUCCESS); +@@ -1482,7 +1489,7 @@ sr_shmsub_change_notify_clear(struct sr_mod_info_s *mod_info) + + /* clear it */ + if ((err_info = sr_shmsub_notify_write_event(sub_shm, 0, mod->request_id, sub_shm->priority, 0, NULL, NULL, +- 0, NULL, NULL, NULL, 0, NULL))) { ++ 0, mod_info->operation_id, NULL, NULL, NULL, 0, NULL))) { + goto cleanup_wrunlock; + } + +@@ -1659,8 +1666,8 @@ sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, const char *orig_ + nsub->mod->request_id = ++nsub->sub_shm->request_id; + } + if ((err_info = sr_shmsub_notify_write_event((sr_sub_shm_t *)nsub->shm_sub.addr, cid, nsub->mod->request_id, +- nsub->cur_priority, SR_SUB_EV_CHANGE, orig_name, orig_data, subscriber_count, &nsub->shm_data_sub, +- NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { ++ nsub->cur_priority, SR_SUB_EV_CHANGE, orig_name, orig_data, subscriber_count, mod_info->operation_id, ++ &nsub->shm_data_sub, NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { + goto cleanup; + } + +@@ -1696,7 +1703,7 @@ sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, const char *orig_ + + /* wait until the events are processed */ + if ((err_info = sr_shmsub_notify_many_wait_wr((struct sr_shmsub_many_info_s *)notify_subs, sizeof *notify_subs, +- notify_count, SR_SUB_EV_SUCCESS, 0, cid, timeout_ms))) { ++ notify_count, SR_SUB_EV_SUCCESS, 0, cid, mod_info->operation_id, timeout_ms))) { + goto cleanup; + } + +@@ -1715,7 +1722,7 @@ sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, const char *orig_ + if (nsub->cb_err_info) { + /* failed callback or timeout */ + SR_LOG_WRN("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " failed (%s).", +- nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_CHANGE), nsub->mod->request_id, nsub->cur_priority, ++ nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_CHANGE), mod_info->operation_id, nsub->cur_priority, + sr_strerror(nsub->cb_err_info->err[0].err_code)); + + /* merge the error */ +@@ -1723,7 +1730,7 @@ sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, const char *orig_ + nsub->cb_err_info = NULL; + } else { + SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", +- nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_CHANGE), nsub->mod->request_id, nsub->cur_priority); ++ nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_CHANGE), mod_info->operation_id, nsub->cur_priority); + } + nsub->pending_event = 0; + } +@@ -1852,8 +1859,8 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * + nsub->mod->request_id = ++nsub->sub_shm->request_id; + } + if ((err_info = sr_shmsub_notify_write_event((sr_sub_shm_t *)nsub->shm_sub.addr, cid, nsub->mod->request_id, +- nsub->cur_priority, SR_SUB_EV_DONE, orig_name, orig_data, subscriber_count, &nsub->shm_data_sub, +- NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { ++ nsub->cur_priority, SR_SUB_EV_DONE, orig_name, orig_data, subscriber_count, mod_info->operation_id, ++ &nsub->shm_data_sub, NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { + goto cleanup; + } + +@@ -1888,7 +1895,7 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * + + /* wait until the events are processed */ + if ((err_info = sr_shmsub_notify_many_wait_wr((struct sr_shmsub_many_info_s *)notify_subs, sizeof *notify_subs, +- notify_count, SR_SUB_EV_FINISHED, 1, cid, timeout_ms))) { ++ notify_count, SR_SUB_EV_FINISHED, 1, cid, mod_info->operation_id, timeout_ms))) { + goto cleanup; + } + +@@ -1908,7 +1915,7 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * + sr_errinfo_free(&nsub->cb_err_info); + + SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", +- nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_DONE), nsub->mod->request_id, nsub->cur_priority); ++ nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_DONE), mod_info->operation_id, nsub->cur_priority); + + nsub->pending_event = 0; + } +@@ -2017,8 +2024,8 @@ sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_info, const char + + /* clear the error */ + assert(nsub->sub_shm->request_id == nsub->mod->request_id); +- if ((err_info = sr_shmsub_notify_write_event(sub_shm, 0, nsub->mod->request_id, +- nsub->cur_priority, 0, NULL, NULL, 0, &nsub->shm_data_sub, NULL, NULL, 0, NULL))) { ++ if ((err_info = sr_shmsub_notify_write_event(sub_shm, 0, nsub->mod->request_id, nsub->cur_priority, 0, NULL, ++ NULL, 0, mod_info->operation_id, &nsub->shm_data_sub, NULL, NULL, 0, NULL))) { + goto cleanup; + } + } +@@ -2094,8 +2101,8 @@ sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_info, const char + + /* write the event */ + if ((err_info = sr_shmsub_notify_write_event(sub_shm, cid, nsub->mod->request_id, nsub->cur_priority, +- SR_SUB_EV_ABORT, orig_name, orig_data, subscriber_count, &nsub->shm_data_sub, NULL, diff_lyb, +- diff_lyb_len, nsub->mod->ly_mod->name))) { ++ SR_SUB_EV_ABORT, orig_name, orig_data, subscriber_count, mod_info->operation_id, &nsub->shm_data_sub, ++ NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { + goto cleanup; + } + +@@ -2130,7 +2137,7 @@ sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_info, const char + + /* wait until the events are processed */ + if ((err_info = sr_shmsub_notify_many_wait_wr((struct sr_shmsub_many_info_s *)notify_subs, sizeof *notify_subs, +- notify_count, SR_SUB_EV_FINISHED, 1, cid, timeout_ms))) { ++ notify_count, SR_SUB_EV_FINISHED, 1, cid, mod_info->operation_id, timeout_ms))) { + goto cleanup; + } + +@@ -2150,7 +2157,7 @@ sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_info, const char + sr_errinfo_free(&nsub->cb_err_info); + + SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", +- nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_ABORT), nsub->mod->request_id, nsub->cur_priority); ++ nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_ABORT), mod_info->operation_id, nsub->cur_priority); + + nsub->pending_event = 0; + } +@@ -2183,8 +2190,9 @@ cleanup: + + sr_error_info_t * + sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, const char *request_xpath, +- const struct lyd_node *parent, const char *orig_name, const void *orig_data, sr_mod_oper_get_sub_t *oper_get_subs, +- uint32_t idx1, uint32_t timeout_ms, sr_conn_ctx_t *conn, struct lyd_node **data, sr_error_info_t **cb_err_info) ++ const struct lyd_node *parent, const char *orig_name, const void *orig_data, uint32_t operation_id, ++ sr_mod_oper_get_sub_t *oper_get_subs, uint32_t idx1, uint32_t timeout_ms, sr_conn_ctx_t *conn, ++ struct lyd_node **data, sr_error_info_t **cb_err_info) + { + sr_error_info_t *err_info = NULL; + uint32_t i, notify_count = 0, parent_lyb_len, request_id; +@@ -2257,11 +2265,11 @@ sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, cons + /* write the request for state data */ + request_id = ATOMIC_LOAD_RELAXED(nsub->sub_shm->request_id) + 1; + if ((err_info = sr_shmsub_notify_write_event(nsub->sub_shm, cid, request_id, 0, SR_SUB_EV_OPER, orig_name, +- orig_data, 1, &nsub->shm_data_sub, request_xpath, parent_lyb, parent_lyb_len, NULL))) { ++ orig_data, 1, operation_id, &nsub->shm_data_sub, request_xpath, parent_lyb, parent_lyb_len, NULL))) { + goto cleanup; + } + SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" index %" PRIu32 " ID %" PRIu32 " published.", xpath, +- sr_ev2str(SR_SUB_EV_OPER), i, request_id); ++ sr_ev2str(SR_SUB_EV_OPER), i, operation_id); + + /* notify using event pipe */ + if ((err_info = sr_shmsub_notify_evpipe(nsub->xpath_sub->evpipe_num))) { +@@ -2273,7 +2281,7 @@ sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, cons + + /* wait until the events are processed */ + if ((err_info = sr_shmsub_notify_many_wait_wr((struct sr_shmsub_many_info_s *)notify_subs, sizeof *notify_subs, +- notify_count, SR_SUB_EV_ERROR, 1, cid, timeout_ms))) { ++ notify_count, SR_SUB_EV_ERROR, 1, cid, operation_id, timeout_ms))) { + goto cleanup; + } + +@@ -2286,7 +2294,7 @@ sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, cons + if (nsub->cb_err_info) { + /* failed callback */ + SR_LOG_WRN("EV ORIGIN: \"%s\" \"%s\" index %" PRIu32 " ID %" PRIu32 " failed (%s).", xpath, +- sr_ev2str(SR_SUB_EV_OPER), i, nsub->request_id, sr_strerror(nsub->cb_err_info->err[0].err_code)); ++ sr_ev2str(SR_SUB_EV_OPER), i, operation_id, sr_strerror(nsub->cb_err_info->err[0].err_code)); + + /* merge the error and continue */ + sr_errinfo_merge(cb_err_info, nsub->cb_err_info); +@@ -2295,7 +2303,7 @@ sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, cons + continue; + } else { + SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" index %" PRIu32 " ID %" PRIu32 " succeeded.", xpath, +- sr_ev2str(SR_SUB_EV_OPER), i, nsub->request_id); ++ sr_ev2str(SR_SUB_EV_OPER), i, operation_id); + } + + assert(ATOMIC_LOAD_RELAXED(nsub->sub_shm->event) == SR_SUB_EV_SUCCESS); +@@ -2348,11 +2356,13 @@ cleanup: + * + * @param[in] conn Connection to use. + * @param[in] input Input tree pointing to the operation node. ++ * @param[in] operation_id Operation ID. + * @param[out] output Output tree pointing to the operation node. + * @return err_info, NULL on success. + */ + static sr_error_info_t * +-sr_shmsub_rpc_internal_call_callback(sr_conn_ctx_t *conn, const struct lyd_node *input, struct lyd_node **output) ++sr_shmsub_rpc_internal_call_callback(sr_conn_ctx_t *conn, const struct lyd_node *input, uint32_t operation_id, ++ struct lyd_node **output) + { + sr_error_info_t *err_info = NULL, *cb_err_info = NULL; + struct sr_mod_info_s mod_info; +@@ -2365,7 +2375,7 @@ sr_shmsub_rpc_internal_call_callback(sr_conn_ctx_t *conn, const struct lyd_node + + assert(input->schema->nodetype & (LYS_RPC | LYS_ACTION)); + +- SR_MODINFO_INIT(mod_info, conn, SR_DS_FACTORY_DEFAULT, SR_DS_FACTORY_DEFAULT); ++ SR_MODINFO_INIT(mod_info, conn, SR_DS_FACTORY_DEFAULT, SR_DS_FACTORY_DEFAULT, operation_id); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(conn, SR_LOCK_READ, 0, __func__))) { +@@ -2689,8 +2699,8 @@ cleanup: + + sr_error_info_t * + sr_shmsub_rpc_notify(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, const char *path, +- const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t timeout_ms, +- uint32_t *request_id, struct lyd_node **output, sr_error_info_t **cb_err_info) ++ const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t operation_id, ++ uint32_t timeout_ms, uint32_t *request_id, struct lyd_node **output, sr_error_info_t **cb_err_info) + { + sr_error_info_t *err_info = NULL; + char *input_lyb = NULL; +@@ -2725,7 +2735,7 @@ first_sub: + assert(subscriber_count == 1); + + /* internal RPC subscription */ +- if ((err_info = sr_shmsub_rpc_internal_call_callback(conn, input, output))) { ++ if ((err_info = sr_shmsub_rpc_internal_call_callback(conn, input, operation_id, output))) { + goto cleanup; + } + free(evpipes); +@@ -2763,7 +2773,7 @@ first_sub: + assert(subscriber_count == 1); + + /* internal RPC subscription */ +- if ((err_info = sr_shmsub_rpc_internal_call_callback(conn, input, output))) { ++ if ((err_info = sr_shmsub_rpc_internal_call_callback(conn, input, operation_id, output))) { + goto cleanup; + } + goto next_sub; +@@ -2774,7 +2784,7 @@ first_sub: + *request_id = ++sub_shm->request_id; + } + if ((err_info = sr_shmsub_notify_write_event(sub_shm, conn->cid, *request_id, cur_priority, SR_SUB_EV_RPC, +- orig_name, orig_data, subscriber_count, &shm_data_sub, NULL, input_lyb, input_lyb_len, path))) { ++ orig_name, orig_data, subscriber_count, operation_id, &shm_data_sub, NULL, input_lyb, input_lyb_len, path))) { + goto cleanup_wrunlock; + } + +@@ -2786,8 +2796,8 @@ first_sub: + } + + /* wait until the event is processed */ +- if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_ERROR, 0, conn->cid, &shm_data_sub, timeout_ms, +- &lock_lost, cb_err_info))) { ++ if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_ERROR, 0, conn->cid, operation_id, &shm_data_sub, ++ timeout_ms, &lock_lost, cb_err_info))) { + if (lock_lost) { + goto cleanup; + } else { +@@ -2798,11 +2808,11 @@ first_sub: + if (*cb_err_info) { + /* failed callback or timeout */ + SR_LOG_WRN("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " failed (%s).", path, +- sr_ev2str(SR_SUB_EV_RPC), *request_id, cur_priority, sr_strerror((*cb_err_info)->err[0].err_code)); ++ sr_ev2str(SR_SUB_EV_RPC), operation_id, cur_priority, sr_strerror((*cb_err_info)->err[0].err_code)); + goto cleanup_wrunlock; + } else { + SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", path, +- sr_ev2str(SR_SUB_EV_RPC), *request_id, cur_priority); ++ sr_ev2str(SR_SUB_EV_RPC), operation_id, cur_priority); + } + + assert(sub_shm->event == SR_SUB_EV_SUCCESS); +@@ -2850,7 +2860,7 @@ cleanup: + + sr_error_info_t * + sr_shmsub_rpc_notify_abort(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, const char *path, +- const struct lyd_node *input, const char *orig_name, const void *orig_data, ++ const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t operation_id, + uint32_t timeout_ms, uint32_t request_id) + { + sr_error_info_t *err_info = NULL, *cb_err_info = NULL; +@@ -2884,7 +2894,7 @@ clear_shm: + /* clear the SHM */ + assert(sub_shm->event == SR_SUB_EV_ERROR); + if ((err_info = sr_shmsub_notify_write_event(sub_shm, 0, request_id, cur_priority, 0, NULL, NULL, 0, +- &shm_data_sub, NULL, NULL, 0, NULL))) { ++ operation_id, &shm_data_sub, NULL, NULL, 0, NULL))) { + goto cleanup_wrunlock; + } + +@@ -2928,7 +2938,7 @@ clear_shm: + + /* write "abort" event with the same input */ + if ((err_info = sr_shmsub_notify_write_event(sub_shm, conn->cid, request_id, cur_priority, SR_SUB_EV_ABORT, +- orig_name, orig_data, subscriber_count, &shm_data_sub, NULL, input_lyb, input_lyb_len, path))) { ++ orig_name, orig_data, operation_id, subscriber_count, &shm_data_sub, NULL, input_lyb, input_lyb_len, path))) { + goto cleanup_wrunlock; + } + +@@ -2940,8 +2950,8 @@ clear_shm: + } + + /* wait until the event is processed */ +- if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_FINISHED, 1, conn->cid, &shm_data_sub, timeout_ms, +- &lock_lost, &cb_err_info))) { ++ if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_FINISHED, 1, conn->cid, operation_id, &shm_data_sub, ++ timeout_ms, &lock_lost, &cb_err_info))) { + if (lock_lost) { + goto cleanup; + } else { +@@ -2980,7 +2990,8 @@ cleanup: + + sr_error_info_t * + sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_node *notif, struct timespec notif_ts_mono, +- struct timespec notif_ts_real, const char *orig_name, const void *orig_data, uint32_t timeout_ms, int wait) ++ struct timespec notif_ts_real, const char *orig_name, const void *orig_data, uint32_t operation_id, ++ uint32_t timeout_ms, int wait) + { + sr_error_info_t *err_info = NULL, *cb_err_info = NULL; + const struct lys_module *ly_mod; +@@ -3062,7 +3073,7 @@ sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_node *notif, struct + /* write the notification, use first subscriber CID if not waiting - depends on the subscriber, not originator */ + request_id = sub_shm->request_id + 1; + if ((err_info = sr_shmsub_notify_write_event(sub_shm, wait ? conn->cid : sub_cid, request_id, 0, SR_SUB_EV_NOTIF, +- orig_name, orig_data, notif_sub_count, &shm_data_sub, NULL, data, data_len, ly_mod->name))) { ++ orig_name, orig_data, notif_sub_count, operation_id, &shm_data_sub, NULL, data, data_len, ly_mod->name))) { + goto cleanup_ext_sub_unlock; + } + +@@ -3088,8 +3099,8 @@ sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_node *notif, struct + + if (wait) { + /* wait until the event is processed */ +- if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_NONE, 1, conn->cid, &shm_data_sub, timeout_ms, +- &lock_lost, &cb_err_info))) { ++ if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_NONE, 1, conn->cid, operation_id, &shm_data_sub, ++ timeout_ms, &lock_lost, &cb_err_info))) { + if (lock_lost) { + goto cleanup; + } else { +@@ -3236,8 +3247,8 @@ sr_shmsub_listen_write_event(sr_sub_shm_t *sub_shm, uint32_t valid_subscr_count, + } + + SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " %s (remaining %" PRIu32 " subscribers).", +- event_desc, sr_ev2str(event), (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->request_id), +- (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->priority), result_str, sub_shm->subscriber_count); ++ event_desc, sr_ev2str(event), sub_shm->operation_id, (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->priority), ++ result_str, sub_shm->subscriber_count); + return NULL; + } + +@@ -3337,6 +3348,7 @@ struct info_sub_s { + sr_sub_event_t event; + uint32_t request_id; + uint32_t priority; ++ uint32_t operation_id; + }; + + /** +@@ -3379,7 +3391,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc + sr_rwunlock(&sub_shm->lock, SR_SUBSHM_LOCK_TIMEOUT, mode, ev_sess->conn->cid, __func__); + + SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing %s (after timeout or earlier error).", +- module_name, sr_ev2str(sub_info->event), sub_info->request_id, sub_info->priority, err_code ? "fail" : "success"); ++ module_name, sr_ev2str(sub_info->event), sub_info->operation_id, sub_info->priority, err_code ? "fail" : "success"); + + /* self-generate abort event in case the change was applied successfully */ + if ((sub_info->event == SR_SUB_EV_CHANGE) && (err_code == SR_ERR_OK) && filter_valid && +@@ -3394,7 +3406,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc + ev_sess->dt[ev_sess->ds].diff = abort_diff; + + SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing (self-generated).", +- module_name, sr_ev2str(SR_SUB_EV_ABORT), sub_info->request_id, sub_info->priority); ++ module_name, sr_ev2str(SR_SUB_EV_ABORT), sub_info->operation_id, sub_info->priority); + + /* call callback */ + sub->cb(ev_sess, sub->sub_id, module_name, sub->xpath, sr_ev2api(SR_SUB_EV_ABORT), sub_info->request_id, +@@ -3465,6 +3477,7 @@ sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_sub + sub_info.event = ATOMIC_LOAD_RELAXED(sub_shm->event); + sub_info.request_id = ATOMIC_LOAD_RELAXED(sub_shm->request_id); + sub_info.priority = ATOMIC_LOAD_RELAXED(sub_shm->priority); ++ sub_info.operation_id = sub_shm->operation_id; + + /* parse originator name and data (while creating the event session) */ + if ((err_info = _sr_session_start(conn, change_subs->ds, sub_info.event, &shm_data_ptr, &ev_sess))) { +@@ -3483,7 +3496,7 @@ sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_sub + + /* process event */ + SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing (remaining %" PRIu32 " subscribers).", +- change_subs->module_name, sr_ev2str(sub_info.event), sub_info.request_id, sub_info.priority, ++ change_subs->module_name, sr_ev2str(sub_info.event), sub_info.operation_id, sub_info.priority, + sub_shm->subscriber_count); + + /* process individual subscriptions (starting at the last found subscription, it was valid) */ +@@ -3506,7 +3519,7 @@ process_event: + filter_valid = sr_shmsub_change_filter_is_valid(change_sub->xpath, diff); + if (filter_valid) { + ret = change_sub->cb(ev_sess, change_sub->sub_id, change_subs->module_name, change_sub->xpath, +- sr_ev2api(sub_info.event), sub_info.request_id, change_sub->private_data); ++ sr_ev2api(sub_info.event), sub_info.operation_id, change_sub->private_data); + } else if (!(change_sub->opts & SR_SUBSCR_FILTER_ORIG)) { + /* filtered out (not by originator) */ + ATOMIC_INC_RELAXED(change_sub->filtered_out); +@@ -3528,7 +3541,7 @@ process_event: + if (ret == SR_ERR_CALLBACK_SHELVE) { + /* this subscription did not process the event yet, skip it */ + SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing shelved.", +- change_subs->module_name, sr_ev2str(sub_info.event), sub_info.request_id, sub_info.priority); ++ change_subs->module_name, sr_ev2str(sub_info.event), sub_info.operation_id, sub_info.priority); + continue; + } else if (ret) { + /* whole event failed */ +@@ -3622,6 +3635,7 @@ cleanup: + * @param[in] mode SHM lock mode. + * @param[in] cid Connection ID. + * @param[in] exp_req_id Expected event request ID in the SHM. ++ * @param[in] operation_id Operation ID. + * @param[in] err_code Error code of the callback. + * @param[out] err_info Optional error info on error. + * @return 0 if SHM content is as expected. +@@ -3630,7 +3644,7 @@ cleanup: + */ + static int + sr_shmsub_oper_get_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, sr_cid_t cid, uint32_t exp_req_id, +- sr_error_t err_code, sr_error_info_t **err_info) ++ uint32_t operation_id, sr_error_t err_code, sr_error_info_t **err_info) + { + assert(!*err_info); + +@@ -3645,7 +3659,7 @@ sr_shmsub_oper_get_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, sr_ + sr_rwunlock(&sub_shm->lock, SR_SUBSHM_LOCK_TIMEOUT, mode, cid, __func__); + + SR_LOG_INF("EV LISTEN: \"%s\" ID %" PRIu32 " processing %s (after timeout).", sr_ev2str(SR_SUB_EV_OPER), +- exp_req_id, err_code ? "fail" : "success"); ++ operation_id, err_code ? "fail" : "success"); + + /* we have completely finished processing (with no error) */ + return 1; +@@ -3659,7 +3673,7 @@ sr_error_info_t * + sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_get_subs, sr_conn_ctx_t *conn) + { + sr_error_info_t *err_info = NULL; +- uint32_t i, data_len = 0, request_id; ++ uint32_t i, data_len = 0, request_id, operation_id; + char *data = NULL, *request_xpath = NULL, *shm_data_ptr; + const char *origin; + sr_error_t err_code = SR_ERR_OK; +@@ -3698,6 +3712,7 @@ sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_ge + continue; + } + request_id = ATOMIC_LOAD_RELAXED(sub_shm->request_id); ++ operation_id = sub_shm->operation_id; + + /* open sub data SHM */ + if ((err_info = sr_shmsub_data_open_remap(oper_get_subs->module_name, "oper", sr_str_hash(oper_get_sub->path, +@@ -3732,12 +3747,12 @@ sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_ge + + /* process event */ + SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " processing.", oper_get_sub->path, sr_ev2str(SR_SUB_EV_OPER), +- request_id); ++ operation_id); + + /* call callback */ + orig_parent = parent; + err_code = oper_get_sub->cb(ev_sess, oper_get_sub->sub_id, oper_get_subs->module_name, oper_get_sub->path, +- request_xpath[0] ? request_xpath : NULL, request_id, &parent, oper_get_sub->private_data); ++ request_xpath[0] ? request_xpath : NULL, operation_id, &parent, oper_get_sub->private_data); + + /* go again to the top-level root for printing */ + if (parent) { +@@ -3758,7 +3773,7 @@ sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_ge + if (err_code == SR_ERR_CALLBACK_SHELVE) { + /* this subscription did not process the event yet, skip it */ + SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " processing shelved.", oper_get_sub->path, +- sr_ev2str(SR_SUB_EV_OPER), request_id); ++ sr_ev2str(SR_SUB_EV_OPER), operation_id); + goto next_iter; + } + +@@ -3779,7 +3794,8 @@ sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_ge + } + + /* SUB WRITE LOCK */ +- if (sr_shmsub_oper_get_listen_relock(sub_shm, SR_LOCK_WRITE, conn->cid, request_id, err_code, &err_info)) { ++ if (sr_shmsub_oper_get_listen_relock(sub_shm, SR_LOCK_WRITE, conn->cid, request_id, operation_id, err_code, ++ &err_info)) { + /* not necessarily an error */ + goto error; + } +@@ -3806,7 +3822,6 @@ next_iter: + sr_shm_clear(&shm_data_sub); + } + +- /* success */ + return NULL; + + error_wrunlock: +@@ -3944,7 +3959,7 @@ sr_shmsub_oper_poll_listen_process_module_events(struct modsub_operpoll_s *oper_ + SR_CHECK_INT_GOTO(!ly_mod, err_info, cleanup); + + /* init mod info */ +- SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL); ++ SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL, 0); + if ((err_info = sr_modinfo_add(ly_mod, NULL, 0, 0, &mod_info))) { + goto cleanup; + } +@@ -4174,14 +4189,14 @@ cleanup: + * @param[in] ev_sess Temporary event callback session. + * @param[in] input_op Input tree pointing to the operation node. + * @param[in] event Subscription event. +- * @param[in] request_id Request ID. ++ * @param[in] operation_id Operation ID. + * @param[out] output_op Output tree pointing to the operation node. + * @param[out] err_code Returned error code if the callback failed. + * @return err_info, NULL on success. + */ + static sr_error_info_t * + sr_shmsub_rpc_listen_call_callback(struct opsub_rpcsub_s *rpc_sub, sr_session_ctx_t *ev_sess, const struct lyd_node *input_op, +- sr_sub_event_t event, uint32_t request_id, struct lyd_node **output_op, sr_error_t *err_code) ++ sr_sub_event_t event, uint32_t operation_id, struct lyd_node **output_op, sr_error_t *err_code) + { + sr_error_info_t *err_info = NULL; + const struct lyd_node *elem; +@@ -4203,7 +4218,7 @@ sr_shmsub_rpc_listen_call_callback(struct opsub_rpcsub_s *rpc_sub, sr_session_ct + } + + /* callback */ +- *err_code = rpc_sub->tree_cb(ev_sess, rpc_sub->sub_id, rpc_sub->xpath, input_op, sr_ev2api(event), request_id, ++ *err_code = rpc_sub->tree_cb(ev_sess, rpc_sub->sub_id, rpc_sub->xpath, input_op, sr_ev2api(event), operation_id, + *output_op, rpc_sub->private_data); + if (*err_code) { + goto cleanup; +@@ -4240,7 +4255,7 @@ sr_shmsub_rpc_listen_call_callback(struct opsub_rpcsub_s *rpc_sub, sr_session_ct + output_vals = NULL; + output_val_count = 0; + *err_code = rpc_sub->cb(ev_sess, rpc_sub->sub_id, op_xpath, input_vals, input_val_count, sr_ev2api(event), +- request_id, &output_vals, &output_val_count, rpc_sub->private_data); ++ operation_id, &output_vals, &output_val_count, rpc_sub->private_data); + if (*err_code) { + goto cleanup; + } +@@ -4376,7 +4391,7 @@ sr_shmsub_rpc_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struct i + sr_rwunlock(&sub_shm->lock, SR_SUBSHM_LOCK_TIMEOUT, mode, ev_sess->conn->cid, __func__); + + SR_LOG_INF("EV LISTEN: \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing %s (after timeout or earlier error).", +- sr_ev2str(sub_info->event), sub_info->request_id, sub_info->priority, err_code ? "Failed" : "Successful"); ++ sr_ev2str(sub_info->event), sub_info->operation_id, sub_info->priority, err_code ? "Failed" : "Successful"); + + /* self-generate abort event in case the RPC was applied successfully */ + if (err_code == SR_ERR_OK) { +@@ -4384,11 +4399,11 @@ sr_shmsub_rpc_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struct i + ev_sess->ev = SR_SUB_EV_ABORT; + + SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing (self-generated).", +- path, sr_ev2str(SR_SUB_EV_ABORT), sub_info->request_id, sub_info->priority); ++ path, sr_ev2str(SR_SUB_EV_ABORT), sub_info->operation_id, sub_info->priority); + + /* call callback */ + *err_info = sr_shmsub_rpc_listen_call_callback(sub, ev_sess, input_op, SR_SUB_EV_ABORT, +- sub_info->request_id, &output, &err_code); ++ sub_info->operation_id, &output, &err_code); + + /* we do not care about output of error code */ + lyd_free_all(output); +@@ -4477,6 +4492,7 @@ sr_shmsub_rpc_listen_process_rpc_events(struct opsub_rpc_s *rpc_subs, sr_conn_ct + sub_info.event = ATOMIC_LOAD_RELAXED(sub_shm->event); + sub_info.request_id = ATOMIC_LOAD_RELAXED(sub_shm->request_id); + sub_info.priority = ATOMIC_LOAD_RELAXED(sub_shm->priority); ++ sub_info.operation_id = sub_shm->operation_id; + + /* go to the operation, not the root */ + input_op = input; +@@ -4486,7 +4502,7 @@ sr_shmsub_rpc_listen_process_rpc_events(struct opsub_rpc_s *rpc_subs, sr_conn_ct + + /* process event */ + SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing (remaining %" PRIu32 " subscribers).", +- rpc_subs->path, sr_ev2str(sub_info.event), sub_info.request_id, sub_info.priority, ++ rpc_subs->path, sr_ev2str(sub_info.event), sub_info.operation_id, sub_info.priority, + sub_shm->subscriber_count); + + /* process individual subscriptions (starting at the last found subscription, it was valid) */ +@@ -4510,7 +4526,7 @@ process_event: + + /* call callback */ + if ((err_info = sr_shmsub_rpc_listen_call_callback(rpc_sub, ev_sess, input_op, sub_info.event, +- sub_info.request_id, &output, &ret))) { ++ sub_info.operation_id, &output, &ret))) { + goto cleanup; + } + +@@ -4525,8 +4541,7 @@ process_event: + if (ret == SR_ERR_CALLBACK_SHELVE) { + /* processing was shelved, so interupt the whole RPC processing in order to get correct final output */ + SR_LOG_INF("EV LISTEN: \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing shelved.", +- sr_ev2str(ATOMIC_LOAD_RELAXED(sub_shm->event)), +- (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->request_id), ++ sr_ev2str(ATOMIC_LOAD_RELAXED(sub_shm->event)), sub_shm->operation_id, + (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->priority)); + goto cleanup; + } else if (ret != SR_ERR_OK) { +@@ -4626,7 +4641,7 @@ sr_error_info_t * + sr_shmsub_notif_listen_process_module_events(struct modsub_notif_s *notif_subs, sr_conn_ctx_t *conn) + { + sr_error_info_t *err_info = NULL; +- uint32_t i, request_id, valid_subscr_count; ++ uint32_t i, request_id, operation_id, valid_subscr_count; + struct lyd_node *notif = NULL, *notif_op; + struct sr_denied denied = {0}; + struct timespec notif_ts_mono, notif_ts_real; +@@ -4656,6 +4671,7 @@ sr_shmsub_notif_listen_process_module_events(struct modsub_notif_s *notif_subs, + goto cleanup_rdunlock; + } + request_id = ATOMIC_LOAD_RELAXED(sub_shm->request_id); ++ operation_id = sub_shm->operation_id; + + /* open sub data SHM */ + if ((err_info = sr_shmsub_data_open_remap(notif_subs->module_name, "notif", -1, &shm_data_sub, 0))) { +@@ -4695,13 +4711,13 @@ sr_shmsub_notif_listen_process_module_events(struct modsub_notif_s *notif_subs, + + if (!valid_subscr_count) { + /* Print a message only the first time we get here */ +- SR_LOG_DBG("EV LISTEN: \"%s\" \"notif\" ID %" PRIu32 " processing.", notif_subs->module_name, request_id); ++ SR_LOG_DBG("EV LISTEN: \"%s\" \"notif\" ID %" PRIu32 " processing.", notif_subs->module_name, operation_id); + } + + if (sr_time_cmp(&sub->listen_since_mono, ¬if_ts_mono) > 0) { + /* generated before this subscription has been made */ + SR_LOG_DBG("EV LISTEN: \"%s\" \"notif\" ID %" PRIu32 " ignored, subscription created after the notification.", +- notif_subs->module_name, request_id); ++ notif_subs->module_name, operation_id); + continue; + } + +@@ -4747,7 +4763,7 @@ sr_shmsub_notif_listen_process_module_events(struct modsub_notif_s *notif_subs, + if ((ATOMIC_LOAD_RELAXED(sub_shm->event) != SR_SUB_EV_NOTIF) || + (ATOMIC_LOAD_RELAXED(sub_shm->request_id) != ATOMIC_LOAD_RELAXED(notif_subs->request_id))) { + SR_LOG_INF("EV LISTEN: \"%s\" ID %" PRIu32 " processing success (after timeout).", sr_ev2str(SR_SUB_EV_NOTIF), +- (uint32_t)ATOMIC_LOAD_RELAXED(notif_subs->request_id)); ++ operation_id); + goto cleanup_wrunlock; + } + +diff --git a/src/shm_sub.h b/src/shm_sub.h +index 62050e98..505dab39 100644 +--- a/src/shm_sub.h ++++ b/src/shm_sub.h +@@ -167,6 +167,7 @@ sr_error_info_t *sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_ + * @param[in] parent Existing parent to append the data to. + * @param[in] orig_name Event originator name. + * @param[in] orig_data Event originator data. ++ * @param[in] operation_id Operation ID. + * @param[in] oper_get_subs An array of operational get subscriptions. + * @param[in] idx1 Index of the array where operational subscriptions with the same XPath are. + * @param[in] timeout_ms Operational callback timeout in milliseconds. +@@ -176,8 +177,9 @@ sr_error_info_t *sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_ + * @return err_info, NULL on success. + */ + sr_error_info_t *sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, const char *request_xpath, +- const struct lyd_node *parent, const char *orig_name, const void *orig_data, sr_mod_oper_get_sub_t *oper_get_subs, +- uint32_t idx1, uint32_t timeout_ms, sr_conn_ctx_t *conn, struct lyd_node **data, sr_error_info_t **cb_err_info); ++ const struct lyd_node *parent, const char *orig_name, const void *orig_data, uint32_t operation_id, ++ sr_mod_oper_get_sub_t *oper_get_subs, uint32_t idx1, uint32_t timeout_ms, sr_conn_ctx_t *conn, ++ struct lyd_node **data, sr_error_info_t **cb_err_info); + + /** + * @brief Notify about (generate) an RPC/action event. +@@ -191,6 +193,7 @@ sr_error_info_t *sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const + * @param[in] input Operation input tree. + * @param[in] orig_name Event originator name. + * @param[in] orig_data Event originator data. ++ * @param[in] operation_id Operation ID. + * @param[in] timeout_ms RPC/action callback timeout in milliseconds. + * @param[in,out] request_id Generated request ID, set to 0 when passing. + * @param[out] output Operation output returned by the last subscriber on success. +@@ -198,8 +201,8 @@ sr_error_info_t *sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const + * @return err_info, NULL on success. + */ + sr_error_info_t *sr_shmsub_rpc_notify(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, const char *path, +- const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t timeout_ms, +- uint32_t *request_id, struct lyd_node **output, sr_error_info_t **cb_err_info); ++ const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t operation_id, ++ uint32_t timeout_ms, uint32_t *request_id, struct lyd_node **output, sr_error_info_t **cb_err_info); + + /** + * @brief Notify about (generate) an RPC/action abort event. +@@ -211,13 +214,14 @@ sr_error_info_t *sr_shmsub_rpc_notify(sr_conn_ctx_t *conn, off_t *subs, uint32_t + * @param[in] input Operation input tree. + * @param[in] orig_name Event originator name. + * @param[in] orig_data Event originator data. ++ * @param[in] operation_id Operation ID. + * @param[in] timeout_ms RPC/action callback timeout in milliseconds. + * @param[in] request_id Generated request ID from previous event. + * @return err_info, NULL on success. + */ +-sr_error_info_t *sr_shmsub_rpc_notify_abort(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, +- const char *path, const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t timeout_ms, +- uint32_t request_id); ++sr_error_info_t *sr_shmsub_rpc_notify_abort(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, const char *path, ++ const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t operation_id, ++ uint32_t timeout_ms, uint32_t request_id); + + /** + * @brief Notify about (generate) a notification event. +@@ -228,12 +232,14 @@ sr_error_info_t *sr_shmsub_rpc_notify_abort(sr_conn_ctx_t *conn, off_t *subs, ui + * @param[in] notif_ts_real Notification realtime timestamp. + * @param[in] orig_name Event originator name. + * @param[in] orig_data Event originator data. ++ * @param[in] operation_id Operation ID. + * @param[in] timeout_ms Notification callback timeout in milliseconds. Used only if @p wait is set. + * @param[in] wait Whether to wait for the callbacks or not. + * @return err_info, NULL on success. + */ + sr_error_info_t *sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_node *notif, struct timespec notif_ts_mono, +- struct timespec notif_ts_real, const char *orig_name, const void *orig_data, uint32_t timeout_ms, int wait); ++ struct timespec notif_ts_real, const char *orig_name, const void *orig_data, uint32_t operation_id, ++ uint32_t timeout_ms, int wait); + + /** + * @brief Write the result of having processed an event. +@@ -250,9 +256,8 @@ sr_error_info_t *sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_no + * @param[in] result_str Result of processing the event in string. + * @return err_info, NULL on success. + */ +-sr_error_info_t *sr_shmsub_listen_write_event(sr_sub_shm_t *sub_shm, uint32_t valid_subscr_count, +- sr_error_t err_code, sr_shm_t *shm_data_sub, const char *data, uint32_t data_len, const char *event_desc, +- const char *result_str); ++sr_error_info_t *sr_shmsub_listen_write_event(sr_sub_shm_t *sub_shm, uint32_t valid_subscr_count, sr_error_t err_code, ++ sr_shm_t *shm_data_sub, const char *data, uint32_t data_len, const char *event_desc, const char *result_str); + + /** + * @brief Process all module change events, if any. +diff --git a/src/shm_types.h b/src/shm_types.h +index 7b43ecef..5e5f4e77 100644 +--- a/src/shm_types.h ++++ b/src/shm_types.h +@@ -25,7 +25,7 @@ + #include "common_types.h" + #include "sysrepo_types.h" + +-#define SR_SHM_VER 18 /**< Main, mod, and ext SHM version of their expected content structures. */ ++#define SR_SHM_VER 19 /**< Main, mod, and ext SHM version of their expected content structures. */ + #define SR_MAIN_SHM_LOCK "sr_main_lock" /**< Main SHM file lock name. */ + + /** +@@ -201,6 +201,7 @@ typedef struct { + ATOMIC_T new_sr_sid; /**< SID for a new session. */ + ATOMIC_T new_sub_id; /**< Subscription ID of a new subscription. */ + ATOMIC_T new_evpipe_num; /**< Event pipe number for a new subscription. */ ++ ATOMIC_T new_operation_id; /**< Operation ID to use for each callback of every operation. */ + + char repo_path[256]; /**< Repository path used when main SHM was created. */ + } sr_main_shm_t; +@@ -367,6 +368,7 @@ typedef struct { + + ATOMIC_T priority; /**< Priority of the subscriber. */ + uint32_t subscriber_count; /**< Number of subscribers to process this event. */ ++ uint32_t operation_id; /**< Operation ID for the callback. */ + } sr_sub_shm_t; + + #endif /* _SHM_TYPES_H */ +diff --git a/src/sysrepo.c b/src/sysrepo.c +index 797de6aa..97537207 100644 +--- a/src/sysrepo.c ++++ b/src/sysrepo.c +@@ -2778,7 +2778,7 @@ sr_get_item(sr_session_ctx_t *session, const char *path, uint32_t timeout_ms, sr + } + *value = NULL; + /* for operational, use operational and running datastore */ +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -2882,7 +2882,7 @@ sr_get_items(sr_session_ctx_t *session, const char *xpath, uint32_t timeout_ms, + *values = NULL; + *value_cnt = 0; + /* for operational, use operational and running datastore */ +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -3041,7 +3041,7 @@ sr_get_subtree(sr_session_ctx_t *session, const char *path, uint32_t timeout_ms, + timeout_ms = SR_OPER_CB_TIMEOUT; + } + /* for operational, use operational and running datastore */ +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -3156,7 +3156,7 @@ sr_get_data(sr_session_ctx_t *session, const char *xpath, uint32_t max_depth, ui + } + + /* for operational, use operational and running datastore */ +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -3293,7 +3293,7 @@ sr_get_node(sr_session_ctx_t *session, const char *path, uint32_t timeout_ms, sr + } + + /* for operational, use operational and running datastore */ +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -3866,7 +3866,7 @@ sr_validate(sr_session_ctx_t *session, const char *module_name, uint32_t timeout + timeout_ms = SR_OPER_CB_TIMEOUT; + } + /* for operational, use operational and running datastore */ +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -4281,7 +4281,7 @@ sr_apply_changes(sr_session_ctx_t *session, uint32_t timeout_ms) + } + + /* prepare mod_info */ +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds, 0); + + if (session->ds == SR_DS_OPERATIONAL) { + /* handle specially */ +@@ -4419,20 +4419,21 @@ cleanup: + * + * @param[in] session Session to use. + * @param[in] ly_mod Optional specific module. ++ * @param[in] operation_id Operation ID. + * @param[in,out] src_config Source data for the replace, they are spent. + * @param[in] timeout_ms Change callback timeout in milliseconds. + * @return err_info, NULL on success. + */ + static sr_error_info_t * +-_sr_replace_config(sr_session_ctx_t *session, const struct lys_module *ly_mod, struct lyd_node **src_config, +- uint32_t timeout_ms) ++_sr_replace_config(sr_session_ctx_t *session, const struct lys_module *ly_mod, uint32_t operation_id, ++ struct lyd_node **src_config, uint32_t timeout_ms) + { + sr_error_info_t *err_info = NULL, *cb_err_info = NULL; + struct sr_mod_info_s mod_info; + + assert(!*src_config || !(*src_config)->prev->next); + assert(session->ds != SR_DS_OPERATIONAL); +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds, operation_id); + + /* single module/all modules */ + if (ly_mod) { +@@ -4514,7 +4515,7 @@ sr_replace_config(sr_session_ctx_t *session, const char *module_name, struct lyd + } + + /* replace the data */ +- if ((err_info = _sr_replace_config(session, ly_mod, &src_config, timeout_ms))) { ++ if ((err_info = _sr_replace_config(session, ly_mod, 0, &src_config, timeout_ms))) { + goto cleanup_unlock; + } + +@@ -4547,9 +4548,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ + } + if ((src_datastore == SR_DS_RUNNING) && (session->ds == SR_DS_CANDIDATE)) { + /* discard-changes, need no data, but lock running for READ and candidate for WRITE */ +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, src_datastore); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, src_datastore, 0); + } else { +- SR_MODINFO_INIT(mod_info, session->conn, src_datastore, src_datastore); ++ SR_MODINFO_INIT(mod_info, session->conn, src_datastore, src_datastore, 0); + } + + /* CONTEXT LOCK */ +@@ -4598,7 +4599,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ + } + + /* replace the data */ +- if ((err_info = _sr_replace_config(session, ly_mod, &mod_info.data, timeout_ms))) { ++ if ((err_info = _sr_replace_config(session, ly_mod, mod_info.operation_id, &mod_info.data, timeout_ms))) { + goto cleanup; + } + +@@ -4616,7 +4617,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ + sr_shmmod_modinfo_unlock(&mod_info); + + /* replace the data */ +- if ((err_info = _sr_replace_config(session, ly_mod, &mod_info.data, timeout_ms))) { ++ if ((err_info = _sr_replace_config(session, ly_mod, mod_info.operation_id, &mod_info.data, timeout_ms))) { + goto cleanup; + } + } +@@ -4655,7 +4656,7 @@ _sr_discard_oper_changes(sr_session_ctx_t *session, const char *module_name, int + if (!timeout_ms) { + timeout_ms = SR_CHANGE_CB_TIMEOUT; + } +- SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL); ++ SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL, 0); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -4720,7 +4721,7 @@ sr_get_oper_changes(sr_session_ctx_t *session, const char *module_name, sr_data_ + return sr_api_ret(session, err_info); + } + +- SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL); ++ SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL, 0); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(conn, SR_LOCK_READ, 0, __func__))) { +@@ -4937,7 +4938,7 @@ _sr_un_lock(sr_session_ctx_t *session, const char *module_name, int lock, uint32 + + SR_CHECK_ARG_APIRET(!session || !SR_IS_CONVENTIONAL_DS(session->ds), session, err_info); + +- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds, 1); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -5023,7 +5024,7 @@ sr_get_lock(sr_conn_ctx_t *conn, sr_datastore_t datastore, const char *module_na + if (timestamp) { + memset(timestamp, 0, sizeof *timestamp); + } +- SR_MODINFO_INIT(mod_info, conn, datastore, datastore); ++ SR_MODINFO_INIT(mod_info, conn, datastore, datastore, 1); + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(conn, SR_LOCK_READ, 0, __func__))) { +@@ -5686,8 +5687,6 @@ sr_module_change_subscribe_enable(sr_session_ctx_t *session, struct sr_mod_info_ + sr_session_ctx_t *ev_sess = NULL; + sr_error_t err_code; + +- SR_MODINFO_INIT((*mod_info), session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); +- + /* create mod_info structure with this module only, do not use cache to allow reading data in the callback + * (avoid dead-lock) */ + if ((err_info = sr_modinfo_add(ly_mod, NULL, 0, 0, mod_info))) { +@@ -5739,7 +5738,8 @@ sr_module_change_subscribe_enable(sr_session_ctx_t *session, struct sr_mod_info_ + SR_LOG_INF("Triggering \"%s\" \"%s\" event on enabled data.", ly_mod->name, sr_ev2str(ev_sess->ev)); + + /* present all changes in an "enabled" event */ +- err_code = callback(ev_sess, sub_id, ly_mod->name, xpath, sr_ev2api(ev_sess->ev), 0, private_data); ++ err_code = callback(ev_sess, sub_id, ly_mod->name, xpath, sr_ev2api(ev_sess->ev), mod_info->operation_id, ++ private_data); + if (err_code != SR_ERR_OK) { + /* callback failed but it is the only one so no "abort" event is necessary */ + if (ev_sess->ev_err_info) { +@@ -5856,7 +5856,7 @@ sr_module_change_subscribe(sr_session_ctx_t *session, const char *module_name, c + SR_CHECK_ARG_APIRET(!session || !SR_IS_STANDARD_DS(session->ds) || SR_IS_EVENT_SESS(session) || !module_name || + !callback || !subscription, session, err_info); + +- SR_MODINFO_INIT(mod_info, session->conn, SR_DS_RUNNING, SR_DS_RUNNING); ++ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); + + conn = session->conn; + /* only these options are relevant outside this function and will be stored */ +@@ -6761,7 +6761,7 @@ _sr_rpc_send_tree(sr_session_ctx_t *session, struct sr_mod_info_s *mod_info, con + sr_rpc_t *shm_rpc; + sr_dep_t *shm_deps; + uint16_t shm_dep_count; +- uint32_t event_id = 0; ++ uint32_t request_id = 0; + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -6794,7 +6794,7 @@ _sr_rpc_send_tree(sr_session_ctx_t *session, struct sr_mod_info_s *mod_info, con + sr_shmmod_modinfo_unlock(mod_info); + + sr_modinfo_erase(mod_info); +- SR_MODINFO_INIT(*mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING); ++ SR_MODINFO_INIT(*mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING, 0); + + if (!strcmp(path, SR_RPC_FACTORY_RESET_PATH)) { + /* update the input as needed */ +@@ -6815,14 +6815,15 @@ _sr_rpc_send_tree(sr_session_ctx_t *session, struct sr_mod_info_s *mod_info, con + + /* publish RPC in an event and wait for a reply from the last subscriber */ + if ((err_info = sr_shmsub_rpc_notify(session->conn, &shm_rpc->subs, &shm_rpc->sub_count, path, input, +- session->orig_name, session->orig_data, timeout_ms, &event_id, &(*output)->tree, &cb_err_info))) { ++ session->orig_name, session->orig_data, mod_info->operation_id, timeout_ms, &request_id, &(*output)->tree, ++ &cb_err_info))) { + goto cleanup_rpcsub_unlock; + } + + if (cb_err_info) { + /* "rpc" event failed, publish "abort" event and finish */ + err_info = sr_shmsub_rpc_notify_abort(session->conn, &shm_rpc->subs, &shm_rpc->sub_count, path, +- input, session->orig_name, session->orig_data, timeout_ms, event_id); ++ input, session->orig_name, session->orig_data, mod_info->operation_id, timeout_ms, request_id); + goto cleanup_rpcsub_unlock; + } + +@@ -6890,7 +6891,7 @@ _sr_rpc_ext_send_tree(sr_session_ctx_t *session, const struct lyd_node *ext_pare + { + sr_error_info_t *err_info = NULL, *cb_err_info = NULL; + sr_mod_t *shm_mod; +- uint32_t event_id = 0; ++ uint32_t request_id = 0; + + /* CONTEXT LOCK */ + if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { +@@ -6930,16 +6931,16 @@ _sr_rpc_ext_send_tree(sr_session_ctx_t *session, const struct lyd_node *ext_pare + } + + /* publish RPC in an event and wait for a reply from the last subscriber */ +- if ((err_info = sr_shmsub_rpc_notify(session->conn, &shm_mod->rpc_ext_subs, +- &shm_mod->rpc_ext_sub_count, path, input, session->orig_name, session->orig_data, timeout_ms, &event_id, ++ if ((err_info = sr_shmsub_rpc_notify(session->conn, &shm_mod->rpc_ext_subs, &shm_mod->rpc_ext_sub_count, path, ++ input, session->orig_name, session->orig_data, mod_info->operation_id, timeout_ms, &request_id, + &(*output)->tree, &cb_err_info))) { + goto cleanup_rpcsub_unlock; + } + + if (cb_err_info) { + /* "rpc" event failed, publish "abort" event and finish */ +- err_info = sr_shmsub_rpc_notify_abort(session->conn, &shm_mod->rpc_ext_subs, +- &shm_mod->rpc_ext_sub_count, path, input, session->orig_name, session->orig_data, timeout_ms, event_id); ++ err_info = sr_shmsub_rpc_notify_abort(session->conn, &shm_mod->rpc_ext_subs, &shm_mod->rpc_ext_sub_count, path, ++ input, session->orig_name, session->orig_data, mod_info->operation_id, timeout_ms, request_id); + goto cleanup_rpcsub_unlock; + } + +@@ -6963,7 +6964,6 @@ _sr_rpc_ext_send_tree(sr_session_ctx_t *session, const struct lyd_node *ext_pare + goto cleanup; + } + +- /* success */ + goto cleanup; + + cleanup_rpcsub_unlock: +@@ -7003,7 +7003,7 @@ sr_rpc_send_tree(sr_session_ctx_t *session, struct lyd_node *input, uint32_t tim + if (!timeout_ms) { + timeout_ms = SR_RPC_CB_TIMEOUT; + } +- SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING); ++ SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING, 0); + + /* check input data tree */ + input_op = NULL; +@@ -7330,7 +7330,7 @@ sr_notif_send_tree(sr_session_ctx_t *session, struct lyd_node *notif, uint32_t t + if (!timeout_ms) { + timeout_ms = SR_NOTIF_CB_TIMEOUT; + } +- SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING); ++ SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING, 0); + + /* check notif data tree */ + notif_op = NULL; +@@ -7423,7 +7423,7 @@ sr_notif_send_tree(sr_session_ctx_t *session, struct lyd_node *notif, uint32_t t + + /* publish notif in an event */ + err_info = sr_shmsub_notif_notify(session->conn, notif_top, notif_ts_mono, notif_ts_real, session->orig_name, +- session->orig_data, timeout_ms, wait); ++ session->orig_data, mod_info.operation_id, timeout_ms, wait); + + /* NOTIF SUB READ UNLOCK */ + sr_rwunlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, session->conn->cid, __func__); +diff --git a/src/sysrepo_types.h b/src/sysrepo_types.h +index dff6f5bb..337ee51a 100644 +--- a/src/sysrepo_types.h ++++ b/src/sysrepo_types.h +@@ -542,13 +542,12 @@ typedef struct sr_change_iter_s sr_change_iter_t; + * @param[in] module_name Name of the module where the change has occurred. + * @param[in] xpath [XPath](@ref paths) used when subscribing, NULL if the whole module was subscribed to. + * @param[in] event Type of the callback event that has occurred. +- * @param[in] request_id Request ID unique for the specific @p module_name. Connected events +- * for one request (::SR_EV_CHANGE and ::SR_EV_DONE, for example) have the same request ID. ++ * @param[in] operation_id Operation ID of the current operation used for all the callbacks and events. + * @param[in] private_data Private context opaque to sysrepo, as passed to ::sr_module_change_subscribe call. + * @return User error code (::SR_ERR_OK on success). + */ + typedef int (*sr_module_change_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char *module_name, const char *xpath, +- sr_event_t event, uint32_t request_id, void *private_data); ++ sr_event_t event, uint32_t operation_id, void *private_data); + + /** @} datasubs */ + +@@ -568,7 +567,7 @@ typedef int (*sr_module_change_cb)(sr_session_ctx_t *session, uint32_t sub_id, c + * @param[in] input Array of input parameters. + * @param[in] input_cnt Number of input parameters. + * @param[in] event Type of the callback event that has occurred. +- * @param[in] request_id Request ID unique for the specific @p op_path. ++ * @param[in] operation_id Operation ID of the current operation used for all the callbacks and events. + * @param[out] output Array of output parameters. Should be allocated on heap, + * will be freed by sysrepo after sending of the RPC response. + * @param[out] output_cnt Number of output parameters. +@@ -576,7 +575,7 @@ typedef int (*sr_module_change_cb)(sr_session_ctx_t *session, uint32_t sub_id, c + * @return User error code (::SR_ERR_OK on success). + */ + typedef int (*sr_rpc_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath, const sr_val_t *input, +- const size_t input_cnt, sr_event_t event, uint32_t request_id, sr_val_t **output, size_t *output_cnt, ++ const size_t input_cnt, sr_event_t event, uint32_t operation_id, sr_val_t **output, size_t *output_cnt, + void *private_data); + + /** +@@ -589,13 +588,13 @@ typedef int (*sr_rpc_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char + * @param[in] op_path Simple operation [path](@ref paths) identifying the RPC/action. + * @param[in] input Data tree of input parameters. Always points to the __RPC/action__ itself, even for nested operations. + * @param[in] event Type of the callback event that has occurred. +- * @param[in] request_id Request ID unique for the specific @p op_path. ++ * @param[in] operation_id Operation ID of the current operation used for all the callbacks and events. + * @param[out] output Data tree for appending any output parameters, the operation root node is provided.. + * @param[in] private_data Private context opaque to sysrepo, as passed to ::sr_rpc_subscribe_tree call. + * @return User error code (::SR_ERR_OK on success). + */ + typedef int (*sr_rpc_tree_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char *op_path, const struct lyd_node *input, +- sr_event_t event, uint32_t request_id, struct lyd_node *output, void *private_data); ++ sr_event_t event, uint32_t operation_id, struct lyd_node *output, void *private_data); + + /** @} rpcsubs */ + +@@ -680,7 +679,7 @@ typedef void (*sr_event_notif_tree_cb)(sr_session_ctx_t *session, uint32_t sub_i + * @param[in] path [Path](@ref paths) identifying the subtree that is supposed to be provided, same as the one used + * for the subscription. + * @param[in] request_xpath [XPath](@ref paths) as requested by a client. Can be NULL. +- * @param[in] request_id Request ID unique for the specific @p module_name. ++ * @param[in] operation_id Operation ID of the current operation used for all the callbacks and events. + * @param[in,out] parent Pointer to an existing parent of the requested nodes. Is NULL for top-level nodes. + * Caller is supposed to append the requested nodes to this data subtree and return either the original parent + * or a top-level node. +@@ -688,7 +687,7 @@ typedef void (*sr_event_notif_tree_cb)(sr_session_ctx_t *session, uint32_t sub_i + * @return User error code (::SR_ERR_OK on success). + */ + typedef int (*sr_oper_get_items_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char *module_name, const char *path, +- const char *request_xpath, uint32_t request_id, struct lyd_node **parent, void *private_data); ++ const char *request_xpath, uint32_t operation_id, struct lyd_node **parent, void *private_data); + + /** @} oper_subs */ + +diff --git a/tests/test_oper_pull.c b/tests/test_oper_pull.c +index 91ba526e..793a6456 100644 +--- a/tests/test_oper_pull.c ++++ b/tests/test_oper_pull.c +@@ -974,8 +974,7 @@ enabled_change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *module + int ret, *called = (int *)private_data; + + (void)sub_id; +- +- assert_int_equal(request_id, 0); ++ (void)request_id; + + if (!strcmp(xpath, "/ietf-interfaces:interfaces/interface[name='eth128']")) { + assert_string_equal(module_name, "ietf-interfaces"); +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0014-SOVERSION-bump-to-version-7.29.13.patch b/patches/sysrepo/3.3.10/0014-SOVERSION-bump-to-version-7.29.13.patch new file mode 100644 index 00000000..17910306 --- /dev/null +++ b/patches/sysrepo/3.3.10/0014-SOVERSION-bump-to-version-7.29.13.patch @@ -0,0 +1,30 @@ +From 1977326b9a63bc9e1b7746dc0a5becb9b2aece26 Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Tue, 14 Jan 2025 13:36:59 +0100 +Subject: [PATCH 14/22] SOVERSION bump to version 7.29.13 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 7d49cedb..040d2242 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -62,7 +62,7 @@ set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_ + # with backward compatible change and micro version is connected with any internal change of the library. + set(SYSREPO_MAJOR_SOVERSION 7) + set(SYSREPO_MINOR_SOVERSION 29) +-set(SYSREPO_MICRO_SOVERSION 12) ++set(SYSREPO_MICRO_SOVERSION 13) + set(SYSREPO_SOVERSION_FULL ${SYSREPO_MAJOR_SOVERSION}.${SYSREPO_MINOR_SOVERSION}.${SYSREPO_MICRO_SOVERSION}) + set(SYSREPO_SOVERSION ${SYSREPO_MAJOR_SOVERSION}) + +-- +2.43.0 + diff --git a/patches/sysrepo/3.3.10/0015-VERSION-bump-to-version-3.4.1.patch b/patches/sysrepo/3.3.10/0015-VERSION-bump-to-version-3.4.1.patch new file mode 100644 index 00000000..2ff17b68 --- /dev/null +++ b/patches/sysrepo/3.3.10/0015-VERSION-bump-to-version-3.4.1.patch @@ -0,0 +1,30 @@ +From df88c15e014d75dbd6e48d4ff1291fb6068f296d Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Tue, 14 Jan 2025 13:37:09 +0100 +Subject: [PATCH 15/22] VERSION bump to version 3.4.1 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Addiva Elektronik + +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 040d2242..7b86c0f3 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -54,7 +54,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1) + # micro version is changed with a set of small changes or bugfixes anywhere in the project. + set(SYSREPO_MAJOR_VERSION 3) + set(SYSREPO_MINOR_VERSION 4) +-set(SYSREPO_MICRO_VERSION 0) ++set(SYSREPO_MICRO_VERSION 1) + set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_MICRO_VERSION}) + + # Version of the library +-- +2.43.0 + diff --git a/patches/sysrepo/2.11.7/0001-sysrepo-plugind-add-support-for-running-in-foregroun.patch b/patches/sysrepo/3.3.10/0016-sysrepo-plugind-add-support-for-running-in-foregroun.patch similarity index 78% rename from patches/sysrepo/2.11.7/0001-sysrepo-plugind-add-support-for-running-in-foregroun.patch rename to patches/sysrepo/3.3.10/0016-sysrepo-plugind-add-support-for-running-in-foregroun.patch index a3d93c56..bcc85d5f 100644 --- a/patches/sysrepo/2.11.7/0001-sysrepo-plugind-add-support-for-running-in-foregroun.patch +++ b/patches/sysrepo/3.3.10/0016-sysrepo-plugind-add-support-for-running-in-foregroun.patch @@ -1,20 +1,24 @@ -From e8daa7e3e6d24e66e1a74b00237af4c32438cb92 Mon Sep 17 00:00:00 2001 +From 401c5b8ccce07b84e3b364283a8c789908f62f01 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 28 Mar 2023 10:37:53 +0200 -Subject: [PATCH 1/7] sysrepo-plugind: add support for running in foreground +Subject: [PATCH 16/22] sysrepo-plugind: add support for running in foreground with syslog +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit Organization: Addiva Elektronik Signed-off-by: Joachim Wiberg +Signed-off-by: Mattias Walström --- src/executables/sysrepo-plugind.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/executables/sysrepo-plugind.c b/src/executables/sysrepo-plugind.c -index a6a817cc..5baebec3 100644 +index 3b66cfa6..c531ca60 100644 --- a/src/executables/sysrepo-plugind.c +++ b/src/executables/sysrepo-plugind.c -@@ -98,6 +98,7 @@ help_print(void) +@@ -100,6 +100,7 @@ help_print(void) " Change verbosity to a level (none, error, warning, info, debug) or\n" " number (0, 1, 2, 3, 4).\n" " -d, --debug Debug mode - is not daemonized and logs to stderr instead of syslog.\n" @@ -22,7 +26,7 @@ index a6a817cc..5baebec3 100644 " -P, --plugin-install \n" " Install a sysrepo-plugind plugin. The plugin is simply copied\n" " to the designated plugin directory.\n" -@@ -201,6 +202,8 @@ daemon_init(int debug, sr_log_level_t log_level) +@@ -214,6 +215,8 @@ daemon_init(int debug, sr_log_level_t log_level) if (debug) { handle_signals(); @@ -31,7 +35,7 @@ index a6a817cc..5baebec3 100644 sr_log_stderr(log_level); return; } -@@ -241,6 +244,7 @@ daemon_init(int debug, sr_log_level_t log_level) +@@ -254,6 +257,7 @@ daemon_init(int debug, sr_log_level_t log_level) close(fd); } @@ -39,7 +43,7 @@ index a6a817cc..5baebec3 100644 /* set verbosity */ sr_log_syslog("sysrepo-plugind", log_level); } -@@ -466,6 +470,7 @@ main(int argc, char **argv) +@@ -479,6 +483,7 @@ main(int argc, char **argv) {"version", no_argument, NULL, 'V'}, {"verbosity", required_argument, NULL, 'v'}, {"debug", no_argument, NULL, 'd'}, @@ -47,7 +51,7 @@ index a6a817cc..5baebec3 100644 {"plugin-install", required_argument, NULL, 'P'}, {"pid-file", required_argument, NULL, 'p'}, {"fatal-plugin-fail", no_argument, NULL, 'f'}, -@@ -474,7 +479,7 @@ main(int argc, char **argv) +@@ -487,7 +492,7 @@ main(int argc, char **argv) /* process options */ opterr = 0; @@ -56,7 +60,7 @@ index a6a817cc..5baebec3 100644 switch (opt) { case 'h': version_print(); -@@ -506,6 +511,9 @@ main(int argc, char **argv) +@@ -519,6 +524,9 @@ main(int argc, char **argv) case 'd': debug = 1; break; diff --git a/patches/sysrepo/2.11.7/0002-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch b/patches/sysrepo/3.3.10/0017-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch similarity index 79% rename from patches/sysrepo/2.11.7/0002-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch rename to patches/sysrepo/3.3.10/0017-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch index f4e3fb73..35f79f7d 100644 --- a/patches/sysrepo/2.11.7/0002-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch +++ b/patches/sysrepo/3.3.10/0017-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch @@ -1,7 +1,10 @@ -From 936d4f34a12ef1a2fe3372df051b77ea19ce5d59 Mon Sep 17 00:00:00 2001 +From 6cba2363b8d3a56755365f59af7830f5bd25824a Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 7 May 2024 15:41:53 +0200 -Subject: [PATCH 2/7] Allow SR_EV_DONE to return any error to sysrepocfg +Subject: [PATCH 17/22] Allow SR_EV_DONE to return any error to sysrepocfg +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit Organization: Addiva Elektronik Importing a system configuration with sysrepocfg the model callbacks do @@ -17,17 +20,18 @@ This patch is a clumsy way of forcing the (first) error to bubble up to the surface and cause a non-zero exit code from sysrepocfg. Signed-off-by: Joachim Wiberg +Signed-off-by: Mattias Walström --- src/shm_sub.c | 36 ++++++++++++++++++++++++++++-------- src/shm_sub.h | 2 +- - src/sysrepo.c | 2 +- - 3 files changed, 30 insertions(+), 10 deletions(-) + src/sysrepo.c | 4 ++-- + 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/shm_sub.c b/src/shm_sub.c -index 000f5906..4c59cbbf 100644 +index fd6f105b..588cce09 100644 --- a/src/shm_sub.c +++ b/src/shm_sub.c -@@ -1659,7 +1659,7 @@ cleanup: +@@ -1766,7 +1766,7 @@ cleanup: sr_error_info_t * sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *orig_name, const void *orig_data, @@ -36,7 +40,7 @@ index 000f5906..4c59cbbf 100644 { sr_error_info_t *err_info = NULL; struct sr_mod_info_mod_s *mod = NULL; -@@ -1791,12 +1791,19 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * +@@ -1911,12 +1911,19 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * sr_rwunlock(&nsub->sub_shm->lock, 0, SR_LOCK_WRITE, cid, __func__); nsub->lock = SR_LOCK_NONE; @@ -44,7 +48,7 @@ index 000f5906..4c59cbbf 100644 - sr_errinfo_free(&nsub->cb_err_info); - SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", - nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_DONE), nsub->mod->request_id, nsub->cur_priority); + nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_DONE), mod_info->operation_id, nsub->cur_priority); + /* + * unexpected critical error, merge and cleanup, let @@ -59,7 +63,7 @@ index 000f5906..4c59cbbf 100644 nsub->pending_event = 0; } } while (1); -@@ -3278,7 +3285,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc +@@ -3424,7 +3431,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc sr_error_info_t * sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_subs, sr_conn_ctx_t *conn) { @@ -68,7 +72,7 @@ index 000f5906..4c59cbbf 100644 uint32_t i, data_len = 0, valid_subscr_count; char *data = NULL, *shm_data_ptr; int ret = SR_ERR_OK, filter_valid; -@@ -3406,6 +3413,11 @@ process_event: +@@ -3553,6 +3560,11 @@ process_event: } break; } @@ -80,7 +84,7 @@ index 000f5906..4c59cbbf 100644 } /* subscription processed this event */ -@@ -3438,6 +3450,11 @@ process_event: +@@ -3585,6 +3597,11 @@ process_event: } break; case SR_SUB_EV_DONE: @@ -92,7 +96,7 @@ index 000f5906..4c59cbbf 100644 case SR_SUB_EV_ABORT: /* nothing to do */ break; -@@ -3457,14 +3474,17 @@ process_event: +@@ -3604,14 +3621,17 @@ process_event: /* SUB WRITE URGE LOCK */ if (sr_shmsub_change_listen_relock(sub_shm, SR_LOCK_WRITE_URGE, &sub_info, change_sub, change_subs->module_name, @@ -112,7 +116,7 @@ index 000f5906..4c59cbbf 100644 goto cleanup; } -@@ -3953,7 +3973,7 @@ finish_iter: +@@ -4101,7 +4121,7 @@ finish_iter: sr_errinfo_free(&cb_err_info); /* publish "done" event */ @@ -122,7 +126,7 @@ index 000f5906..4c59cbbf 100644 } diff --git a/src/shm_sub.h b/src/shm_sub.h -index 62050e98..04637223 100644 +index 505dab39..617a88b7 100644 --- a/src/shm_sub.h +++ b/src/shm_sub.h @@ -144,7 +144,7 @@ sr_error_info_t *sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, @@ -135,15 +139,24 @@ index 62050e98..04637223 100644 /** * @brief Notify about (generate) a change "abort" event. diff --git a/src/sysrepo.c b/src/sysrepo.c -index 06428bd8..3f08a627 100644 +index 97537207..a1bbc61f 100644 --- a/src/sysrepo.c +++ b/src/sysrepo.c -@@ -3913,7 +3913,7 @@ store: +@@ -3994,7 +3994,7 @@ sr_error_info_t * + sr_changes_notify_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, int shmmod_session_del, + uint32_t timeout_ms, sr_error_info_t **err_info2) + { +- sr_error_info_t *err_info = NULL; ++ sr_error_info_t *err_info = NULL, *cb_err_info = NULL; + struct sr_denied denied = {0}; + sr_lock_mode_t change_sub_lock = SR_LOCK_NONE; + uint32_t sid = 0, err_count; +@@ -4141,7 +4141,7 @@ store: } /* publish "done" event, all changes were applied */ - if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms))) { -+ if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms, cb_err_info))) { ++ if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms, &cb_err_info))) { goto cleanup; } diff --git a/patches/sysrepo/2.11.7/0003-Allow-to-copy-from-factory-default.patch b/patches/sysrepo/3.3.10/0018-Allow-to-copy-from-factory-default.patch similarity index 68% rename from patches/sysrepo/2.11.7/0003-Allow-to-copy-from-factory-default.patch rename to patches/sysrepo/3.3.10/0018-Allow-to-copy-from-factory-default.patch index 02b8a2e5..e5f93298 100644 --- a/patches/sysrepo/2.11.7/0003-Allow-to-copy-from-factory-default.patch +++ b/patches/sysrepo/3.3.10/0018-Allow-to-copy-from-factory-default.patch @@ -1,18 +1,22 @@ -From e518c0ee5b4a328c5cbc7e7f25d329d094183798 Mon Sep 17 00:00:00 2001 +From e328151d027e408befb8b941418f84e2909f92e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 8 May 2024 17:00:50 +0200 -Subject: [PATCH 3/7] Allow to copy from factory default +Subject: [PATCH 18/22] Allow to copy from factory default +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit Organization: Addiva Elektronik +Signed-off-by: Mattias Walström --- src/sysrepo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sysrepo.c b/src/sysrepo.c -index 3f08a627..3b11c0b8 100644 +index a1bbc61f..22ee9fe4 100644 --- a/src/sysrepo.c +++ b/src/sysrepo.c -@@ -4161,7 +4161,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ +@@ -4535,7 +4535,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ struct sr_mod_info_s mod_info; const struct lys_module *ly_mod = NULL; diff --git a/patches/sysrepo/2.11.7/0004-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch b/patches/sysrepo/3.3.10/0019-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch similarity index 92% rename from patches/sysrepo/2.11.7/0004-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch rename to patches/sysrepo/3.3.10/0019-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch index e56c3fdc..a58f8730 100644 --- a/patches/sysrepo/2.11.7/0004-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch +++ b/patches/sysrepo/3.3.10/0019-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch @@ -1,11 +1,16 @@ -From a7e91aac7d7669c6b82cb06a1b4e3c3e3d79275f Mon Sep 17 00:00:00 2001 +From f833cf10647b0c76386fe0850011204a321bb0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 6 May 2024 14:49:32 +0200 -Subject: [PATCH 4/7] Add -z switch to sysrepoctl to install factory config +Subject: [PATCH 19/22] Add -z switch to sysrepoctl to install factory config from a json file +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit Organization: Addiva Elektronik This to be able to load the yang modules during build time instead on boot. + +Signed-off-by: Mattias Walström --- src/executables/sysrepoctl.c | 20 +++++++++++++-- src/lyd_mods.h | 7 ++++++ @@ -84,10 +89,10 @@ index 1960bc06..a8ca5add 100644 + #endif diff --git a/src/sysrepo.c b/src/sysrepo.c -index 3b11c0b8..2e02de39 100644 +index 22ee9fe4..518f34e6 100644 --- a/src/sysrepo.c +++ b/src/sysrepo.c -@@ -1646,6 +1646,55 @@ sr_free_int_install_mods(sr_int_install_mod_t *new_mods, uint32_t new_mod_count) +@@ -1621,6 +1621,55 @@ sr_free_int_install_mods(sr_int_install_mod_t *new_mods, uint32_t new_mod_count) free(new_mods); } @@ -144,7 +149,7 @@ index 3b11c0b8..2e02de39 100644 sr_install_module(sr_conn_ctx_t *conn, const char *schema_path, const char *search_dirs, const char **features) { diff --git a/src/sysrepo.h b/src/sysrepo.h -index 8a6a70b8..41c9e52b 100644 +index 6881830d..0280244a 100644 --- a/src/sysrepo.h +++ b/src/sysrepo.h @@ -34,7 +34,6 @@ extern "C" { @@ -155,7 +160,7 @@ index 8a6a70b8..41c9e52b 100644 /** * @defgroup log_api Logging API * @{ -@@ -708,6 +707,15 @@ int sr_get_module_info(sr_conn_ctx_t *conn, sr_data_t **sysrepo_data); +@@ -717,6 +716,15 @@ int sr_get_module_info(sr_conn_ctx_t *conn, sr_data_t **sysrepo_data); */ int sr_is_module_internal(const struct lys_module *ly_mod); diff --git a/patches/sysrepo/2.11.7/0005-Introduce-new-log-level-SEC-for-audit-trails.patch b/patches/sysrepo/3.3.10/0020-Introduce-new-log-level-SEC-for-audit-trails.patch similarity index 91% rename from patches/sysrepo/2.11.7/0005-Introduce-new-log-level-SEC-for-audit-trails.patch rename to patches/sysrepo/3.3.10/0020-Introduce-new-log-level-SEC-for-audit-trails.patch index 4a2123e1..cebf6a21 100644 --- a/patches/sysrepo/2.11.7/0005-Introduce-new-log-level-SEC-for-audit-trails.patch +++ b/patches/sysrepo/3.3.10/0020-Introduce-new-log-level-SEC-for-audit-trails.patch @@ -1,7 +1,10 @@ -From a4060077214c573f34f2a82b18083ccd27600a38 Mon Sep 17 00:00:00 2001 +From fa14b6da3db9cf08d10108ce95fdfaecf7f76bf7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 21 Aug 2024 16:00:35 +0200 -Subject: [PATCH 5/7] Introduce new log level [SEC] for audit trails +Subject: [PATCH 20/22] Introduce new log level [SEC] for audit trails +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit Organization: Addiva Elektronik This adds a new log level for security and audit trail related log @@ -19,6 +22,7 @@ system log daemon, dropping any [SEVERITY] prefix. Also, \n is most often dropped by log daemons. Signed-off-by: Joachim Wiberg +Signed-off-by: Mattias Walström --- src/log.c | 18 +++++++++++++++++- src/log.h | 1 + @@ -70,10 +74,10 @@ index e15055ac..25eab8fa 100644 /* logging callback */ diff --git a/src/log.h b/src/log.h -index d7e65b88..8722e51d 100644 +index 9a60f1f6..acd7eead 100644 --- a/src/log.h +++ b/src/log.h -@@ -32,6 +32,7 @@ +@@ -31,6 +31,7 @@ #define SR_LOG_WRN(...) sr_log(SR_LL_WRN, __VA_ARGS__) #define SR_LOG_INF(...) sr_log(SR_LL_INF, __VA_ARGS__) @@ -82,7 +86,7 @@ index d7e65b88..8722e51d 100644 #define SR_CHECK_MEM_GOTO(cond, err_info, go) if (cond) { SR_ERRINFO_MEM(&(err_info)); goto go; } diff --git a/src/sysrepo_types.h b/src/sysrepo_types.h -index 9f820b84..9a0de2be 100644 +index 337ee51a..bec7317d 100644 --- a/src/sysrepo_types.h +++ b/src/sysrepo_types.h @@ -65,7 +65,8 @@ typedef enum { diff --git a/patches/sysrepo/2.11.7/0006-Add-audit-trail-for-high-priority-system-changes.patch b/patches/sysrepo/3.3.10/0021-Add-audit-trail-for-high-priority-system-changes.patch similarity index 79% rename from patches/sysrepo/2.11.7/0006-Add-audit-trail-for-high-priority-system-changes.patch rename to patches/sysrepo/3.3.10/0021-Add-audit-trail-for-high-priority-system-changes.patch index 80080e16..3d1889f8 100644 --- a/patches/sysrepo/2.11.7/0006-Add-audit-trail-for-high-priority-system-changes.patch +++ b/patches/sysrepo/3.3.10/0021-Add-audit-trail-for-high-priority-system-changes.patch @@ -1,7 +1,10 @@ -From 3ee457861d98c62b2659b4fa49ec4571e78c83d4 Mon Sep 17 00:00:00 2001 +From 0c28a5f1c99d8fe200f23647cf06ba640e767166 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 21 Aug 2024 16:04:43 +0200 -Subject: [PATCH 6/7] Add audit trail for high priority system changes +Subject: [PATCH 21/22] Add audit trail for high priority system changes +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit Organization: Addiva Elektronik Committing a change to running, copying to a datastore, or calling an @@ -13,15 +16,16 @@ is when the system actually activates the changes. Copying to startup or other datastores is handled separately. Signed-off-by: Joachim Wiberg +Signed-off-by: Mattias Walström --- src/sysrepo.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sysrepo.c b/src/sysrepo.c -index 2e02de39..85a8e41c 100644 +index 518f34e6..a7e6563a 100644 --- a/src/sysrepo.c +++ b/src/sysrepo.c -@@ -3961,6 +3961,9 @@ store: +@@ -4189,6 +4189,9 @@ store: goto cleanup; } @@ -29,9 +33,9 @@ index 2e02de39..85a8e41c 100644 + SR_LOG_SEC("user \"%s\" commiting changes to %s ...", session->nacm_user, sr_ds2str(mod_info->ds)); + /* publish "done" event, all changes were applied */ - if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms, cb_err_info))) { + if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms, &cb_err_info))) { goto cleanup; -@@ -3972,6 +3975,9 @@ store: +@@ -4200,6 +4203,9 @@ store: } cleanup: @@ -41,7 +45,7 @@ index 2e02de39..85a8e41c 100644 if (change_sub_lock) { assert(change_sub_lock == SR_LOCK_READ); -@@ -4300,6 +4306,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ +@@ -4671,6 +4677,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ } } @@ -51,7 +55,7 @@ index 2e02de39..85a8e41c 100644 cleanup: /* MODULES UNLOCK */ sr_shmmod_modinfo_unlock(&mod_info); -@@ -6580,6 +6589,9 @@ sr_rpc_send_tree(sr_session_ctx_t *session, struct lyd_node *input, uint32_t tim +@@ -7132,6 +7141,9 @@ sr_rpc_send_tree(sr_session_ctx_t *session, struct lyd_node *input, uint32_t tim } } diff --git a/patches/sysrepo/2.11.7/0007-On-error-in-sr_shmsub_listen_thread-exit-process.patch b/patches/sysrepo/3.3.10/0022-On-error-in-sr_shmsub_listen_thread-exit-process.patch similarity index 53% rename from patches/sysrepo/2.11.7/0007-On-error-in-sr_shmsub_listen_thread-exit-process.patch rename to patches/sysrepo/3.3.10/0022-On-error-in-sr_shmsub_listen_thread-exit-process.patch index d905a493..d4c1c7d1 100644 --- a/patches/sysrepo/2.11.7/0007-On-error-in-sr_shmsub_listen_thread-exit-process.patch +++ b/patches/sysrepo/3.3.10/0022-On-error-in-sr_shmsub_listen_thread-exit-process.patch @@ -1,7 +1,10 @@ -From be7579a3a787d7089acbda7536a21a2a9edc487d Mon Sep 17 00:00:00 2001 +From 72be8d7e245fe8cd30bd1e2b5d308aacbf0f7b07 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 23 Aug 2024 12:22:06 +0200 -Subject: [PATCH 7/7] On error in sr_shmsub_listen_thread(), exit process +Subject: [PATCH 22/22] On error in sr_shmsub_listen_thread(), exit process +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit Organization: Addiva Elektronik If processing callback events in, e.g., sysrepo-plugind, make sure to @@ -9,23 +12,16 @@ log the error and exit(1) the entire process so the system can decide to handle the problem. For example, restart all dependent services. Signed-off-by: Joachim Wiberg +Signed-off-by: Mattias Walström --- - src/shm_sub.c | 3 +++ - 1 file changed, 3 insertions(+) + src/shm_sub.c | 2 ++ + 1 file changed, 2 insertions(+) diff --git a/src/shm_sub.c b/src/shm_sub.c -index 4c59cbbf..3d703cac 100644 +index 588cce09..07d8960f 100644 --- a/src/shm_sub.c +++ b/src/shm_sub.c -@@ -4825,6 +4825,7 @@ sr_shmsub_listen_thread(void *arg) - * another event is generated, our event pipe will not get notified */ - continue; - } else if (ret) { -+ SR_LOG_WRN("failed sr_subscription_process_events(), ret:%d", ret); - goto error; - } - -@@ -4865,5 +4866,7 @@ error: +@@ -4987,5 +4987,7 @@ error: /* free our own resources */ ATOMIC_STORE_RELAXED(subscr->thread_running, 0); pthread_detach(pthread_self());