Skip to content

schema: helpers for tracking backlinks #2351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions src/tree_schema.c
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,108 @@ lys_find_path(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const
return snode;
}

typedef struct {
const struct lysc_node *match_node;
ly_bool match_ancestors;
struct ly_set *set;
} lys_find_backlinks_t;

static LY_ERR
lys_find_backlinks_clb(struct lysc_node *node, void *data, ly_bool *dfs_continue)
{
lys_find_backlinks_t *bldata = data;
LY_ERR ret = LY_SUCCESS;
struct ly_set *set = NULL;
size_t i;

(void)dfs_continue;

/* Not a node type we are interested in */
if ((node->nodetype != LYS_LEAF) && (node->nodetype != LYS_LEAFLIST)) {
return LY_SUCCESS;
}

/* Fetch leafrefs targets for comparison against our match node. Even if we
* are going to throw them away, we still need a count to know if this has
* valid leafref targets*/
ret = lysc_node_find_lref_targets(node, &set);
if (ret == LY_ENOTFOUND) {
return LY_SUCCESS;
} else if (ret != LY_SUCCESS) {
goto cleanup;
}

/* If set contains no entries, don't add node */
if ((set == NULL) || (set->count == 0)) {
goto cleanup;
}

/* If we're not requiring a match of a target node, just add this node to
* the returned set */
if (bldata->match_node == NULL) {
ly_set_add(bldata->set, node, 1, NULL);
goto cleanup;
}

/* We are doing target matching, scan to see if this node should be added */
for (i = 0; i < set->count; i++) {
if (bldata->match_ancestors) {
if (!lysc_node_has_ancestor(set->snodes[i], bldata->match_node)) {
continue;
}
} else {
if (set->snodes[i] != bldata->match_node) {
continue;
}
}

/* Found a match, add self */
ly_set_add(bldata->set, node, 1, NULL);
goto cleanup;
}

cleanup:
ly_set_free(set, NULL);
return ret;
}

LIBYANG_API_DEF LY_ERR
lys_find_backlinks(const struct ly_ctx *ctx, const struct lysc_node *match_node, ly_bool match_ancestors, struct ly_set **set)
{
LY_ERR ret;
uint32_t module_idx = 0;
const struct lys_module *module;

LY_CHECK_ARG_RET(NULL, ctx, set, LY_EINVAL);

/* allocate return set */
ret = ly_set_new(set);
LY_CHECK_GOTO(ret, cleanup);

/* Iterate across all loaded modules */
for (module_idx = 0; (module = ly_ctx_get_module_iter(ctx, &module_idx)) != NULL; ) {
lys_find_backlinks_t data = {match_node, match_ancestors, *set};

if (!module->compiled) {
continue;
}
ret = lysc_module_dfs_full(module, lys_find_backlinks_clb, &data);
LY_CHECK_GOTO(ret, cleanup);
}

cleanup:
if ((ret != LY_SUCCESS) || ((*set)->count == 0)) {
if (ret != LY_SUCCESS) {
ret = LY_ENOTFOUND;
}
ly_set_free(*set, NULL);
*set = NULL;
return ret;
}

return ret;
}

char *
lysc_path_until(const struct lysc_node *node, const struct lysc_node *parent, LYSC_PATH_TYPE pathtype, char *buffer,
size_t buflen)
Expand Down
61 changes: 61 additions & 0 deletions src/tree_schema.h
Original file line number Diff line number Diff line change
Expand Up @@ -1915,6 +1915,67 @@ LIBYANG_API_DECL struct lysc_when **lysc_node_when(const struct lysc_node *node)
*/
LIBYANG_API_DECL const struct lysc_node *lysc_node_lref_target(const struct lysc_node *node);

/**
* @brief Get the target node of a leafref type
*
* This is similar to lysc_node_lref_target() except it operates on a struct lysc_type
* object, which may be a member of another structure such as a union. The node
* owning the lysc_type must also be specified since the type object does not
* reference its parent.
*
* @param[in] node Node ownining the type object
* @param[in] type Type node which has a basetype of LY_TYPE_LEAFREF
* @return target schema node if found, otherwise NULL
*/
LIBYANG_API_DECL const struct lysc_node *lysc_type_lref_target(const struct lysc_node *node, const struct lysc_type *type);

/**
* @brief Determine if the node provided has an ancestor of the specified node.
*
* This will scan backwards in the tree using the parent node of each subsequent
* node to see if the ancestor matches. This will also match on self.
*
* @param[in] node Node to examine
* @param[in] ancestor node to match
* @return true if ancestor is found in tree, otherwise false
*/
LIBYANG_API_DECL ly_bool lysc_node_has_ancestor(const struct lysc_node *node, const struct lysc_node *ancestor);

/**
* @brief Fetch all leafref targets of the specified node
*
* This is an enhanced version of lysc_node_lref_target() which will return a
* set of leafref target nodes retrieved from the specified node. While
* lysc_node_lref_target() will only work on nodetype of LYS_LEAF and LYS_LEAFLIST
* this function will also evaluate other datatypes that may contain leafrefs
* such as LYS_UNION. This does not, however, search for children with leafref
* targets.
*
* @param[in] node node containing a leafref
* @param[out] set Set of found leafref targets (schema nodes).
* @return LY_ENOTFOUND if node specified does not contain lref targets, otherwise LY_SUCCESS
*
*/
LIBYANG_API_DECL LY_ERR lysc_node_find_lref_targets(const struct lysc_node *node, struct ly_set **set);

/**
* @brief Search entire schema for nodes that contain leafrefs and return as a set of schema nodes
*
* Perform a complete scan of the schema tree looking for nodes that contain leafref entries.
* When a node contains a leafref entry, and match_node is specified, determine if reference
* points to match_node, if so add the node to returned set. If no match_node is specified, the node
* containing the leafref is always added to the returned set. When match_ancestors is true,
* will evaluate if match_node is self or an ansestor of self.
*
* This does not return the leafref targets, but the actual node that contains a leafref.
*
* @param[in] ly_ctx
* @param[in] match_node Leafref target node to use for matching.
* @param[in] match_ancestors Whether match_node may be an anscestor instead of an exact node.
* @param[out] set Set of found nodes containing leafrefs
*/
LIBYANG_API_DECL LY_ERR lys_find_backlinks(const struct ly_ctx *ctx, const struct lysc_node *match_node, ly_bool match_ancestors, struct ly_set **set);

/**
* @brief Callback to be called for every schema node in a DFS traversal.
*
Expand Down
115 changes: 115 additions & 0 deletions src/tree_schema_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -1834,6 +1834,121 @@ lysc_node_lref_target(const struct lysc_node *node)
return target;
}

LIBYANG_API_DEF const struct lysc_node *
lysc_type_lref_target(const struct lysc_node *node, const struct lysc_type *type)
{
struct ly_set *set = NULL;
LY_ERR err;
const struct lysc_node *ret = NULL;
const struct lysc_type_leafref *lref;

if ((node == NULL) || (type == NULL) || (type->basetype != LY_TYPE_LEAFREF)) {
return NULL;
}

lref = (const struct lysc_type_leafref *)type;

err = lys_find_expr_atoms(node, node->module, lref->path, lref->prefixes, 0, &set);
if (err != LY_SUCCESS) {
return NULL;
}

if (set->count != 0) {
ret = set->snodes[set->count - 1];
}
ly_set_free(set, NULL);
return ret;
}

LIBYANG_API_DEF LY_ERR
lysc_node_find_lref_targets(const struct lysc_node *node, struct ly_set **set)
{
LY_ERR ret = LY_SUCCESS;
const struct lysc_type *type;

LY_CHECK_ARG_RET(NULL, node, set, LY_EINVAL);

/* Not a node type we are interested in */
if ((node->nodetype != LYS_LEAF) && (node->nodetype != LYS_LEAFLIST)) {
return LY_ENOTFOUND;
}

/* allocate return set */
ret = ly_set_new(set);
LY_CHECK_GOTO(ret, cleanup);

if (node->nodetype == LYS_LEAF) {
type = ((const struct lysc_node_leaf *)node)->type;
} else {
type = ((const struct lysc_node_leaflist *)node)->type;
}

if (type->basetype == LY_TYPE_UNION) {
/* Unions are a bit of a pain as they aren't represented by nodes,
* so we need to iterate across them to see if they contain any
* leafrefs */
const struct lysc_type_union *un = (const struct lysc_type_union *)type;
size_t i;

for (i = 0; i < LY_ARRAY_COUNT(un->types); i++) {
const struct lysc_type *utype = un->types[i];
const struct lysc_node *target;

if (utype->basetype != LY_TYPE_LEAFREF) {
continue;
}

target = lysc_type_lref_target(node, utype);
if (target == NULL) {
continue;
}

ret = ly_set_add(*set, target, 1, NULL);
LY_CHECK_GOTO(ret, cleanup);
}
} else if (type->basetype == LY_TYPE_LEAFREF) {
const struct lysc_node *target = lysc_node_lref_target(node);

if (target == NULL) {
ret = LY_ENOTFOUND;
goto cleanup;
}
ret = ly_set_add(*set, target, 1, NULL);
LY_CHECK_GOTO(ret, cleanup);
} else {
/* Not a node type we're interested in */
ret = LY_ENOTFOUND;
goto cleanup;
}

cleanup:
if ((*set)->count == 0) {
ly_set_free(*set, NULL);
*set = NULL;
if (ret == LY_SUCCESS) {
ret = LY_ENOTFOUND;
}
}
return ret;
}

LIBYANG_API_DEF ly_bool
lysc_node_has_ancestor(const struct lysc_node *node, const struct lysc_node *ancestor)
{
const struct lysc_node *n;

if ((node == NULL) || (ancestor == NULL)) {
return 0;
}

for (n = node; n != NULL; n = n->parent) {
if (n == ancestor) {
return 1;
}
}
return 0;
}

enum ly_stmt
lysp_match_kw(struct ly_in *in, uint64_t *indent)
{
Expand Down
Loading