blob: a0dc4b4f25d8a6006314aa6baf730f0f2d1645e0 [file] [log] [blame]
/**
* @file resolve.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libyang resolve functions
*
* Copyright (c) 2015 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.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include "libyang.h"
#include "resolve.h"
#include "common.h"
#include "xpath.h"
#include "parser.h"
#include "parser_yang.h"
#include "xml_internal.h"
#include "dict_private.h"
#include "tree_internal.h"
#include "extensions.h"
int
parse_range_dec64(const char **str_num, uint8_t dig, int64_t *num)
{
const char *ptr;
int minus = 0;
int64_t ret = 0;
int8_t str_exp, str_dig = -1, trailing_zeros = 0;
ptr = *str_num;
if (ptr[0] == '-') {
minus = 1;
++ptr;
} else if (ptr[0] == '+') {
++ptr;
}
if (!isdigit(ptr[0])) {
/* there must be at least one */
return 1;
}
for (str_exp = 0; isdigit(ptr[0]) || ((ptr[0] == '.') && (str_dig < 0)); ++ptr) {
if (str_exp > 18) {
return 1;
}
if (ptr[0] == '.') {
if (ptr[1] == '.') {
/* it's the next interval */
break;
}
++str_dig;
} else {
ret = ret * 10 + (ptr[0] - '0');
if (str_dig > -1) {
++str_dig;
if (ptr[0] == '0') {
/* possibly trailing zero */
trailing_zeros++;
} else {
trailing_zeros = 0;
}
}
++str_exp;
}
}
if (str_dig == 0) {
/* no digits after '.' */
return 1;
} else if (str_dig == -1) {
/* there are 0 numbers after the floating point */
str_dig = 0;
}
/* remove trailing zeros */
if (trailing_zeros) {
str_dig -= trailing_zeros;
str_exp -= trailing_zeros;
ret = ret / dec_pow(trailing_zeros);
}
/* it's parsed, now adjust the number based on fraction-digits, if needed */
if (str_dig < dig) {
if ((str_exp - 1) + (dig - str_dig) > 18) {
return 1;
}
ret *= dec_pow(dig - str_dig);
}
if (str_dig > dig) {
return 1;
}
if (minus) {
ret *= -1;
}
*str_num = ptr;
*num = ret;
return 0;
}
/**
* @brief Parse an identifier.
*
* ;; An identifier MUST NOT start with (('X'|'x') ('M'|'m') ('L'|'l'))
* identifier = (ALPHA / "_")
* *(ALPHA / DIGIT / "_" / "-" / ".")
*
* @param[in] id Identifier to use.
*
* @return Number of characters successfully parsed.
*/
int
parse_identifier(const char *id)
{
int parsed = 0;
assert(id);
if (!isalpha(id[0]) && (id[0] != '_')) {
return -parsed;
}
++parsed;
++id;
while (isalnum(id[0]) || (id[0] == '_') || (id[0] == '-') || (id[0] == '.')) {
++parsed;
++id;
}
return parsed;
}
/**
* @brief Parse a node-identifier.
*
* node-identifier = [module-name ":"] identifier
*
* @param[in] id Identifier to use.
* @param[out] mod_name Points to the module name, NULL if there is not any.
* @param[out] mod_name_len Length of the module name, 0 if there is not any.
* @param[out] name Points to the node name.
* @param[out] nam_len Length of the node name.
* @param[out] all_desc Whether the path starts with '/', only supported in extended paths.
* @param[in] extended Whether to accept an extended path (support for [prefix:]*, /[prefix:]*, /[prefix:].).
*
* @return Number of characters successfully parsed,
* positive on success, negative on failure.
*/
static int
parse_node_identifier(const char *id, const char **mod_name, int *mod_name_len, const char **name, int *nam_len,
int *all_desc, int extended)
{
int parsed = 0, ret;
assert(id);
assert((mod_name && mod_name_len) || (!mod_name && !mod_name_len));
assert((name && nam_len) || (!name && !nam_len));
assert(!extended || all_desc);
if (mod_name) {
*mod_name = NULL;
*mod_name_len = 0;
}
if (name) {
*name = NULL;
*nam_len = 0;
}
if (extended) {
/* try to parse only the extended expressions */
if (id[parsed] == '/') {
*all_desc = 1;
} else {
*all_desc = 0;
}
/* is there a prefix? */
ret = parse_identifier(id + *all_desc);
if (ret > 0) {
if (id[*all_desc + ret] != ':') {
/* this is not a prefix, so not an extended id */
goto standard_id;
}
if (mod_name) {
*mod_name = id + *all_desc;
*mod_name_len = ret;
}
/* "/" and ":" */
ret += *all_desc + 1;
} else {
ret = *all_desc;
}
/* parse either "*" or "." */
if (!strcmp(id + ret, "*")) {
if (name) {
*name = id + ret;
*nam_len = 1;
}
++ret;
return ret;
} else if (!strcmp(id + ret, ".")) {
if (!*all_desc) {
/* /. is redundant expression, we do not accept it */
return -ret;
}
if (name) {
*name = id + ret;
*nam_len = 1;
}
++ret;
return ret;
}
/* else a standard id, parse it all again */
}
standard_id:
if ((ret = parse_identifier(id)) < 1) {
return ret;
}
if (mod_name) {
*mod_name = id;
*mod_name_len = ret;
}
parsed += ret;
id += ret;
/* there is prefix */
if (id[0] == ':') {
++parsed;
++id;
/* there isn't */
} else {
if (name && mod_name) {
*name = *mod_name;
}
if (mod_name) {
*mod_name = NULL;
}
if (nam_len && mod_name_len) {
*nam_len = *mod_name_len;
}
if (mod_name_len) {
*mod_name_len = 0;
}
return parsed;
}
/* identifier (node name) */
if ((ret = parse_identifier(id)) < 1) {
return -parsed+ret;
}
if (name) {
*name = id;
*nam_len = ret;
}
return parsed+ret;
}
/**
* @brief Parse a path-predicate (leafref).
*
* path-predicate = "[" *WSP path-equality-expr *WSP "]"
* path-equality-expr = node-identifier *WSP "=" *WSP path-key-expr
*
* @param[in] id Identifier to use.
* @param[out] prefix Points to the prefix, NULL if there is not any.
* @param[out] pref_len Length of the prefix, 0 if there is not any.
* @param[out] name Points to the node name.
* @param[out] nam_len Length of the node name.
* @param[out] path_key_expr Points to the path-key-expr.
* @param[out] pke_len Length of the path-key-expr.
* @param[out] has_predicate Flag to mark whether there is another predicate following.
*
* @return Number of characters successfully parsed,
* positive on success, negative on failure.
*/
static int
parse_path_predicate(const char *id, const char **prefix, int *pref_len, const char **name, int *nam_len,
const char **path_key_expr, int *pke_len, int *has_predicate)
{
const char *ptr;
int parsed = 0, ret;
assert(id);
if (prefix) {
*prefix = NULL;
}
if (pref_len) {
*pref_len = 0;
}
if (name) {
*name = NULL;
}
if (nam_len) {
*nam_len = 0;
}
if (path_key_expr) {
*path_key_expr = NULL;
}
if (pke_len) {
*pke_len = 0;
}
if (has_predicate) {
*has_predicate = 0;
}
if (id[0] != '[') {
return -parsed;
}
++parsed;
++id;
while (isspace(id[0])) {
++parsed;
++id;
}
if ((ret = parse_node_identifier(id, prefix, pref_len, name, nam_len, NULL, 0)) < 1) {
return -parsed+ret;
}
parsed += ret;
id += ret;
while (isspace(id[0])) {
++parsed;
++id;
}
if (id[0] != '=') {
return -parsed;
}
++parsed;
++id;
while (isspace(id[0])) {
++parsed;
++id;
}
if ((ptr = strchr(id, ']')) == NULL) {
return -parsed;
}
--ptr;
while (isspace(ptr[0])) {
--ptr;
}
++ptr;
ret = ptr-id;
if (path_key_expr) {
*path_key_expr = id;
}
if (pke_len) {
*pke_len = ret;
}
parsed += ret;
id += ret;
while (isspace(id[0])) {
++parsed;
++id;
}
assert(id[0] == ']');
if (id[1] == '[') {
*has_predicate = 1;
}
return parsed+1;
}
/**
* @brief Parse a path-key-expr (leafref). First call parses "current()", all
* the ".." and the first node-identifier, other calls parse a single
* node-identifier each.
*
* path-key-expr = current-function-invocation *WSP "/" *WSP
* rel-path-keyexpr
* rel-path-keyexpr = 1*(".." *WSP "/" *WSP)
* *(node-identifier *WSP "/" *WSP)
* node-identifier
*
* @param[in] id Identifier to use.
* @param[out] prefix Points to the prefix, NULL if there is not any.
* @param[out] pref_len Length of the prefix, 0 if there is not any.
* @param[out] name Points to the node name.
* @param[out] nam_len Length of the node name.
* @param[out] parent_times Number of ".." in the path. Must be 0 on the first call,
* must not be changed between consecutive calls.
* @return Number of characters successfully parsed,
* positive on success, negative on failure.
*/
static int
parse_path_key_expr(const char *id, const char **prefix, int *pref_len, const char **name, int *nam_len,
int *parent_times)
{
int parsed = 0, ret, par_times = 0;
assert(id);
assert(parent_times);
if (prefix) {
*prefix = NULL;
}
if (pref_len) {
*pref_len = 0;
}
if (name) {
*name = NULL;
}
if (nam_len) {
*nam_len = 0;
}
if (!*parent_times) {
/* current-function-invocation *WSP "/" *WSP rel-path-keyexpr */
if (strncmp(id, "current()", 9)) {
return -parsed;
}
parsed += 9;
id += 9;
while (isspace(id[0])) {
++parsed;
++id;
}
if (id[0] != '/') {
return -parsed;
}
++parsed;
++id;
while (isspace(id[0])) {
++parsed;
++id;
}
/* rel-path-keyexpr */
if (strncmp(id, "..", 2)) {
return -parsed;
}
++par_times;
parsed += 2;
id += 2;
while (isspace(id[0])) {
++parsed;
++id;
}
}
/* 1*(".." *WSP "/" *WSP) *(node-identifier *WSP "/" *WSP) node-identifier
*
* first parent reference with whitespaces already parsed
*/
if (id[0] != '/') {
return -parsed;
}
++parsed;
++id;
while (isspace(id[0])) {
++parsed;
++id;
}
while (!strncmp(id, "..", 2) && !*parent_times) {
++par_times;
parsed += 2;
id += 2;
while (isspace(id[0])) {
++parsed;
++id;
}
if (id[0] != '/') {
return -parsed;
}
++parsed;
++id;
while (isspace(id[0])) {
++parsed;
++id;
}
}
if (!*parent_times) {
*parent_times = par_times;
}
/* all parent references must be parsed at this point */
if ((ret = parse_node_identifier(id, prefix, pref_len, name, nam_len, NULL, 0)) < 1) {
return -parsed + ret;
}
parsed += ret;
id += ret;
return parsed;
}
/**
* @brief Parse path-arg (leafref).
*
* path-arg = absolute-path / relative-path
* absolute-path = 1*("/" (node-identifier *path-predicate))
* relative-path = 1*(".." "/") descendant-path
*
* @param[in] mod Module of the context node to get correct prefix in case it is not explicitly specified
* @param[in] id Identifier to use.
* @param[out] prefix Points to the prefix, NULL if there is not any.
* @param[out] pref_len Length of the prefix, 0 if there is not any.
* @param[out] name Points to the node name.
* @param[out] nam_len Length of the node name.
* @param[out] parent_times Number of ".." in the path. Must be 0 on the first call,
* must not be changed between consecutive calls. -1 if the
* path is relative.
* @param[out] has_predicate Flag to mark whether there is a predicate specified.
*
* @return Number of characters successfully parsed,
* positive on success, negative on failure.
*/
static int
parse_path_arg(const struct lys_module *mod, const char *id, const char **prefix, int *pref_len,
const char **name, int *nam_len, int *parent_times, int *has_predicate)
{
int parsed = 0, ret, par_times = 0;
assert(id);
assert(parent_times);
if (prefix) {
*prefix = NULL;
}
if (pref_len) {
*pref_len = 0;
}
if (name) {
*name = NULL;
}
if (nam_len) {
*nam_len = 0;
}
if (has_predicate) {
*has_predicate = 0;
}
if (!*parent_times && !strncmp(id, "..", 2)) {
++par_times;
parsed += 2;
id += 2;
while (!strncmp(id, "/..", 3)) {
++par_times;
parsed += 3;
id += 3;
}
}
if (!*parent_times) {
if (par_times) {
*parent_times = par_times;
} else {
*parent_times = -1;
}
}
if (id[0] != '/') {
return -parsed;
}
/* skip '/' */
++parsed;
++id;
/* node-identifier ([prefix:]identifier) */
if ((ret = parse_node_identifier(id, prefix, pref_len, name, nam_len, NULL, 0)) < 1) {
return -parsed - ret;
}
if (prefix && !(*prefix)) {
/* actually we always need prefix even it is not specified */
*prefix = lys_main_module(mod)->name;
*pref_len = strlen(*prefix);
}
parsed += ret;
id += ret;
/* there is no predicate */
if ((id[0] == '/') || !id[0]) {
return parsed;
} else if (id[0] != '[') {
return -parsed;
}
if (has_predicate) {
*has_predicate = 1;
}
return parsed;
}
/**
* @brief Parse instance-identifier in JSON data format. That means that prefixes
* (which are mandatory for every node-identifier) are actually model names.
*
* instance-identifier = 1*("/" (node-identifier *predicate))
*
* @param[in] id Identifier to use.
* @param[out] model Points to the model name.
* @param[out] mod_len Length of the model name.
* @param[out] name Points to the node name.
* @param[out] nam_len Length of the node name.
* @param[out] has_predicate Flag to mark whether there is a predicate specified.
*
* @return Number of characters successfully parsed,
* positive on success, negative on failure.
*/
static int
parse_instance_identifier(const char *id, const char **model, int *mod_len, const char **name, int *nam_len,
int *has_predicate)
{
int parsed = 0, ret;
if (has_predicate) {
*has_predicate = 0;
}
if (id[0] != '/') {
return -parsed;
}
++parsed;
++id;
if ((ret = parse_identifier(id)) < 1) {
return ret;
}
*model = id;
*mod_len = ret;
parsed += ret;
id += ret;
if (id[0] != ':') {
return -parsed;
}
++parsed;
++id;
if ((ret = parse_identifier(id)) < 1) {
return ret;
}
*name = id;
*nam_len = ret;
parsed += ret;
id += ret;
if (id[0] == '[' && has_predicate) {
*has_predicate = 1;
}
return parsed;
}
/**
* @brief Parse predicate (instance-identifier) in JSON data format. That means that prefixes
* (which are mandatory) are actually model names.
*
* predicate = "[" *WSP (predicate-expr / pos) *WSP "]"
* predicate-expr = (node-identifier / ".") *WSP "=" *WSP
* ((DQUOTE string DQUOTE) /
* (SQUOTE string SQUOTE))
* pos = non-negative-integer-value
*
* @param[in] id Identifier to use.
* @param[out] model Points to the model name.
* @param[out] mod_len Length of the model name.
* @param[out] name Points to the node name. Can be identifier (from node-identifier), "." or pos.
* @param[out] nam_len Length of the node name.
* @param[out] value Value the node-identifier must have (string from the grammar),
* NULL if there is not any.
* @param[out] val_len Length of the value, 0 if there is not any.
* @param[out] has_predicate Flag to mark whether there is a predicate specified.
*
* @return Number of characters successfully parsed,
* positive on success, negative on failure.
*/
static int
parse_predicate(const char *id, const char **model, int *mod_len, const char **name, int *nam_len,
const char **value, int *val_len, int *has_predicate)
{
const char *ptr;
int parsed = 0, ret;
char quote;
assert(id);
if (model) {
*model = NULL;
}
if (mod_len) {
*mod_len = 0;
}
if (name) {
*name = NULL;
}
if (nam_len) {
*nam_len = 0;
}
if (value) {
*value = NULL;
}
if (val_len) {
*val_len = 0;
}
if (has_predicate) {
*has_predicate = 0;
}
if (id[0] != '[') {
return -parsed;
}
++parsed;
++id;
while (isspace(id[0])) {
++parsed;
++id;
}
/* pos */
if (isdigit(id[0])) {
if (name) {
*name = id;
}
if (id[0] == '0') {
return -parsed;
}
while (isdigit(id[0])) {
++parsed;
++id;
}
if (nam_len) {
*nam_len = id-(*name);
}
/* "." or node-identifier */
} else {
if (id[0] == '.') {
if (name) {
*name = id;
}
if (nam_len) {
*nam_len = 1;
}
++parsed;
++id;
} else {
if ((ret = parse_node_identifier(id, model, mod_len, name, nam_len, NULL, 0)) < 1) {
return -parsed+ret;
} else if (model && !*model) {
return -parsed;
}
parsed += ret;
id += ret;
}
while (isspace(id[0])) {
++parsed;
++id;
}
if (id[0] != '=') {
return -parsed;
}
++parsed;
++id;
while (isspace(id[0])) {
++parsed;
++id;
}
/* ((DQUOTE string DQUOTE) / (SQUOTE string SQUOTE)) */
if ((id[0] == '\"') || (id[0] == '\'')) {
quote = id[0];
++parsed;
++id;
if ((ptr = strchr(id, quote)) == NULL) {
return -parsed;
}
ret = ptr-id;
if (value) {
*value = id;
}
if (val_len) {
*val_len = ret;
}
parsed += ret+1;
id += ret+1;
} else {
return -parsed;
}
}
while (isspace(id[0])) {
++parsed;
++id;
}
if (id[0] != ']') {
return -parsed;
}
++parsed;
++id;
if ((id[0] == '[') && has_predicate) {
*has_predicate = 1;
}
return parsed;
}
/**
* @brief Parse schema-nodeid.
*
* schema-nodeid = absolute-schema-nodeid /
* descendant-schema-nodeid
* absolute-schema-nodeid = 1*("/" node-identifier)
* descendant-schema-nodeid = ["." "/"]
* node-identifier
* absolute-schema-nodeid
*
* @param[in] id Identifier to use.
* @param[out] mod_name Points to the module name, NULL if there is not any.
* @param[out] mod_name_len Length of the module name, 0 if there is not any.
* @param[out] name Points to the node name.
* @param[out] nam_len Length of the node name.
* @param[out] is_relative Flag to mark whether the nodeid is absolute or descendant. Must be -1
* on the first call, must not be changed between consecutive calls.
* @param[out] has_predicate Flag to mark whether there is a predicate specified. It cannot be
* based on the grammar, in those cases use NULL.
* @param[in] extended Whether to accept an extended path (support for /[prefix:]*, //[prefix:]*, //[prefix:].).
*
* @return Number of characters successfully parsed,
* positive on success, negative on failure.
*/
int
parse_schema_nodeid(const char *id, const char **mod_name, int *mod_name_len, const char **name, int *nam_len,
int *is_relative, int *has_predicate, int *all_desc, int extended)
{
int parsed = 0, ret;
assert(id);
assert(is_relative);
if (has_predicate) {
*has_predicate = 0;
}
if (id[0] != '/') {
if (*is_relative != -1) {
return -parsed;
} else {
*is_relative = 1;
}
if (!strncmp(id, "./", 2)) {
parsed += 2;
id += 2;
}
} else {
if (*is_relative == -1) {
*is_relative = 0;
}
++parsed;
++id;
}
if ((ret = parse_node_identifier(id, mod_name, mod_name_len, name, nam_len, all_desc, extended)) < 1) {
return -parsed + ret;
}
parsed += ret;
id += ret;
if ((id[0] == '[') && has_predicate) {
*has_predicate = 1;
}
return parsed;
}
/**
* @brief Parse schema predicate (special format internally used).
*
* predicate = "[" *WSP predicate-expr *WSP "]"
* predicate-expr = "." / [prefix:]identifier / positive-integer / key-with-value
* key-with-value = identifier *WSP "=" *WSP
* ((DQUOTE string DQUOTE) /
* (SQUOTE string SQUOTE))
*
* @param[in] id Identifier to use.
* @param[out] mod_name Points to the list key module name.
* @param[out] mod_name_len Length of \p mod_name.
* @param[out] name Points to the list key name.
* @param[out] nam_len Length of \p name.
* @param[out] value Points to the key value. If specified, key-with-value is expected.
* @param[out] val_len Length of \p value.
* @param[out] has_predicate Flag to mark whether there is another predicate specified.
*/
int
parse_schema_json_predicate(const char *id, const char **mod_name, int *mod_name_len, const char **name, int *nam_len,
const char **value, int *val_len, int *has_predicate)
{
const char *ptr;
int parsed = 0, ret;
char quote;
assert(id);
if (mod_name) {
*mod_name = NULL;
}
if (mod_name_len) {
*mod_name_len = 0;
}
if (name) {
*name = NULL;
}
if (nam_len) {
*nam_len = 0;
}
if (value) {
*value = NULL;
}
if (val_len) {
*val_len = 0;
}
if (has_predicate) {
*has_predicate = 0;
}
if (id[0] != '[') {
return -parsed;
}
++parsed;
++id;
while (isspace(id[0])) {
++parsed;
++id;
}
/* identifier */
if (id[0] == '.') {
ret = 1;
if (name) {
*name = id;
}
if (nam_len) {
*nam_len = ret;
}
} else if (isdigit(id[0])) {
if (id[0] == '0') {
return -parsed;
}
ret = 1;
while (isdigit(id[ret])) {
++ret;
}
if (name) {
*name = id;
}
if (nam_len) {
*nam_len = ret;
}
} else if ((ret = parse_node_identifier(id, mod_name, mod_name_len, name, nam_len, NULL, 0)) < 1) {
return -parsed + ret;
}
parsed += ret;
id += ret;
while (isspace(id[0])) {
++parsed;
++id;
}
/* there is value as well */
if (id[0] == '=') {
if (name && isdigit(**name)) {
return -parsed;
}
++parsed;
++id;
while (isspace(id[0])) {
++parsed;
++id;
}
/* ((DQUOTE string DQUOTE) / (SQUOTE string SQUOTE)) */
if ((id[0] == '\"') || (id[0] == '\'')) {
quote = id[0];
++parsed;
++id;
if ((ptr = strchr(id, quote)) == NULL) {
return -parsed;
}
ret = ptr - id;
if (value) {
*value = id;
}
if (val_len) {
*val_len = ret;
}
parsed += ret + 1;
id += ret + 1;
} else {
return -parsed;
}
while (isspace(id[0])) {
++parsed;
++id;
}
}
if (id[0] != ']') {
return -parsed;
}
++parsed;
++id;
if ((id[0] == '[') && has_predicate) {
*has_predicate = 1;
}
return parsed;
}
/**
* @brief Resolve (find) a feature definition. Logs directly.
*
* @param[in] feat_name Feature name to resolve.
* @param[in] len Length of \p feat_name.
* @param[in] node Node with the if-feature expression.
* @param[out] feature Pointer to be set to point to the feature definition, if feature not found
* (return code 1), the pointer is untouched.
*
* @return 0 on success, 1 on forward reference, -1 on error.
*/
static int
resolve_feature(const char *feat_name, uint16_t len, const struct lys_node *node, struct lys_feature **feature)
{
char *str;
const char *mod_name, *name;
int mod_name_len, nam_len, i, j;
const struct lys_module *module;
assert(feature);
/* check prefix */
if ((i = parse_node_identifier(feat_name, &mod_name, &mod_name_len, &name, &nam_len, NULL, 0)) < 1) {
LOGVAL(LYE_INCHAR, LY_VLOG_NONE, NULL, feat_name[-i], &feat_name[-i]);
return -1;
}
module = lys_get_import_module(lys_node_module(node), NULL, 0, mod_name, mod_name_len);
if (!module) {
/* identity refers unknown data model */
LOGVAL(LYE_INMOD_LEN, LY_VLOG_NONE, NULL, mod_name_len, mod_name);
return -1;
}
if (module != node->module && module == lys_node_module(node)) {
/* first, try to search directly in submodule where the feature was mentioned */
for (j = 0; j < node->module->features_size; j++) {
if (!strncmp(name, node->module->features[j].name, nam_len) && !node->module->features[j].name[nam_len]) {
/* check status */
if (lyp_check_status(node->flags, lys_node_module(node), node->name, node->module->features[j].flags,
node->module->features[j].module, node->module->features[j].name, NULL)) {
return -1;
}
*feature = &node->module->features[j];
return 0;
}
}
}
/* search in the identified module ... */
for (j = 0; j < module->features_size; j++) {
if (!strncmp(name, module->features[j].name, nam_len) && !module->features[j].name[nam_len]) {
/* check status */
if (lyp_check_status(node->flags, lys_node_module(node), node->name, module->features[j].flags,
module->features[j].module, module->features[j].name, NULL)) {
return -1;
}
*feature = &module->features[j];
return 0;
}
}
/* ... and all its submodules */
for (i = 0; i < module->inc_size && module->inc[i].submodule; i++) {
for (j = 0; j < module->inc[i].submodule->features_size; j++) {
if (!strncmp(name, module->inc[i].submodule->features[j].name, nam_len)
&& !module->inc[i].submodule->features[j].name[nam_len]) {
/* check status */
if (lyp_check_status(node->flags, lys_node_module(node), node->name,
module->inc[i].submodule->features[j].flags,
module->inc[i].submodule->features[j].module,
module->inc[i].submodule->features[j].name, NULL)) {
return -1;
}
*feature = &module->inc[i].submodule->features[j];
return 0;
}
}
}
/* not found */
str = strndup(feat_name, len);
LOGVAL(LYE_INRESOLV, LY_VLOG_NONE, NULL, "feature", str);
free(str);
return 1;
}
/*
* @return
* - 1 if enabled
* - 0 if disabled
*/
static int
resolve_feature_value(const struct lys_feature *feat)
{
int i;
for (i = 0; i < feat->iffeature_size; i++) {
if (!resolve_iffeature(&feat->iffeature[i])) {
return 0;
}
}
return feat->flags & LYS_FENABLED ? 1 : 0;
}
static int
resolve_iffeature_recursive(struct lys_iffeature *expr, int *index_e, int *index_f)
{
uint8_t op;
int a, b;
op = iff_getop(expr->expr, *index_e);
(*index_e)++;
switch (op) {
case LYS_IFF_F:
/* resolve feature */
return resolve_feature_value(expr->features[(*index_f)++]);
case LYS_IFF_NOT:
/* invert result */
return resolve_iffeature_recursive(expr, index_e, index_f) ? 0 : 1;
case LYS_IFF_AND:
case LYS_IFF_OR:
a = resolve_iffeature_recursive(expr, index_e, index_f);
b = resolve_iffeature_recursive(expr, index_e, index_f);
if (op == LYS_IFF_AND) {
return a && b;
} else { /* LYS_IFF_OR */
return a || b;
}
}
return 0;
}
int
resolve_iffeature(struct lys_iffeature *expr)
{
int index_e = 0, index_f = 0;
if (expr->expr) {
return resolve_iffeature_recursive(expr, &index_e, &index_f);
}
return 0;
}
struct iff_stack {
int size;
int index; /* first empty item */
uint8_t *stack;
};
static int
iff_stack_push(struct iff_stack *stack, uint8_t value)
{
if (stack->index == stack->size) {
stack->size += 4;
stack->stack = ly_realloc(stack->stack, stack->size * sizeof *stack->stack);
LY_CHECK_ERR_RETURN(!stack->stack, LOGMEM; stack->size = 0, EXIT_FAILURE);
}
stack->stack[stack->index++] = value;
return EXIT_SUCCESS;
}
static uint8_t
iff_stack_pop(struct iff_stack *stack)
{
stack->index--;
return stack->stack[stack->index];
}
static void
iff_stack_clean(struct iff_stack *stack)
{
stack->size = 0;
free(stack->stack);
}
static void
iff_setop(uint8_t *list, uint8_t op, int pos)
{
uint8_t *item;
uint8_t mask = 3;
assert(pos >= 0);
assert(op <= 3); /* max 2 bits */
item = &list[pos / 4];
mask = mask << 2 * (pos % 4);
*item = (*item) & ~mask;
*item = (*item) | (op << 2 * (pos % 4));
}
uint8_t
iff_getop(uint8_t *list, int pos)
{
uint8_t *item;
uint8_t mask = 3, result;
assert(pos >= 0);
item = &list[pos / 4];
result = (*item) & (mask << 2 * (pos % 4));
return result >> 2 * (pos % 4);
}
#define LYS_IFF_LP 0x04 /* ( */
#define LYS_IFF_RP 0x08 /* ) */
/* internal structure for passing data for UNRES_IFFEAT */
struct unres_iffeat_data {
struct lys_node *node;
const char *fname;
int infeature;
};
void
resolve_iffeature_getsizes(struct lys_iffeature *iffeat, unsigned int *expr_size, unsigned int *feat_size)
{
unsigned int e = 0, f = 0, r = 0;
uint8_t op;
assert(iffeat);
if (!iffeat->expr) {
goto result;
}
do {
op = iff_getop(iffeat->expr, e++);
switch (op) {
case LYS_IFF_NOT:
if (!r) {
r += 1;
}
break;
case LYS_IFF_AND:
case LYS_IFF_OR:
if (!r) {
r += 2;
} else {
r += 1;
}
break;
case LYS_IFF_F:
f++;
if (r) {
r--;
}
break;
}
} while(r);
result:
if (expr_size) {
*expr_size = e;
}
if (feat_size) {
*feat_size = f;
}
}
int
resolve_iffeature_compile(struct lys_iffeature *iffeat_expr, const char *value, struct lys_node *node,
int infeature, struct unres_schema *unres)
{
const char *c = value;
int r, rc = EXIT_FAILURE;
int i, j, last_not, checkversion = 0;
unsigned int f_size = 0, expr_size = 0, f_exp = 1;
uint8_t op;
struct iff_stack stack = {0, 0, NULL};
struct unres_iffeat_data *iff_data;
assert(c);
if (isspace(c[0])) {
LOGVAL(LYE_INCHAR, LY_VLOG_NONE, NULL, c[0], c);
return EXIT_FAILURE;
}
/* pre-parse the expression to get sizes for arrays, also do some syntax checks of the expression */
for (i = j = last_not = 0; c[i]; i++) {
if (c[i] == '(') {
checkversion = 1;
j++;
continue;
} else if (c[i] == ')') {
j--;
continue;
} else if (isspace(c[i])) {
continue;
}
if (!strncmp(&c[i], "not", r = 3) || !strncmp(&c[i], "and", r = 3) || !strncmp(&c[i], "or", r = 2)) {
if (c[i + r] == '\0') {
LOGVAL(LYE_INARG, LY_VLOG_NONE, NULL, value, "if-feature");
return EXIT_FAILURE;
} else if (!isspace(c[i + r])) {
/* feature name starting with the not/and/or */
last_not = 0;
f_size++;
} else if (c[i] == 'n') { /* not operation */
if (last_not) {
/* double not */
expr_size = expr_size - 2;
last_not = 0;
} else {
last_not = 1;
}
} else { /* and, or */
f_exp++;
/* not a not operation */
last_not = 0;
}
i += r;
} else {
f_size++;
last_not = 0;
}
expr_size++;
while (!isspace(c[i])) {
if (!c[i] || c[i] == ')') {
i--;
break;
}
i++;
}
}
if (j || f_exp != f_size) {
/* not matching count of ( and ) */
LOGVAL(LYE_INARG, LY_VLOG_NONE, NULL, value, "if-feature");
return EXIT_FAILURE;
}
if (checkversion || expr_size > 1) {
/* check that we have 1.1 module */
if (node->module->version != 2) {
LOGVAL(LYE_INARG, LY_VLOG_NONE, NULL, value, "if-feature");
LOGVAL(LYE_SPEC, LY_VLOG_NONE, NULL, "YANG 1.1 if-feature expression found in 1.0 module.");
return EXIT_FAILURE;
}
}
/* allocate the memory */
iffeat_expr->expr = calloc((j = (expr_size / 4) + ((expr_size % 4) ? 1 : 0)), sizeof *iffeat_expr->expr);
iffeat_expr->features = calloc(f_size, sizeof *iffeat_expr->features);
stack.stack = malloc(expr_size * sizeof *stack.stack);
LY_CHECK_ERR_GOTO(!stack.stack || !iffeat_expr->expr || !iffeat_expr->features, LOGMEM, error);
stack.size = expr_size;
f_size--; expr_size--; /* used as indexes from now */
for (i--; i >= 0; i--) {
if (c[i] == ')') {
/* push it on stack */
iff_stack_push(&stack, LYS_IFF_RP);
continue;
} else if (c[i] == '(') {
/* pop from the stack into result all operators until ) */
while((op = iff_stack_pop(&stack)) != LYS_IFF_RP) {
iff_setop(iffeat_expr->expr, op, expr_size--);
}
continue;
} else if (isspace(c[i])) {
continue;
}
/* end operator or operand -> find beginning and get what is it */
j = i + 1;
while (i >= 0 && !isspace(c[i]) && c[i] != '(') {
i--;
}
i++; /* get back by one step */
if (!strncmp(&c[i], "not ", 4)) {
if (stack.index && stack.stack[stack.index - 1] == LYS_IFF_NOT) {
/* double not */
iff_stack_pop(&stack);
} else {
/* not has the highest priority, so do not pop from the stack
* as in case of AND and OR */
iff_stack_push(&stack, LYS_IFF_NOT);
}
} else if (!strncmp(&c[i], "and ", 4)) {
/* as for OR - pop from the stack all operators with the same or higher
* priority and store them to the result, then push the AND to the stack */
while (stack.index && stack.stack[stack.index - 1] <= LYS_IFF_AND) {
op = iff_stack_pop(&stack);
iff_setop(iffeat_expr->expr, op, expr_size--);
}
iff_stack_push(&stack, LYS_IFF_AND);
} else if (!strncmp(&c[i], "or ", 3)) {
while (stack.index && stack.stack[stack.index - 1] <= LYS_IFF_OR) {
op = iff_stack_pop(&stack);
iff_setop(iffeat_expr->expr, op, expr_size--);
}
iff_stack_push(&stack, LYS_IFF_OR);
} else {
/* feature name, length is j - i */
/* add it to the result */
iff_setop(iffeat_expr->expr, LYS_IFF_F, expr_size--);
/* now get the link to the feature definition. Since it can be
* forward referenced, we have to keep the feature name in auxiliary
* structure passed into unres */
iff_data = malloc(sizeof *iff_data);
LY_CHECK_ERR_GOTO(!iff_data, LOGMEM, error);
iff_data->node = node;
iff_data->fname = lydict_insert(node->module->ctx, &c[i], j - i);
iff_data->infeature = infeature;
r = unres_schema_add_node(node->module, unres, &iffeat_expr->features[f_size], UNRES_IFFEAT,
(struct lys_node *)iff_data);
f_size--;
if (r == -1) {
free(iff_data);
goto error;
}
}
}
while (stack.index) {
op = iff_stack_pop(&stack);
iff_setop(iffeat_expr->expr, op, expr_size--);
}
if (++expr_size || ++f_size) {
/* not all expected operators and operands found */
LOGVAL(LYE_INARG, LY_VLOG_NONE, NULL, value, "if-feature");
rc = EXIT_FAILURE;
} else {
rc = EXIT_SUCCESS;
}
error:
/* cleanup */
iff_stack_clean(&stack);
return rc;
}
/**
* @brief Resolve (find) a data node based on a schema-nodeid.
*
* Used for resolving unique statements - so id is expected to be relative and local (without reference to a different
* module).
*
*/
struct lyd_node *
resolve_data_descendant_schema_nodeid(const char *nodeid, struct lyd_node *start)
{
char *str, *token, *p;
struct lyd_node *result = NULL, *iter;
const struct lys_node *schema = NULL;
assert(nodeid && start);
if (nodeid[0] == '/') {
return NULL;
}
str = p = strdup(nodeid);
LY_CHECK_ERR_RETURN(!str, LOGMEM, NULL);
while (p) {
token = p;
p = strchr(p, '/');
if (p) {
*p = '\0';
p++;
}
if (p) {
/* inner node */
if (resolve_descendant_schema_nodeid(token, schema ? schema->child : start->schema,
LYS_CONTAINER | LYS_CHOICE | LYS_CASE | LYS_LEAF, 0, &schema)
|| !schema) {
result = NULL;
break;
}
if (schema->nodetype & (LYS_CHOICE | LYS_CASE)) {
continue;
}
} else {
/* final node */
if (resolve_descendant_schema_nodeid(token, schema ? schema->child : start->schema, LYS_LEAF, 0, &schema)
|| !schema) {
result = NULL;
break;
}
}
LY_TREE_FOR(result ? result->child : start, iter) {
if (iter->schema == schema) {
/* move in data tree according to returned schema */
result = iter;
break;
}
}
if (!iter) {
/* instance not found */
result = NULL;
break;
}
}
free(str);
return result;
}
int
schema_nodeid_siblingcheck(const struct lys_node *sibling, const struct lys_module *cur_module, const char *mod_name,
int mod_name_len, const char *name, int nam_len)
{
const struct lys_module *prefix_mod;
/* name check */
if ((name[0] != '*') && (name[0] != '.') && (strncmp(name, sibling->name, nam_len) || sibling->name[nam_len])) {
return 1;
}
/* module check */
if (mod_name) {
prefix_mod = lys_get_import_module(cur_module, NULL, 0, mod_name, mod_name_len);
if (!prefix_mod) {
return -1;
}
} else {
prefix_mod = cur_module;
}
if (prefix_mod != lys_node_module(sibling)) {
return 1;
}
/* match */
switch (name[0]) {
case '*':
return 2;
case '.':
return 3;
default:
return 0;
}
}
/* keys do not have to be ordered and do not have to be all of them */
static int
resolve_extended_schema_nodeid_predicate(const char *nodeid, const struct lys_node *node,
const struct lys_module *cur_module, int *nodeid_end)
{
int mod_len, nam_len, has_predicate, r, i;
const char *model, *name;
struct lys_node_list *list;
if (!(node->nodetype & (LYS_LIST | LYS_LEAFLIST))) {
return 1;
}
list = (struct lys_node_list *)node;
do {
r = parse_schema_json_predicate(nodeid, &model, &mod_len, &name, &nam_len, NULL, NULL, &has_predicate);
if (r < 1) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, nodeid[r], &nodeid[r]);
return -1;
}
nodeid += r;
if (node->nodetype == LYS_LEAFLIST) {
/* just check syntax */
if (model || !name || (name[0] != '.') || has_predicate) {
return 1;
}
break;
} else {
/* check the key */
for (i = 0; i < list->keys_size; ++i) {
if (strncmp(list->keys[i]->name, name, nam_len) || list->keys[i]->name[nam_len]) {
continue;
}
if (model) {
if (strncmp(lys_node_module((struct lys_node *)list->keys[i])->name, model, mod_len)
|| lys_node_module((struct lys_node *)list->keys[i])->name[mod_len]) {
continue;
}
} else {
if (lys_node_module((struct lys_node *)list->keys[i]) != cur_module) {
continue;
}
}
/* match */
break;
}
if (i == list->keys_size) {
return 1;
}
}
} while (has_predicate);
if (!nodeid[0]) {
*nodeid_end = 1;
}
return 0;
}
/* start - relative, module - absolute, -1 error (logged), EXIT_SUCCESS ok
*/
int
resolve_schema_nodeid(const char *nodeid, const struct lys_node *start, const struct lys_module *cur_module,
struct ly_set **ret, int extended, int no_node_error)
{
const char *name, *mod_name, *id;
const struct lys_node *sibling, *start_parent, *next, *elem;
struct lys_node_augment *last_aug;
int r, nam_len, mod_name_len = 0, is_relative = -1, all_desc, has_predicate, nodeid_end = 0;
/* resolved import module from the start module, it must match the next node-name-match sibling */
const struct lys_module *start_mod, *aux_mod;
char *str;
assert(nodeid && (start || cur_module) && ret);
*ret = NULL;
if (!cur_module) {
cur_module = lys_node_module(start);
}
id = nodeid;
r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, &has_predicate,
(extended ? &all_desc : NULL), extended);
if (r < 1) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[r], &id[r]);
return -1;
}
id += r;
if (is_relative && !start) {
LOGINT;
return -1;
}
/* descendant-schema-nodeid */
if (is_relative) {
cur_module = start_mod = start->module;
start_parent = lys_parent(start);
/* absolute-schema-nodeid */
} else {
start_mod = lys_get_import_module(cur_module, NULL, 0, mod_name, mod_name_len);
if (!start_mod) {
str = strndup(mod_name, mod_name_len);
LOGVAL(LYE_PATH_INMOD, LY_VLOG_STR, str);
free(str);
return -1;
}
start_parent = NULL;
}
while (1) {
sibling = NULL;
last_aug = NULL;
if (start_parent) {
if (mod_name && (strncmp(mod_name, cur_module->name, mod_name_len)
|| (mod_name_len != (signed)strlen(cur_module->name)))) {
/* we are getting into another module (augment) */
aux_mod = lys_get_import_module(cur_module, NULL, 0, mod_name, mod_name_len);
if (!aux_mod) {
str = strndup(mod_name, mod_name_len);
LOGVAL(LYE_PATH_INMOD, LY_VLOG_STR, str);
free(str);
return -1;
}
} else {
/* there is no mod_name, so why are we checking augments again?
* because this module may be not implemented and it augments something in another module and
* there is another augment augmenting that previous one */
aux_mod = cur_module;
}
/* if the module is implemented, all the augments will be connected */
if (!aux_mod->implemented && !extended) {
get_next_augment:
last_aug = lys_getnext_target_aug(last_aug, aux_mod, start_parent);
}
}
while ((sibling = lys_getnext(sibling, (last_aug ? (struct lys_node *)last_aug : start_parent), start_mod,
LYS_GETNEXT_WITHCHOICE | LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHINOUT | LYS_GETNEXT_PARENTUSES))) {
r = schema_nodeid_siblingcheck(sibling, cur_module, mod_name, mod_name_len, name, nam_len);
/* resolve predicate */
if (extended && ((r == 0) || (r == 2) || (r == 3)) && has_predicate) {
r = resolve_extended_schema_nodeid_predicate(id, sibling, cur_module, &nodeid_end);
if (r == 1) {
continue;
} else if (r == -1) {
return -1;
}
} else if (!id[0]) {
nodeid_end = 1;
}
if (r == 0) {
/* one matching result */
if (nodeid_end) {
*ret = ly_set_new();
LY_CHECK_ERR_RETURN(!*ret, LOGMEM, -1);
ly_set_add(*ret, (void *)sibling, LY_SET_OPT_USEASLIST);
} else {
if (sibling->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)) {
return -1;
}
start_parent = sibling;
}
break;
} else if (r == 1) {
continue;
} else if (r == 2) {
/* "*" */
if (!*ret) {
*ret = ly_set_new();
LY_CHECK_ERR_RETURN(!*ret, LOGMEM, -1);
}
ly_set_add(*ret, (void *)sibling, LY_SET_OPT_USEASLIST);
if (all_desc) {
LY_TREE_DFS_BEGIN(sibling, next, elem) {
if (elem != sibling) {
ly_set_add(*ret, (void *)elem, LY_SET_OPT_USEASLIST);
}
LY_TREE_DFS_END(sibling, next, elem);
}
}
} else if (r == 3) {
/* "." */
if (!*ret) {
*ret = ly_set_new();
LY_CHECK_ERR_RETURN(!*ret, LOGMEM, -1);
ly_set_add(*ret, (void *)start_parent, LY_SET_OPT_USEASLIST);
}
ly_set_add(*ret, (void *)sibling, LY_SET_OPT_USEASLIST);
if (all_desc) {
LY_TREE_DFS_BEGIN(sibling, next, elem) {
if (elem != sibling) {
ly_set_add(*ret, (void *)elem, LY_SET_OPT_USEASLIST);
}
LY_TREE_DFS_END(sibling, next, elem);
}
}
} else {
LOGINT;
return -1;
}
}
/* skip predicate */
if (extended && has_predicate) {
while (id[0] == '[') {
id = strchr(id, ']');
if (!id) {
LOGINT;
return -1;
}
++id;
}
}
if (nodeid_end && ((r == 0) || (r == 2) || (r == 3))) {
return EXIT_SUCCESS;
}
/* no match */
if (!sibling) {
if (last_aug) {
/* it still could be in another augment */
goto get_next_augment;
}
if (no_node_error) {
str = strndup(nodeid, (name - nodeid) + nam_len);
LOGVAL(LYE_PATH_INNODE, LY_VLOG_STR, str);
free(str);
return -1;
}
*ret = NULL;
return EXIT_SUCCESS;
}
r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, &has_predicate,
(extended ? &all_desc : NULL), extended);
if (r < 1) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[r], &id[r]);
return -1;
}
id += r;
}
/* cannot get here */
LOGINT;
return -1;
}
/* unique, refine,
* >0 - unexpected char on position (ret - 1),
* 0 - ok (but ret can still be NULL),
* -1 - error,
* -2 - violated no_innerlist */
int
resolve_descendant_schema_nodeid(const char *nodeid, const struct lys_node *start, int ret_nodetype,
int no_innerlist, const struct lys_node **ret)
{
const char *name, *mod_name, *id;
const struct lys_node *sibling, *start_parent;
int r, nam_len, mod_name_len, is_relative = -1;
/* resolved import module from the start module, it must match the next node-name-match sibling */
const struct lys_module *module;
assert(nodeid && ret);
assert(!(ret_nodetype & (LYS_USES | LYS_AUGMENT | LYS_GROUPING)));
if (!start) {
/* leaf not found */
return 0;
}
id = nodeid;
module = lys_node_module(start);
if ((r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, NULL, NULL, 0)) < 1) {
return ((id - nodeid) - r) + 1;
}
id += r;
if (!is_relative) {
return -1;
}
start_parent = lys_parent(start);
while ((start_parent->nodetype == LYS_USES) && lys_parent(start_parent)) {
start_parent = lys_parent(start_parent);
}
while (1) {
sibling = NULL;
while ((sibling = lys_getnext(sibling, start_parent, module,
LYS_GETNEXT_WITHCHOICE | LYS_GETNEXT_WITHCASE | LYS_GETNEXT_PARENTUSES))) {
r = schema_nodeid_siblingcheck(sibling, module, mod_name, mod_name_len, name, nam_len);
if (r == 0) {
if (!id[0]) {
if (!(sibling->nodetype & ret_nodetype)) {
/* wrong node type, too bad */
continue;
}
*ret = sibling;
return EXIT_SUCCESS;
}
start_parent = sibling;
break;
} else if (r == 1) {
continue;
} else {
return -1;
}
}
/* no match */
if (!sibling) {
*ret = NULL;
return EXIT_SUCCESS;
} else if (no_innerlist && sibling->nodetype == LYS_LIST) {
*ret = NULL;
return -2;
}
if ((r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, NULL, NULL, 0)) < 1) {
return ((id - nodeid) - r) + 1;
}
id += r;
}
/* cannot get here */
LOGINT;
return -1;
}
/* choice default */
int
resolve_choice_default_schema_nodeid(const char *nodeid, const struct lys_node *start, const struct lys_node **ret)
{
/* cannot actually be a path */
if (strchr(nodeid, '/')) {
return -1;
}
return resolve_descendant_schema_nodeid(nodeid, start, LYS_NO_RPC_NOTIF_NODE, 0, ret);
}
/* uses, -1 error, EXIT_SUCCESS ok (but ret can still be NULL), >0 unexpected char on ret - 1 */
static int
resolve_uses_schema_nodeid(const char *nodeid, const struct lys_node *start, const struct lys_node_grp **ret)
{
const struct lys_module *module;
const char *mod_prefix, *name;
int i, mod_prefix_len, nam_len;
/* parse the identifier, it must be parsed on one call */
if (((i = parse_node_identifier(nodeid, &mod_prefix, &mod_prefix_len, &name, &nam_len, NULL, 0)) < 1) || nodeid[i]) {
return -i + 1;
}
module = lys_get_import_module(start->module, mod_prefix, mod_prefix_len, NULL, 0);
if (!module) {
return -1;
}
if (module != lys_main_module(start->module)) {
start = module->data;
}
*ret = lys_find_grouping_up(name, (struct lys_node *)start);
return EXIT_SUCCESS;
}
int
resolve_absolute_schema_nodeid(const char *nodeid, const struct lys_module *module, int ret_nodetype,
const struct lys_node **ret)
{
const char *name, *mod_name, *id;
const struct lys_node *sibling, *start_parent;
int r, nam_len, mod_name_len, is_relative = -1;
const struct lys_module *abs_start_mod;
assert(nodeid && module && ret);
assert(!(ret_nodetype & (LYS_USES | LYS_AUGMENT)) && ((ret_nodetype == LYS_GROUPING) || !(ret_nodetype & LYS_GROUPING)));
id = nodeid;
start_parent = NULL;
if ((r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, NULL, NULL, 0)) < 1) {
return ((id - nodeid) - r) + 1;
}
id += r;
if (is_relative) {
return -1;
}
abs_start_mod = lys_get_import_module(module, NULL, 0, mod_name, mod_name_len);
if (!abs_start_mod) {
return -1;
}
while (1) {
sibling = NULL;
while ((sibling = lys_getnext(sibling, start_parent, abs_start_mod, LYS_GETNEXT_WITHCHOICE
| LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHINOUT | LYS_GETNEXT_WITHGROUPING))) {
r = schema_nodeid_siblingcheck(sibling, module, mod_name, mod_name_len, name, nam_len);
if (r == 0) {
if (!id[0]) {
if (!(sibling->nodetype & ret_nodetype)) {
/* wrong node type, too bad */
continue;
}
*ret = sibling;
return EXIT_SUCCESS;
}
start_parent = sibling;
break;
} else if (r == 1) {
continue;
} else {
return -1;
}
}
/* no match */
if (!sibling) {
*ret = NULL;
return EXIT_SUCCESS;
}
if ((r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, NULL, NULL, 0)) < 1) {
return ((id - nodeid) - r) + 1;
}
id += r;
}
/* cannot get here */
LOGINT;
return -1;
}
static int
resolve_json_schema_list_predicate(const char *predicate, const struct lys_node_list *list, int *parsed)
{
const char *mod_name, *name;
int mod_name_len, nam_len, has_predicate, i;
struct lys_node *key;
if (((i = parse_schema_json_predicate(predicate, &mod_name, &mod_name_len, &name, &nam_len, NULL, NULL, &has_predicate)) < 1)
|| !strncmp(name, ".", nam_len)) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, predicate[-i], &predicate[-i]);
return -1;
}
predicate += i;
*parsed += i;
if (!isdigit(name[0])) {
for (i = 0; i < list->keys_size; ++i) {
key = (struct lys_node *)list->keys[i];
if (!strncmp(key->name, name, nam_len) && !key->name[nam_len]) {
break;
}
}
if (i == list->keys_size) {
LOGVAL(LYE_PATH_INKEY, LY_VLOG_NONE, NULL, name);
return -1;
}
}
/* more predicates? */
if (has_predicate) {
return resolve_json_schema_list_predicate(predicate, list, parsed);
}
return 0;
}
/* cannot return LYS_GROUPING, LYS_AUGMENT, LYS_USES, logs directly */
const struct lys_node *
resolve_json_nodeid(const char *nodeid, struct ly_ctx *ctx, const struct lys_node *start)
{
char *module_name = ly_buf(), *buf_backup = NULL, *str;
const char *name, *mod_name, *id;
const struct lys_node *sibling, *start_parent;
int r, nam_len, mod_name_len, is_relative = -1, has_predicate;
/* resolved import module from the start module, it must match the next node-name-match sibling */
const struct lys_module *prefix_mod, *module, *prev_mod;
assert(nodeid && (ctx || start));
if (!ctx) {
ctx = start->module->ctx;
}
id = nodeid;
if ((r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, &has_predicate, NULL, 0)) < 1) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[-r], &id[-r]);
return NULL;
}
id += r;
if (is_relative) {
assert(start);
start_parent = start;
while (start_parent && (start_parent->nodetype == LYS_USES)) {
start_parent = lys_parent(start_parent);
}
module = start->module;
} else {
if (!mod_name) {
str = strndup(nodeid, (name + nam_len) - nodeid);
LOGVAL(LYE_PATH_MISSMOD, LY_VLOG_STR, nodeid);
free(str);
return NULL;
} else if (mod_name_len > LY_BUF_SIZE - 1) {
LOGINT;
return NULL;
}
if (ly_buf_used && module_name[0]) {
buf_backup = strndup(module_name, LY_BUF_SIZE - 1);
}
ly_buf_used++;
memmove(module_name, mod_name, mod_name_len);
module_name[mod_name_len] = '\0';
module = ly_ctx_get_module(ctx, module_name, NULL);
if (buf_backup) {
/* return previous internal buffer content */
strcpy(module_name, buf_backup);
free(buf_backup);
buf_backup = NULL;
}
ly_buf_used--;
if (!module) {
str = strndup(nodeid, (mod_name + mod_name_len) - nodeid);
LOGVAL(LYE_PATH_INMOD, LY_VLOG_STR, str);
free(str);
return NULL;
}
start_parent = NULL;
/* now it's as if there was no module name */
mod_name = NULL;
mod_name_len = 0;
}
prev_mod = module;
while (1) {
sibling = NULL;
while ((sibling = lys_getnext(sibling, start_parent, module,
LYS_GETNEXT_WITHCHOICE | LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHINOUT))) {
/* name match */
if (sibling->name && !strncmp(name, sibling->name, nam_len) && !sibling->name[nam_len]) {
/* module check */
if (mod_name) {
if (mod_name_len > LY_BUF_SIZE - 1) {
LOGINT;
return NULL;
}
if (ly_buf_used && module_name[0]) {
buf_backup = strndup(module_name, LY_BUF_SIZE - 1);
}
ly_buf_used++;
memmove(module_name, mod_name, mod_name_len);
module_name[mod_name_len] = '\0';
/* will also find an augment module */
prefix_mod = ly_ctx_get_module(ctx, module_name, NULL);
if (buf_backup) {
/* return previous internal buffer content */
strncpy(module_name, buf_backup, LY_BUF_SIZE - 1);
free(buf_backup);
buf_backup = NULL;
}
ly_buf_used--;
if (!prefix_mod) {
str = strndup(nodeid, (mod_name + mod_name_len) - nodeid);
LOGVAL(LYE_PATH_INMOD, LY_VLOG_STR, str);
free(str);
return NULL;
}
} else {
prefix_mod = prev_mod;
}
if (prefix_mod != lys_node_module(sibling)) {
continue;
}
/* do we have some predicates on it? */
if (has_predicate) {
r = 0;
if (sibling->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
if ((r = parse_schema_json_predicate(id, NULL, NULL, NULL, NULL, NULL, NULL, &has_predicate)) < 1) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[-r], &id[-r]);
return NULL;
}
} else if (sibling->nodetype == LYS_LIST) {
if (resolve_json_schema_list_predicate(id, (const struct lys_node_list *)sibling, &r)) {
return NULL;
}
} else {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[0], id);
return NULL;
}
id += r;
}
/* the result node? */
if (!id[0]) {
return sibling;
}
/* move down the tree, if possible */
if (sibling->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[0], id);
return NULL;
}
start_parent = sibling;
/* update prev mod */
prev_mod = (start_parent->child ? lys_node_module(start_parent->child) : module);
break;
}
}
/* no match */
if (!sibling) {
str = strndup(nodeid, (name + nam_len) - nodeid);
LOGVAL(LYE_PATH_INNODE, LY_VLOG_STR, str);
free(str);
return NULL;
}
if ((r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, &has_predicate, NULL, 0)) < 1) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[-r], &id[-r]);
return NULL;
}
id += r;
}
/* cannot get here */
LOGINT;
return NULL;
}
static int
resolve_partial_json_data_list_predicate(const char *predicate, const char *node_name, struct lyd_node *node,
int position, int *parsed)
{
const char *mod_name, *name, *value, *key_val;
int mod_name_len, nam_len, val_len, has_predicate = 1, r;
uint16_t i;
struct lyd_node_leaf_list *key;
assert(node);
assert(node->schema->nodetype == LYS_LIST);
/* is the predicate a number? */
if (((r = parse_schema_json_predicate(predicate, &mod_name, &mod_name_len, &name, &nam_len, &value, &val_len, &has_predicate)) < 1)
|| !strncmp(name, ".", nam_len)) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, predicate[-r], &predicate[-r]);
return -1;
}
if (isdigit(name[0])) {
if (position == atoi(name)) {
/* match */
*parsed += r;
return 0;
} else {
/* not a match */
return 1;
}
}
if (!((struct lys_node_list *)node->schema)->keys_size) {
/* no keys in schema - causes an error later */
return 0;
}
key = (struct lyd_node_leaf_list *)node->child;
if (!key) {
/* it is not a position, so we need a key for it to be a match */
return 1;
}
/* go through all the keys */
i = 0;
goto check_parsed_values;
for (; i < ((struct lys_node_list *)node->schema)->keys_size; ++i) {
if (!has_predicate) {
LOGVAL(LYE_PATH_MISSKEY, LY_VLOG_NONE, NULL, node_name);
return -1;
}
if (((r = parse_schema_json_predicate(predicate, &mod_name, &mod_name_len, &name, &nam_len, &value, &val_len, &has_predicate)) < 1)
|| !strncmp(name, ".", nam_len)) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, predicate[-r], &predicate[-r]);
return -1;
}
check_parsed_values:
predicate += r;
*parsed += r;
if (strncmp(key->schema->name, name, nam_len) || key->schema->name[nam_len]) {
LOGVAL(LYE_PATH_INKEY, LY_VLOG_NONE, NULL, name);
return -1;
}
if (mod_name) {
/* specific module, check that the found key is from that module */
if (strncmp(lyd_node_module((struct lyd_node *)key)->name, mod_name, mod_name_len)
|| lyd_node_module((struct lyd_node *)key)->name[mod_name_len]) {
LOGVAL(LYE_PATH_INKEY, LY_VLOG_NONE, NULL, name);
return -1;
}
/* but if the module is the same as the parent, it should have been omitted */
if (lyd_node_module((struct lyd_node *)key) == lyd_node_module(node)) {
LOGVAL(LYE_PATH_INKEY, LY_VLOG_NONE, NULL, name);
return -1;
}
} else {
/* no module, so it must be the same as the list (parent) */
if (lyd_node_module((struct lyd_node *)key) != lyd_node_module(node)) {
LOGVAL(LYE_PATH_INKEY, LY_VLOG_NONE, NULL, name);
return -1;
}
}
/* make value canonical */
if ((key->value_type & LY_TYPE_IDENT)
&& !strncmp(key->value_str, lyd_node_module(node)->name, strlen(lyd_node_module(node)->name))
&& (key->value_str[strlen(lyd_node_module(node)->name)] == ':')) {
key_val = key->value_str + strlen(lyd_node_module(node)->name) + 1;
} else {
key_val = key->value_str;
}
/* value does not match */
if (strncmp(key_val, value, val_len) || key_val[val_len]) {
return 1;
}
key = (struct lyd_node_leaf_list *)key->next;
}
if (has_predicate) {
LOGVAL(LYE_PATH_INKEY, LY_VLOG_NONE, NULL, name);
return -1;
}
return 0;
}
/**
* @brief get the closest parent of the node (or the node itself) identified by the nodeid (path)
*
* @param[in] nodeid Node data path to find
* @param[in] llist_value If the \p nodeid identifies leaf-list, this is expected value of the leaf-list instance.
* @param[in] options Bitmask of options flags, see @ref pathoptions.
* @param[out] parsed Number of characters processed in \p id
* @return The closes parent (or the node itself) from the path
*/
struct lyd_node *
resolve_partial_json_data_nodeid(const char *nodeid, const char *llist_value, struct lyd_node *start, int options,
int *parsed)
{
char *module_name = ly_buf(), *buf_backup = NULL, *str;
const char *id, *mod_name, *name, *pred_name, *data_val;
int r, ret, mod_name_len, nam_len, is_relative = -1, list_instance_position;
int has_predicate, last_parsed, llval_len, pred_name_len, last_has_pred;
struct lyd_node *sibling, *last_match = NULL;
struct lyd_node_leaf_list *llist;
const struct lys_module *prefix_mod, *prev_mod;
struct ly_ctx *ctx;
assert(nodeid && start && parsed);
ctx = start->schema->module->ctx;
id = nodeid;
if ((r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, &has_predicate, NULL, 0)) < 1) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[-r], &id[-r]);
*parsed = -1;
return NULL;
}
id += r;
/* add it to parsed only after the data node was actually found */
last_parsed = r;
if (is_relative) {
prev_mod = lyd_node_module(start);
start = start->child;
} else {
for (; start->parent; start = start->parent);
prev_mod = lyd_node_module(start);
}
while (1) {
list_instance_position = 0;
LY_TREE_FOR(start, sibling) {
/* RPC/action data check, return simply invalid argument, because the data tree is invalid */
if (lys_parent(sibling->schema)) {
if (options & LYD_PATH_OPT_OUTPUT) {
if (lys_parent(sibling->schema)->nodetype == LYS_INPUT) {
LOGERR(LY_EINVAL, "Provided data tree includes some RPC input nodes (%s).", sibling->schema->name);
*parsed = -1;
return NULL;
}
} else {
if (lys_parent(sibling->schema)->nodetype == LYS_OUTPUT) {
LOGERR(LY_EINVAL, "Provided data tree includes some RPC output nodes (%s).", sibling->schema->name);
*parsed = -1;
return NULL;
}
}
}
/* name match */
if (!strncmp(name, sibling->schema->name, nam_len) && !sibling->schema->name[nam_len]) {
/* module check */
if (mod_name) {
if (mod_name_len > LY_BUF_SIZE - 1) {
LOGINT;
*parsed = -1;
return NULL;
}
if (ly_buf_used && module_name[0]) {
buf_backup = strndup(module_name, LY_BUF_SIZE - 1);
}
ly_buf_used++;
memmove(module_name, mod_name, mod_name_len);
module_name[mod_name_len] = '\0';
/* will also find an augment module */
prefix_mod = ly_ctx_get_module(ctx, module_name, NULL);
if (buf_backup) {
/* return previous internal buffer content */
strncpy(module_name, buf_backup, LY_BUF_SIZE - 1);
free(buf_backup);
buf_backup = NULL;
}
ly_buf_used--;
if (!prefix_mod) {
str = strndup(nodeid, (mod_name + mod_name_len) - nodeid);
LOGVAL(LYE_PATH_INMOD, LY_VLOG_STR, str);
free(str);
*parsed = -1;
return NULL;
}
} else {
prefix_mod = prev_mod;
}
if (prefix_mod != lyd_node_module(sibling)) {
continue;
}
/* leaf-list, did we find it with the correct value or not? */
if (sibling->schema->nodetype == LYS_LEAFLIST) {
llist = (struct lyd_node_leaf_list *)sibling;
last_has_pred = 0;
if (has_predicate) {
if ((r = parse_schema_json_predicate(id, NULL, NULL, &pred_name, &pred_name_len, &llist_value,
&llval_len, &last_has_pred)) < 1) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[0], id);
*parsed = -1;
return NULL;
}
if ((pred_name[0] != '.') || (pred_name_len != 1)) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[1], id + 1);
*parsed = -1;
return NULL;
}
} else {
r = 0;
if (llist_value) {
llval_len = strlen(llist_value);
}
}
/* make value canonical */
if ((llist->value_type & LY_TYPE_IDENT)
&& !strncmp(llist->value_str, lyd_node_module(sibling)->name, strlen(lyd_node_module(sibling)->name))
&& (llist->value_str[strlen(lyd_node_module(sibling)->name)] == ':')) {
data_val = llist->value_str + strlen(lyd_node_module(sibling)->name) + 1;
} else {
data_val = llist->value_str;
}
if ((!llist_value && data_val && data_val[0])
|| (llist_value && (strncmp(llist_value, data_val, llval_len) || data_val[llval_len]))) {
continue;
}
id += r;
last_parsed += r;
has_predicate = last_has_pred;
} else if (sibling->schema->nodetype == LYS_LIST) {
/* list, we likely need predicates'n'stuff then, but if without a predicate, we are always creating it */
if (!has_predicate) {
/* none match */
return last_match;
}
++list_instance_position;
r = 0;
ret = resolve_partial_json_data_list_predicate(id, name, sibling, list_instance_position, &r);
if (ret == -1) {
*parsed = -1;
return NULL;
} else if (ret == 1) {
/* this list instance does not match */
continue;
}
id += r;
last_parsed += r;
}
*parsed += last_parsed;
/* the result node? */
if (!id[0]) {
return sibling;
}
/* move down the tree, if possible */
if (sibling->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[0], id);
*parsed = -1;
return NULL;
}
last_match = sibling;
prev_mod = lyd_node_module(sibling);
start = sibling->child;
break;
}
}
/* no match, return last match */
if (!sibling) {
return last_match;
}
if ((r = parse_schema_nodeid(id, &mod_name, &mod_name_len, &name, &nam_len, &is_relative, &has_predicate, NULL, 0)) < 1) {
LOGVAL(LYE_PATH_INCHAR, LY_VLOG_NONE, NULL, id[-r], &id[-r]);
*parsed = -1;
return NULL;
}
id += r;
last_parsed = r;
}
/* cannot get here */
LOGINT;
*parsed = -1;
return NULL;
}
/**
* @brief Resolves length or range intervals. Does not log.
* Syntax is assumed to be correct, *ret MUST be NULL.
*
* @param[in] str_restr Restriction as a string.
* @param[in] type Type of the restriction.
* @param[out] ret Final interval structure that starts with
* the interval of the initial type, continues with intervals
* of any superior types derived from the initial one, and
* finishes with intervals from our \p type.
*
* @return EXIT_SUCCESS on succes, -1 on error.
*/
int
resolve_len_ran_interval(const char *str_restr, struct lys_type *type, struct len_ran_intv **ret)
{
/* 0 - unsigned, 1 - signed, 2 - floating point */
int kind;
int64_t local_smin, local_smax, local_fmin, local_fmax;
uint64_t local_umin, local_umax;
uint8_t local_fdig;
const char *seg_ptr, *ptr;
struct len_ran_intv *local_intv = NULL, *tmp_local_intv = NULL, *tmp_intv, *intv = NULL;
switch (type->base) {
case LY_TYPE_BINARY:
kind = 0;
local_umin = 0;
local_umax = 18446744073709551615UL;
if (!str_restr && type->info.binary.length) {
str_restr = type->info.binary.length->expr;
}
break;
case LY_TYPE_DEC64:
kind = 2;
local_fmin = __INT64_C(-9223372036854775807) - __INT64_C(1);
local_fmax = __INT64_C(9223372036854775807);
local_fdig = type->info.dec64.dig;
if (!str_restr && type->info.dec64.range) {
str_restr = type->info.dec64.range->expr;
}
break;
case LY_TYPE_INT8:
kind = 1;
local_smin = __INT64_C(-128);
local_smax = __INT64_C(127);
if (!str_restr && type->info.num.range) {
str_restr = type->info.num.range->expr;
}
break;
case LY_TYPE_INT16:
kind = 1;
local_smin = __INT64_C(-32768);
local_smax = __INT64_C(32767);
if (!str_restr && type->info.num.range) {
str_restr = type->info.num.range->expr;
}
break;
case LY_TYPE_INT32:
kind = 1;
local_smin = __INT64_C(-2147483648);
local_smax = __INT64_C(2147483647);
if (!str_restr && type->info.num.range) {
str_restr = type->info.num.range->expr;
}
break;
case LY_TYPE_INT64:
kind = 1;
local_smin = __INT64_C(-9223372036854775807) - __INT64_C(1);
local_smax = __INT64_C(9223372036854775807);
if (!str_restr && type->info.num.range) {
str_restr = type->info.num.range->expr;
}
break;
case LY_TYPE_UINT8:
kind = 0;
local_umin = __UINT64_C(0);
local_umax = __UINT64_C(255);
if (!str_restr && type->info.num.range) {
str_restr = type->info.num.range->expr;
}
break;
case LY_TYPE_UINT16:
kind = 0;
local_umin = __UINT64_C(0);
local_umax = __UINT64_C(65535);
if (!str_restr && type->info.num.range) {
str_restr = type->info.num.range->expr;
}
break;
case LY_TYPE_UINT32:
kind = 0;
local_umin = __UINT64_C(0);
local_umax = __UINT64_C(4294967295);
if (!str_restr && type->info.num.range) {
str_restr = type->info.num.range->expr;
}
break;
case LY_TYPE_UINT64:
kind = 0;
local_umin = __UINT64_C(0);
local_umax = __UINT64_C(18446744073709551615);
if (!str_restr && type->info.num.range) {
str_restr = type->info.num.range->expr;
}
break;
case LY_TYPE_STRING:
kind = 0;
local_umin = __UINT64_C(0);
local_umax = __UINT64_C(18446744073709551615);
if (!str_restr && type->info.str.length) {
str_restr = type->info.str.length->expr;
}
break;
default:
LOGINT;
return -1;
}
/* process superior types */
if (type->der) {
if (resolve_len_ran_interval(NULL, &type->der->type, &intv)) {
LOGINT;
return -1;
}
assert(!intv || (intv->kind == kind));
}
if (!str_restr) {
/* we do not have any restriction, return superior ones */
*ret = intv;
return EXIT_SUCCESS;
}
/* adjust local min and max */
if (intv) {
tmp_intv = intv;
if (kind == 0) {
local_umin = tmp_intv->value.uval.min;
} else if (kind == 1) {
local_smin = tmp_intv->value.sval.min;
} else if (kind == 2) {
local_fmin = tmp_intv->value.fval.min;
}
while (tmp_intv->next) {
tmp_intv = tmp_intv->next;
}
if (kind == 0) {
local_umax = tmp_intv->value.uval.max;
} else if (kind == 1) {
local_smax = tmp_intv->value.sval.max;
} else if (kind == 2) {
local_fmax = tmp_intv->value.fval.max;
}
}
/* finally parse our restriction */
seg_ptr = str_restr;
tmp_intv = NULL;
while (1) {
if (!tmp_local_intv) {
assert(!local_intv);
local_intv = malloc(sizeof *local_intv);
tmp_local_intv = local_intv;
} else {
tmp_local_intv->next = malloc(sizeof *tmp_local_intv);
tmp_local_intv = tmp_local_intv->next;
}
LY_CHECK_ERR_GOTO(!tmp_local_intv, LOGMEM, error);
tmp_local_intv->kind = kind;
tmp_local_intv->type = type;
tmp_local_intv->next = NULL;
/* min */
ptr = seg_ptr;
while (isspace(ptr[0])) {
++ptr;
}
if (isdigit(ptr[0]) || (ptr[0] == '+') || (ptr[0] == '-')) {
if (kind == 0) {
tmp_local_intv->value.uval.min = strtoll(ptr, (char **)&ptr, 10);
} else if (kind == 1) {
tmp_local_intv->value.sval.min = strtoll(ptr, (char **)&ptr, 10);
} else if (kind == 2) {
if (parse_range_dec64(&ptr, local_fdig, &tmp_local_intv->value.fval.min)) {
LOGVAL(LYE_INARG, LY_VLOG_NONE, NULL, ptr, "range");
goto error;
}
}
} else if (!strncmp(ptr, "min", 3)) {
if (kind == 0) {
tmp_local_intv->value.uval.min = local_umin;
} else if (kind == 1) {
tmp_local_intv->value.sval.min = local_smin;
} else if (kind == 2) {
tmp_local_intv->value.fval.min = local_fmin;
}
ptr += 3;
} else if (!strncmp(ptr, "max", 3)) {
if (kind == 0) {
tmp_local_intv->value.uval.min = local_umax;
} else if (kind == 1) {
tmp_local_intv->value.sval.min = local_smax;
} else if (kind == 2) {
tmp_local_intv->value.fval.min = local_fmax;
}
ptr += 3;
} else {
LOGINT;
goto error;
}
while (isspace(ptr[0])) {
ptr++;
}
/* no interval or interval */
if ((ptr[0] == '|') || !ptr[0]) {
if (kind == 0) {
tmp_local_intv->value.uval.max = tmp_local_intv->value.uval.min;
} else if (kind == 1) {
tmp_local_intv->value.sval.max = tmp_local_intv->value.sval.min;
} else if (kind == 2) {
tmp_local_intv->value.fval.max = tmp_local_intv->value.fval.min;
}
} else if (!strncmp(ptr, "..", 2)) {
/* skip ".." */
ptr += 2;
while (isspace(ptr[0])) {
++ptr;
}
/* max */
if (isdigit(ptr[0]) || (ptr[0] == '+') || (ptr[0] == '-')) {
if (kind == 0) {
tmp_local_intv->value.uval.max = strtoll(ptr, (char **)&ptr, 10);
} else if (kind == 1) {
tmp_local_intv->value.sval.max = strtoll(ptr, (char **)&ptr, 10);
} else if (kind == 2) {
if (parse_range_dec64(&ptr, local_fdig, &tmp_local_intv->value.fval.max)) {
LOGVAL(LYE_INARG, LY_VLOG_NONE, NULL, ptr, "range");
goto error;
}
}
} else if (!strncmp(ptr, "max", 3)) {
if (kind == 0) {
tmp_local_intv->value.uval.max = local_umax;
} else if (kind == 1) {
tmp_local_intv->value.sval.max = local_smax;
} else if (kind == 2) {
tmp_local_intv->value.fval.max = local_fmax;
}
} else {
LOGINT;
goto error;
}
} else {
LOGINT;
goto error;
}
/* check min and max in correct order*/
if (kind == 0) {
/* current segment */
if (tmp_local_intv->value.uval.min > tmp_local_intv->value.uval.max) {
goto error;
}
if (tmp_local_intv->value.uval.min < local_umin || tmp_local_intv->value.uval.max > local_umax) {
goto error;
}
/* segments sholud be ascending order */
if (tmp_intv && (tmp_intv->value.uval.max >= tmp_local_intv->value.uval.min)) {
goto error;
}
} else if (kind == 1) {
if (tmp_local_intv->value.sval.min > tmp_local_intv->value.sval.max) {
goto error;
}
if (tmp_local_intv->value.sval.min < local_smin || tmp_local_intv->value.sval.max > local_smax) {
goto error;
}
if (tmp_intv && (tmp_intv->value.sval.max >= tmp_local_intv->value.sval.min)) {
goto error;
}
} else if (kind == 2) {
if (tmp_local_intv->value.fval.min > tmp_local_intv->value.fval.max) {
goto error;
}
if (tmp_local_intv->value.fval.min < local_fmin || tmp_local_intv->value.fval.max > local_fmax) {
goto error;
}
if (tmp_intv && (tmp_intv->value.fval.max >= tmp_local_intv->value.fval.min)) {
/* fraction-digits value is always the same (it cannot be changed in derived types) */
goto error;
}
}
/* next segment (next OR) */
seg_ptr = strchr(seg_ptr, '|');
if (!seg_ptr) {
break;
}
seg_ptr++;
tmp_intv = tmp_local_intv;
}
/* check local restrictions against superior ones */
if (intv) {
tmp_intv = intv;
tmp_local_intv = local_intv;
while (tmp_local_intv && tmp_intv) {
/* reuse local variables */
if (kind == 0) {
local_umin = tmp_local_intv->value.uval.min;
local_umax = tmp_local_intv->value.uval.max;
/* it must be in this interval */
if ((local_umin >= tmp_intv->value.uval.min) && (local_umin <= tmp_intv->value.uval.max)) {
/* this interval is covered, next one */
if (local_umax <= tmp_intv->value.uval.max) {
tmp_local_intv = tmp_local_intv->next;
continue;
/* ascending order of restrictions -> fail */
} else {
goto error;
}
}
} else if (kind == 1) {
local_smin = tmp_local_intv->value.sval.min;
local_smax = tmp_local_intv->value.sval.max;
if ((local_smin >= tmp_intv->value.sval.min) && (local_smin <= tmp_intv->value.sval.max)) {
if (local_smax <= tmp_intv->value.sval.max) {
tmp_local_intv = tmp_local_intv->next;
continue;
} else {
goto error;
}
}
} else if (kind == 2) {
local_fmin = tmp_local_intv->value.fval.min;
local_fmax = tmp_local_intv->value.fval.max;
if ((dec64cmp(local_fmin, local_fdig, tmp_intv->value.fval.min, local_fdig) > -1)
&& (dec64cmp(local_fmin, local_fdig, tmp_intv->value.fval.max, local_fdig) < 1)) {
if (dec64cmp(local_fmax, local_fdig, tmp_intv->value.fval.max, local_fdig) < 1) {
tmp_local_intv = tmp_local_intv->next;
continue;
} else {
goto error;
}
}
}
tmp_intv = tmp_intv->next;
}
/* some interval left uncovered -> fail */
if (tmp_local_intv) {
goto error;
}
}
/* append the local intervals to all the intervals of the superior types, return it all */
if (intv) {
for (tmp_intv = intv; tmp_intv->next; tmp_intv = tmp_intv->next);
tmp_intv->next = local_intv;
} else {
intv = local_intv;
}
*ret = intv;
return EXIT_SUCCESS;
error:
while (intv) {
tmp_intv = intv->next;
free(intv);
intv = tmp_intv;
}
while (local_intv) {
tmp_local_intv = local_intv->next;
free(local_intv);
local_intv = tmp_local_intv;
}
return -1;
}
/**
* @brief Resolve a typedef, return only resolved typedefs if derived. If leafref, it must be
* resolved for this function to return it. Does not log.
*
* @param[in] name Typedef name.
* @param[in] mod_name Typedef name module name.
* @param[in] module Main module.
* @param[in] parent Parent of the resolved type definition.
* @param[out] ret Pointer to the resolved typedef. Can be NULL.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
int
resolve_superior_type(const char *name, const char *mod_name, const struct lys_module *module,
const struct lys_node *parent, struct lys_tpdf **ret)
{
int i, j;
struct lys_tpdf *tpdf, *match;
int tpdf_size;
if (!mod_name) {
/* no prefix, try built-in types */
for (i = 1; i < LY_DATA_TYPE_COUNT; i++) {
if (!strcmp(ly_types[i]->name, name)) {
if (ret) {
*ret = ly_types[i];
}
return EXIT_SUCCESS;
}
}
} else {
if (!strcmp(mod_name, module->name)) {
/* prefix refers to the current module, ignore it */
mod_name = NULL;
}
}
if (!mod_name && parent) {
/* search in local typedefs */
while (parent) {
switch (parent->nodetype) {
case LYS_CONTAINER:
tpdf_size = ((struct lys_node_container *)parent)->tpdf_size;
tpdf = ((struct lys_node_container *)parent)->tpdf;
break;
case LYS_LIST:
tpdf_size = ((struct lys_node_list *)parent)->tpdf_size;
tpdf = ((struct lys_node_list *)parent)->tpdf;
break;
case LYS_GROUPING:
tpdf_size = ((struct lys_node_grp *)parent)->tpdf_size;
tpdf = ((struct lys_node_grp *)parent)->tpdf;
break;
case LYS_RPC:
case LYS_ACTION:
tpdf_size = ((struct lys_node_rpc_action *)parent)->tpdf_size;
tpdf = ((struct lys_node_rpc_action *)parent)->tpdf;
break;
case LYS_NOTIF:
tpdf_size = ((struct lys_node_notif *)parent)->tpdf_size;
tpdf = ((struct lys_node_notif *)parent)->tpdf;
break;
case LYS_INPUT:
case LYS_OUTPUT:
tpdf_size = ((struct lys_node_inout *)parent)->tpdf_size;
tpdf = ((struct lys_node_inout *)parent)->tpdf;
break;
default:
parent = lys_parent(parent);
continue;
}
for (i = 0; i < tpdf_size; i++) {
if (!strcmp(tpdf[i].name, name) && tpdf[i].type.base > 0) {
match = &tpdf[i];
goto check_leafref;
}
}
parent = lys_parent(parent);
}
} else {
/* get module where to search */
module = lys_get_import_module(module, NULL, 0, mod_name, 0);
if (!module) {
return -1;
}
}
/* search in top level typedefs */
for (i = 0; i < module->tpdf_size; i++) {
if (!strcmp(module->tpdf[i].name, name) && module->tpdf[i].type.base > 0) {
match = &module->tpdf[i];
goto check_leafref;
}
}
/* search in submodules */
for (i = 0; i < module->inc_size && module->inc[i].submodule; i++) {
for (j = 0; j < module->inc[i].submodule->tpdf_size; j++) {
if (!strcmp(module->inc[i].submodule->tpdf[j].name, name) && module->inc[i].submodule->tpdf[j].type.base > 0) {
match = &module->inc[i].submodule->tpdf[j];
goto check_leafref;
}
}
}
return EXIT_FAILURE;
check_leafref:
if (ret) {
*ret = match;
}
if (match->type.base == LY_TYPE_LEAFREF) {
while (!match->type.info.lref.path) {
match = match->type.der;
assert(match);
}
}
return EXIT_SUCCESS;
}
/**
* @brief Check the default \p value of the \p type. Logs directly.
*
* @param[in] type Type definition to use.
* @param[in] value Default value to check.
* @param[in] module Type module.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
static int
check_default(struct lys_type *type, const char **value, struct lys_module *module, int tpdf)
{
struct lys_tpdf *base_tpdf = NULL;
struct lyd_node_leaf_list node;
const char *dflt = NULL;
char *s;
int ret = EXIT_SUCCESS;
assert(value);
if (type->base <= LY_TYPE_DER) {
/* the type was not resolved yet, nothing to do for now */
return EXIT_FAILURE;
} else if (!tpdf && !module->implemented) {
/* do not check defaults in not implemented module's data */
return EXIT_SUCCESS;
} else if (tpdf && !module->implemented && type->base == LY_TYPE_IDENT) {
/* identityrefs are checked when instantiated in data instead of typedef,
* but in typedef the value has to be modified to include the prefix */
if (*value) {
if (strchr(*value, ':')) {
dflt = transform_schema2json(module, *value);
} else {
/* default prefix of the module where the typedef is defined */
asprintf(&s, "%s:%s", lys_main_module(module)->name, *value);
dflt = lydict_insert_zc(module->ctx, s);
}
lydict_remove(module->ctx, *value);
*value = dflt;
}
return EXIT_SUCCESS;
} else if (type->base == LY_TYPE_LEAFREF && tpdf) {
/* leafref in typedef cannot be checked */
return EXIT_SUCCESS;
}
dflt = *value;
if (!dflt) {
/* we do not have a new default value, so is there any to check even, in some base type? */
for (base_tpdf = type->der; base_tpdf->type.der; base_tpdf = base_tpdf->type.der) {
if (base_tpdf->dflt) {
dflt = base_tpdf->dflt;
break;
}
}
if (!dflt) {
/* no default value, nothing to check, all is well */
return EXIT_SUCCESS;
}
/* so there is a default value in a base type, but can the default value be no longer valid (did we define some new restrictions)? */
switch (type->base) {
case LY_TYPE_IDENT:
if (lys_main_module(base_tpdf->type.parent->module)->implemented) {
return EXIT_SUCCESS;
} else {
/* check the default value from typedef, but use also the typedef's module
* due to possible searching in imported modules which is expected in
* typedef's module instead of module where the typedef is used */
module = base_tpdf->module;
}
break;
case LY_TYPE_INST:
case LY_TYPE_LEAFREF:
case LY_TYPE_BOOL:
case LY_TYPE_EMPTY:
/* these have no restrictions, so we would do the exact same work as the unres in the base typedef */
return EXIT_SUCCESS;
case LY_TYPE_BITS:
/* the default value must match the restricted list of values, if the type was restricted */
if (type->info.bits.count) {
break;
}
return EXIT_SUCCESS;
case LY_TYPE_ENUM:
/* the default value must match the restricted list of values, if the type was restricted */
if (type->info.enums.count) {
break;
}
return EXIT_SUCCESS;
case LY_TYPE_DEC64:
if (type->info.dec64.range) {
break;
}
return EXIT_SUCCESS;
case LY_TYPE_BINARY:
if (type->info.binary.length) {
break;
}
return EXIT_SUCCESS;
case LY_TYPE_INT8:
case LY_TYPE_INT16:
case LY_TYPE_INT32:
case LY_TYPE_INT64:
case LY_TYPE_UINT8:
case LY_TYPE_UINT16:
case LY_TYPE_UINT32:
case LY_TYPE_UINT64:
if (type->info.num.range) {
break;
}
return EXIT_SUCCESS;
case LY_TYPE_STRING:
if (type->info.str.length || type->info.str.patterns) {
break;
}
return EXIT_SUCCESS;
case LY_TYPE_UNION:
/* way too much trouble learning whether we need to check the default again, so just do it */
break;
default:
LOGINT;
return -1;
}
} else if (type->base == LY_TYPE_EMPTY) {
LOGVAL(LYE_INCHILDSTMT, LY_VLOG_NONE, NULL, "default", type->parent->name);
LOGVAL(LYE_SPEC, LY_VLOG_NONE, NULL, "The \"empty\" data type cannot have a default value.");
return -1;
}
/* dummy leaf */
memset(&node, 0, sizeof node);
node.value_str = dflt;
node.value_type = type->base;
node.schema = calloc(1, sizeof (struct lys_node_leaf));
LY_CHECK_ERR_RETURN(!node.schema, LOGMEM, -1);
node.schema->name = strdup("fake-default");
LY_CHECK_ERR_RETURN(!node.schema->name, LOGMEM; free(node.schema), -1);
node.schema->module = module;
memcpy(&((struct lys_node_leaf *)node.schema)->type, type, sizeof *type);
if (type->base == LY_TYPE_LEAFREF) {
if (!type->info.lref.target) {
ret = EXIT_FAILURE;
goto finish;
}
ret = check_default(&type->info.lref.target->type, &dflt, module, 0);
if (!ret) {
/* adopt possibly changed default value to its canonical form */
if (*value) {
*value = dflt;
}
}
} else {
if (!lyp_parse_value(&((struct lys_node_leaf *)node.schema)->type, &node.value_str, NULL, &node, NULL, 1, 1)) {
/* possible forward reference */
ret = 1;
if (base_tpdf) {
/* default value is defined in some base typedef */
if ((type->base == LY_TYPE_BITS && type->der->type.der) ||
(type->base == LY_TYPE_ENUM && type->der->type.der)) {
/* we have refined bits/enums */
LOGVAL(LYE_SPEC, LY_VLOG_NONE, NULL,
"Invalid value \"%s\" of the default statement inherited to \"%s\" from \"%s\" base type.",
dflt, type->parent->name, base_tpdf->name);
}
}
} else {
/* success - adopt canonical form from the node into the default value */
if (dflt != node.value_str) {
/* this can happen only if we have non-inherited default value,
* inherited default values are already in canonical form */
assert(dflt == *value);
*value = node.value_str;
}
}
}
finish:
if (node.value_type == LY_TYPE_BITS) {
free(node.value.bit);
}
free((char *)node.schema->name);
free(node.schema);
return ret;
}
/**
* @brief Check a key for mandatory attributes. Logs directly.
*
* @param[in] key The key to check.
* @param[in] flags What flags to check.
* @param[in] list The list of all the keys.
* @param[in] index Index of the key in the key list.
* @param[in] name The name of the keys.
* @param[in] len The name length.
*
* @return EXIT_SUCCESS on success, -1 on error.
*/
static int
check_key(struct lys_node_list *list, int index, const char *name, int len)
{
struct lys_node_leaf *key = list->keys[index];
char *dup = NULL;
int j;
/* existence */
if (!key) {
if (name[len] != '\0') {
dup = strdup(name);
LY_CHECK_ERR_RETURN(!dup, LOGMEM, -1);
dup[len] = '\0';
name = dup;
}
LOGVAL(LYE_KEY_MISS, LY_VLOG_LYS, list, name);
free(dup);
return -1;
}
/* uniqueness */
for (j = index - 1; j >= 0; j--) {
if (key == list->keys[j]) {
LOGVAL(LYE_KEY_DUP, LY_VLOG_LYS, list, key->name);
return -1;
}
}
/* key is a leaf */
if (key->nodetype != LYS_LEAF) {
LOGVAL(LYE_KEY_NLEAF, LY_VLOG_LYS, list, key->name);
return -1;
}
/* type of the leaf is not built-in empty */
if (key->type.base == LY_TYPE_EMPTY && key->module->version < 2) {
LOGVAL(LYE_KEY_TYPE, LY_VLOG_LYS, list, key->name);
return -1;
}
/* config attribute is the same as of the list */
if ((key->flags & LYS_CONFIG_MASK) && (list->flags & LYS_CONFIG_MASK) != (key->flags & LYS_CONFIG_MASK)) {
LOGVAL(LYE_KEY_CONFIG, LY_VLOG_LYS, list, key->name);
return -1;
}
/* key is not placed from augment */
if (key->parent->nodetype == LYS_AUGMENT) {
LOGVAL(LYE_KEY_MISS, LY_VLOG_LYS, key, key->name);
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Key inserted from augment.");
return -1;
}
/* key is not when/if-feature -conditional */
j = 0;
if (key->when || (key->iffeature_size && (j = 1))) {
LOGVAL(LYE_INCHILDSTMT, LY_VLOG_LYS, key, j ? "if-feature" : "when", "leaf");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Key definition cannot depend on a \"%s\" condition.",
j ? "if-feature" : "when");
return -1;
}
return EXIT_SUCCESS;
}
/**
* @brief Resolve (test the target exists) unique. Logs directly.
*
* @param[in] parent The parent node of the unique structure.
* @param[in] uniq_str_path One path from the unique string.
*
* @return EXIT_SUCCESS on succes, EXIT_FAILURE on forward reference, -1 on error.
*/
int
resolve_unique(struct lys_node *parent, const char *uniq_str_path, uint8_t *trg_type)
{
int rc;
const struct lys_node *leaf = NULL;
rc = resolve_descendant_schema_nodeid(uniq_str_path, *lys_child(parent, LYS_LEAF), LYS_LEAF, 1, &leaf);
if (rc || !leaf) {
if (rc) {
LOGVAL(LYE_INARG, LY_VLOG_LYS, parent, uniq_str_path, "unique");
if (rc > 0) {
LOGVAL(LYE_INCHAR, LY_VLOG_PREV, NULL, uniq_str_path[rc - 1], &uniq_str_path[rc - 1]);
} else if (rc == -2) {
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Unique argument references list.");
}
rc = -1;
} else {
LOGVAL(LYE_INARG, LY_VLOG_LYS, parent, uniq_str_path, "unique");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Target leaf not found.");
rc = EXIT_FAILURE;
}
goto error;
}
if (leaf->nodetype != LYS_LEAF) {
LOGVAL(LYE_INARG, LY_VLOG_LYS, parent, uniq_str_path, "unique");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Target is not a leaf.");
return -1;
}
/* check status */
if (parent->nodetype != LYS_EXT && lyp_check_status(parent->flags, parent->module, parent->name,
leaf->flags, leaf->module, leaf->name, leaf)) {
return -1;
}
/* check that all unique's targets are of the same config type */
if (*trg_type) {
if (((*trg_type == 1) && (leaf->flags & LYS_CONFIG_R)) || ((*trg_type == 2) && (leaf->flags & LYS_CONFIG_W))) {
LOGVAL(LYE_INARG, LY_VLOG_LYS, parent, uniq_str_path, "unique");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL,
"Leaf \"%s\" referenced in unique statement is config %s, but previous referenced leaf is config %s.",
uniq_str_path, *trg_type == 1 ? "false" : "true", *trg_type == 1 ? "true" : "false");
return -1;
}
} else {
/* first unique */
if (leaf->flags & LYS_CONFIG_W) {
*trg_type = 1;
} else {
*trg_type = 2;
}
}
/* set leaf's unique flag */
((struct lys_node_leaf *)leaf)->flags |= LYS_UNIQUE;
return EXIT_SUCCESS;
error:
return rc;
}
void
unres_data_del(struct unres_data *unres, uint32_t i)
{
/* there are items after the one deleted */
if (i+1 < unres->count) {
/* we only move the data, memory is left allocated, why bother */
memmove(&unres->node[i], &unres->node[i+1], (unres->count-(i+1)) * sizeof *unres->node);
/* deleting the last item */
} else if (i == 0) {
free(unres->node);
unres->node = NULL;
}
/* if there are no items after and it is not the last one, just move the counter */
--unres->count;
}
/**
* @brief Resolve (find) a data node from a specific module. Does not log.
*
* @param[in] mod Module to search in.
* @param[in] name Name of the data node.
* @param[in] nam_len Length of the name.
* @param[in] start Data node to start the search from.
* @param[in,out] parents Resolved nodes. If there are some parents,
* they are replaced (!!) with the resolvents.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
static int
resolve_data(const struct lys_module *mod, const char *name, int nam_len, struct lyd_node *start, struct unres_data *parents)
{
struct lyd_node *node;
int flag;
uint32_t i;
if (!parents->count) {
parents->count = 1;
parents->node = malloc(sizeof *parents->node);
LY_CHECK_ERR_RETURN(!parents->node, LOGMEM, -1);
parents->node[0] = NULL;
}
for (i = 0; i < parents->count;) {
if (parents->node[i] && (parents->node[i]->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA))) {
/* skip */
++i;
continue;
}
flag = 0;
LY_TREE_FOR(parents->node[i] ? parents->node[i]->child : start, node) {
if (lyd_node_module(node) == mod && !strncmp(node->schema->name, name, nam_len)
&& node->schema->name[nam_len] == '\0') {
/* matching target */
if (!flag) {
/* put node instead of the current parent */
parents->node[i] = node;
flag = 1;
} else {
/* multiple matching, so create a new node */
++parents->count;
parents->node = ly_realloc(parents->node, parents->count * sizeof *parents->node);
LY_CHECK_ERR_RETURN(!parents->node, LOGMEM, EXIT_FAILURE);
parents->node[parents->count-1] = node;
++i;
}
}
}
if (!flag) {
/* remove item from the parents list */
unres_data_del(parents, i);
} else {
++i;
}
}
return parents->count ? EXIT_SUCCESS : EXIT_FAILURE;
}
static int
resolve_schema_leafref_valid_dep_flag(const struct lys_node *op_node, const struct lys_node *first_node, int abs_path)
{
int dep1, dep2;
const struct lys_node *node;
if (lys_parent(op_node)) {
/* inner operation (notif/action) */
if (abs_path) {
return 1;
} else {
/* compare depth of both nodes */
for (dep1 = 0, node = op_node; lys_parent(node); node = lys_parent(node));
for (dep2 = 0, node = first_node; lys_parent(node); node = lys_parent(node));
if ((dep2 > dep1) || ((dep2 == dep1) && (op_node != first_node))) {
return 1;
}
}
} else {
/* top-level operation (notif/rpc) */
if (op_node != first_node) {
return 1;
}
}
return 0;
}
/**
* @brief Resolve a path (leafref) predicate in JSON schema context. Logs directly.
*
* @param[in] path Path to use.
* @param[in] context_node Predicate context node (where the predicate is placed).
* @param[in] parent Path context node (where the path begins/is placed).
* @param[in] op_node Optional node if the leafref is in an operation (action/rpc/notif).
*
* @return 0 on forward reference, otherwise the number
* of characters successfully parsed,
* positive on success, negative on failure.
*/
static int
resolve_schema_leafref_predicate(const char *path, const struct lys_node *context_node,
struct lys_node *parent, const struct lys_node *op_node)
{
const struct lys_module *trg_mod;
const struct lys_node *src_node, *dst_node;
const char *path_key_expr, *source, *sour_pref, *dest, *dest_pref;
int pke_len, sour_len, sour_pref_len, dest_len, dest_pref_len, pke_parsed, parsed = 0;
int has_predicate, dest_parent_times, i, rc, first_iter;
do {
if ((i = parse_path_predicate(path, &sour_pref, &sour_pref_len, &source, &sour_len, &path_key_expr,
&pke_len, &has_predicate)) < 1) {
LOGVAL(LYE_INCHAR, parent ? LY_VLOG_LYS : LY_VLOG_NONE, parent, path[-i], path-i);
return -parsed+i;
}
parsed += i;
path += i;
/* source (must be leaf) */
if (sour_pref) {
trg_mod = lys_get_import_module(lys_node_module(parent), NULL, 0, sour_pref, sour_pref_len);
} else {
trg_mod = NULL;
}
rc = lys_getnext_data(trg_mod, context_node, source, sour_len, LYS_LEAF | LYS_LEAFLIST, &src_node);
if (rc) {
LOGVAL(LYE_NORESOLV, parent ? LY_VLOG_LYS : LY_VLOG_NONE, parent, "leafref predicate", path-parsed);
return 0;
}
/* destination */
dest_parent_times = 0;
pke_parsed = 0;
if ((i = parse_path_key_expr(path_key_expr, &dest_pref, &dest_pref_len, &dest, &dest_len,
&dest_parent_times)) < 1) {
LOGVAL(LYE_INCHAR, parent ? LY_VLOG_LYS : LY_VLOG_NONE, parent, path_key_expr[-i], path_key_expr-i);
return -parsed;
}
pke_parsed += i;
for (i = 0, dst_node = parent; i < dest_parent_times; ++i) {
if (dst_node->parent && (dst_node->parent->nodetype == LYS_AUGMENT)
&& !((struct lys_node_augment *)dst_node->parent)->target) {
/* we are in an unresolved augment, cannot evaluate */
LOGVAL(LYE_SPEC, LY_VLOG_LYS, dst_node->parent,
"Cannot resolve leafref predicate \"%s\" because it is in an unresolved augment.", path_key_expr);
return 0;
}
/* path is supposed to be evaluated in data tree, so we have to skip
* all schema nodes that cannot be instantiated in data tree */
for (dst_node = lys_parent(dst_node);
dst_node && !(dst_node->nodetype & (LYS_CONTAINER | LYS_LIST | LYS_ACTION | LYS_NOTIF | LYS_RPC));
dst_node = lys_parent(dst_node));
if (!dst_node) {
LOGVAL(LYE_NORESOLV, parent ? LY_VLOG_LYS : LY_VLOG_NONE, parent, "leafref predicate", path_key_expr);
return 0;
}
}
first_iter = 1;
while (1) {
if (dest_pref) {
trg_mod = lys_get_import_module(lys_node_module(parent), NULL, 0, dest_pref, dest_pref_len);
} else {
trg_mod = NULL;
}
rc = lys_getnext_data(trg_mod, dst_node, dest, dest_len, LYS_CONTAINER | LYS_LIST | LYS_LEAF, &dst_node);
if (rc) {
LOGVAL(LYE_NORESOLV, parent ? LY_VLOG_LYS : LY_VLOG_NONE, parent, "leafref predicate", path_key_expr);
return 0;
}
if (first_iter) {
if (resolve_schema_leafref_valid_dep_flag(op_node, dst_node, 0)) {
parent->flags |= LYS_LEAFREF_DEP;
}
first_iter = 0;
}
if (pke_len == pke_parsed) {
break;
}
if ((i = parse_path_key_expr(path_key_expr + pke_parsed, &dest_pref, &dest_pref_len, &dest, &dest_len,
&dest_parent_times)) < 1) {
LOGVAL(LYE_INCHAR, parent ? LY_VLOG_LYS : LY_VLOG_NONE, parent,
(path_key_expr + pke_parsed)[-i], (path_key_expr + pke_parsed)-i);
return -parsed;
}
pke_parsed += i;
}
/* check source - dest match */
if (dst_node->nodetype != src_node->nodetype) {
LOGVAL(LYE_NORESOLV, parent ? LY_VLOG_LYS : LY_VLOG_NONE, parent, "leafref predicate", path - parsed);
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Destination node is not a %s, but a %s.",
strnodetype(src_node->nodetype), strnodetype(dst_node->nodetype));
return -parsed;
}
} while (has_predicate);
return parsed;
}
/**
* @brief Resolve a path (leafref) in JSON schema context. Logs directly.
*
* @param[in] path Path to use.
* @param[in] parent_node Parent of the leafref.
* @param[out] ret Pointer to the resolved schema node. Can be NULL.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
static int
resolve_schema_leafref(const char *path, struct lys_node *parent, const struct lys_node **ret)
{
const struct lys_node *node, *op_node = NULL;
struct lys_node_augment *last_aug;
const struct lys_module *tmp_mod, *cur_module;
const char *id, *prefix, *name;
int pref_len, nam_len, parent_times, has_predicate;
int i, first_iter, rc;
first_iter = 1;
parent_times = 0;
id = path;
/* find operation schema we are in */
for (op_node = lys_parent(parent);
op_node && !(op_node->nodetype & (LYS_ACTION | LYS_NOTIF | LYS_RPC));
op_node = lys_parent(op_node));
cur_module = lys_node_module(parent);
do {
if ((i = parse_path_arg(cur_module, id, &prefix, &pref_len, &name, &nam_len, &parent_times, &has_predicate)) < 1) {
LOGVAL(LYE_INCHAR, LY_VLOG_LYS, parent, id[-i], &id[-i]);
return -1;
}
id += i;
/* get the current module */
tmp_mod = prefix ? lys_get_import_module(cur_module, NULL, 0, prefix, pref_len) : cur_module;
if (!tmp_mod) {
LOGVAL(LYE_NORESOLV, LY_VLOG_LYS, parent, "leafref", path);
return EXIT_FAILURE;
}
last_aug = NULL;
if (first_iter) {
if (parent_times == -1) {
/* use module data */
node = NULL;
} else if (parent_times > 0) {
/* we are looking for the right parent */
for (i = 0, node = parent; i < parent_times; i++) {
if (node->parent && (node->parent->nodetype == LYS_AUGMENT)
&& !((struct lys_node_augment *)node->parent)->target) {
/* we are in an unresolved augment, cannot evaluate */
LOGVAL(LYE_SPEC, LY_VLOG_LYS, node->parent,
"Cannot resolve leafref \"%s\" because it is in an unresolved augment.", path);
return EXIT_FAILURE;
}
/* path is supposed to be evaluated in data tree, so we have to skip
* all schema nodes that cannot be instantiated in data tree */
for (node = lys_parent(node);
node && !(node->nodetype & (LYS_CONTAINER | LYS_LIST | LYS_ACTION | LYS_NOTIF | LYS_RPC));
node = lys_parent(node));
if (!node) {
if (i == parent_times - 1) {
/* top-level */
break;
}
/* higher than top-level */
LOGVAL(LYE_NORESOLV, LY_VLOG_LYS, parent, "leafref", path);
return EXIT_FAILURE;
}
}
} else {
LOGINT;
return -1;
}
}
/* find the next node (either in unconnected augment or as a schema sibling, node is NULL for top-level node -
* - useless to search for that in augments) */
if (!tmp_mod->implemented && node) {
get_next_augment:
last_aug = lys_getnext_target_aug(last_aug, tmp_mod, node);
}
rc = lys_getnext_data(tmp_mod, (last_aug ? (struct lys_node *)last_aug : node), name, nam_len, LYS_LIST
| LYS_CONTAINER | LYS_RPC | LYS_ACTION | LYS_NOTIF | LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA, &node);
if (rc) {
if (last_aug) {
goto get_next_augment;
}
LOGVAL(LYE_NORESOLV, LY_VLOG_LYS, parent, "leafref", path);
return EXIT_FAILURE;
}
if (first_iter) {
/* set external dependency flag, we can decide based on the first found node */
if (op_node && parent_times &&
resolve_schema_leafref_valid_dep_flag(op_node, node, (parent_times == -1 ? 1 : 0))) {
parent->flags |= LYS_LEAFREF_DEP;
}
first_iter = 0;
}
if (has_predicate) {
/* we have predicate, so the current result must be list */
if (node->nodetype != LYS_LIST) {
LOGVAL(LYE_NORESOLV, LY_VLOG_LYS, parent, "leafref", path);
return -1;
}
i = resolve_schema_leafref_predicate(id, node, parent, op_node);
if (!i) {
return EXIT_FAILURE;
} else if (i < 0) {
return -1;
}
id += i;
has_predicate = 0;
}
} while (id[0]);
/* the target must be leaf or leaf-list (in YANG 1.1 only) */
if ((node->nodetype != LYS_LEAF) && (node->nodetype != LYS_LEAFLIST)) {
LOGVAL(LYE_NORESOLV, LY_VLOG_LYS, parent, "leafref", path);
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Leafref target \"%s\" is not a leaf nor a leaf-list.", path);
return -1;
}
/* check status */
if (lyp_check_status(parent->flags, parent->module, parent->name,
node->flags, node->module, node->name, node)) {
return -1;
}
if (ret) {
*ret = node;
}
return EXIT_SUCCESS;
}
/**
* @brief Resolve instance-identifier predicate in JSON data format.
* Does not log.
*
* @param[in] pred Predicate to use.
* @param[in,out] node_match Nodes matching the restriction without
* the predicate. Nodes not satisfying
* the predicate are removed.
*
* @return Number of characters successfully parsed,
* positive on success, negative on failure.
*/
static int
resolve_instid_predicate(const char *pred, struct unres_data *node_match)
{
/* ... /node[target = value] ... */
struct lyd_node *target;
const char *model, *name, *value;
int mod_len, nam_len, val_len, i, has_predicate, cur_idx, idx, parsed, pred_iter, k;
uint32_t j;
assert(pred && node_match->count);
idx = -1;
parsed = 0;
pred_iter = -1;
do {
if ((i = parse_predicate(pred, &model, &mod_len, &name, &nam_len, &value, &val_len, &has_predicate)) < 1) {
return -parsed+i;
}
parsed += i;
pred += i;
if (isdigit(name[0])) {
/* pos */
assert(!value);
idx = atoi(name);
} else if (name[0] != '.') {
/* list keys */
if (pred_iter < 0) {
pred_iter = 1;
} else {
++pred_iter;
}
}
for (cur_idx = 1, j = 0; j < node_match->count; ++cur_idx) {
/* target */
if (name[0] == '.') {
/* leaf-list value */
if (node_match->node[j]->schema->nodetype != LYS_LEAFLIST) {
goto remove_instid;
}
target = node_match->node[j];
/* check the value */
if (strncmp(((struct lyd_node_leaf_list *)target)->value_str, value, val_len)
|| ((struct lyd_node_leaf_list *)target)->value_str[val_len]) {
goto remove_instid;
}
} else if (!value) {
/* keyless list position */
if ((node_match->node[j]->schema->nodetype != LYS_LIST)
|| ((struct lys_node_list *)node_match->node[j]->schema)->keys) {
goto remove_instid;
}
if (idx != cur_idx) {
goto remove_instid;
}
} else {
/* list key value */
if (node_match->node[j]->schema->nodetype != LYS_LIST) {
goto remove_instid;
}
/* key module must match the list module */
if (strncmp(node_match->node[j]->schema->module->name, model, mod_len)
|| node_match->node[j]->schema->module->name[mod_len]) {
goto remove_instid;
}
/* find the key leaf */
for (k = 1, target = node_match->node[j]->child; target && (k < pred_iter); k++, target = target->next);
if (!target) {
goto remove_instid;
}
if ((struct lys_node_leaf *)target->schema !=
((struct lys_node_list *)node_match->node[j]->schema)->keys[pred_iter - 1]) {
goto remove_instid;
}
/* check the value */
if (strncmp(((struct lyd_node_leaf_list *)target)->value_str, value, val_len)
|| ((struct lyd_node_leaf_list *)target)->value_str[val_len]) {
goto remove_instid;
}
}
/* instid is ok, continue check with the next one */
++j;
continue;
remove_instid:
/* does not fulfill conditions, remove instid record */
unres_data_del(node_match, j);
}
} while (has_predicate);
/* check that all list keys were specified */
if ((pred_iter > 0) && node_match->count) {
j = 0;
while (j < node_match->count) {
assert(node_match->node[j]->schema->nodetype == LYS_LIST);
if (pred_iter < ((struct lys_node_list *)node_match->node[j]->schema)->keys_size) {
/* not enough predicates, just remove the list instance */
unres_data_del(node_match, j);
} else {
++j;
}
}
if (!node_match->count) {
LOGVAL(LYE_SPEC, LY_VLOG_NONE, NULL, "Instance identifier is missing some list keys.");
}
}
return parsed;
}
int
lys_check_xpath(struct lys_node *node, int check_place, int warn_on_fwd_ref)
{
struct lys_node *parent, *elem;
struct lyxp_set set;
uint32_t i;
int ret;
if (check_place) {
parent = node;
while (parent) {
if (parent->nodetype == LYS_GROUPING) {
/* unresolved grouping, skip for now (will be checked later) */
return EXIT_SUCCESS;
}
if (parent->nodetype == LYS_AUGMENT) {
if (!((struct lys_node_augment *)parent)->target) {
/* unresolved augment, skip for now (will be checked later) */
return EXIT_FAILURE;
} else {
parent = ((struct lys_node_augment *)parent)->target;
continue;
}
}
parent = parent->parent;
}
}
ret = lyxp_node_atomize(node, &set, warn_on_fwd_ref);
if (ret == -1) {
return -1;
}
for (parent = node; parent && !(parent->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)); parent = lys_parent(parent));
for (i = 0; i < set.used; ++i) {
/* skip roots'n'stuff */
if (set.val.snodes[i].type == LYXP_NODE_ELEM) {
/* XPath expression cannot reference "lower" status than the node that has the definition */
if (lyp_check_status(node->flags, lys_node_module(node), node->name, set.val.snodes[i].snode->flags,
lys_node_module(set.val.snodes[i].snode), set.val.snodes[i].snode->name, node)) {
return -1;
}
if (parent) {
for (elem = set.val.snodes[i].snode; elem && (elem != parent); elem = lys_parent(elem));
if (!elem) {
/* not in node's RPC or notification subtree, set the flag */
node->flags |= LYS_XPATH_DEP;
break;
}
}
}
}
free(set.val.snodes);
return ret;
}
static int
check_leafref_config(struct lys_node_leaf *leaf, struct lys_type *type)
{
int i;
if (type->base == LY_TYPE_LEAFREF) {
if ((leaf->flags & LYS_CONFIG_W) && type->info.lref.target && type->info.lref.req != -1 &&
(type->info.lref.target->flags & LYS_CONFIG_R)) {
LOGVAL(LYE_SPEC, LY_VLOG_LYS, leaf, "The leafref %s is config but refers to a non-config %s.",
strnodetype(leaf->nodetype), strnodetype(type->info.lref.target->nodetype));
return -1;
}
/* we can skip the test in case the leafref is not yet resolved. In that case the test is done in the time
* of leafref resolving (lys_leaf_add_leafref_target()) */
} else if (type->base == LY_TYPE_UNION) {
for (i = 0; i < type->info.uni.count; i++) {
if (check_leafref_config(leaf, &type->info.uni.types[i])) {
return -1;
}
}
}
return 0;
}
/**
* @brief Passes config flag down to children, skips nodes without config flags.
* Logs.
*
* @param[in] node Siblings and their children to have flags changed.
* @param[in] clear Flag to clear all config flags if parent is LYS_NOTIF, LYS_INPUT, LYS_OUTPUT, LYS_RPC.
* @param[in] flags Flags to assign to all the nodes.
* @param[in,out] unres List of unresolved items.
*
* @return 0 on success, -1 on error.
*/
int
inherit_config_flag(struct lys_node *node, int flags, int clear)
{
struct lys_node_leaf *leaf;
assert(!(flags ^ (flags & LYS_CONFIG_MASK)));
LY_TREE_FOR(node, node) {
if (clear) {
node->flags &= ~LYS_CONFIG_MASK;
node->flags &= ~LYS_CONFIG_SET;
} else {
if (node->flags & LYS_CONFIG_SET) {
/* skip nodes with an explicit config value */
if ((flags & LYS_CONFIG_R) && (node->flags & LYS_CONFIG_W)) {
LOGVAL(LYE_INARG, LY_VLOG_LYS, node, "true", "config");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "State nodes cannot have configuration nodes as children.");
return -1;
}
continue;
}
if (!(node->nodetype & (LYS_USES | LYS_GROUPING))) {
node->flags = (node->flags & ~LYS_CONFIG_MASK) | flags;
/* check that configuration lists have keys */
if ((node->nodetype == LYS_LIST) && (node->flags & LYS_CONFIG_W)
&& !((struct lys_node_list *)node)->keys_size) {
LOGVAL(LYE_MISSCHILDSTMT, LY_VLOG_LYS, node, "key", "list");
return -1;
}
}
}
if (!(node->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA))) {
if (inherit_config_flag(node->child, flags, clear)) {
return -1;
}
} else if (node->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
leaf = (struct lys_node_leaf *)node;
if (check_leafref_config(leaf, &leaf->type)) {
return -1;
}
}
}
return 0;
}
/**
* @brief Resolve augment target. Logs directly.
*
* @param[in] aug Augment to use.
* @param[in] siblings Nodes where to start the search in. If set, uses augment, if not, standalone augment.
* @param[in,out] unres List of unresolved items.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
static int
resolve_augment(struct lys_node_augment *aug, struct lys_node *siblings, struct unres_schema *unres)
{
int rc;
struct lys_node *sub;
struct lys_module *mod;
struct ly_set *set;
assert(aug);
mod = lys_main_module(aug->module);
/* set it as not applied for now */
aug->flags |= LYS_NOTAPPLIED;
/* it can already be resolved in case we returned EXIT_FAILURE from if block below */
if (!aug->target) {
/* resolve target node */
rc = resolve_schema_nodeid(aug->target_name, siblings, (siblings ? NULL : lys_node_module((struct lys_node *)aug)), &set, 0, 0);
if (rc == -1) {
LOGVAL(LYE_PATH, LY_VLOG_LYS, aug);
return -1;
}
if (!set) {
LOGVAL(LYE_INRESOLV, LY_VLOG_LYS, aug, "augment", aug->target_name);
return EXIT_FAILURE;
}
aug->target = set->set.s[0];
ly_set_free(set);
}
/* check for mandatory nodes - if the target node is in another module
* the added nodes cannot be mandatory
*/
if (!aug->parent && (lys_node_module((struct lys_node *)aug) != lys_node_module(aug->target))
&& (rc = lyp_check_mandatory_augment(aug, aug->target))) {
return rc;
}
/* check augment target type and then augment nodes type */
if (aug->target->nodetype & (LYS_CONTAINER | LYS_LIST)) {
LY_TREE_FOR(aug->child, sub) {
if (!(sub->nodetype & (LYS_ANYDATA | LYS_CONTAINER | LYS_LEAF | LYS_LIST | LYS_LEAFLIST | LYS_USES
| LYS_CHOICE | LYS_ACTION | LYS_NOTIF))) {
LOGVAL(LYE_INCHILDSTMT, LY_VLOG_LYS, aug, strnodetype(sub->nodetype), "augment");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Cannot augment \"%s\" with a \"%s\".",
strnodetype(aug->target->nodetype), strnodetype(sub->nodetype));
return -1;
}
}
} else if (aug->target->nodetype & (LYS_CASE | LYS_INPUT | LYS_OUTPUT | LYS_NOTIF)) {
LY_TREE_FOR(aug->child, sub) {
if (!(sub->nodetype & (LYS_ANYDATA | LYS_CONTAINER | LYS_LEAF | LYS_LIST | LYS_LEAFLIST | LYS_USES | LYS_CHOICE))) {
LOGVAL(LYE_INCHILDSTMT, LY_VLOG_LYS, aug, strnodetype(sub->nodetype), "augment");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Cannot augment \"%s\" with a \"%s\".",
strnodetype(aug->target->nodetype), strnodetype(sub->nodetype));
return -1;
}
}
} else if (aug->target->nodetype == LYS_CHOICE) {
LY_TREE_FOR(aug->child, sub) {
if (!(sub->nodetype & (LYS_CASE | LYS_ANYDATA | LYS_CONTAINER | LYS_LEAF | LYS_LIST | LYS_LEAFLIST))) {
LOGVAL(LYE_INCHILDSTMT, LY_VLOG_LYS, aug, strnodetype(sub->nodetype), "augment");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Cannot augment \"%s\" with a \"%s\".",
strnodetype(aug->target->nodetype), strnodetype(sub->nodetype));
return -1;
}
}
} else {
LOGVAL(LYE_INARG, LY_VLOG_LYS, aug, aug->target_name, "target-node");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Invalid augment target node type \"%s\".", strnodetype(aug->target->nodetype));
return -1;
}
/* check identifier uniqueness as in lys_node_addchild() */
LY_TREE_FOR(aug->child, sub) {
if (lys_check_id(sub, aug->target, NULL)) {
return -1;
}
}
if (!aug->child) {
/* empty augment, nothing to connect, but it is techincally applied */
LOGWRN("Augment \"%s\" without children.", aug->target_name);
aug->flags &= ~LYS_NOTAPPLIED;
} else if (mod->implemented && apply_aug(aug, unres)) {
/* we tried to connect it, we failed */
return -1;
}
return EXIT_SUCCESS;
}
static int
resolve_extension(struct unres_ext *info, struct lys_ext_instance **ext, struct unres_schema *unres)
{
enum LY_VLOG_ELEM vlog_type;
void *vlog_node;
unsigned int i, j;
struct lys_ext *e;
char *ext_name, *ext_prefix, *tmp;
struct lyxml_elem *next_yin, *yin;
const struct lys_module *mod;
struct lys_ext_instance *tmp_ext;
LYEXT_TYPE etype;
switch (info->parent_type) {
case LYEXT_PAR_NODE:
vlog_node = info->parent;
vlog_type = LY_VLOG_LYS;
break;
case LYEXT_PAR_MODULE:
case LYEXT_PAR_IMPORT:
case LYEXT_PAR_INCLUDE:
vlog_node = NULL;
vlog_type = LY_VLOG_LYS;
break;
default:
vlog_node = NULL;
vlog_type = LY_VLOG_NONE;
break;
}
if (info->datatype == LYS_IN_YIN) {
/* YIN */
/* get the module where the extension is supposed to be defined */
mod = lys_get_import_module_ns(info->mod, info->data.yin->ns->value);
if (!mod) {
LOGVAL(LYE_INSTMT, vlog_type, vlog_node, info->data.yin->name);
return EXIT_FAILURE;
}
/* find the extension definition */
e = NULL;
for (i = 0; i < mod->extensions_size; i++) {
if (ly_strequal(mod->extensions[i].name, info->data.yin->name, 1)) {
e = &mod->extensions[i];
break;
}
}
/* try submodules */
for (j = 0; !e && j < mod->inc_size; j++) {
for (i = 0; i < mod->inc[j].submodule->extensions_size; i++) {
if (ly_strequal(mod->inc[j].submodule->extensions[i].name, info->data.yin->name, 1)) {
e = &mod->inc[j].submodule->extensions[i];
break;
}
}
}
if (!e) {
LOGVAL(LYE_INSTMT, vlog_type, vlog_node, info->data.yin->name);
return EXIT_FAILURE;
}
/* we have the extension definition, so now it cannot be forward referenced and error is always fatal */
if (e->plugin && e->plugin->check_position) {
/* common part - we have plugin with position checking function, use it first */
if ((*e->plugin->check_position)(info->parent, info->parent_type, info->substmt)) {
/* extension is not allowed here */
LOGVAL(LYE_INSTMT, vlog_type, vlog_node, e->name);
return -1;
}
}
/* extension type-specific part - allocation */
if (e->plugin) {
etype = e->plugin->type;
} else {
/* default type */
etype = LYEXT_FLAG;
}
switch (etype) {
case LYEXT_FLAG:
(*ext) = calloc(1, sizeof(struct lys_ext_instance));
break;
case LYEXT_COMPLEX:
(*ext) = calloc(1, ((struct lyext_plugin_complex*)e->plugin)->instance_size);
break;
case LYEXT_ERR:
/* we never should be here */
LOGINT;
return -1;
}
LY_CHECK_ERR_RETURN(!*ext, LOGMEM, -1);
/* common part for all extension types */
(*ext)->def = e;
(*ext)->parent = info->parent;
(*ext)->parent_type = info->parent_type;
(*ext)->insubstmt = info->substmt;
(*ext)->insubstmt_index = info->substmt_index;
(*ext)->ext_type = e->plugin ? e->plugin->type : LYEXT_FLAG;
if (!(e->flags & LYS_YINELEM) && e->argument) {
(*ext)->arg_value = lyxml_get_attr(info->data.yin, e->argument, NULL);
if (!(*ext)->arg_value) {
LOGVAL(LYE_MISSARG, LY_VLOG_NONE, NULL, e->argument, info->data.yin->name);
return -1;
}
(*ext)->arg_value = lydict_insert(mod->ctx, (*ext)->arg_value, 0);
}
(*ext)->nodetype = LYS_EXT;
(*ext)->module = info->mod;
/* extension type-specific part - parsing content */
switch (etype) {
case LYEXT_FLAG:
LY_TREE_FOR_SAFE(info->data.yin->child, next_yin, yin) {
if (!yin->ns) {
/* garbage */
lyxml_free(mod->ctx, yin);
continue;
} else if (!strcmp(yin->ns->value, LY_NSYIN)) {
/* standard YANG statements are not expected here */
LOGVAL(LYE_INCHILDSTMT, vlog_type, vlog_node, yin->name, info->data.yin->name);
return -1;
} else if (yin->ns == info->data.yin->ns &&
(e->flags & LYS_YINELEM) && ly_strequal(yin->name, e->argument, 1)) {
/* we have the extension's argument */
if ((*ext)->arg_value) {
LOGVAL(LYE_TOOMANY, vlog_type, vlog_node, yin->name, info->data.yin->name);
return -1;
}
(*ext)->arg_value = yin->content;
yin->content = NULL;
lyxml_free(mod->ctx, yin);
} else {
/* extension instance */
if (lyp_yin_parse_subnode_ext(info->mod, *ext, LYEXT_PAR_EXTINST, yin,
LYEXT_SUBSTMT_SELF, 0, unres)) {
return -1;
}
continue;
}
}
break;
case LYEXT_COMPLEX:
((struct lys_ext_instance_complex*)(*ext))->substmt = ((struct lyext_plugin_complex*)e->plugin)->substmt;
if (lyp_yin_parse_complex_ext(info->mod, (struct lys_ext_instance_complex*)(*ext), info->data.yin, unres)) {
/* TODO memory cleanup */
return -1;
}
break;
default:
break;
}
/* TODO - lyext_check_result_clb, other than LYEXT_FLAG plugins */
} else {
/* YANG */
ext_prefix = (char *)(*ext)->def;
tmp = strchr(ext_prefix, ':');
if (!tmp) {
LOGVAL(LYE_INSTMT, vlog_type, vlog_node, ext_prefix);
goto error;
}
ext_name = tmp + 1;
/* get the module where the extension is supposed to be defined */
mod = lys_get_import_module(info->mod, ext_prefix, tmp - ext_prefix, NULL, 0);
if (!mod) {
LOGVAL(LYE_INSTMT, vlog_type, vlog_node, ext_prefix);
return EXIT_FAILURE;
}
/* find the extension definition */
e = NULL;
for (i = 0; i < mod->extensions_size; i++) {
if (ly_strequal(mod->extensions[i].name, ext_name, 0)) {
e = &mod->extensions[i];
break;
}
}
/* try submodules */
for (j = 0; !e && j < mod->inc_size; j++) {
for (i = 0; i < mod->inc[j].submodule->extensions_size; i++) {
if (ly_strequal(mod->inc[j].submodule->extensions[i].name, ext_name, 0)) {
e = &mod->inc[j].submodule->extensions[i];
break;
}
}
}
if (!e) {
LOGVAL(LYE_INSTMT, vlog_type, vlog_node, ext_prefix);
return EXIT_FAILURE;
}
/* we have the extension definition, so now it cannot be forward referenced and error is always fatal */
if (e->plugin && e->plugin->check_position) {
/* common part - we have plugin with position checking function, use it first */
if ((*e->plugin->check_position)(info->parent, info->parent_type, info->substmt)) {
/* extension is not allowed here */
LOGVAL(LYE_INSTMT, vlog_type, vlog_node, e->name);
goto error;
}
}
/* extension common part */
(*ext)->flags &= ~LYEXT_OPT_YANG;
(*ext)->def = e;
(*ext)->parent = info->parent;
(*ext)->ext_type = e->plugin ? e->plugin->type : LYEXT_FLAG;
if (e->argument && !(*ext)->arg_value) {
LOGVAL(LYE_MISSARG, LY_VLOG_NONE, NULL, e->argument, ext_name);
goto error;
}
(*ext)->module = info->mod;
(*ext)->nodetype = LYS_EXT;
/* extension type-specific part */
if (e->plugin) {
etype = e->plugin->type;
} else {
/* default type */
etype = LYEXT_FLAG;
}
switch (etype) {
case LYEXT_FLAG:
/* nothing change */
break;
case LYEXT_COMPLEX:
tmp_ext = realloc(*ext, ((struct lyext_plugin_complex*)e->plugin)->instance_size);
LY_CHECK_ERR_GOTO(!tmp_ext, LOGMEM, error);
memset((char *)tmp_ext + sizeof **ext, 0, ((struct lyext_plugin_complex*)e->plugin)->instance_size - sizeof **ext);
(*ext) = tmp_ext;
((struct lys_ext_instance_complex*)(*ext))->substmt = ((struct lyext_plugin_complex*)e->plugin)->substmt;
if (info->data.yang) {
*tmp = ':';
if (yang_parse_ext_substatement(info->mod, unres, info->data.yang->ext_substmt, ext_prefix,
(struct lys_ext_instance_complex*)(*ext))) {
goto error;
}
if (yang_fill_extcomplex_module(info->mod->ctx, (struct lys_ext_instance_complex*)(*ext), ext_prefix,
info->data.yang->ext_modules, info->mod->implemented)) {
goto error;
}
}
if (lyp_mand_check_ext((struct lys_ext_instance_complex*)(*ext), ext_prefix)) {
goto error;
}
break;
case LYEXT_ERR:
/* we never should be here */
LOGINT;
goto error;
}
if (yang_check_ext_instance(info->mod, &(*ext)->ext, (*ext)->ext_size, *ext, unres)) {
goto error;
}
free(ext_prefix);
}
return EXIT_SUCCESS;
error:
free(ext_prefix);
return -1;
}
/**
* @brief Resolve (find) choice default case. Does not log.
*
* @param[in] choic Choice to use.
* @param[in] dflt Name of the default case.
*
* @return Pointer to the default node or NULL.
*/
static struct lys_node *
resolve_choice_dflt(struct lys_node_choice *choic, const char *dflt)
{
struct lys_node *child, *ret;
LY_TREE_FOR(choic->child, child) {
if (child->nodetype == LYS_USES) {
ret = resolve_choice_dflt((struct lys_node_choice *)child, dflt);
if (ret) {
return ret;
}
}
if (ly_strequal(child->name, dflt, 1) && (child->nodetype & (LYS_ANYDATA | LYS_CASE
| LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_CHOICE))) {
return child;
}
}
return NULL;
}
/**
* @brief Resolve uses, apply augments, refines. Logs directly.
*
* @param[in] uses Uses to use.
* @param[in,out] unres List of unresolved items.
*
* @return EXIT_SUCCESS on success, -1 on error.
*/
static int
resolve_uses(struct lys_node_uses *uses, struct unres_schema *unres)
{
struct ly_ctx *ctx = uses->module->ctx; /* shortcut */
struct lys_node *node = NULL, *next, *iter, **refine_nodes = NULL;
struct lys_node *node_aux, *parent, *tmp;
struct lys_node_leaflist *llist;
struct lys_node_leaf *leaf;
struct lys_refine *rfn;
struct lys_restr *must, **old_must;
struct lys_iffeature *iff, **old_iff;
int i, j, k, rc;
uint8_t size, *old_size;
unsigned int usize, usize1, usize2;
assert(uses->grp);
/* check that the grouping is resolved (no unresolved uses inside) */
assert(!uses->grp->unres_count);
if (!uses->grp->child) {
/* grouping without children, warning was already displayed */
return EXIT_SUCCESS;
}
/* copy the data nodes from grouping into the uses context */
LY_TREE_FOR(uses->grp->child, node_aux) {
if (node_aux->nodetype & LYS_GROUPING) {
/* do not instantiate groupings from groupings */
continue;
}
node = lys_node_dup(uses->module, (struct lys_node *)uses, node_aux, unres, 0);
if (!node) {
LOGVAL(LYE_INARG, LY_VLOG_LYS, uses, uses->grp->name, "uses");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Copying data from grouping failed.");
goto fail;
}
/* test the name of siblings */
LY_TREE_FOR((uses->parent) ? *lys_child(uses->parent, LYS_USES) : lys_main_module(uses->module)->data, tmp) {
if (!(tmp->nodetype & (LYS_USES | LYS_GROUPING | LYS_CASE)) && ly_strequal(tmp->name, node_aux->name, 1)) {
goto fail;
}
}
}
/* we managed to copy the grouping, the rest must be possible to resolve */
if (uses->refine_size) {
refine_nodes = malloc(uses->refine_size * sizeof *refine_nodes);
LY_CHECK_ERR_GOTO(!refine_nodes, LOGMEM, fail);
}
/* apply refines */
for (i = 0; i < uses->refine_size; i++) {
rfn = &uses->refine[i];
rc = resolve_descendant_schema_nodeid(rfn->target_name, uses->child,
LYS_NO_RPC_NOTIF_NODE | LYS_ACTION | LYS_NOTIF,
0, (const struct lys_node **)&node);
if (rc || !node) {
LOGVAL(LYE_INARG, LY_VLOG_LYS, uses, rfn->target_name, "refine");
goto fail;
}
if (rfn->target_type && !(node->nodetype & rfn->target_type)) {
LOGVAL(LYE_INARG, LY_VLOG_LYS, uses, rfn->target_name, "refine");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Refine substatements not applicable to the target-node.");
goto fail;
}
refine_nodes[i] = node;
/* description on any nodetype */
if (rfn->dsc) {
lydict_remove(ctx, node->dsc);
node->dsc = lydict_insert(ctx, rfn->dsc, 0);
}
/* reference on any nodetype */
if (rfn->ref) {
lydict_remove(ctx, node->ref);
node->ref = lydict_insert(ctx, rfn->ref, 0);
}
/* config on any nodetype,
* in case of notification or rpc/action, the config is not applicable (there is no config status) */
if ((rfn->flags & LYS_CONFIG_MASK) && (node->flags & LYS_CONFIG_MASK)) {
node->flags &= ~LYS_CONFIG_MASK;
node->flags |= (rfn->flags & LYS_CONFIG_MASK);
}
/* default value ... */
if (rfn->dflt_size) {
if (node->nodetype == LYS_LEAF) {
/* leaf */
leaf = (struct lys_node_leaf *)node;
/* replace default value */
lydict_remove(ctx, leaf->dflt);
leaf->dflt = lydict_insert(ctx, rfn->dflt[0], 0);
/* check the default value */
if (unres_schema_add_node(leaf->module, unres, &leaf->type, UNRES_TYPE_DFLT,
(struct lys_node *)(&leaf->dflt)) == -1) {
goto fail;
}
} else if (node->nodetype == LYS_LEAFLIST) {
/* leaf-list */
llist = (struct lys_node_leaflist *)node;
/* remove complete set of defaults in target */
for (j = 0; j < llist->dflt_size; j++) {
lydict_remove(ctx, llist->dflt[j]);
}
free(llist->dflt);
/* copy the default set from refine */
llist->dflt = malloc(rfn->dflt_size * sizeof *llist->dflt);
LY_CHECK_ERR_GOTO(!llist->dflt, LOGMEM, fail);
llist->dflt_size = rfn->dflt_size;
for (j = 0; j < llist->dflt_size; j++) {
llist->dflt[j] = lydict_insert(ctx, rfn->dflt[j], 0);
}
/* check default value */
for (j = 0; j < llist->dflt_size; j++) {
if (unres_schema_add_node(llist->module, unres, &llist->type, UNRES_TYPE_DFLT,
(struct lys_node *)(&llist->dflt[j])) == -1) {
goto fail;
}
}
}
}
/* mandatory on leaf, anyxml or choice */
if (rfn->flags & LYS_MAND_MASK) {
/* remove current value */
node->flags &= ~LYS_MAND_MASK;
/* set new value */
node->flags |= (rfn->flags & LYS_MAND_MASK);
if (rfn->flags & LYS_MAND_TRUE) {
/* check if node has default value */
if ((node->nodetype & LYS_LEAF) && ((struct lys_node_leaf *)node)->dflt) {
LOGVAL(LYE_SPEC, LY_VLOG_LYS, uses,
"The \"mandatory\" statement is forbidden on leaf with \"default\".");
goto fail;
}
if ((node->nodetype & LYS_CHOICE) && ((struct lys_node_choice *)node)->dflt) {
LOGVAL(LYE_SPEC, LY_VLOG_LYS, uses,
"The \"mandatory\" statement is forbidden on choices with \"default\".");
goto fail;
}
}
}
/* presence on container */
if ((node->nodetype & LYS_CONTAINER) && rfn->mod.presence) {
lydict_remove(ctx, ((struct lys_node_container *)node)->presence);
((struct lys_node_container *)node)->presence = lydict_insert(ctx, rfn->mod.presence, 0);
}
/* min/max-elements on list or leaf-list */
if (node->nodetype == LYS_LIST) {
if (rfn->flags & LYS_RFN_MINSET) {
((struct lys_node_list *)node)->min = rfn->mod.list.min;
}
if (rfn->flags & LYS_RFN_MAXSET) {
((struct lys_node_list *)node)->max = rfn->mod.list.max;
}
} else if (node->nodetype == LYS_LEAFLIST) {
if (rfn->flags & LYS_RFN_MINSET) {
((struct lys_node_leaflist *)node)->min = rfn->mod.list.min;
}
if (rfn->flags & LYS_RFN_MAXSET) {
((struct lys_node_leaflist *)node)->max = rfn->mod.list.max;
}
}
/* must in leaf, leaf-list, list, container or anyxml */
if (rfn->must_size) {
switch (node->nodetype) {
case LYS_LEAF:
old_size = &((struct lys_node_leaf *)node)->must_size;
old_must = &((struct lys_node_leaf *)node)->must;
break;
case LYS_LEAFLIST:
old_size = &((struct lys_node_leaflist *)node)->must_size;
old_must = &((struct lys_node_leaflist *)node)->must;
break;
case LYS_LIST:
old_size = &((struct lys_node_list *)node)->must_size;
old_must = &((struct lys_node_list *)node)->must;
break;
case LYS_CONTAINER:
old_size = &((struct lys_node_container *)node)->must_size;
old_must = &((struct lys_node_container *)node)->must;
break;
case LYS_ANYXML:
case LYS_ANYDATA:
old_size = &((struct lys_node_anydata *)node)->must_size;
old_must = &((struct lys_node_anydata *)node)->must;
break;
default:
LOGINT;
goto fail;
}
size = *old_size + rfn->must_size;
must = realloc(*old_must, size * sizeof *rfn->must);
LY_CHECK_ERR_GOTO(!must, LOGMEM, fail);
for (k = 0, j = *old_size; k < rfn->must_size; k++, j++) {
must[j].ext_size = rfn->must[k].ext_size;
lys_ext_dup(rfn->module, rfn->must[k].ext, rfn->must[k].ext_size, &rfn->must[k], LYEXT_PAR_RESTR,
&must[j].ext, 0, unres);
must[j].expr = lydict_insert(ctx, rfn->must[k].expr, 0);
must[j].dsc = lydict_insert(ctx, rfn->must[k].dsc, 0);
must[j].ref = lydict_insert(ctx, rfn->must[k].ref, 0);
must[j].eapptag = lydict_insert(ctx, rfn->must[k].eapptag, 0);
must[j].emsg = lydict_insert(ctx, rfn->must[k].emsg, 0);
}
*old_must = must;
*old_size = size;
/* check XPath dependencies again */
if (unres_schema_add_node(node->module, unres, node, UNRES_XPATH, NULL) == -1) {
goto fail;
}
}
/* if-feature in leaf, leaf-list, list, container or anyxml */
if (rfn->iffeature_size) {
old_size = &node->iffeature_size;
old_iff = &node->iffeature;
size = *old_size + rfn->iffeature_size;
iff = realloc(*old_iff, size * sizeof *rfn->iffeature);
LY_CHECK_ERR_GOTO(!iff, LOGMEM, fail);
for (k = 0, j = *old_size; k < rfn->iffeature_size; k++, j++) {
resolve_iffeature_getsizes(&rfn->iffeature[k], &usize1, &usize2);
if (usize1) {
/* there is something to duplicate */
/* duplicate compiled expression */
usize = (usize1 / 4) + (usize1 % 4) ? 1 : 0;
iff[j].expr = malloc(usize * sizeof *iff[j].expr);
LY_CHECK_ERR_GOTO(!iff[j].expr, LOGMEM, fail);
memcpy(iff[j].expr, rfn->iffeature[k].expr, usize * sizeof *iff[j].expr);
/* duplicate list of feature pointers */
iff[j].features = malloc(usize2 * sizeof *iff[k].features);
LY_CHECK_ERR_GOTO(!iff[j].expr, LOGMEM, fail);
memcpy(iff[j].features, rfn->iffeature[k].features, usize2 * sizeof *iff[j].features);
}
}
*old_iff = iff;
*old_size = size;
}
}
/* apply augments */
for (i = 0; i < uses->augment_size; i++) {
rc = resolve_augment(&uses->augment[i], uses->child, unres);
if (rc) {
goto fail;
}
}
/* check refines */
for (i = 0; i < uses->refine_size; i++) {
node = refine_nodes[i];
rfn = &uses->refine[i];
/* config on any nodetype */
if ((rfn->flags & LYS_CONFIG_MASK) && (node->flags & LYS_CONFIG_MASK)) {
for (parent = lys_parent(node); parent && parent->nodetype == LYS_USES; parent = lys_parent(parent));
if (parent && parent->nodetype != LYS_GROUPING && (parent->flags & LYS_CONFIG_MASK) &&
((parent->flags & LYS_CONFIG_MASK) != (rfn->flags & LYS_CONFIG_MASK)) &&
(rfn->flags & LYS_CONFIG_W)) {
/* setting config true under config false is prohibited */
LOGVAL(LYE_INARG, LY_VLOG_LYS, uses, "config", "refine");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL,
"changing config from 'false' to 'true' is prohibited while "
"the target's parent is still config 'false'.");
goto fail;
}
/* inherit config change to the target children */
LY_TREE_DFS_BEGIN(node->child, next, iter) {
if (rfn->flags & LYS_CONFIG_W) {
if (iter->flags & LYS_CONFIG_SET) {
/* config is set explicitely, go to next sibling */
next = NULL;
goto nextsibling;
}
} else { /* LYS_CONFIG_R */
if ((iter->flags & LYS_CONFIG_SET) && (iter->flags & LYS_CONFIG_W)) {
/* error - we would have config data under status data */
LOGVAL(LYE_INARG, LY_VLOG_LYS, uses, "config", "refine");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL,
"changing config from 'true' to 'false' is prohibited while the target "
"has still a children with explicit config 'true'.");
goto fail;
}
}
/* change config */
iter->flags &= ~LYS_CONFIG_MASK;
iter->flags |= (rfn->flags & LYS_CONFIG_MASK);
/* select next iter - modified LY_TREE_DFS_END */
if (iter->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)) {
next = NULL;
} else {
next = iter->child;
}
nextsibling:
if (!next) {
/* try siblings */
next = iter->next;
}
while (!next) {
/* parent is already processed, go to its sibling */
iter = lys_parent(iter);
/* no siblings, go back through parents */
if (iter == node) {
/* we are done, no next element to process */
break;
}
next = iter->next;
}
}
}
/* default value */
if (rfn->dflt_size) {
if (node->nodetype == LYS_CHOICE) {
/* choice */
((struct lys_node_choice *)node)->dflt = resolve_choice_dflt((struct lys_node_choice *)node,
rfn->dflt[0]);
if (!((struct lys_node_choice *)node)->dflt) {
LOGVAL(LYE_INARG, LY_VLOG_LYS, uses, rfn->dflt[0], "default");
goto fail;
}
if (lyp_check_mandatory_choice(node)) {
goto fail;
}
}
}
/* min/max-elements on list or leaf-list */
if (node->nodetype == LYS_LIST && ((struct lys_node_list *)node)->max) {
if (((struct lys_node_list *)node)->min > ((struct lys_node_list *)node)->max) {
LOGVAL(LYE_SPEC, LY_VLOG_LYS, uses, "Invalid value \"%d\" of \"%s\".", rfn->mod.list.min, "min-elements");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "\"min-elements\" is bigger than \"max-elements\".");
goto fail;
}
} else if (node->nodetype == LYS_LEAFLIST && ((struct lys_node_leaflist *)node)->max) {
if (((struct lys_node_leaflist *)node)->min > ((struct lys_node_leaflist *)node)->max) {
LOGVAL(LYE_SPEC, LY_VLOG_LYS, uses, "Invalid value \"%d\" of \"%s\".", rfn->mod.list.min, "min-elements");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "\"min-elements\" is bigger than \"max-elements\".");
goto fail;
}
}
/* additional checks */
/* default value with mandatory/min-elements */
if (node->nodetype == LYS_LEAFLIST) {
llist = (struct lys_node_leaflist *)node;
if (llist->dflt_size && llist->min) {
LOGVAL(LYE_INCHILDSTMT, LY_VLOG_LYS, uses, rfn->dflt_size ? "default" : "min-elements", "refine");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL,
"The \"min-elements\" statement with non-zero value is forbidden on leaf-lists with the \"default\" statement.");
goto fail;
}
} else if (node->nodetype == LYS_LEAF) {
leaf = (struct lys_node_leaf *)node;
if (leaf->dflt && (leaf->flags & LYS_MAND_TRUE)) {
LOGVAL(LYE_INCHILDSTMT, LY_VLOG_LYS, uses, rfn->dflt_size ? "default" : "mandatory", "refine");
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL,
"The \"mandatory\" statement is forbidden on leafs with the \"default\" statement.");
goto fail;
}
}
/* check for mandatory node in default case, first find the closest parent choice to the changed node */
if ((rfn->flags & LYS_MAND_TRUE) || rfn->mod.list.min) {
for (parent = node->parent;
parent && !(parent->nodetype & (LYS_CHOICE | LYS_GROUPING | LYS_ACTION | LYS_USES));
parent = parent->parent) {
if (parent->nodetype == LYS_CONTAINER && ((struct lys_node_container *)parent)->presence) {
/* stop also on presence containers */
break;
}
}
/* and if it is a choice with the default case, check it for presence of a mandatory node in it */
if (parent && parent->nodetype == LYS_CHOICE && ((struct lys_node_choice *)parent)->dflt) {
if (lyp_check_mandatory_choice(parent)) {
goto fail;
}
}
}
}
free(refine_nodes);
return EXIT_SUCCESS;
fail:
LY_TREE_FOR_SAFE(uses->child, next, iter) {
lys_node_free(iter, NULL, 0);
}
free(refine_nodes);
return -1;
}
void
resolve_identity_backlink_update(struct lys_ident *der, struct lys_ident *base)
{
int i;
assert(der && base);
if (!base->der) {
/* create a set for backlinks if it does not exist */
base->der = ly_set_new();
}
/* store backlink */
ly_set_add(base->der, der, LY_SET_OPT_USEASLIST);
/* do it recursively */
for (i = 0; i < base->base_size; i++) {
resolve_identity_backlink_update(der, base->base[i]);
}
}
/**
* @brief Resolve base identity recursively. Does not log.
*
* @param[in] module Main module.
* @param[in] ident Identity to use.
* @param[in] basename Base name of the identity.
* @param[out] ret Pointer to the resolved identity. Can be NULL.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on crucial error.
*/
static int
resolve_base_ident_sub(const struct lys_module *module, struct lys_ident *ident, const char *basename,
struct unres_schema *unres, struct lys_ident **ret)
{
uint32_t i, j;
struct lys_ident *base = NULL;
assert(ret);
/* search module */
for (i = 0; i < module->ident_size; i++) {
if (!strcmp(basename, module->ident[i].name)) {
if (!ident) {
/* just search for type, so do not modify anything, just return
* the base identity pointer */
*ret = &module->ident[i];
return EXIT_SUCCESS;
}
base = &module->ident[i];
goto matchfound;
}
}
/* search submodules */
for (j = 0; j < module->inc_size && module->inc[j].submodule; j++) {
for (i = 0; i < module->inc[j].submodule->ident_size; i++) {
if (!strcmp(basename, module->inc[j].submodule->ident[i].name)) {
if (!ident) {
*ret = &module->inc[j].submodule->ident[i];
return EXIT_SUCCESS;
}
base = &module->inc[j].submodule->ident[i];
goto matchfound;
}
}
}
matchfound:
/* we found it somewhere */
if (base) {
/* is it already completely resolved? */
for (i = 0; i < unres->count; i++) {
if ((unres->item[i] == base) && (unres->type[i] == UNRES_IDENT)) {
/* identity found, but not yet resolved, so do not return it in *res and try it again later */
/* simple check for circular reference,
* the complete check is done as a side effect of using only completely
* resolved identities (previous check of unres content) */
if (ly_strequal((const char *)unres->str_snode[i], ident->name, 1)) {
LOGVAL(LYE_INARG, LY_VLOG_NONE, NULL, basename, "base");
LOGVAL(LYE_SPEC, LY_VLOG_NONE, NULL, "Circular reference of \"%s\" identity.", basename);
return -1;
}
return EXIT_FAILURE;
}
}
/* checks done, store the result */
*ret = base;
return EXIT_SUCCESS;
}
/* base not found (maybe a forward reference) */
return EXIT_FAILURE;
}
/**
* @brief Resolve base identity. Logs directly.
*
* @param[in] module Main module.
* @param[in] ident Identity to use.
* @param[in] basename Base name of the identity.
* @param[in] parent Either "type" or "identity".
* @param[in,out] type Type structure where we want to resolve identity. Can be NULL.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
static int
resolve_base_ident(const struct lys_module *module, struct lys_ident *ident, const char *basename, const char *parent,
struct lys_type *type, struct unres_schema *unres)
{
const char *name;
int mod_name_len = 0, rc;
struct lys_ident *target, **ret;
uint16_t flags;
struct lys_module *mod;
assert((ident && !type) || (!ident && type));
if (!type) {
/* have ident to resolve */
ret = &target;
flags = ident->flags;
mod = ident->module;
} else {
/* have type to fill */
++type->info.ident.count;
type->info.ident.ref = ly_realloc(type->info.ident.ref, type->info.ident.count * sizeof *type->info.ident.ref);
LY_CHECK_ERR_RETURN(!type->info.ident.ref, LOGMEM, -1);
ret = &type->info.ident.ref[type->info.ident.count - 1];
flags = type->parent->flags;
mod = type->parent->module;
}
*ret = NULL;
/* search for the base identity */
name = strchr(basename, ':');
if (name) {
/* set name to correct position after colon */
mod_name_len = name - basename;
name++;
if (!strncmp(basename, module->name, mod_name_len) && !module->name[mod_name_len]) {
/* prefix refers to the current module, ignore it */
mod_name_len = 0;
}
} else {
name = basename;
}
/* get module where to search */
module = lys_get_import_module(module, NULL, 0, mod_name_len ? basename : NULL, mod_name_len);
if (!module) {
/* identity refers unknown data model */
LOGVAL(LYE_INMOD, LY_VLOG_NONE, NULL, basename);
return -1;
}
/* search in the identified module ... */
rc = resolve_base_ident_sub(module, ident, name, unres, ret);
if (!rc) {
assert(*ret);
/* check status */
if (lyp_check_status(flags, mod, ident ? ident->name : "of type",
(*ret)->flags, (*ret)->module, (*ret)->name, NULL)) {
rc = -1;
} else if (ident) {
ident->base[ident->base_size++] = *ret;
if (lys_main_module(mod)->implemented) {
/* in case of the implemented identity, maintain backlinks to it
* from the base identities to make it available when resolving
* data with the identity values (not implemented identity is not
* allowed as an identityref value). */
resolve_identity_backlink_update(ident, *ret);
}
}
} else if (rc == EXIT_FAILURE) {
LOGVAL(LYE_INRESOLV, LY_VLOG_NONE, NULL, parent, basename);
if (type) {
--type->info.ident.count;
}
}
return rc;
}
/*
* 1 - true (der is derived from base)
* 0 - false (der is not derived from base)
*/
static int
search_base_identity(struct lys_ident *der, struct lys_ident *base)
{
int i;
if (der == base) {
return 1;
} else {
for(i = 0; i < der->base_size; i++) {
if (search_base_identity(der->base[i], base) == 1) {
return 1;
}
}
}
return 0;
}
/**
* @brief Resolve JSON data format identityref. Logs directly.
*
* @param[in] type Identityref type.
* @param[in] ident_name Identityref name.
* @param[in] node Node where the identityref is being resolved
* @param[in] dflt flag if we are resolving default value in the schema
*
* @return Pointer to the identity resolvent, NULL on error.
*/
struct lys_ident *
resolve_identref(struct lys_type *type, const char *ident_name, struct lyd_node *node, struct lys_module *mod, int dflt)
{
const char *mod_name, *name;
int mod_name_len, nam_len, rc, i, j;
int make_implemented = 0;
unsigned int u;
struct lys_ident *der, *cur;
struct lys_module *imod = NULL, *m;
assert(type && ident_name && node && mod);
if (!type || (!type->info.ident.count && !type->der) || !ident_name) {
return NULL;
}
rc = parse_node_identifier(ident_name, &mod_name, &mod_name_len, &name, &nam_len, NULL, 0);
if (rc < 1) {
LOGVAL(LYE_INCHAR, LY_VLOG_LYD, node, ident_name[-rc], &ident_name[-rc]);
return NULL;
} else if (rc < (signed)strlen(ident_name)) {
LOGVAL(LYE_INCHAR, LY_VLOG_LYD, node, ident_name[rc], &ident_name[rc]);
return NULL;
}
m = lys_main_module(mod); /* shortcut */
if (!mod_name || (!strncmp(mod_name, m->name, mod_name_len) && !m->name[mod_name_len])) {
/* identity is defined in the same module as node */
imod = m;
} else if (dflt) {
/* solving identityref in default definition in schema -
* find the identity's module in the imported modules list to have a correct revision */
for (i = 0; i < mod->imp_size; i++) {
if (!strncmp(mod_name, mod->imp[i].module->name, mod_name_len) && !mod->imp[i].module->name[mod_name_len]) {
imod = mod->imp[i].module;
break;
}
}
} else {
/* solving identityref in data - get the (implemented) module from the context */
u = 0;
while ((imod = (struct lys_module*)ly_ctx_get_module_iter(mod->ctx, &u))) {
if (imod->implemented && !strncmp(mod_name, imod->name, mod_name_len) && !imod->name[mod_name_len]) {
break;
}
}
}
if (!imod) {
goto fail;
}
if (dflt && (m != imod || lys_main_module(type->parent->module) != mod)) {
/* we are solving default statement in schema AND the type is not referencing the same schema,
* THEN, we may need to make the module with the identity implemented, but only if it really
* contains the identity */
if (!imod->implemented) {
cur = NULL;
/* get the identity in the module */
for (i = 0; i < imod->ident_size; i++) {
if (!strcmp(name, imod->ident[i].name)) {
cur = &imod->ident[i];
break;
}
}
if (!cur) {
/* go through includes */
for (j = 0; j < imod->inc_size; j++) {
for (i = 0; i < imod->inc[j].submodule->ident_size; i++) {
if (!strcmp(name, imod->inc[j].submodule->ident[i].name)) {
cur = &imod->inc[j].submodule->ident[i];
break;
}
}
}
if (!cur) {
goto fail;
}
}
/* check that identity is derived from one of the type's base */
while (type->der) {
for (i = 0; i < type->info.ident.count; i++) {
if (search_base_identity(cur, type->info.ident.ref[i])) {
/* cur's base matches the type's base */
make_implemented = 1;
goto match;
}
}
type = &type->der->type;
}
/* matching base not found */
LOGVAL(LYE_SPEC, LY_VLOG_LYD, node, "Identity used as identityref value is not implemented.");
goto fail;
}
}
/* go through all the derived types of all the bases */
while (type->der) {
for (i = 0; i < type->info.ident.count; ++i) {
cur = type->info.ident.ref[i];
if (cur->der) {
/* there are some derived identities */
for (u = 0; u < cur->der->number; u++) {
der = (struct lys_ident *)cur->der->set.g[u]; /* shortcut */
if (!strcmp(der->name, name) && lys_main_module(der->module) == imod) {
/* we have match */
cur = der;
goto match;
}
}
}
}
type = &type->der->type;
}
fail:
LOGVAL(LYE_INRESOLV, LY_VLOG_LYD, node, "identityref", ident_name);
return NULL;
match:
for (i = 0; i < cur->iffeature_size; i++) {
if (!resolve_iffeature(&cur->iffeature[i])) {
LOGVAL(LYE_INVAL, LY_VLOG_LYD, node, cur->name, node->schema->name);
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, "Identity \"%s\" is disabled by its if-feature condition.", cur->name);
return NULL;
}
}
if (make_implemented) {
LOGVRB("Making \"%s\" module implemented because of identityref default value \"%s\" used in the implemented \"%s\" module",
imod->name, cur->name, mod->name);
if (lys_set_implemented(imod)) {
LOGERR(ly_errno, "Setting the module \"%s\" implemented because of used default identity \"%s\" failed.",
imod->name, cur->name);
LOGVAL(LYE_SPEC, LY_VLOG_LYD, node, "Identity used as identityref value is not implemented.");
goto fail;
}
}
return cur;
}
/**
* @brief Resolve unresolved uses. Logs directly.
*
* @param[in] uses Uses to use.
* @param[in] unres Specific unres item.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
static int
resolve_unres_schema_uses(struct lys_node_uses *uses, struct unres_schema *unres)
{
int rc;
struct lys_node *par_grp;
/* HACK: when a grouping has uses inside, all such uses have to be resolved before the grouping itself is used
* in some uses. When we see such a uses, the grouping's unres counter is used to store number of so far
* unresolved uses. The grouping cannot be used unless this counter is decreased back to 0. To remember
* that the uses already increased grouping's counter, the LYS_USESGRP flag is used. */
for (par_grp = lys_parent((struct lys_node *)uses); par_grp && (par_grp->nodetype != LYS_GROUPING); par_grp = lys_parent(par_grp));
if (!uses->grp) {
rc = resolve_uses_schema_nodeid(uses->name, (const struct lys_node *)uses, (const struct lys_node_grp **)&uses->grp);
if (rc == -1) {
LOGVAL(LYE_INRESOLV, LY_VLOG_LYS, uses, "uses", uses->name);
return -1;
} else if (rc > 0) {
LOGVAL(LYE_INCHAR, LY_VLOG_LYS, uses, uses->name[rc - 1], &uses->name[rc - 1]);
return -1;
} else if (!uses->grp) {
if (par_grp && !(uses->flags & LYS_USESGRP)) {
if (++((struct lys_node_grp *)par_grp)->unres_count == 0) {
LOGERR(LY_EINT, "Too many unresolved items (uses) inside a grouping.");
return -1;
}
uses->flags |= LYS_USESGRP;
}
LOGVAL(LYE_INRESOLV, LY_VLOG_LYS, uses, "uses", uses->name);
return EXIT_FAILURE;
}
}
if (uses->grp->unres_count) {
if (par_grp && !(uses->flags & LYS_USESGRP)) {
if (++((struct lys_node_grp *)par_grp)->unres_count == 0) {
LOGERR(LY_EINT, "Too many unresolved items (uses) inside a grouping.");
return -1;
}
uses->flags |= LYS_USESGRP;
} else {
/* instantiate grouping only when it is completely resolved */
uses->grp = NULL;
}
return EXIT_FAILURE;
}
rc = resolve_uses(uses, unres);
if (!rc) {
/* decrease unres count only if not first try */
if (par_grp && (uses->flags & LYS_USESGRP)) {
assert(((struct lys_node_grp *)par_grp)->unres_count);
((struct lys_node_grp *)par_grp)->unres_count--;
uses->flags &= ~LYS_USESGRP;
}
/* check status */
if (lyp_check_status(uses->flags, uses->module, "of uses",
uses->grp->flags, uses->grp->module, uses->grp->name,
(struct lys_node *)uses)) {
return -1;
}
return EXIT_SUCCESS;
}
return rc;
}
/**
* @brief Resolve list keys. Logs directly.
*
* @param[in] list List to use.
* @param[in] keys_str Keys node value.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
static int
resolve_list_keys(struct lys_node_list *list, const char *keys_str)
{
int i, len, rc;
const char *value;
char *s = NULL;
for (i = 0; i < list->keys_size; ++i) {
assert(keys_str);
if (!list->child) {
/* no child, possible forward reference */
LOGVAL(LYE_INRESOLV, LY_VLOG_LYS, list, "list keys", keys_str);
return EXIT_FAILURE;
}
/* get the key name */
if ((value = strpbrk(keys_str, " \t\n"))) {
len = value - keys_str;
while (isspace(value[0])) {
value++;
}
} else {
len = strlen(keys_str);
}
if (list->keys[i]) {
/* skip already resolved keys */
goto next;
}
rc = lys_getnext_data(lys_node_module((struct lys_node *)list), (struct lys_node *)list, keys_str, len, LYS_LEAF,
(const struct lys_node **)&list->keys[i]);
if (rc) {
LOGVAL(LYE_INRESOLV, LY_VLOG_LYS, list, "list key", keys_str);
return EXIT_FAILURE;
}
if (check_key(list, i, keys_str, len)) {
/* check_key logs */
return -1;
}
/* check status */
if (lyp_check_status(list->flags, list->module, list->name,
list->keys[i]->flags, list->keys[i]->module, list->keys[i]->name,
(struct lys_node *)list->keys[i])) {
return -1;
}
/* default value - is ignored, keep it but print a warning */
if (list->keys[i]->dflt) {
/* log is not hidden only in case this resolving fails and in such a case
* we cannot get here
*/
assert(*ly_vlog_hide_location());
ly_vlog_hide(0);
LOGWRN("Default value \"%s\" in the list key \"%s\" is ignored. (%s)", list->keys[i]->dflt,
list->keys[i]->name, s = lys_path((struct lys_node*)list));
ly_vlog_hide(1);
free(s);
}
next:
/* prepare for next iteration */
while (value && isspace(value[0])) {
value++;
}
keys_str = value;
}
return EXIT_SUCCESS;
}
/**
* @brief Resolve (check) all must conditions of \p node.
* Logs directly.
*
* @param[in] node Data node with optional must statements.
* @param[in] inout_parent If set, must in input or output parent of node->schema will be resolved.
*
* @return EXIT_SUCCESS on pass, EXIT_FAILURE on fail, -1 on error.
*/
static int
resolve_must(struct lyd_node *node, int inout_parent, int ignore_fail)
{
int node_flags;
uint8_t i, must_size;
struct lys_node *schema;
struct lys_restr *must;
struct lyxp_set set;
assert(node);
memset(&set, 0, sizeof set);
if (inout_parent) {
for (schema = lys_parent(node->schema);
schema && (schema->nodetype & (LYS_CHOICE | LYS_CASE | LYS_USES));
schema = lys_parent(schema));
if (!schema || !(schema->nodetype & (LYS_INPUT | LYS_OUTPUT))) {
LOGINT;
return -1;
}
must_size = ((struct lys_node_inout *)schema)->must_size;
must = ((struct lys_node_inout *)schema)->must;
node_flags = schema->flags;
/* context node is the RPC/action */
node = node->parent;
if (!(node->schema->nodetype & (LYS_RPC | LYS_ACTION))) {
LOGINT;
return -1;
}
} else {
switch (node->schema->nodetype) {
case LYS_CONTAINER:
must_size = ((struct lys_node_container *)node->schema)->must_size;
must = ((struct lys_node_container *)node->schema)->must;
break;
case LYS_LEAF:
must_size = ((struct lys_node_leaf *)node->schema)->must_size;
must = ((struct lys_node_leaf *)node->schema)->must;
break;
case LYS_LEAFLIST:
must_size = ((struct lys_node_leaflist *)node->schema)->must_size;
must = ((struct lys_node_leaflist *)node->schema)->must;
break;
case LYS_LIST:
must_size = ((struct lys_node_list *)node->schema)->must_size;
must = ((struct lys_node_list *)node->schema)->must;
break;
case LYS_ANYXML:
case LYS_ANYDATA:
must_size = ((struct lys_node_anydata *)node->schema)->must_size;
must = ((struct lys_node_anydata *)node->schema)->must;
break;
case LYS_NOTIF:
must_size = ((struct lys_node_notif *)node->schema)->must_size;
must = ((struct lys_node_notif *)node->schema)->must;
break;
default:
must_size = 0;
break;
}
node_flags = node->schema->flags;
}
for (i = 0; i < must_size; ++i) {
if (lyxp_eval(must[i].expr, node, LYXP_NODE_ELEM, lyd_node_module(node), &set, LYXP_MUST)) {
return -1;
}
lyxp_set_cast(&set, LYXP_SET_BOOLEAN, node, lyd_node_module(node), LYXP_MUST);
if (!set.val.bool) {
if ((ignore_fail == 1) || ((node_flags & LYS_XPATH_DEP) && (ignore_fail == 2))) {
LOGVRB("Must condition \"%s\" not satisfied, but it is not required.", must[i].expr);
} else {
LOGVAL(LYE_NOMUST, LY_VLOG_LYD, node, must[i].expr);
if (must[i].emsg) {
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL, must[i].emsg);
}
if (must[i].eapptag) {
strncpy(((struct ly_err *)&ly_errno)->apptag, must[i].eapptag, LY_APPTAG_LEN - 1);
}
return 1;
}
}
}
return EXIT_SUCCESS;
}
/**
* @brief Resolve (find) when condition schema context node. Does not log.
*
* @param[in] schema Schema node with the when condition.
* @param[out] ctx_snode When schema context node.
* @param[out] ctx_snode_type Schema context node type.
*/
void
resolve_when_ctx_snode(const struct lys_node *schema, struct lys_node **ctx_snode, enum lyxp_node_type *ctx_snode_type)
{
const struct lys_node *sparent;
/* find a not schema-only node */
*ctx_snode_type = LYXP_NODE_ELEM;
while (schema->nodetype & (LYS_USES | LYS_CHOICE | LYS_CASE | LYS_AUGMENT | LYS_INPUT | LYS_OUTPUT)) {
if (schema->nodetype == LYS_AUGMENT) {
sparent = ((struct lys_node_augment *)schema)->target;
} else {
sparent = schema->parent;
}
if (!sparent) {
/* context node is the document root (fake root in our case) */
if (schema->flags & LYS_CONFIG_W) {
*ctx_snode_type = LYXP_NODE_ROOT_CONFIG;
} else {
*ctx_snode_type = LYXP_NODE_ROOT;
}
/* we need the first top-level sibling, but no uses or groupings */
schema = lys_getnext(NULL, NULL, lys_node_module(schema), 0);
break;
}
schema = sparent;
}
*ctx_snode = (struct lys_node *)schema;
}
/**
* @brief Resolve (find) when condition context node. Does not log.
*
* @param[in] node Data node, whose conditional definition is being decided.
* @param[in] schema Schema node with the when condition.
* @param[out] ctx_node Context node.
* @param[out] ctx_node_type Context node type.
*
* @return EXIT_SUCCESS on success, -1 on error.
*/
static int
resolve_when_ctx_node(struct lyd_node *node, struct lys_node *schema, struct lyd_node **ctx_node,
enum lyxp_node_type *ctx_node_type)
{
struct lyd_node *parent;
struct lys_node *sparent;
enum lyxp_node_type node_type;
uint16_t i, data_depth, schema_depth;
resolve_when_ctx_snode(schema, &schema, &node_type);
if (node_type == LYXP_NODE_ELEM) {
/* standard element context node */
for (parent = node, data_depth = 0; parent; parent = parent->parent, ++data_depth);
for (sparent = schema, schema_depth = 0;
sparent;
sparent = (sparent->nodetype == LYS_AUGMENT ? ((struct lys_node_augment *)sparent)->target : sparent->parent)) {
if (sparent->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA | LYS_NOTIF | LYS_RPC)) {
++schema_depth;
}
}
if (data_depth < schema_depth) {
return -1;
}
/* find the corresponding data node */
for (i = 0; i < data_depth - schema_depth; ++i) {
node = node->parent;
}
if (node->schema != schema) {
return -1;
}
} else {
/* root context node */
while (node->parent) {
node = node->parent;
}
while (node->prev->next) {
node = node->prev;
}
}
*ctx_node = node;
*ctx_node_type = node_type;
return EXIT_SUCCESS;
}
/**
* @brief Temporarily unlink nodes as per YANG 1.1 RFC section 7.21.5 for when XPath evaluation.
* The context node is adjusted if needed.
*
* @param[in] snode Schema node, whose children instances need to be unlinked.
* @param[in,out] node Data siblings where to look for the children of \p snode. If it is unlinked,
* it is moved to point to another sibling still in the original tree.
* @param[in,out] ctx_node When context node, adjusted if needed.
* @param[in] ctx_node_type Context node type, just for information to detect invalid situations.
* @param[out] unlinked_nodes Unlinked siblings. Can be safely appended to \p node afterwards.
* Ordering may change, but there will be no semantic change.
*
* @return EXIT_SUCCESS on success, -1 on error.
*/
static int
resolve_when_unlink_nodes(struct lys_node *snode, struct lyd_node **node, struct lyd_node **ctx_node,
enum lyxp_node_type ctx_node_type, struct lyd_node **unlinked_nodes)
{
struct lyd_node *next, *elem;
const struct lys_node *slast;
switch (snode->nodetype) {
case LYS_AUGMENT:
case LYS_USES:
case LYS_CHOICE:
case LYS_CASE:
slast = NULL;
while ((slast = lys_getnext(slast, snode, NULL, LYS_GETNEXT_PARENTUSES))) {
if (slast->nodetype & (LYS_ACTION | LYS_NOTIF)) {
continue;
}
if (resolve_when_unlink_nodes((struct lys_node *)slast, node, ctx_node, ctx_node_type, unlinked_nodes)) {
return -1;
}
}
break;
case LYS_CONTAINER:
case LYS_LIST:
case LYS_LEAF:
case LYS_LEAFLIST:
case LYS_ANYXML:
case LYS_ANYDATA:
LY_TREE_FOR_SAFE(lyd_first_sibling(*node), next, elem) {
if (elem->schema == snode) {
if (elem == *ctx_node) {
/* We are going to unlink our context node! This normally cannot happen,
* but we use normal top-level data nodes for faking a document root node,
* so if this is the context node, we just use the next top-level node.
* Additionally, it can even happen that there are no top-level data nodes left,
* all were unlinked, so in this case we pass NULL as the context node/data tree,
* lyxp_eval() can handle this special situation.
*/
if (ctx_node_type == LYXP_NODE_ELEM) {
LOGINT;
return -1;
}
if (elem->prev == elem) {
/* unlinking last top-level element, use an empty data tree */
*ctx_node = NULL;
} else {
/* in this case just use the previous/last top-level data node */
*ctx_node = elem->prev;
}
} else if (elem == *node) {
/* We are going to unlink the currently processed node. This does not matter that
* much, but we would lose access to the original data tree, so just move our
* pointer somewhere still inside it.
*/
if ((*node)->prev != *node) {
*node = (*node)->prev;
} else {
/* the processed node with sibings were all unlinked, oh well */
*node = NULL;
}
}
/* temporarily unlink the node */
lyd_unlink_internal(elem, 0);
if (*unlinked_nodes) {
if (lyd_insert_after((*unlinked_nodes)->prev, elem)) {
LOGINT;
return -1;
}
} else {
*unlinked_nodes = elem;
}
if (snode->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_ANYDATA)) {
/* there can be only one instance */
break;
}
}
}
break;
default:
LOGINT;
return -1;
}
return EXIT_SUCCESS;
}
/**
* @brief Relink the unlinked nodes back.
*
* @param[in] node Data node to link the nodes back to. It can actually be the adjusted context node,
* we simply need a sibling from the original data tree.
* @param[in] unlinked_nodes Unlinked nodes to relink to \p node.
* @param[in] ctx_node_type Context node type to distinguish between \p node being the parent
* or the sibling of \p unlinked_nodes.
*
* @return EXIT_SUCCESS on success, -1 on error.
*/
static int
resolve_when_relink_nodes(struct lyd_node *node, struct lyd_node *unlinked_nodes, enum lyxp_node_type ctx_node_type)
{
struct lyd_node *elem;
LY_TREE_FOR_SAFE(unlinked_nodes, unlinked_nodes, elem) {
lyd_unlink_internal(elem, 0);
if (ctx_node_type == LYXP_NODE_ELEM) {
if (lyd_insert_common(node, NULL, elem, 0)) {
return -1;
}
} else {
if (lyd_insert_nextto(node, elem, 0, 0)) {
return -1;
}
}
}
return EXIT_SUCCESS;
}
int
resolve_applies_must(const struct lyd_node *node)
{
int ret = 0;
uint8_t must_size;
struct lys_node *schema, *iter;
assert(node);
schema = node->schema;
/* their own must */
switch (schema->nodetype) {
case LYS_CONTAINER:
must_size = ((struct lys_node_container *)schema)->must_size;
break;
case LYS_LEAF:
must_size = ((struct lys_node_leaf *)schema)->must_size;
break;
case LYS_LEAFLIST:
must_size = ((struct lys_node_leaflist *)schema)->must_size;
break;
case LYS_LIST:
must_size = ((struct lys_node_list *)schema)->must_size;
break;
case LYS_ANYXML:
case LYS_ANYDATA:
must_size = ((struct lys_node_anydata *)schema)->must_size;
break;
case LYS_NOTIF:
must_size = ((struct lys_node_notif *)schema)->must_size;
break;
default:
must_size = 0;
break;
}
if (must_size) {
++ret;
}
/* schema may be a direct data child of input/output with must (but it must be first, it needs to be evaluated only once) */
if (!node->prev->next) {
for (iter = lys_parent(schema); iter && (iter->nodetype & (LYS_CHOICE | LYS_CASE | LYS_USES)); iter = lys_parent(iter));
if (iter && (iter->nodetype & (LYS_INPUT | LYS_OUTPUT))) {
ret += 0x2;
}
}
return ret;
}
int
resolve_applies_when(const struct lys_node *schema, int mode, const struct lys_node *stop)
{
const struct lys_node *parent;
assert(schema);
if (!(schema->nodetype & (LYS_NOTIF | LYS_RPC)) && (((struct lys_node_container *)schema)->when)) {
return 1;
}
parent = schema;
goto check_augment;
while (parent) {
/* stop conditions */
if (!mode) {
/* stop on node that can be instantiated in data tree */
if (!(parent->nodetype & (LYS_USES | LYS_CHOICE | LYS_CASE))) {
break;
}
} else {
/* stop on the specified node */
if (parent == stop) {
break;
}
}
if (((const struct lys_node_uses *)parent)->when) {
return 1;
}
check_augment:
if ((parent->parent && (parent->parent->nodetype == LYS_AUGMENT) &&
(((const struct lys_node_augment *)parent->parent)->when))) {
return 1;
}
parent = lys_parent(parent);
}
return 0;
}
/**
* @brief Resolve (check) all when conditions relevant for \p node.
* Logs directly.
*
* @param[in] node Data node, whose conditional reference, if such, is being decided.
*
* @return
* -1 - error, ly_errno is set
* 0 - true "when" statement
* 0, ly_vecode = LYVE_NOWHEN - false "when" statement
* 1, ly_vecode = LYVE_INWHEN - nodes needed to resolve are conditional and not yet resolved (under another "when")
*/
int
resolve_when(struct lyd_node *node, int *result, int ignore_fail)
{
struct lyd_node *ctx_node = NULL, *unlinked_nodes, *tmp_node;
struct lys_node *sparent;
struct lyxp_set set;
enum lyxp_node_type ctx_node_type;
int rc = 0;
assert(node);
memset(&set, 0, sizeof set);
if (!(node->schema->nodetype & (LYS_NOTIF | LYS_RPC | LYS_ACTION)) && (((struct lys_node_container *)node->schema)->when)) {
/* make the node dummy for the evaluation */
node->validity |= LYD_VAL_INUSE;
rc = lyxp_eval(((struct lys_node_container *)node->schema)->when->cond, node, LYXP_NODE_ELEM, lyd_node_module(node),
&set, LYXP_WHEN);
node->validity &= ~LYD_VAL_INUSE;
if (rc) {
if (rc == 1) {
LOGVAL(LYE_INWHEN, LY_VLOG_LYD, node, ((struct lys_node_container *)node->schema)->when->cond);
}
goto cleanup;
}
/* set boolean result of the condition */
lyxp_set_cast(&set, LYXP_SET_BOOLEAN, node, lyd_node_module(node), LYXP_WHEN);
if (!set.val.bool) {
node->when_status |= LYD_WHEN_FALSE;
if ((ignore_fail == 1) || ((node->schema->flags & LYS_XPATH_DEP) && (ignore_fail == 2))) {
LOGVRB("When condition \"%s\" is not satisfied, but it is not required.",
((struct lys_node_container *)node->schema)->when->cond);
} else {
LOGVAL(LYE_NOWHEN, LY_VLOG_LYD, node, ((struct lys_node_container *)node->schema)->when->cond);
goto cleanup;
}
}
/* free xpath set content */
lyxp_set_cast(&set, LYXP_SET_EMPTY, node, lyd_node_module(node), 0);
}
sparent = node->schema;
goto check_augment;
/* check when in every schema node that affects node */
while (sparent && (sparent->nodetype & (LYS_USES | LYS_CHOICE | LYS_CASE))) {
if (((struct lys_node_uses *)sparent)->when) {
if (!ctx_node) {
rc = resolve_when_ctx_node(node, sparent, &ctx_node, &ctx_node_type);
if (rc) {
LOGINT;
goto cleanup;
}
}
unlinked_nodes = NULL;
/* we do not want our node pointer to change */
tmp_node = node;
rc = resolve_when_unlink_nodes(sparent, &tmp_node, &ctx_node, ctx_node_type, &unlinked_nodes);
if (rc) {
goto cleanup;
}
rc = lyxp_eval(((struct lys_node_uses *)sparent)->when->cond, ctx_node, ctx_node_type, lys_node_module(sparent),
&set, LYXP_WHEN);
if (unlinked_nodes && ctx_node) {
if (resolve_when_relink_nodes(ctx_node, unlinked_nodes, ctx_node_type)) {
rc = -1;
goto cleanup;
}
}
if (rc) {
if (rc == 1) {
LOGVAL(LYE_INWHEN, LY_VLOG_LYD, node, ((struct lys_node_uses *)sparent)->when->cond);
}
goto cleanup;
}
lyxp_set_cast(&set, LYXP_SET_BOOLEAN, ctx_node, lys_node_module(sparent), LYXP_WHEN);
if (!set.val.bool) {
if ((ignore_fail == 1) || ((sparent->flags & LYS_XPATH_DEP) || (ignore_fail == 2))) {
LOGVRB("When condition \"%s\" is not satisfied, but it is not required.",
((struct lys_node_uses *)sparent)->when->cond);
} else {
node->when_status |= LYD_WHEN_FALSE;
LOGVAL(LYE_NOWHEN, LY_VLOG_LYD, node, ((struct lys_node_uses *)sparent)->when->cond);
goto cleanup;
}
}
/* free xpath set content */
lyxp_set_cast(&set, LYXP_SET_EMPTY, ctx_node, lys_node_module(sparent), 0);
}
check_augment:
if ((sparent->parent && (sparent->parent->nodetype == LYS_AUGMENT) && (((struct lys_node_augment *)sparent->parent)->when))) {
if (!ctx_node) {
rc = resolve_when_ctx_node(node, sparent->parent, &ctx_node, &ctx_node_type);
if (rc) {
LOGINT;
goto cleanup;
}
}
unlinked_nodes = NULL;
tmp_node = node;
rc = resolve_when_unlink_nodes(sparent->parent, &tmp_node, &ctx_node, ctx_node_type, &unlinked_nodes);
if (rc) {
goto cleanup;
}
rc = lyxp_eval(((struct lys_node_augment *)sparent->parent)->when->cond, ctx_node, ctx_node_type,
lys_node_module(sparent->parent), &set, LYXP_WHEN);
/* reconnect nodes, if ctx_node is NULL then all the nodes were unlinked, but linked together,
* so the tree did not actually change and there is nothing for us to do
*/
if (unlinked_nodes && ctx_node) {
if (resolve_when_relink_nodes(ctx_node, unlinked_nodes, ctx_node_type)) {
rc = -1;
goto cleanup;
}
}
if (rc) {
if (rc == 1) {
LOGVAL(LYE_INWHEN, LY_VLOG_LYD, node, ((struct lys_node_augment *)sparent->parent)->when->cond);
}
goto cleanup;
}
lyxp_set_cast(&set, LYXP_SET_BOOLEAN, ctx_node, lys_node_module(sparent->parent), LYXP_WHEN);
if (!set.val.bool) {
node->when_status |= LYD_WHEN_FALSE;
if ((ignore_fail == 1) || ((sparent->parent->flags & LYS_XPATH_DEP) && (ignore_fail == 2))) {
LOGVRB("When condition \"%s\" is not satisfied, but it is not required.",
((struct lys_node_augment *)sparent->parent)->when->cond);
} else {
LOGVAL(LYE_NOWHEN, LY_VLOG_LYD, node, ((struct lys_node_augment *)sparent->parent)->when->cond);
goto cleanup;
}
}
/* free xpath set content */
lyxp_set_cast(&set, LYXP_SET_EMPTY, ctx_node, lys_node_module(sparent->parent), 0);
}
sparent = lys_parent(sparent);
}
node->when_status |= LYD_WHEN_TRUE;
cleanup:
/* free xpath set content */
lyxp_set_cast(&set, LYXP_SET_EMPTY, ctx_node ? ctx_node : node, NULL, 0);
if (result) {
if (node->when_status & LYD_WHEN_TRUE) {
*result = 1;
} else {
*result = 0;
}
}
return rc;
}
static int
check_leafref_features(struct lys_type *type)
{
struct lys_node *iter;
struct ly_set *src_parents, *trg_parents, *features;
unsigned int i, j, size, x;
int ret = EXIT_SUCCESS;
assert(type->parent);
src_parents = ly_set_new();
trg_parents = ly_set_new();
features = ly_set_new();
/* get parents chain of source (leafref) */
for (iter = (struct lys_node *)type->parent; iter; iter = lys_parent(iter)) {
if (iter->nodetype & (LYS_INPUT | LYS_OUTPUT)) {
continue;
}
ly_set_add(src_parents, iter, LY_SET_OPT_USEASLIST);
}
/* get parents chain of target */
for (iter = (struct lys_node *)type->info.lref.target; iter; iter = lys_parent(iter)) {
if (iter->nodetype & (LYS_INPUT | LYS_OUTPUT)) {
continue;
}
ly_set_add(trg_parents, iter, LY_SET_OPT_USEASLIST);
}
/* compare the features used in if-feature statements in the rest of both
* chains of parents. The set of features used for target must be a subset
* of features used for the leafref. This is not a perfect, we should compare
* the truth tables but it could require too much resources, so we simplify that */
for (i = 0; i < src_parents->number; i++) {
iter = src_parents->set.s[i]; /* shortcut */
if (!iter->iffeature_size) {
continue;
}
for (j = 0; j < iter->iffeature_size; j++) {
resolve_iffeature_getsizes(&iter->iffeature[j], NULL, &size);
for (; size; size--) {
if (!iter->iffeature[j].features[size - 1]) {
/* not yet resolved feature, postpone this check */
ret = EXIT_FAILURE;
goto cleanup;
}
ly_set_add(features, iter->iffeature[j].features[size - 1], 0);
}
}
}
x = features->number;
for (i = 0; i < trg_parents->number; i++) {
iter = trg_parents->set.s[i]; /* shortcut */
if (!iter->iffeature_size) {
continue;
}
for (j = 0; j < iter->iffeature_size; j++) {
resolve_iffeature_getsizes(&iter->iffeature[j], NULL, &size);
for (; size; size--) {
if (!iter->iffeature[j].features[size - 1]) {
/* not yet resolved feature, postpone this check */
ret = EXIT_FAILURE;
goto cleanup;
}
if ((unsigned int)ly_set_add(features, iter->iffeature[j].features[size - 1], 0) >= x) {
/* the feature is not present in features set of target's parents chain */
LOGVAL(LYE_NORESOLV, LY_VLOG_LYS, type->parent, "leafref", type->info.lref.path);
LOGVAL(LYE_SPEC, LY_VLOG_PREV, NULL,
"Leafref is not conditional based on \"%s\" feature as its target.",
iter->iffeature[j].features[size - 1]->name);
ret = -1;
goto cleanup;
}
}
}
}
cleanup:
ly_set_free(features);
ly_set_free(src_parents);
ly_set_free(trg_parents);
return ret;
}
static int
check_type_union_leafref(struct lys_type *type)
{
uint8_t i;
if ((type->base == LY_TYPE_UNION) && type->info.uni.count) {
/* go through unions and look for leafref */
for (i = 0; i < type->info.uni.count; ++i) {
switch (type->info.uni.types[i].base) {
case LY_TYPE_LEAFREF:
return 1;
case LY_TYPE_UNION:
if (check_type_union_leafref(&type->info.uni.types[i])) {
return 1;
}
break;
default:
break;
}
}
return 0;
}
/* just inherit the flag value */
return type->der->has_union_leafref;
}
/**
* @brief Resolve a single unres schema item. Logs indirectly.
*
* @param[in] mod Main module.
* @param[in] item Item to resolve. Type determined by \p type.
* @param[in] type Type of the unresolved item.
* @param[in] str_snode String, a schema node, or NULL.
* @param[in] unres Unres schema structure to use.
* @param[in] final_fail Whether we are just printing errors of the failed unres items.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
static int
resolve_unres_schema_item(struct lys_module *mod, void *item, enum UNRES_ITEM type, void *str_snode,
struct unres_schema *unres, int final_fail)
{
/* has_str - whether the str_snode is a string in a dictionary that needs to be freed */
int rc = -1, has_str = 0, parent_type = 0, i, k;
unsigned int j;
struct lys_node *root, *next, *node, *par_grp;
const char *expr;
uint8_t *u;
struct ly_set *refs, *procs;
struct lys_feature *ref, *feat;
struct lys_ident *ident;
struct lys_type *stype;
struct lys_node_choice *choic;
struct lyxml_elem *yin;
struct yang_type *yang;
struct unres_list_uniq *unique_info;
struct unres_iffeat_data *iff_data;
struct unres_ext *ext_data;
struct lys_ext_instance *ext, **extlist;
struct lyext_plugin *eplugin;
switch (type) {
case UNRES_IDENT:
expr = str_snode;
has_str = 1;
ident = item;
rc = resolve_base_ident(mod, ident, expr, "identity", NULL, unres);
break;
case UNRES_TYPE_IDENTREF:
expr = str_snode;
has_str = 1;
stype = item;
rc = resolve_base_ident(mod, NULL, expr, "type", stype, unres);
break;
case UNRES_TYPE_LEAFREF:
node = str_snode;
stype = item;
rc = resolve_schema_leafref(stype->info.lref.path, node, (const struct lys_node **)&stype->info.lref.target);
if (!rc) {
assert(stype->info.lref.target);
/* check if leafref and its target are under a common if-features */
rc = check_leafref_features(stype);
if (rc) {
break;
}
if (lys_node_module(node)->implemented) {
/* make all the modules on the path implemented */
for (next = (struct lys_node *)stype->info.lref.target; next; next = lys_parent(next)) {
if (!lys_node_module(next)->implemented) {
if (lys_set_implemented(lys_node_module(next))) {
rc = -1;
break;
}
}
}
if (next) {
break;
}
/* store the backlink from leafref target */
if (lys_leaf_add_leafref_target(stype->info.lref.target, (struct lys_node *)stype->parent)) {
rc = -1;
}
}
}
break;
case UNRES_TYPE_DER_EXT:
parent_type++;
/* falls through */
case UNRES_TYPE_DER_TPDF:
parent_type++;
/* falls through */
case UNRES_TYPE_DER:
/* parent */
node = str_snode;
stype = item;
/* HACK type->der is temporarily unparsed type statement */
yin = (struct lyxml_elem *)stype->der;
stype->der = NULL;
if (yin->flags & LY_YANG_STRUCTURE_FLAG) {
yang = (struct yang_type *)yin;
rc = yang_check_type(mod, node, yang, stype, parent_type, unres);
if (rc) {
/* may try again later */
stype->der = (struct lys_tpdf *)yang;
} else {
/* we need to always be able to free this, it's safe only in this case */
lydict_remove(mod->ctx, yang->name);
free(yang);
}
} else {
rc = fill_yin_type(mod, node, yin, stype, parent_type, unres);
if (!rc || rc == -1) {
/* we need to always be able to free this, it's safe only in this case */
lyxml_free(mod->ctx, yin);
} else {
/* may try again later, put all back how it was */
stype->der = (struct lys_tpdf *)yin;
}
}
if (rc == EXIT_SUCCESS) {
/* it does not make sense to have leaf-list of empty type */
if (!parent_type && node->nodetype == LYS_LEAFLIST && stype->base == LY_TYPE_EMPTY) {
LOGWRN("The leaf-list \"%s\" is of \"empty\" type, which does not make sense.", node->name);
}
if ((type == UNRES_TYPE_DER_TPDF) && (stype->base == LY_TYPE_UNION)) {
/* fill typedef union leafref flag */
((struct lys_tpdf *)stype->parent)->has_union_leafref = check_type_union_leafref(stype);
} else if ((type == UNRES_TYPE_DER) && stype->der->has_union_leafref) {
/* copy the type in case it has union leafref flag */
if (lys_copy_union_leafrefs(mod, node, stype, NULL, unres)) {
LOGERR(LY_EINT, "Failed to duplicate type.");
return -1;
}
}
} else if (rc == EXIT_FAILURE && stype->base != LY_TYPE_ERR) {
/* forward reference - in case the type is in grouping, we have to make the grouping unusable
* by uses statement until the type is resolved. We do that the same way as uses statements inside
* grouping. The grouping cannot be used unless the unres counter is 0.
* To remember that the grouping already increased the counter, the LY_TYPE_ERR is used as value
* of the type's base member. */
for (par_grp = node; par_grp && (par_grp->nodetype != LYS_GROUPING); par_grp = lys_parent(par_grp));
if (par_grp) {
if (++((struct lys_node_grp *)par_grp)->unres_count == 0) {
LOGERR(LY_EINT, "Too many unresolved items (type) inside a grouping.");
return -1;
}
stype->base = LY_TYPE_ERR;
}
}
break;
case UNRES_IFFEAT:
iff_data = str_snode;
rc = resolve_feature(iff_data->fname, strlen(iff_data->fname), iff_data->node, item);
if (!rc) {
/* success */
if (iff_data->infeature) {
/* store backlink into the target feature to allow reverse changes in case of changing feature status */
feat = *((struct lys_feature **)item);
if (!feat->depfeatures) {
feat->depfeatures = ly_set_new();
}
ly_set_add(feat->depfeatures, iff_data->node, LY_SET_OPT_USEASLIST);
}
/* cleanup temporary data */
lydict_remove(mod->ctx, iff_data->fname);
free(iff_data);
}
break;
case UNRES_FEATURE:
feat = (struct lys_feature *)item;
if (feat->iffeature_size) {
refs = ly_set_new();
procs = ly_set_new();
ly_set_add(procs, feat, 0);
while (procs->number) {
ref = procs->set.g[procs->number - 1];
ly_set_rm_index(procs, procs->number - 1);
for (i = 0; i < ref->iffeature_size; i++) {
resolve_iffeature_getsizes(&ref->iffeature[i], NULL, &j);
for (; j > 0 ; j--) {
if (ref->iffeature[i].features[j - 1]) {
if (ref->iffeature[i].features[j - 1] == feat) {
LOGVAL(LYE_CIRC_FEATURES, LY_VLOG_NONE, NULL, feat->name);
goto featurecheckdone;
}
if (ref->iffeature[i].features[j - 1]->iffeature_size) {
k = refs->number;
if (ly_set_add(refs, ref->iffeature[i].features[j - 1], 0) == k) {
/* not yet seen feature, add it for processing */
ly_set_add(procs, ref->iffeature[i].features[j - 1], 0);
}
}
} else {
/* forward reference */
rc = EXIT_FAILURE;
goto featurecheckdone;
}
}
}
}
rc = EXIT_SUCCESS;
featurecheckdone:
ly_set_free(refs);
ly_set_free(procs);
}
break;
case UNRES_USES:
rc = resolve_unres_schema_uses(item, unres);
break;
case UNRES_TYPEDEF_DFLT:
parent_type++;
/* falls through */
case UNRES_TYPE_DFLT:
stype = item;
rc = check_default(stype, (const char **)str_snode, mod, parent_type);
break;
case UNRES_CHOICE_DFLT:
expr = str_snode;
has_str = 1;
choic = item;
if (!choic->dflt) {
choic->dflt = resolve_choice_dflt(choic, expr);
}
if (choic->dflt) {
rc = lyp_check_mandatory_choice((struct lys_node *)choic);
} else {
rc = EXIT_FAILURE;
}
break;
case UNRES_LIST_KEYS:
rc = resolve_list_keys(item, ((struct lys_node_list *)item)->keys_str);
break;
case UNRES_LIST_UNIQ:
unique_info = (struct unres_list_uniq *)item;
rc = resolve_unique(unique_info->list, unique_info->expr, unique_info->trg_type);
break;
case UNRES_AUGMENT:
rc = resolve_augment(item, NULL, unres);
break;
case UNRES_XPATH:
node = (struct lys_node *)item;
rc = lys_check_xpath(node, 1, final_fail);
break;
case UNRES_EXT:
ext_data = (struct unres_ext *)str_snode;
extlist = &(*(struct lys_ext_instance ***)item)[ext_data->ext_index];
rc = resolve_extension(ext_data, extlist, unres);
if (!rc) {
/* success */
/* is there a callback to be done to finalize the extension? */
eplugin = extlist[0]->def->plugin;
if (eplugin) {
if (eplugin->check_result || (eplugin->flags & LYEXT_OPT_INHERIT)) {
u = malloc(sizeof *u);
LY_CHECK_ERR_RETURN(!u, LOGMEM, -1);
(*u) = ext_data->ext_index;
if (unres_schema_add_node(mod, unres, item, UNRES_EXT_FINALIZE, (struct lys_node *)u) == -1) {
/* something really bad happend since the extension finalization is not actually
* being resolved while adding into unres, so something more serious with the unres
* list itself must happened */
return -1;
}
}
}
}
if (!rc || rc == -1) {
/* cleanup on success or fatal error */
if (ext_data->datatype == LYS_IN_YIN) {
/* YIN */
lyxml_free(mod->ctx, ext_data->data.yin);
} else {
/* YANG */
yang_free_ext_data(ext_data->data.yang);
}
free(ext_data);
}
break;
case UNRES_EXT_FINALIZE:
u = (uint8_t *)str_snode;
ext = (*(struct lys_ext_instance ***)item)[*u];
free(u);
eplugin = ext->def->plugin;
/* inherit */
if ((eplugin->flags & LYEXT_OPT_INHERIT) && (ext->parent_type == LYEXT_PAR_NODE)) {
root = (struct lys_node *)ext->parent;
if (!(root->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA))) {
LY_TREE_DFS_BEGIN(root->child, next, node) {
/* first, check if the node already contain instance of the same extension,
* in such a case we won't inherit. In case the node was actually defined as
* augment data, we are supposed to check the same way also the augment node itself */
if (lys_ext_instance_presence(ext->def, node->ext, node->ext_size) != -1) {
goto inherit_dfs_sibling;
} else if (node->parent != root && node->parent->nodetype == LYS_AUGMENT &&
lys_ext_instance_presence(ext->def, node->parent->ext, node->parent->ext_size) != -1) {
goto inherit_dfs_sibling;
}
if (eplugin->check_inherit) {
/* we have a callback to check the inheritance, use it */
switch ((rc = (*eplugin->check_inherit)(ext, node))) {
case 0:
/* yes - continue with the inheriting code */
break;
case 1:
/* no - continue with the node's sibling */
goto inherit_dfs_sibling;
case 2:
/* no, but continue with the children, just skip the inheriting code for this node */
goto inherit_dfs_child;
default:
LOGERR(LY_EINT, "Plugin's (%s:%s) check_inherit callback returns invalid value (%d),",
ext->def->module->name, ext->def->name, rc);
}
}
/* inherit the extension */
extlist = realloc(node->ext, (node->ext_size + 1) * sizeof *node->ext);
LY_CHECK_ERR_RETURN(!extlist, LOGMEM, -1);
extlist[node->ext_size] = malloc(sizeof **extlist);
LY_CHECK_ERR_RETURN(!extlist[node->ext_size], LOGMEM; node->ext = extlist, -1);
memcpy(extlist[node->ext_size], ext, sizeof *ext);
extlist[node->ext_size]->flags |= LYEXT_OPT_INHERIT;
node->ext = extlist;
node->ext_size++;
inherit_dfs_child:
/* modification of - select element for the next run - children first */
if (node->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)) {
next = NULL;
} else {
next = node->child;
}
if (!next) {
inherit_dfs_sibling:
/* no children, try siblings */
next = node->next;
}
while (!next) {
/* go to the parent */
node = lys_parent(node);
/* we are done if we are back in the root (the starter's parent */
if (node == root) {
break;
}
/* parent is already processed, go to its sibling */
next = node->next;
}
}
}
}
/* final check */
if (eplugin->check_result) {
if ((*eplugin->check_result)(ext)) {
ly_errno = LY_EEXT;
return -1;
}
}
rc = 0;
break;
default:
LOGINT;
break;
}
if (has_str && !rc) {
/* the string is no more needed in case of success.
* In case of forward reference, we will try to resolve the string later */
lydict_remove(mod->ctx, str_snode);
}
return rc;
}
/* logs directly */
static void
print_unres_schema_item_fail(void *item, enum UNRES_ITEM type, void *str_node)
{
struct lyxml_elem *xml;
struct lyxml_attr *attr;
struct unres_iffeat_data *iff_data;
const char *name = NULL;
struct unres_ext *extinfo;
switch (type) {
case UNRES_IDENT:
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "identity", (char *)str_node);
break;
case UNRES_TYPE_IDENTREF:
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "identityref", (char *)str_node);
break;
case UNRES_TYPE_LEAFREF:
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "leafref",
((struct lys_type *)item)->info.lref.path);
break;
case UNRES_TYPE_DER_EXT:
case UNRES_TYPE_DER_TPDF:
case UNRES_TYPE_DER:
xml = (struct lyxml_elem *)((struct lys_type *)item)->der;
if (xml->flags & LY_YANG_STRUCTURE_FLAG) {
name = ((struct yang_type *)xml)->name;
} else {
LY_TREE_FOR(xml->attr, attr) {
if ((attr->type == LYXML_ATTR_STD) && !strcmp(attr->name, "name")) {
name = attr->value;
break;
}
}
assert(attr);
}
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "derived type", name);
break;
case UNRES_IFFEAT:
iff_data = str_node;
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "if-feature", iff_data->fname);
break;
case UNRES_FEATURE:
LOGVRB("There are unresolved if-features for \"%s\" feature circular dependency check, it will be attempted later",
((struct lys_feature *)item)->name);
break;
case UNRES_USES:
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "uses", ((struct lys_node_uses *)item)->name);
break;
case UNRES_TYPEDEF_DFLT:
case UNRES_TYPE_DFLT:
if (str_node) {
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "type default", (char *)str_node);
} /* else no default value in the type itself, but we are checking some restrictions against
* possible default value of some base type. The failure is caused by not resolved base type,
* so it was already reported */
break;
case UNRES_CHOICE_DFLT:
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "choice default", (char *)str_node);
break;
case UNRES_LIST_KEYS:
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "list keys", (char *)str_node);
break;
case UNRES_LIST_UNIQ:
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "list unique", (char *)str_node);
break;
case UNRES_AUGMENT:
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "augment target",
((struct lys_node_augment *)item)->target_name);
break;
case UNRES_XPATH:
LOGVRB("Resolving %s \"%s\" failed, it will be attempted later.", "XPath expressions of",
((struct lys_node *)item)->name);
break;
case UNRES_EXT:
extinfo = (struct unres_ext *)str_node;
name = extinfo->datatype == LYS_IN_YIN ? extinfo->data.yin->name : NULL; /* TODO YANG extension */
LOGVRB("Resolving extension \"%s\" failed, it will be attempted later.", name);
break;
default:
LOGINT;
break;
}
}
/**
* @brief Resolve every unres schema item in the structure. Logs directly.
*
* @param[in] mod Main module.
* @param[in] unres Unres schema structure to use.
*
* @return EXIT_SUCCESS on success, -1 on error.
*/
int
resolve_unres_schema(struct lys_module *mod, struct unres_schema *unres)
{
uint32_t i, resolved = 0, unres_count, res_count;
struct lyxml_elem *yin;
struct yang_type *yang;
int rc, log_hidden;
assert(unres);
LOGVRB("Resolving \"%s\" unresolved schema nodes and their constraints...", mod->name);
if (*ly_vlog_hide_location()) {
log_hidden = 1;
} else {
log_hidden = 0;
ly_vlog_hide(1);
}
/* uses */
do {
unres_count = 0;
res_count = 0;
for (i = 0; i < unres->count; ++i) {
/* UNRES_TYPE_LEAFREF must be resolved (for storing leafref target pointers);
* if-features are resolved here to make sure that we will have all if-features for
* later check of feature circular dependency */
if (unres->type[i] > UNRES_IDENT) {
continue;
}
/* processes UNRES_USES, UNRES_IFFEAT, UNRES_TYPE_DER, UNRES_TYPE_DER_TPDF, UNRES_TYPE_LEAFREF,
* UNRES_AUGMENT, UNRES_CHOICE_DFLT and UNRES_IDENT */
++unres_count;
rc = resolve_unres_schema_item(unres->module[i], unres->item[i], unres->type[i], unres->str_snode[i], unres, 0);
if (!rc) {
unres->type[i] = UNRES_RESOLVED;
++resolved;
++res_count;
} else if (rc == -1) {
if (!log_hidden) {
ly_vlog_hide(0);
}
/* print the error */
ly_err_repeat();
return -1;
} else {
/* forward reference, erase ly_errno */
ly_err_clean(1);
}
}
} while (res_count && (res_count < unres_count));
if (res_count < unres_count) {
/* just print the errors */
if (!log_hidden) {
ly_vlog_hide(0);
}
for (i = 0; i < unres->count; ++i) {
if (unres->type[i] > UNRES_IDENT) {
continue;
}
resolve_unres_schema_item(unres->module[i], unres->item[i], unres->type[i], unres->str_snode[i], unres, 1);
if (unres->type[i] == UNRES_TYPE_DER_EXT) {
yin = (struct lyxml_elem*)((struct lys_type *)unres->item[i])->der;
if (yin->flags & LY_YANG_STRUCTURE_FLAG) {
yang =(struct yang_type *)yin;
((struct lys_type *)unres->item[i])->base = yang->base;
if (yang->base == LY_TYPE_UNION) {
yang_free_type_union(mod->ctx, (struct lys_type *)unres->item[i]);
}
lydict_remove(mod->ctx, yang->name);
free(yang);
} else {
lyxml_free(mod->ctx, yin);
}
}
}
return -1;
}
/* the rest except finalizing extensions */
for (i = 0; i < unres->count; ++i) {
if (unres->type[i] == UNRES_RESOLVED || unres->type[i] == UNRES_EXT_FINALIZE) {
continue;
}
rc = resolve_unres_schema_item(unres->module[i], unres->item[i], unres->type[i], unres->str_snode[i], unres, 0);
if (rc == 0) {
if (unres->type[i] == UNRES_LIST_UNIQ) {
/* free the allocated structure */
free(unres->item[i]);
}
unres->type[i] = UNRES_RESOLVED;
++resolved;
} else if (rc == -1) {
if (!log_hidden) {
ly_vlog_hide(0);
}
/* print the error */
ly_err_repeat();
return -1;
} else {
/* forward reference, erase ly_errno */
ly_err_clean(1);
}
}
if (!log_hidden) {
ly_vlog_hide(0);
}
/* finalize extensions, keep it last to provide the complete schema tree information to the plugin's checkers */
for (i = 0; i < unres->count; ++i) {
if (unres->type[i] != UNRES_EXT_FINALIZE) {
continue;
}
rc = resolve_unres_schema_item(unres->module[i], unres->item[i], unres->type[i], unres->str_snode[i], unres, 0);
unres->type[i] = UNRES_RESOLVED;
if (rc == 0) {
++resolved;
}
/* else error - it was already printed, but resolved was not increased,
so this unres item will not be resolved again in the following code,
but it will cause returning -1 at the end, this way we are able to
print all the issues with unres */
}
if (resolved < unres->count) {
/* try to resolve the unresolved nodes again, it will not resolve anything, but it will print
* all the validation errors
*/
for (i = 0; i < unres->count; ++i) {
if (unres->type[i] == UNRES_RESOLVED) {
continue;
}
resolve_unres_schema_item(unres->module[i], unres->item[i], unres->type[i], unres->str_snode[i], unres, 1);
if (unres->type[i] == UNRES_XPATH) {
/* XPath referencing an unknown node is actually supposed to be just a warning */
unres->type[i] = UNRES_RESOLVED;
resolved++;
}
}
if (resolved < unres->count) {
return -1;
}
}
LOGVRB("All \"%s\" schema nodes and constraints resolved.", mod->name);
unres->count = 0;
return EXIT_SUCCESS;
}
/**
* @brief Try to resolve an unres schema item with a string argument. Logs indirectly.
*
* @param[in] mod Main module.
* @param[in] unres Unres schema structure to use.
* @param[in] item Item to resolve. Type determined by \p type.
* @param[in] type Type of the unresolved item.
* @param[in] str String argument.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on storing the item in unres, -1 on error.
*/
int
unres_schema_add_str(struct lys_module *mod, struct unres_schema *unres, void *item, enum UNRES_ITEM type,
const char *str)
{
int rc;
const char *dictstr;
dictstr = lydict_insert(mod->ctx, str, 0);
rc = unres_schema_add_node(mod, unres, item, type, (struct lys_node *)dictstr);
if (rc < 0) {
lydict_remove(mod->ctx, dictstr);
}
return rc;
}
/**
* @brief Try to resolve an unres schema item with a schema node argument. Logs indirectly.
*
* @param[in] mod Main module.
* @param[in] unres Unres schema structure to use.
* @param[in] item Item to resolve. Type determined by \p type.
* @param[in] type Type of the unresolved item. UNRES_TYPE_DER is handled specially!
* @param[in] snode Schema node argument.
*
* @return EXIT_SUCCESS on success, EXIT_FIALURE on storing the item in unres, -1 on error, -2 if the unres item
* is already in the unres list.
*/
int
unres_schema_add_node(struct lys_module *mod, struct unres_schema *unres, void *item, enum UNRES_ITEM type,
struct lys_node *snode)
{
int rc, log_hidden;
struct lyxml_elem *yin;
assert(unres && item && ((type != UNRES_LEAFREF) && (type != UNRES_INSTID) && (type != UNRES_WHEN)
&& (type != UNRES_MUST)));
#ifndef NDEBUG
uint32_t u;
/* check for duplicities in unres */
for (u = 0; u < unres->count; u++) {
if (unres->type[u] == type && unres->item[u] == item &&
unres->str_snode[u] == snode && unres->module[u] == mod) {
/* duplication, should not happen */
assert(0);
}
}
#endif
if (type == UNRES_EXT_FINALIZE) {
/* extension finalization is not even tried when adding the item into the inres list */
rc = EXIT_FAILURE;
} else {
if (*ly_vlog_hide_location()) {
log_hidden = 1;
} else {
log_hidden = 0;
ly_vlog_hide(1);
}
rc = resolve_unres_schema_item(mod, item, type, snode, unres, 0);
if (!log_hidden) {
ly_vlog_hide(0);
}
if (rc != EXIT_FAILURE) {
if (rc == -1) {
ly_err_repeat();
}
if (type == UNRES_LIST_UNIQ) {
/* free the allocated structure */
free(item);
} else if (rc == -1 && type == UNRES_IFFEAT) {
/* free the allocated resources */
free(*((char **)item));
}
return rc;
} else {
/* erase info about validation errors */
ly_err_clean(1);
}
print_unres_schema_item_fail(item, type, snode);
/* HACK unlinking is performed here so that we do not do any (NS) copying in vain */
if (type == UNRES_TYPE_DER || type == UNRES_TYPE_DER_TPDF) {
yin = (struct lyxml_elem *)((struct lys_type *)item)->der;
if (!(yin->flags & LY_YANG_STRUCTURE_FLAG)) {
lyxml_unlink_elem(mod->ctx, yin, 1);
((struct lys_type *)item)->der = (struct lys_tpdf *)yin;
}
}
}
unres->count++;
unres->item = ly_realloc(unres->item, unres->count*sizeof *unres->item);
LY_CHECK_ERR_RETURN(!unres->item, LOGMEM, -1);
unres->item[unres->count-1] = item;
unres->type = ly_realloc(unres->type, unres->count*sizeof *unres->type);
LY_CHECK_ERR_RETURN(!unres->type, LOGMEM, -1);
unres->type[unres->count-1] = type;
unres->str_snode = ly_realloc(unres->str_snode, unres->count*sizeof *unres->str_snode);
LY_CHECK_ERR_RETURN(!unres->str_snode, LOGMEM, -1);
unres->str_snode[unres->count-1] = snode;
unres->module = ly_realloc(unres->module, unres->count*sizeof *unres->module);
LY_CHECK_ERR_RETURN(!unres->module, LOGMEM, -1);
unres->module[unres->count-1] = mod;
return rc;
}
/**
* @brief Duplicate an unres schema item. Logs indirectly.
*
* @param[in] mod Main module.
* @param[in] unres Unres schema structure to use.
* @param[in] item Old item to be resolved.
* @param[in] type Type of the old unresolved item.
* @param[in] new_item New item to use in the duplicate.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE if item is not in unres, -1 on error.
*/
int
unres_schema_dup(struct lys_module *mod, struct unres_schema *unres, void *item, enum UNRES_ITEM type, void *new_item)
{
int i;
struct unres_list_uniq aux_uniq;
struct unres_iffeat_data *iff_data;
assert(item && new_item && ((type != UNRES_LEAFREF) && (type != UNRES_INSTID) && (type != UNRES_WHEN)));
/* hack for UNRES_LIST_UNIQ, which stores multiple items behind its item */
if (type == UNRES_LIST_UNIQ) {
aux_uniq.list = item;
aux_uniq.expr = ((struct unres_list_uniq *)new_item)->expr;
item = &aux_uniq;
}
i = unres_schema_find(unres, -1, item, type);
if (i == -1) {
if (type == UNRES_LIST_UNIQ) {
free(new_item);
}
return EXIT_FAILURE;
}
if ((type == UNRES_TYPE_LEAFREF) || (type == UNRES_USES) || (type == UNRES_TYPE_DFLT) ||
(type == UNRES_FEATURE) || (type == UNRES_LIST_UNIQ)) {
if (unres_schema_add_node(mod, unres, new_item, type, unres->str_snode[i]) == -1) {
LOGINT;
return -1;
}
} else if (type == UNRES_IFFEAT) {
/* duplicate unres_iffeature_data */
iff_data = malloc(sizeof *iff_data);
LY_CHECK_ERR_RETURN(!iff_data, LOGMEM, -1);
iff_data->fname = lydict_insert(mod->ctx, ((struct unres_iffeat_data *)unres->str_snode[i])->fname, 0);
iff_data->node = ((struct unres_iffeat_data *)unres->str_snode[i])->node;
if (unres_schema_add_node(mod, unres, new_item, type, (struct lys_node *)iff_data) == -1) {
LOGINT;
return -1;
}
} else {
if (unres_schema_add_str(mod, unres, new_item, type, unres->str_snode[i]) == -1) {
LOGINT;
return -1;
}
}
return EXIT_SUCCESS;
}
/* does not log */
int
unres_schema_find(struct unres_schema *unres, int start_on_backwards, void *item, enum UNRES_ITEM type)
{
int i;
struct unres_list_uniq *aux_uniq1, *aux_uniq2;
if (start_on_backwards >= 0) {
i = start_on_backwards;
} else {
i = unres->count - 1;
}
for (; i > -1; i--) {
if (unres->type[i] != type) {
continue;
}
if (type != UNRES_LIST_UNIQ) {
if (unres->item[i] == item) {
break;
}
} else {
aux_uniq1 = (struct unres_list_uniq *)unres->item[i - 1];
aux_uniq2 = (struct unres_list_uniq *)item;
if ((aux_uniq1->list == aux_uniq2->list) && ly_strequal(aux_uniq1->expr, aux_uniq2->expr, 0)) {
break;
}
}
}
return i;
}
static void
unres_schema_free_item(struct ly_ctx *ctx, struct unres_schema *unres, uint32_t i)
{
struct lyxml_elem *yin;
struct yang_type *yang;
struct unres_iffeat_data *iff_data;
switch (unres->type[i]) {
case UNRES_TYPE_DER_TPDF:
case UNRES_TYPE_DER:
yin = (struct lyxml_elem *)((struct lys_type *)unres->item[i])->der;
if (yin->flags & LY_YANG_STRUCTURE_FLAG) {
yang =(struct yang_type *)yin;
((struct lys_type *)unres->item[i])->base = yang->base;
lydict_remove(ctx, yang->name);
free(yang);
if (((struct lys_type *)unres->item[i])->base == LY_TYPE_UNION) {
yang_free_type_union(ctx, (struct lys_type *)unres->item[i]);
}
} else {
lyxml_free(ctx, yin);
}
break;
case UNRES_IFFEAT:
iff_data = (struct unres_iffeat_data *)unres->str_snode[i];
lydict_remove(ctx, iff_data->fname);
free(unres->str_snode[i]);
break;
case UNRES_IDENT:
case UNRES_TYPE_IDENTREF:
case UNRES_CHOICE_DFLT:
case UNRES_LIST_KEYS:
lydict_remove(ctx, (const char *)unres->str_snode[i]);
break;
case UNRES_LIST_UNIQ:
free(unres->item[i]);
break;
case UNRES_EXT:
free(unres->str_snode[i]);
break;
default:
break;
}
unres->type[i] = UNRES_RESOLVED;
}
void
unres_schema_free(struct lys_module *module, struct unres_schema **unres, int all)
{
uint32_t i;
unsigned int unresolved = 0;
if (!unres || !(*unres)) {
return;
}
assert(module || ((*unres)->count == 0));
for (i = 0; i < (*unres)->count; ++i) {
if (!all && ((*unres)->module[i] != module)) {
if ((*unres)->type[i] != UNRES_RESOLVED) {
unresolved++;
}
continue;
}
/* free heap memory for the specific item */
unres_schema_free_item(module->ctx, *unres, i);
}
/* free it all */
if (!module || all || (!unresolved && !module->type)) {
free((*unres)->item);
free((*unres)->type);
free((*unres)->str_snode);
free((*unres)->module);
free((*unres));
(*unres) = NULL;
}
}
static int
check_instid_ext_dep(const struct lys_node *sleaf, const char *json_instid)
{
struct ly_set *set;
struct lys_node *op_node, *first_node;
char *buf;
int ret = 0;
for (op_node = lys_parent(sleaf);
op_node && !(op_node->nodetype & (LYS_NOTIF | LYS_RPC | LYS_ACTION));
op_node = lys_parent(op_node));
if (op_node && lys_parent(op_node)) {
/* nested operation - any absolute path is external */
return 1;
}
/* get the first node from the instid */
buf = strndup(json_instid, strchr(json_instid + 1, '/') - json_instid);
if (!buf) {
LOGMEM;
return -1;
}
/* there is a predicate, remove it */
if (buf[strlen(buf) - 1] == ']') {
assert(strchr(buf, '['));
*strchr(buf, '[') = '\0';
}
/* find the first schema node */
set = lys_find_path(NULL, sleaf, buf);
if (!set || !set->number) {
free(buf);
ly_set_free(set);
return 1;
}
free(buf);
first_node = set->set.s[0];
/* based on the first schema node in the path we can decide whether it points to an external tree or not */
if (op_node) {
/* it is an operation, so we're good if it points somewhere inside it */
if (op_node == first_node) {
assert(set->number == 1);
} else {
ret = 1;
}
}
/* cleanup */
ly_set_free(set);
/* we cannot know whether it points to a tree that is going to be unlinked (application must handle
* this itself), so we say it's not external */
return ret;
}
/**
* @brief Resolve instance-identifier in JSON data format. Logs directly.
*
* @param[in] data Data node where the path is used
* @param[in] path Instance-identifier node value.
* @param[in,out] ret Resolved instance or NULL.
*
* @return 0 on success (even if unresolved and \p ret is NULL), -1 on error.
*/
static int
resolve_instid(struct lyd_node *data, const char *path, int req_inst, struct lyd_node **ret)
{
int i = 0, j;
const struct lys_module *mod;
struct ly_ctx *ctx = data->schema->module->ctx;
struct lyd_node *root;
const char *model, *name;
char *str;
int mod_len, name_len, has_predicate;
struct unres_data node_match;
memset(&node_match, 0, sizeof node_match);
*ret = NULL;
/* we need root to resolve absolute path */
for (root = data; root->parent; root = root->parent);
/* we're still parsing it and the pointer is not correct yet */
if (root->prev) {
for (; root->prev->next; root = root->prev);
}
/* search for the instance node */
while (path[i]) {
j = parse_instance_identifier(&path[i], &model, &mod_len, &name, &name_len, &has_predicate);
if (j <= 0) {
LOGVAL(LYE_INCHAR, LY_VLOG_LYD, data, path[i-j], &path[i-j]);
goto error;
}
i += j;
str = strndup(model, mod_len);
if (!str) {
LOGMEM;
goto error;
}
mod = ly_ctx_get_module(ctx, str, NULL);
if (ctx->data_clb) {
if (!mod) {
mod = ctx->data_clb(ctx, str, NULL, 0, ctx->data_clb_data);
} else if (!mod->implemented) {
mod = ctx->data_clb(ctx, mod->name, mod->ns, LY_MODCLB_NOT_IMPLEMENTED, ctx->data_clb_data);
}
}
free(str);
if (!mod || !mod->implemented || mod->disabled) {
break;
}
if (resolve_data(mod, name, name_len, root, &node_match)) {
/* no instance exists */
break;
}
if (has_predicate) {
/* we have predicate, so the current results must be list or leaf-list */
j = resolve_instid_predicate(&path[i], &node_match);
if (j < 1) {
LOGVAL(LYE_INPRED, LY_VLOG_LYD, data, &path[i-j]);
goto error;
}
i += j;
if (!node_match.count) {
/* no instance exists */
break;
}
} else if (node_match.count) {
/* check that we are not addressing lists */
for (j = 0; (unsigned)j < node_match.count; ++j) {
if (node_match.node[j]->schema->nodetype == LYS_LIST) {
unres_data_del(&node_match, j--);
}
}
if (!node_match.count) {
LOGVAL(LYE_SPEC, LY_VLOG_LYD, data, "Instance identifier is missing list keys.");
}
}
}
if (!node_match.count) {
/* no instance exists */
if (req_inst > -1) {
LOGVAL(LYE_NOREQINS, LY_VLOG_LYD, data, path);
return EXIT_FAILURE;
}
LOGVRB("There is no instance of \"%s\", but it is not required.", path);
return EXIT_SUCCESS;
} else if (node_match.count > 1) {
/* instance identifier must resolve to a single node */
LOGVAL(LYE_TOOMANY, LY_VLOG_LYD, data, path, "data tree");
goto error;
} else {
/* we have required result, remember it and cleanup */
*ret = node_match.node[0];
free(node_match.node);
return EXIT_SUCCESS;
}
error:
/* cleanup */
free(node_match.node);
return -1;
}
static int
resolve_leafref(struct lyd_node_leaf_list *leaf, const char *path, int req_inst, struct lyd_node **ret)
{
struct ly_set *set;
uint32_t i;
*ret = NULL;
/* syntax was already checked, so just evaluate the path using standard XPath */
set = lyd_find_path((struct lyd_node *)leaf, path);
if (!set) {
return -1;
}
for (i = 0; i < set->number; ++i) {
if (!(set->set.d[i]->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST))) {
continue;
}
/* not that the value is already in canonical form since the parsers does the conversion,
* so we can simply compare just the values */
if (ly_strequal(leaf->value_str, ((struct lyd_node_leaf_list *)set->set.d[i])->value_str, 1)) {
/* we have the match */
*ret = set->set.d[i];
break;
}
}
ly_set_free(set);
if (!*ret) {
/* reference not found */
if (req_inst > -1) {
LOGVAL(LYE_NOLEAFREF, LY_VLOG_LYD, leaf, path, leaf->value_str);
return EXIT_FAILURE;
} else {
LOGVRB("There is no leafref \"%s\" with the value \"%s\", but it is not required.", path, leaf->value_str);
}
}
return EXIT_SUCCESS;
}
/* ignore fail because we are parsing edit-config, get, or get-config - but only if the union includes leafref or instid */
int
resolve_union(struct lyd_node_leaf_list *leaf, struct lys_type *type, int store, int ignore_fail,
struct lys_type **resolved_type)
{
struct lys_type *t;
struct lyd_node *ret;
int found, hidden, success = 0, ext_dep, req_inst;
const char *json_val = NULL;
assert(type->base == LY_TYPE_UNION);
if ((leaf->value_type == LY_TYPE_UNION) || (leaf->value_type == (LY_TYPE_INST | LY_TYPE_INST_UNRES))) {
/* either NULL or instid previously converted to JSON */
json_val = leaf->value.string;
}
if (store) {
if ((leaf->value_type & LY_DATA_TYPE_MASK) == LY_TYPE_BITS) {
free(leaf->value.bit);
}
memset(&leaf->value, 0, sizeof leaf->value);
}
/* turn logging off, we are going to try to validate the value with all the types in order */
hidden = *ly_vlog_hide_location();
ly_vlog_hide(1);
t = NULL;
found = 0;
while ((t = lyp_get_next_union_type(type, t, &found))) {
found = 0;
switch (t->base) {
case LY_TYPE_LEAFREF:
if ((ignore_fail == 1) || ((leaf->schema->flags & LYS_LEAFREF_DEP) && (ignore_fail == 2))) {
req_inst = -1;
} else {
req_inst = t->info.lref.req;
}
if (!resolve_leafref(leaf, t->info.lref.path, req_inst, &ret)) {
if (store) {
if (ret && !(leaf->schema->flags & LYS_LEAFREF_DEP)) {
/* valid resolved */
leaf->value.leafref = ret;
leaf->value_type = LY_TYPE_LEAFREF;
} else {
/* valid unresolved */
if (!lyp_parse_value(t, &leaf->value_str, NULL, leaf, NULL, 1, 0)) {
return -1;
}
}
}
success = 1;
}
break;
case LY_TYPE_INST:
ext_dep = check_instid_ext_dep(leaf->schema, (json_val ? json_val : leaf->value_str));
if ((ignore_fail == 1) || (ext_dep && (ignore_fail == 2))) {
req_inst = -1;
} else {
req_inst = t->info.inst.req;
}
if (!resolve_instid((struct lyd_node *)leaf, (json_val ? json_val : leaf->value_str), req_inst, &ret)) {
if (store) {
if (ret && !ext_dep) {
/* valid resolved */
leaf->value.instance = ret;
leaf->value_type = LY_TYPE_INST;
if (json_val) {
lydict_remove(leaf->schema->module->ctx, leaf->value_str);
leaf->value_str = json_val;
json_val = NULL;
}
} else {
/* valid unresolved */
if (json_val) {
/* put the JSON val back */
leaf->value.string = json_val;
json_val = NULL;
} else {
leaf->value.instance = NULL;
}
leaf->value_type = LY_TYPE_INST | LY_TYPE_INST_UNRES;
}
}
success = 1;
}
break;
default:
if (lyp_parse_value(t, &leaf->value_str, NULL, leaf, NULL, store, 0)) {
success = 1;
}
break;
}
if (success) {
break;
}
/* erase information about errors - they are false or irrelevant
* and will be replaced by a single error messages */
ly_err_clean(1);
/* erase possible present and invalid value data */
if (store) {
if (t->base == LY_TYPE_BITS) {
free(leaf->value.bit);
}
memset(&leaf->value, 0, sizeof leaf->value);
}
}
/* turn logging back on */
if (!hidden) {
ly_vlog_hide(0);
}
if (json_val) {
if (!success) {
/* put the value back for now */
assert(leaf->value_type == LY_TYPE_UNION);
leaf->value.string = json_val;
} else {
/* value was ultimately useless, but we could not have known */
lydict_remove(leaf->schema->module->ctx, json_val);
}
}
if (success) {
if (resolved_type) {
*resolved_type = t;
}
} else if (!ignore_fail || !type->info.uni.has_ptr_type) {
/* not found and it is required */
LOGVAL(LYE_INVAL, LY_VLOG_LYD, leaf, leaf->value_str ? leaf->value_str : "", leaf->schema->name);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
/**
* @brief Resolve a single unres data item. Logs directly.
*
* @param[in] node Data node to resolve.
* @param[in] type Type of the unresolved item.
* @param[in] ignore_fail 0 - no, 1 - yes, 2 - yes, but only for external dependencies.
*
* @return EXIT_SUCCESS on success, EXIT_FAILURE on forward reference, -1 on error.
*/
int
resolve_unres_data_item(struct lyd_node *node, enum UNRES_ITEM type, int ignore_fail)
{
int rc, req_inst, ext_dep;
struct lyd_node_leaf_list *leaf;
struct lyd_node *ret;
struct lys_node_leaf *sleaf;
leaf = (struct lyd_node_leaf_list *)node;
sleaf = (struct lys_node_leaf *)leaf->schema;
switch (type) {
case UNRES_LEAFREF:
assert(sleaf->type.base == LY_TYPE_LEAFREF);
assert(leaf->validity & LYD_VAL_LEAFREF);
if ((ignore_fail == 1) || ((leaf->schema->flags & LYS_LEAFREF_DEP) && (ignore_fail == 2))) {
req_inst = -1;
} else {
req_inst = sleaf->type.info.lref.req;
}
rc = resolve_leafref(leaf, sleaf->type.info.lref.path, req_inst, &ret);
if (!rc) {
if (ret && !(leaf->schema->flags & LYS_LEAFREF_DEP)) {
/* valid resolved */
if ((leaf->value_type & LY_DATA_TYPE_MASK) == LY_TYPE_BITS) {
free(leaf->value.bit);
}
leaf->value.leafref = ret;
leaf->value_type = LY_TYPE_LEAFREF;
} else {
/* valid unresolved */
if (!(leaf->value_type & LY_TYPE_LEAFREF_UNRES)) {
if (!lyp_parse_value(&sleaf->type, &leaf->value_str, NULL, leaf, NULL, 1, 0)) {
return -1;
}
}
}
leaf->validity &= ~LYD_VAL_LEAFREF;
} else {
return rc;
}
break;
case UNRES_INSTID:
assert(sleaf->type.base == LY_TYPE_INST);
ext_dep = check_instid_ext_dep(leaf->schema, leaf->value_str);
if (ext_dep == -1) {
return -1;
}
if ((ignore_fail == 1) || (ext_dep && (ignore_fail == 2))) {
req_inst = -1;
} else {
req_inst = sleaf->type.info.inst.req;
}
rc = resolve_instid(node, leaf->value_str, req_inst, &ret);
if (!rc) {
if (ret && !ext_dep) {
/* valid resolved */
leaf->value.instance = ret;
leaf->value_type = LY_TYPE_INST;
} else {
/* valid unresolved */
leaf->value.instance = NULL;
leaf->value_type = LY_TYPE_INST | LY_TYPE_INST_UNRES;
}
} else {
return rc;
}
break;
case UNRES_UNION:
assert(sleaf->type.base == LY_TYPE_UNION);
return resolve_union(leaf, &sleaf->type, 1, ignore_fail, NULL);
case UNRES_WHEN:
if ((rc = resolve_when(node, NULL, ignore_fail))) {
return rc;
}
break;
case UNRES_MUST:
if ((rc = resolve_must(node, 0, ignore_fail))) {
return rc;
}
break;
case UNRES_MUST_INOUT:
if ((rc = resolve_must(node, 1, ignore_fail))) {
return rc;
}
break;
default:
LOGINT;
return -1;
}
return EXIT_SUCCESS;
}
/**
* @brief add data unres item
*
* @param[in] unres Unres data structure to use.
* @param[in] node Data node to use.
*
* @return 0 on success, -1 on error.
*/
int
unres_data_add(struct unres_data *unres, struct lyd_node *node, enum UNRES_ITEM type)
{
assert(unres && node);
assert((type == UNRES_LEAFREF) || (type == UNRES_INSTID) || (type == UNRES_WHEN) || (type == UNRES_MUST)
|| (type == UNRES_MUST_INOUT) || (type == UNRES_UNION));
unres->count++;
unres->node = ly_realloc(unres->node, unres->count * sizeof *unres->node);
LY_CHECK_ERR_RETURN(!unres->node, LOGMEM, -1);
unres->node[unres->count - 1] = node;
unres->type = ly_realloc(unres->type, unres->count * sizeof *unres->type);
LY_CHECK_ERR_RETURN(!unres->type, LOGMEM, -1);
unres->type[unres->count - 1] = type;
if (type == UNRES_WHEN) {
/* remove previous result */
node->when_status = LYD_WHEN;
}
return EXIT_SUCCESS;
}
/**
* @brief Resolve every unres data item in the structure. Logs directly.
*
* If options includes LYD_OPT_TRUSTED, the data are considered trusted (when, must conditions are not expected,
* unresolved leafrefs/instids are accepted).
*
* If options includes LYD_OPT_NOAUTODEL, the false resulting when condition on non-default nodes, the error is raised.
*
* @param[in] unres Unres data structure to use.
* @param[in,out] root Root node of the data tree, can be changed due to autodeletion.
* @param[in] options Data options as described above.
*
* @return EXIT_SUCCESS on success, -1 on error.
*/
int
resolve_unres_data(struct unres_data *unres, struct lyd_node **root, int options)
{
uint32_t i, j, first, resolved, del_items, stmt_count;
int rc, progress, ignore_fail;
struct lyd_node *parent;
assert(root);
assert(unres);
if (!unres->count) {
return EXIT_SUCCESS;
}
if (options & (LYD_OPT_TRUSTED | LYD_OPT_NOTIF_FILTER | LYD_OPT_GET | LYD_OPT_GETCONFIG | LYD_OPT_EDIT)) {
ignore_fail = 1;
} else if (options & LYD_OPT_NOEXTDEPS) {
ignore_fail = 2;
} else {
ignore_fail = 0;
}
LOGVRB("Resolving unresolved data nodes and their constraints...");
ly_vlog_hide(1);
/* when-stmt first */
first = 1;
stmt_count = 0;
resolved = 0;
del_items = 0;
do {
ly_err_clean(1);
progress = 0;
for (i = 0; i < unres->count; i++) {
if (unres->type[i] != UNRES_WHEN) {
continue;
}
if (first) {
/* count when-stmt nodes in unres list */
stmt_count++;
}
/* resolve when condition only when all parent when conditions are already resolved */
for (parent = unres->node[i]->parent;
parent && LYD_WHEN_DONE(parent->when_status);
parent = parent->parent) {
if (!parent->parent && (parent->when_status & LYD_WHEN_FALSE)) {
/* the parent node was already unlinked, do not resolve this node,
* it will be removed anyway, so just mark it as resolved
*/
unres->node[i]->when_status |= LYD_WHEN_FALSE;
unres->type[i] = UNRES_RESOLVED;
resolved++;
break;
}
}
if (parent) {
continue;
}
rc = resolve_unres_data_item(unres->node[i], unres->type[i], ignore_fail);
if (!rc) {
if (unres->node[i]->when_status & LYD_WHEN_FALSE) {
if ((options & LYD_OPT_NOAUTODEL) && !unres->node[i]->dflt) {
/* false when condition */
ly_vlog_hide(0);
ly_err_repeat();
return -1;
} /* follows else */
/* auto-delete */
LOGVRB("auto-delete node \"%s\" due to when condition (%s)", ly_errpath(),
((struct lys_node_leaf *)unres->node[i]->schema)->when->cond);
/* only unlink now, the subtree can contain another nodes stored in the unres list */
/* if it has parent non-presence containers that would be empty, we should actually
* remove the container
*/
for (parent = unres->node[i];
parent->parent && parent->parent->schema->nodetype == LYS_CONTAINER;
parent = parent->parent) {
if (((struct lys_node_container *)parent->parent->schema)->presence) {
/* presence container */
break;
}
if (parent->next || parent->prev != parent) {
/* non empty (the child we are in and we are going to remove is not the only child) */
break;
}
}
unres->node[i] = parent;
if (*root && *root == unres->node[i]) {
*root = (*root)->next;
}
lyd_unlink(unres->node[i]);
unres->type[i] = UNRES_DELETE;
del_items++;
/* update the rest of unres items */
for (j = 0; j < unres->count; j++) {
if (unres->type[j] == UNRES_RESOLVED || unres->type[j] == UNRES_DELETE) {
continue;
}
/* test if the node is in subtree to be deleted */
for (parent = unres->node[j]; parent; parent = parent->parent) {
if (parent == unres->node[i]) {
/* yes, it is */
unres->type[j] = UNRES_RESOLVED;
resolved++;
break;
}
}
}
} else {
unres->type[i] = UNRES_RESOLVED;
}
ly_err_clean(1);
resolved++;
progress = 1;
} else if (rc == -1) {
ly_vlog_hide(0);
/* print only this last error */
resolve_unres_data_item(unres->node[i], unres->type[i], ignore_fail);
return -1;
} /* else forward reference */
}
first = 0;
} while (progress && resolved < stmt_count);
/* do we have some unresolved when-stmt? */
if (stmt_count > resolved) {
ly_vlog_hide(0);
ly_err_repeat();
return -1;
}
for (i = 0; del_items && i < unres->count; i++) {
/* we had some when-stmt resulted to false, so now we have to sanitize the unres list */
if (unres->type[i] != UNRES_DELETE) {
continue;
}
if (!unres->node[i]) {
unres->type[i] = UNRES_RESOLVED;
del_items--;
continue;
}
/* really remove the complete subtree */
lyd_free(unres->node[i]);
unres->type[i] = UNRES_RESOLVED;
del_items--;
}
/* now leafrefs */
first = 1;
stmt_count = 0;
resolved = 0;
do {
progress = 0;
for (i = 0; i < unres->count; i++) {
if (unres->type[i] != UNRES_LEAFREF) {
continue;
}
if (first) {
/* count leafref nodes in unres list */
stmt_count++;
}
rc = resolve_unres_data_item(unres->node[i], unres->type[i], ignore_fail);
if (!rc) {
unres->type[i] = UNRES_RESOLVED;
ly_err_clean(1);
resolved++;
progress = 1;
} else if (rc == -1) {
ly_vlog_hide(0);
/* print only this last error */
resolve_unres_data_item(unres->node[i], unres->type[i], ignore_fail);
return -1;
} /* else forward reference */
}
first = 0;
} while (progress && resolved < stmt_count);
/* do we have some unresolved leafrefs? */
if (stmt_count > resolved) {
ly_vlog_hide(0);
ly_err_repeat();
return -1;
}
ly_vlog_hide(0);
/* rest */
for (i = 0; i < unres->count; ++i) {
if (unres->type[i] == UNRES_RESOLVED) {
continue;
}
assert(!(options & LYD_OPT_TRUSTED) || ((unres->type[i] != UNRES_MUST) && (unres->type[i] != UNRES_MUST_INOUT)));
rc = resolve_unres_data_item(unres->node[i], unres->type[i], ignore_fail);
if (rc) {
/* since when was already resolved, a forward reference is an error */
return -1;
}
unres->type[i] = UNRES_RESOLVED;
}
LOGVRB("All data nodes and constraints resolved.");
unres->count = 0;
return EXIT_SUCCESS;
}