| #include <boost/algorithm/string/predicate.hpp> |
| #include <experimental/iterator> |
| #include <fstream> |
| #include <iostream> |
| #include <libyang/Tree_Data.hpp> |
| #include <libyang/libyang.h> |
| #include "UniqueResource.hpp" |
| #include "libyang_utils.hpp" |
| #include "utils.hpp" |
| #include "yang_access.hpp" |
| #include "yang_schema.hpp" |
| |
| namespace { |
| template <typename Type> using lyPtrDeleter_type = void (*)(Type*); |
| template <typename Type> const lyPtrDeleter_type<Type> lyPtrDeleter; |
| template <> const auto lyPtrDeleter<ly_set> = ly_set_free; |
| template <> const auto lyPtrDeleter<ly_ctx> = static_cast<lyPtrDeleter_type<ly_ctx>>([] (auto* ptr) {ly_ctx_destroy(ptr, nullptr);}); |
| template <> const auto lyPtrDeleter<lyd_node> = lyd_free_withsiblings; |
| |
| template <typename Type> |
| auto lyWrap(Type* ptr) |
| { |
| return std::unique_ptr<Type, lyPtrDeleter_type<Type>>{ptr, lyPtrDeleter<Type>}; |
| } |
| |
| // Convenient for functions that take m_datastore as an argument |
| using DatastoreType = std::unique_ptr<lyd_node, lyPtrDeleter_type<lyd_node>>; |
| } |
| |
| YangAccess::YangAccess() |
| : m_ctx(lyWrap(ly_ctx_new(nullptr, LY_CTX_DISABLE_SEARCHDIR_CWD))) |
| , m_datastore(lyWrap<lyd_node>(nullptr)) |
| , m_schema(std::make_shared<YangSchema>(libyang::create_new_Context(m_ctx.get()))) |
| , m_validation_mode(LYD_OPT_DATA) |
| { |
| } |
| |
| YangAccess::YangAccess(std::shared_ptr<YangSchema> schema) |
| : m_ctx(schema->m_context->swig_ctx(), [](auto) {}) |
| , m_datastore(lyWrap<lyd_node>(nullptr)) |
| , m_schema(schema) |
| , m_validation_mode(LYD_OPT_RPC) |
| { |
| } |
| |
| YangAccess::~YangAccess() = default; |
| |
| [[noreturn]] void YangAccess::getErrorsAndThrow() const |
| { |
| auto errors = libyang::get_ly_errors(libyang::create_new_Context(m_ctx.get())); |
| std::vector<DatastoreError> errorsRes; |
| for (const auto& error : errors) { |
| using namespace std::string_view_literals; |
| errorsRes.emplace_back(error->errmsg(), error->errpath() != ""sv ? std::optional{error->errpath()} : std::nullopt); |
| } |
| |
| throw DatastoreException(errorsRes); |
| } |
| |
| void YangAccess::impl_newPath(const std::string& path, const std::optional<std::string>& value) |
| { |
| auto newNode = lyd_new_path(m_datastore.get(), m_ctx.get(), path.c_str(), value ? (void*)value->c_str() : nullptr, LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_UPDATE); |
| if (!newNode) { |
| getErrorsAndThrow(); |
| } |
| if (!m_datastore) { |
| m_datastore = lyWrap(newNode); |
| } |
| } |
| |
| namespace { |
| void impl_unlink(DatastoreType& datastore, lyd_node* what) |
| { |
| // If the node to be unlinked is the one our datastore variable points to, we need to find a new one to point to (one of its siblings) |
| if (datastore.get() == what) { |
| auto oldDatastore = datastore.release(); |
| if (oldDatastore->prev != oldDatastore) { |
| datastore = lyWrap(oldDatastore->prev); |
| } else { |
| datastore = lyWrap(oldDatastore->next); |
| } |
| } |
| |
| lyd_unlink(what); |
| } |
| } |
| |
| void YangAccess::impl_removeNode(const std::string& path) |
| { |
| auto set = lyWrap(lyd_find_path(m_datastore.get(), path.c_str())); |
| if (!set || set->number == 0) { |
| // Check if schema node exists - lyd_find_path first checks if the first argument is non-null before checking for path validity |
| if (!ly_ctx_get_node(m_ctx.get(), nullptr, path.c_str(), 0)) { |
| throw DatastoreException{{DatastoreError{"Schema node doesn't exist.", path}}}; |
| } |
| // Check if libyang found another error |
| if (ly_err_first(m_ctx.get())) { |
| getErrorsAndThrow(); |
| } |
| |
| // Otherwise the datastore just doesn't contain the wanted node. |
| throw DatastoreException{{DatastoreError{"Data node doesn't exist.", path}}}; |
| } |
| |
| auto toRemove = set->set.d[0]; |
| |
| impl_unlink(m_datastore, toRemove); |
| |
| lyd_free(toRemove); |
| } |
| |
| void YangAccess::validate() |
| { |
| auto datastore = m_datastore.release(); |
| |
| if (m_validation_mode == LYD_OPT_RPC) { |
| lyd_validate(&datastore, m_validation_mode, nullptr); |
| } else { |
| lyd_validate(&datastore, m_validation_mode | LYD_OPT_DATA_NO_YANGLIB, m_ctx.get()); |
| } |
| m_datastore = lyWrap(datastore); |
| } |
| |
| DatastoreAccess::Tree YangAccess::getItems(const std::string& path) const |
| { |
| DatastoreAccess::Tree res; |
| if (!m_datastore) { |
| return res; |
| } |
| |
| auto set = lyWrap(lyd_find_path(m_datastore.get(), path == "/" ? "/*" : path.c_str())); |
| auto setWrapper = libyang::Set(set.get(), nullptr); |
| std::optional<std::string> ignoredXPathPrefix; |
| if (m_datastore->schema->nodetype == LYS_RPC) { |
| auto path = std::unique_ptr<char, decltype(&free)>(lys_path(m_datastore->schema, 0), &free); |
| ignoredXPathPrefix = joinPaths(path.get(), "/"); |
| } |
| lyNodesToTree(res, setWrapper.data(), ignoredXPathPrefix); |
| return res; |
| } |
| |
| void YangAccess::setLeaf(const std::string& path, leaf_data_ value) |
| { |
| auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value)); |
| impl_newPath(path, lyValue); |
| } |
| |
| void YangAccess::createItem(const std::string& path) |
| { |
| impl_newPath(path); |
| } |
| |
| void YangAccess::deleteItem(const std::string& path) |
| { |
| impl_removeNode(path); |
| } |
| |
| namespace { |
| struct impl_moveItem { |
| DatastoreType& m_datastore; |
| lyd_node* m_sourceNode; |
| |
| void operator()(yang::move::Absolute absolute) const |
| { |
| auto set = lyWrap(lyd_find_instance(m_sourceNode, m_sourceNode->schema)); |
| if (set->number == 1) { // m_sourceNode is the sole instance, do nothing |
| return; |
| } |
| |
| doUnlink(); |
| switch (absolute) { |
| case yang::move::Absolute::Begin: |
| if (set->set.d[0] == m_sourceNode) { // List is already at the beginning, do nothing |
| return; |
| } |
| lyd_insert_before(set->set.d[0], m_sourceNode); |
| return; |
| case yang::move::Absolute::End: |
| if (set->set.d[set->number - 1] == m_sourceNode) { // List is already at the end, do nothing |
| return; |
| } |
| lyd_insert_after(set->set.d[set->number - 1], m_sourceNode); |
| return; |
| } |
| } |
| |
| void operator()(const yang::move::Relative& relative) const |
| { |
| auto keySuffix = m_sourceNode->schema->nodetype == LYS_LIST ? instanceToString(relative.m_path) |
| : leafDataToString(relative.m_path.at(".")); |
| lyd_node* destNode; |
| lyd_find_sibling_val(m_sourceNode, m_sourceNode->schema, keySuffix.c_str(), &destNode); |
| |
| doUnlink(); |
| if (relative.m_position == yang::move::Relative::Position::After) { |
| lyd_insert_after(destNode, m_sourceNode); |
| } else { |
| lyd_insert_before(destNode, m_sourceNode); |
| } |
| } |
| |
| private: |
| void doUnlink() const |
| { |
| impl_unlink(m_datastore, m_sourceNode); |
| } |
| }; |
| } |
| |
| void YangAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move) |
| { |
| auto set = lyWrap(lyd_find_path(m_datastore.get(), source.c_str())); |
| if (!set) { // Error, the node probably doesn't exist in the schema |
| getErrorsAndThrow(); |
| } |
| if (set->number == 0) { |
| return; |
| } |
| auto sourceNode = set->set.d[0]; |
| std::visit(impl_moveItem{m_datastore, sourceNode}, move); |
| } |
| |
| void YangAccess::commitChanges() |
| { |
| validate(); |
| } |
| |
| void YangAccess::discardChanges() |
| { |
| } |
| |
| [[noreturn]] void YangAccess::impl_execute(const std::string& type, const std::string& path, const Tree& input) |
| { |
| auto root = lyWrap(lyd_new_path(nullptr, m_ctx.get(), path.c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0)); |
| if (!root) { |
| getErrorsAndThrow(); |
| } |
| for (const auto& [k, v] : input) { |
| if (v.type() == typeid(special_) && boost::get<special_>(v).m_value != SpecialValue::PresenceContainer) { |
| continue; |
| } |
| auto node = lyd_new_path(root.get(), m_ctx.get(), joinPaths(path, k).c_str(), (void*)leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, 0); |
| if (!node) { |
| getErrorsAndThrow(); |
| } |
| } |
| throw std::logic_error("in-memory datastore doesn't support executing " + type + "s"); |
| } |
| |
| DatastoreAccess::Tree YangAccess::executeRpc(const std::string& path, const Tree& input) |
| { |
| impl_execute("RPC", path, input); |
| } |
| |
| DatastoreAccess::Tree YangAccess::executeAction(const std::string& path, const Tree& input) |
| { |
| impl_execute("action", path, input); |
| } |
| |
| void YangAccess::copyConfig(const Datastore source, const Datastore dest) |
| { |
| if (source == Datastore::Startup && dest == Datastore::Running) { |
| m_datastore = nullptr; |
| } |
| } |
| |
| std::shared_ptr<Schema> YangAccess::schema() |
| { |
| return m_schema; |
| } |
| |
| std::vector<ListInstance> YangAccess::listInstances(const std::string& path) |
| { |
| std::vector<ListInstance> res; |
| if (!m_datastore) { |
| return res; |
| } |
| |
| auto instances = lyWrap(lyd_find_path(m_datastore.get(), path.c_str())); |
| auto instancesWrapper = libyang::Set(instances.get(), nullptr); |
| for (const auto& list : instancesWrapper.data()) { |
| ListInstance instance; |
| for (const auto& child : list->child()->tree_for()) { |
| if (child->schema()->nodetype() == LYS_LEAF) { |
| libyang::Schema_Node_Leaf leafSchema(child->schema()); |
| if (leafSchema.is_key()) { |
| auto leafData = std::make_shared<libyang::Data_Node_Leaf_List>(child); |
| instance.insert({leafSchema.name(), leafValueFromNode(leafData)}); |
| } |
| } |
| } |
| res.emplace_back(instance); |
| } |
| return res; |
| } |
| |
| std::string YangAccess::dump(const DataFormat format) const |
| { |
| char* output; |
| lyd_print_mem(&output, m_datastore.get(), format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT); |
| std::unique_ptr<char, decltype(&free)> deleter{output, free}; |
| |
| if (output) { |
| std::string res = output; |
| return res; |
| } |
| |
| return ""; |
| } |
| |
| void YangAccess::loadModule(const std::string& name) |
| { |
| m_schema->loadModule(name); |
| } |
| |
| void YangAccess::addSchemaFile(const std::string& path) |
| { |
| m_schema->addSchemaFile(path.c_str()); |
| } |
| |
| void YangAccess::addSchemaDir(const std::string& path) |
| { |
| m_schema->addSchemaDirectory(path.c_str()); |
| } |
| |
| void YangAccess::enableFeature(const std::string& module, const std::string& feature) |
| { |
| m_schema->enableFeature(module, feature); |
| } |
| |
| void YangAccess::addDataFile(const std::string& path) |
| { |
| std::ifstream fs(path); |
| char firstChar; |
| fs >> firstChar; |
| |
| std::cout << "Parsing \"" << path << "\" as " << (firstChar == '{' ? "JSON" : "XML") << "...\n"; |
| auto dataNode = lyd_parse_path(m_ctx.get(), path.c_str(), firstChar == '{' ? LYD_JSON : LYD_XML, LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB | LYD_OPT_TRUSTED); |
| |
| if (!dataNode) { |
| throw std::runtime_error("Supplied data file " + path + " couldn't be parsed."); |
| } |
| |
| if (!m_datastore) { |
| m_datastore = lyWrap(dataNode); |
| } else { |
| lyd_merge(m_datastore.get(), dataNode, LYD_OPT_DESTRUCT); |
| } |
| |
| validate(); |
| } |