Skip to content

Commit

Permalink
schema: helpers for tracking backlinks
Browse files Browse the repository at this point in the history
In libyang v1, each schema node had a backlinks object that would
allow a caller to quickly see if the node was referenced by another
node.  Since this functionality was removed, it takes quite a bit
of code to accomplish the same thing, including full tree scans.

This adds these primary new helpers:
 * lys_find_backlinks() scan the entire schema tree (all modules)
   and locate any node that contains leafrefs (including in unions),
   possibly filtered to a single target node or ancestor.
 * lysc_node_find_lref_targets() for a given node, return a set
   of leafref target nodes.  This returns a set instead of a single
   node like lysc_node_lref_target() as it also returns targets
   associated with unions (which there may be more than one).

It also adds a couple of new helpers used by the above new functions
that may be found useful so they were made public:
 * lysc_node_has_ancestor() determine if the ancestor node exists
   as a parent, grandparent, etc within the node schema for the
   specified node.
 * lysc_type_lref_target() similar to lysc_node_lref_target() except
   it can return the target node referenced in a leafref for a
   `struct lysc_type`

This functionality was determined to be needed when porting SONiC from
libyang v1 to v3.  SONiC uses libyang-python, and the API does not
currently expose some of the underlying functions used by these helpers.
Instead of simply modifying the libyang-python to add CFFI wrappers
it was determined it would be cost prohibitive to support
lysc_module_dfs_full() in python due to generating a Python SNode for
each node in the tree, of which most nodes are not needed to be
evaluated by Python.

It is not unlikely that other users may have the same need to track
backlinks so adding these helpers would also benefit other users.

Signed-off-by: Brad House <[email protected]>
  • Loading branch information
bradh352 committed Feb 15, 2025
1 parent 03e294d commit 8b34ca4
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 0 deletions.
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->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_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)
{
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 };
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
111 changes: 111 additions & 0 deletions src/tree_schema_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -1834,6 +1834,117 @@ lysc_node_lref_target(const struct lysc_node *node)
return target;
}

LIBYANG_API_DECL 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_DECL 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;
}
return ret;
}


LIBYANG_API_DECL 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 = node->parent) {
if (n == ancestor) {
return 1;
}
}
return 0;
}

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

0 comments on commit 8b34ca4

Please sign in to comment.