blob: c18ff31816e8ad4300a75a378d830889a42df3cb [file] [log] [blame]
Václav Kubernát80aacc02018-08-22 17:41:54 +02001/*
2 * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
3 * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
4 *
5 * Written by Václav Kubernát <kubervac@fit.cvut.cz>
6 *
7*/
8
Václav Kubernát19097f32020-10-05 10:08:29 +02009#include <experimental/iterator>
Václav Kubernátab612e92019-11-26 19:51:31 +010010#include <libyang/Tree_Data.hpp>
11#include <libyang/Tree_Schema.hpp>
Václav Kubernát19097f32020-10-05 10:08:29 +020012#include <sstream>
Václav Kubernátb4e5b182020-11-16 19:55:09 +010013#include <sysrepo-cpp/Session.hpp>
Václav Kubernátab612e92019-11-26 19:51:31 +010014#include "libyang_utils.hpp"
Václav Kubernát80aacc02018-08-22 17:41:54 +020015#include "sysrepo_access.hpp"
Jan Kundrát6ee84792020-01-24 01:43:36 +010016#include "utils.hpp"
Václav Kubernáta6c5fff2018-09-07 15:16:25 +020017#include "yang_schema.hpp"
Václav Kubernát80aacc02018-08-22 17:41:54 +020018
Václav Kubernát654303f2020-07-31 13:16:54 +020019const auto OPERATION_TIMEOUT_MS = 1000;
20
Jan Kundrát68d4a2c2018-10-01 17:17:09 +020021leaf_data_ leafValueFromVal(const sysrepo::S_Val& value)
Václav Kubernátc89736b2018-08-30 16:14:05 +020022{
Václav Kubernátb6ff0b62018-08-30 16:14:53 +020023 using namespace std::string_literals;
Václav Kubernátc89736b2018-08-30 16:14:05 +020024 switch (value->type()) {
Ivona Oboňová88c78ca2019-07-02 18:40:07 +020025 case SR_INT8_T:
26 return value->data()->get_int8();
27 case SR_UINT8_T:
28 return value->data()->get_uint8();
29 case SR_INT16_T:
30 return value->data()->get_int16();
31 case SR_UINT16_T:
32 return value->data()->get_uint16();
Václav Kubernátb6ff0b62018-08-30 16:14:53 +020033 case SR_INT32_T:
34 return value->data()->get_int32();
35 case SR_UINT32_T:
36 return value->data()->get_uint32();
Ivona Oboňová88c78ca2019-07-02 18:40:07 +020037 case SR_INT64_T:
38 return value->data()->get_int64();
39 case SR_UINT64_T:
40 return value->data()->get_uint64();
Václav Kubernátb6ff0b62018-08-30 16:14:53 +020041 case SR_BOOL_T:
42 return value->data()->get_bool();
43 case SR_STRING_T:
44 return std::string(value->data()->get_string());
45 case SR_ENUM_T:
Václav Kubernát152ce222019-12-19 12:23:32 +010046 return enum_{std::string(value->data()->get_enum())};
Václav Kubernátb4e5b182020-11-16 19:55:09 +010047 case SR_IDENTITYREF_T: {
Jan Kundrát0d8abd12020-05-07 02:00:14 +020048 auto pair = splitModuleNode(value->data()->get_identityref());
49 return identityRef_{*pair.first, pair.second};
50 }
Jan Kundrát68985442020-05-07 02:15:34 +020051 case SR_BINARY_T:
52 return binary_{value->data()->get_binary()};
Jan Kundrát379bb572020-05-07 03:23:13 +020053 case SR_LEAF_EMPTY_T:
54 return empty_{};
Václav Kubernátb6ff0b62018-08-30 16:14:53 +020055 case SR_DECIMAL64_T:
56 return value->data()->get_decimal64();
57 case SR_CONTAINER_T:
Václav Kubernát144729d2020-01-08 15:20:35 +010058 return special_{SpecialValue::Container};
Václav Kubernátb6ff0b62018-08-30 16:14:53 +020059 case SR_CONTAINER_PRESENCE_T:
Václav Kubernát144729d2020-01-08 15:20:35 +010060 return special_{SpecialValue::PresenceContainer};
Václav Kubernátb6ff0b62018-08-30 16:14:53 +020061 case SR_LIST_T:
Václav Kubernát144729d2020-01-08 15:20:35 +010062 return special_{SpecialValue::List};
Václav Kubernátb4e5b182020-11-16 19:55:09 +010063 case SR_BITS_T: {
Václav Kubernát19097f32020-10-05 10:08:29 +020064 bits_ res;
65 std::istringstream ss(value->data()->get_bits());
66 while (!ss.eof()) {
67 std::string bit;
68 ss >> bit;
Václav Kubernát909d9662020-10-30 00:06:34 +010069 res.m_bits.push_back(bit);
Václav Kubernát19097f32020-10-05 10:08:29 +020070 }
71 return res;
Václav Kubernát19097f32020-10-05 10:08:29 +020072 }
Václav Kubernátb6ff0b62018-08-30 16:14:53 +020073 default: // TODO: implement all types
74 return value->val_to_string();
Václav Kubernátc89736b2018-08-30 16:14:05 +020075 }
76}
Václav Kubernát80aacc02018-08-22 17:41:54 +020077
Jan Kundrát68d4a2c2018-10-01 17:17:09 +020078struct valFromValue : boost::static_visitor<sysrepo::S_Val> {
79 sysrepo::S_Val operator()(const enum_& value) const
Václav Kubernát80aacc02018-08-22 17:41:54 +020080 {
Jan Kundrát68d4a2c2018-10-01 17:17:09 +020081 return std::make_shared<sysrepo::Val>(value.m_value.c_str(), SR_ENUM_T);
Václav Kubernát80aacc02018-08-22 17:41:54 +020082 }
83
Václav Kubernátab538992019-03-06 15:30:50 +010084 sysrepo::S_Val operator()(const binary_& value) const
85 {
86 return std::make_shared<sysrepo::Val>(value.m_value.c_str(), SR_BINARY_T);
87 }
88
Jan Kundrát379bb572020-05-07 03:23:13 +020089 sysrepo::S_Val operator()(const empty_) const
90 {
91 return std::make_shared<sysrepo::Val>(nullptr, SR_LEAF_EMPTY_T);
92 }
93
Václav Kubernáteeb38842019-03-20 19:46:05 +010094 sysrepo::S_Val operator()(const identityRef_& value) const
95 {
Jan Kundrát0d8abd12020-05-07 02:00:14 +020096 auto res = value.m_prefix ? (value.m_prefix.value().m_name + ":" + value.m_value) : value.m_value;
Václav Kubernáteeb38842019-03-20 19:46:05 +010097 return std::make_shared<sysrepo::Val>(res.c_str(), SR_IDENTITYREF_T);
98 }
99
Václav Kubernát144729d2020-01-08 15:20:35 +0100100 sysrepo::S_Val operator()(const special_& value) const
101 {
102 throw std::runtime_error("Tried constructing S_Val from a " + specialValueToString(value));
103 }
104
Jan Kundrát68d4a2c2018-10-01 17:17:09 +0200105 sysrepo::S_Val operator()(const std::string& value) const
Václav Kubernát80aacc02018-08-22 17:41:54 +0200106 {
Jan Kundrát68d4a2c2018-10-01 17:17:09 +0200107 return std::make_shared<sysrepo::Val>(value.c_str());
Václav Kubernát80aacc02018-08-22 17:41:54 +0200108 }
109
Václav Kubernát19097f32020-10-05 10:08:29 +0200110 sysrepo::S_Val operator()(const bits_& value) const
111 {
112 std::stringstream ss;
113 std::copy(value.m_bits.begin(), value.m_bits.end(), std::experimental::make_ostream_joiner(ss, " "));
114 return std::make_shared<sysrepo::Val>(ss.str().c_str(), SR_BITS_T);
115 }
116
Jan Kundrátbd178362019-02-05 19:00:04 +0100117 template <typename T>
118 sysrepo::S_Val operator()(const T& value) const
Václav Kubernát80aacc02018-08-22 17:41:54 +0200119 {
Jan Kundrát68d4a2c2018-10-01 17:17:09 +0200120 return std::make_shared<sysrepo::Val>(value);
Václav Kubernát80aacc02018-08-22 17:41:54 +0200121 }
122};
123
Jan Kundrát6ee84792020-01-24 01:43:36 +0100124struct updateSrValFromValue : boost::static_visitor<void> {
125 std::string xpath;
126 sysrepo::S_Val v;
127 updateSrValFromValue(const std::string& xpath, sysrepo::S_Val v)
128 : xpath(xpath)
129 , v(v)
130 {
131 }
132
133 void operator()(const enum_& value) const
134 {
135 v->set(xpath.c_str(), value.m_value.c_str(), SR_ENUM_T);
136 }
137
138 void operator()(const binary_& value) const
139 {
140 v->set(xpath.c_str(), value.m_value.c_str(), SR_BINARY_T);
141 }
142
Jan Kundrát379bb572020-05-07 03:23:13 +0200143 void operator()(const empty_) const
144 {
145 v->set(xpath.c_str(), nullptr, SR_LEAF_EMPTY_T);
146 }
147
Jan Kundrát6ee84792020-01-24 01:43:36 +0100148 void operator()(const identityRef_& value) const
149 {
150 v->set(xpath.c_str(), (value.m_prefix.value().m_name + ":" + value.m_value).c_str(), SR_IDENTITYREF_T);
151 }
152
153 void operator()(const special_& value) const
154 {
Václav Kubernáte7248b22020-06-26 15:38:59 +0200155 switch (value.m_value) {
156 case SpecialValue::PresenceContainer:
157 v->set(xpath.c_str(), nullptr, SR_CONTAINER_PRESENCE_T);
158 break;
159 case SpecialValue::List:
160 v->set(xpath.c_str(), nullptr, SR_LIST_T);
161 break;
162 default:
163 throw std::runtime_error("Tried constructing S_Val from a " + specialValueToString(value));
164 }
Jan Kundrát6ee84792020-01-24 01:43:36 +0100165 }
166
Václav Kubernát19097f32020-10-05 10:08:29 +0200167 auto operator()(const bits_& value) const
168 {
169 std::stringstream ss;
170 std::copy(value.m_bits.begin(), value.m_bits.end(), std::experimental::make_ostream_joiner(ss, " "));
171 v->set(xpath.c_str(), ss.str().c_str(), SR_BITS_T);
172 }
173
Jan Kundrát6ee84792020-01-24 01:43:36 +0100174 void operator()(const std::string& value) const
175 {
176 v->set(xpath.c_str(), value.c_str(), SR_STRING_T);
177 }
178
179 template <typename T>
180 void operator()(const T value) const
181 {
182 v->set(xpath.c_str(), value);
183 }
184};
185
Václav Kubernát812ee282018-08-30 17:10:03 +0200186SysrepoAccess::~SysrepoAccess() = default;
Václav Kubernát80aacc02018-08-22 17:41:54 +0200187
Václav Kubernát715c85c2020-04-14 01:46:08 +0200188sr_datastore_t toSrDatastore(Datastore datastore)
189{
190 switch (datastore) {
191 case Datastore::Running:
192 return SR_DS_RUNNING;
193 case Datastore::Startup:
194 return SR_DS_STARTUP;
195 }
196 __builtin_unreachable();
197}
198
Václav Kubernát654303f2020-07-31 13:16:54 +0200199SysrepoAccess::SysrepoAccess(const Datastore datastore)
200 : m_connection(std::make_shared<sysrepo::Connection>())
201 , m_session(std::make_shared<sysrepo::Session>(m_connection))
202 , m_schema(std::make_shared<YangSchema>(m_session->get_context()))
Václav Kubernát80aacc02018-08-22 17:41:54 +0200203{
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200204 try {
Václav Kubernát715c85c2020-04-14 01:46:08 +0200205 m_session = std::make_shared<sysrepo::Session>(m_connection, toSrDatastore(datastore));
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200206 } catch (sysrepo::sysrepo_exception& ex) {
207 reportErrors();
208 }
Václav Kubernát80aacc02018-08-22 17:41:54 +0200209}
210
Václav Kubernátd6282912020-06-23 14:49:34 +0200211DatastoreAccess::Tree SysrepoAccess::getItems(const std::string& path) const
Václav Kubernát80aacc02018-08-22 17:41:54 +0200212{
Václav Kubernátb6ff0b62018-08-30 16:14:53 +0200213 using namespace std::string_literals;
Jan Kundrátb331b552020-01-23 15:25:29 +0100214 Tree res;
Václav Kubernát80aacc02018-08-22 17:41:54 +0200215
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200216 try {
Václav Kubernát654303f2020-07-31 13:16:54 +0200217 auto oldDs = m_session->session_get_ds();
218 m_session->session_switch_ds(SR_DS_OPERATIONAL);
Václav Kubernátf90a0b52020-11-06 05:53:03 +0100219 auto config = m_session->get_data(((path == "/") ? "/*" : path).c_str());
Václav Kubernát654303f2020-07-31 13:16:54 +0200220 m_session->session_switch_ds(oldDs);
221 if (config) {
222 lyNodesToTree(res, config->tree_for());
Václav Kubernátb6ff0b62018-08-30 16:14:53 +0200223 }
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200224 } catch (sysrepo::sysrepo_exception& ex) {
225 reportErrors();
Václav Kubernátc89736b2018-08-30 16:14:05 +0200226 }
Václav Kubernát80aacc02018-08-22 17:41:54 +0200227 return res;
228}
229
230void SysrepoAccess::setLeaf(const std::string& path, leaf_data_ value)
231{
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200232 try {
233 m_session->set_item(path.c_str(), boost::apply_visitor(valFromValue(), value));
234 } catch (sysrepo::sysrepo_exception& ex) {
235 reportErrors();
236 }
Václav Kubernát80aacc02018-08-22 17:41:54 +0200237}
238
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200239void SysrepoAccess::createItem(const std::string& path)
Václav Kubernát80aacc02018-08-22 17:41:54 +0200240{
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200241 try {
242 m_session->set_item(path.c_str());
243 } catch (sysrepo::sysrepo_exception& ex) {
244 reportErrors();
245 }
Václav Kubernát80aacc02018-08-22 17:41:54 +0200246}
247
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200248void SysrepoAccess::deleteItem(const std::string& path)
Václav Kubernátf5f64f02019-03-19 17:15:47 +0100249{
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200250 try {
Václav Kubernát654303f2020-07-31 13:16:54 +0200251 // Have to use SR_EDIT_ISOLATE, because deleting something that's been set without committing is not supported
252 // https://github.com/sysrepo/sysrepo/issues/1967#issuecomment-625085090
253 m_session->delete_item(path.c_str(), SR_EDIT_ISOLATE);
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200254 } catch (sysrepo::sysrepo_exception& ex) {
255 reportErrors();
256 }
Václav Kubernátf5f64f02019-03-19 17:15:47 +0100257}
258
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200259struct impl_toSrMoveOp {
260 sr_move_position_t operator()(yang::move::Absolute& absolute)
261 {
262 return absolute == yang::move::Absolute::Begin ? SR_MOVE_FIRST : SR_MOVE_LAST;
263 }
264 sr_move_position_t operator()(yang::move::Relative& relative)
265 {
266 return relative.m_position == yang::move::Relative::Position::After ? SR_MOVE_AFTER : SR_MOVE_BEFORE;
267 }
268};
269
270sr_move_position_t toSrMoveOp(std::variant<yang::move::Absolute, yang::move::Relative> move)
271{
272 return std::visit(impl_toSrMoveOp{}, move);
273}
274
275void SysrepoAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
276{
Václav Kubernát654303f2020-07-31 13:16:54 +0200277 std::string destination;
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200278 if (std::holds_alternative<yang::move::Relative>(move)) {
279 auto relative = std::get<yang::move::Relative>(move);
280 if (m_schema->nodeType(source) == yang::NodeTypes::LeafList) {
Václav Kubernát654303f2020-07-31 13:16:54 +0200281 destination = leafDataToString(relative.m_path.at("."));
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200282 } else {
Václav Kubernát654303f2020-07-31 13:16:54 +0200283 destination = instanceToString(relative.m_path);
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200284 }
285 }
Václav Kubernát654303f2020-07-31 13:16:54 +0200286 m_session->move_item(source.c_str(), toSrMoveOp(move), destination.c_str(), destination.c_str());
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200287}
288
Václav Kubernát812ee282018-08-30 17:10:03 +0200289void SysrepoAccess::commitChanges()
290{
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200291 try {
Václav Kubernát654303f2020-07-31 13:16:54 +0200292 m_session->apply_changes(OPERATION_TIMEOUT_MS, 1);
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200293 } catch (sysrepo::sysrepo_exception& ex) {
294 reportErrors();
295 }
Václav Kubernát812ee282018-08-30 17:10:03 +0200296}
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200297
Václav Kubernát6d791432018-10-25 16:00:35 +0200298void SysrepoAccess::discardChanges()
299{
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200300 try {
301 m_session->discard_changes();
302 } catch (sysrepo::sysrepo_exception& ex) {
303 reportErrors();
304 }
Václav Kubernát6d791432018-10-25 16:00:35 +0200305}
306
Václav Kubernátb3960f82020-12-01 03:21:48 +0100307DatastoreAccess::Tree SysrepoAccess::execute(const std::string& path, const Tree& input)
Václav Kubernáta8789602020-07-20 15:18:19 +0200308{
Václav Kubernát40776132021-02-03 08:47:33 +0100309 auto inputNode = m_schema->dataNodeFromPath(path);
310 for (const auto& [k, v] : input) {
311 auto node = m_schema->dataNodeFromPath(joinPaths(path, k), leafDataToString(v));
312 inputNode->merge(node, 0);
313 }
314
Václav Kubernátd57ec452021-02-05 16:38:03 +0100315 Tree res;
Václav Kubernát40776132021-02-03 08:47:33 +0100316 auto output = m_session->rpc_send(inputNode);
Václav Kubernátd57ec452021-02-05 16:38:03 +0100317 if (output) {
318 // The output is "some top-level node". If we actually want the output of our RPC/action we need to use
319 // find_path.
320 lyNodesToTree(res, output->find_path(path.c_str())->data(), joinPaths(path, "/"));
321 }
322 return res;
Václav Kubernáta8789602020-07-20 15:18:19 +0200323}
Jan Kundrát6ee84792020-01-24 01:43:36 +0100324
Václav Kubernát7160a132020-04-03 02:11:01 +0200325void SysrepoAccess::copyConfig(const Datastore source, const Datastore destination)
326{
Václav Kubernát654303f2020-07-31 13:16:54 +0200327 auto oldDs = m_session->session_get_ds();
328 m_session->session_switch_ds(toSrDatastore(destination));
329 m_session->copy_config(toSrDatastore(source), nullptr, OPERATION_TIMEOUT_MS, 1);
330 m_session->session_switch_ds(oldDs);
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200331}
332
333std::shared_ptr<Schema> SysrepoAccess::schema()
334{
335 return m_schema;
336}
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200337
Václav Kubernátd6282912020-06-23 14:49:34 +0200338[[noreturn]] void SysrepoAccess::reportErrors() const
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200339{
Václav Kubernát654303f2020-07-31 13:16:54 +0200340 // I only use get_error to get error info, since the error code from
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200341 // sysrepo_exception doesn't really give any meaningful information. For
342 // example an "invalid argument" error could mean a node isn't enabled, or
343 // it could mean something totally different and there is no documentation
344 // for that, so it's better to just use the message sysrepo gives me.
Václav Kubernát654303f2020-07-31 13:16:54 +0200345 auto srErrors = m_session->get_error();
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200346 std::vector<DatastoreError> res;
347
348 for (size_t i = 0; i < srErrors->error_cnt(); i++) {
Václav Kubernát654303f2020-07-31 13:16:54 +0200349 res.emplace_back(srErrors->message(i), srErrors->xpath(i) ? std::optional<std::string>{srErrors->xpath(i)} : std::nullopt);
Václav Kubernátc58e4aa2019-04-03 18:37:32 +0200350 }
351
352 throw DatastoreException(res);
353}
Václav Kubernátab612e92019-11-26 19:51:31 +0100354
355std::vector<ListInstance> SysrepoAccess::listInstances(const std::string& path)
356{
357 std::vector<ListInstance> res;
358 auto lists = getItems(path);
359
360 decltype(lists) instances;
361 auto wantedTree = *(m_schema->dataNodeFromPath(path)->find_path(path.c_str())->data().begin());
Václav Kubernátb4e5b182020-11-16 19:55:09 +0100362 std::copy_if(lists.begin(), lists.end(), std::inserter(instances, instances.end()), [this, pathToCheck = wantedTree->schema()->path()](const auto& item) {
Václav Kubernátab612e92019-11-26 19:51:31 +0100363 // This filters out non-instances.
364 if (item.second.type() != typeid(special_) || boost::get<special_>(item.second).m_value != SpecialValue::List) {
365 return false;
366 }
367
368 // Now, getItems is recursive: it gives everything including nested lists. So I try create a tree from the instance...
369 auto instanceTree = *(m_schema->dataNodeFromPath(item.first)->find_path(item.first.c_str())->data().begin());
370 // And then check if its schema path matches the list we actually want. This filters out lists which are not the ones I requested.
371 return instanceTree->schema()->path() == pathToCheck;
372 });
373
374 // If there are no instances, then just return
375 if (instances.empty()) {
376 return res;
377 }
378
379 // I need to find out which keys does the list have. To do that, I create a
380 // tree from the first instance. This is gives me some top level node,
381 // which will be our list in case out list is a top-level node. In case it
382 // isn't, we have call find_path on the top level node. After that, I just
383 // retrieve the keys.
384 auto topLevelTree = m_schema->dataNodeFromPath(instances.begin()->first);
385 auto list = *(topLevelTree->find_path(path.c_str())->data().begin());
386 auto keys = libyang::Schema_Node_List{list->schema()}.keys();
387
388 // Creating a full tree at the same time from the values sysrepo gives me
389 // would be a pain (and after sysrepo switches to libyang meaningless), so
390 // I just use this algorithm to create data nodes one by one and get the
391 // key values from them.
392 for (const auto& instance : instances) {
393 auto wantedList = *(m_schema->dataNodeFromPath(instance.first)->find_path(path.c_str())->data().begin());
394 ListInstance instanceRes;
395 for (const auto& key : keys) {
396 auto vec = wantedList->find_path(key->name())->data();
Václav Kubernát2e4cafe2020-11-05 01:53:21 +0100397 auto leaf = std::make_shared<libyang::Data_Node_Leaf_List>(*(vec.begin()));
398 instanceRes.emplace(key->name(), leafValueFromNode(leaf));
Václav Kubernátab612e92019-11-26 19:51:31 +0100399 }
Václav Kubernátfaacd022020-07-08 16:44:38 +0200400 res.emplace_back(instanceRes);
Václav Kubernátab612e92019-11-26 19:51:31 +0100401 }
402
403 return res;
404}
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200405
406std::string SysrepoAccess::dump(const DataFormat format) const
407{
Václav Kubernát654303f2020-07-31 13:16:54 +0200408 auto root = m_session->get_data("/*");
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200409 return root->print_mem(format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
410}