Skip to content

Commit 238bf5f

Browse files
committed
schema: helpers for tracking backlinks
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]>
1 parent 03e294d commit 238bf5f

File tree

4 files changed

+504
-0
lines changed

4 files changed

+504
-0
lines changed

src/tree_schema.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,110 @@ lys_find_path(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const
684684
return snode;
685685
}
686686

687+
688+
typedef struct {
689+
const struct lysc_node *match_node;
690+
ly_bool match_ancestors;
691+
struct ly_set *set;
692+
} lys_find_backlinks_t;
693+
694+
static LY_ERR
695+
lys_find_backlinks_clb(struct lysc_node *node, void *data, ly_bool *dfs_continue)
696+
{
697+
lys_find_backlinks_t *bldata = data;
698+
LY_ERR ret = LY_SUCCESS;
699+
struct ly_set *set = NULL;
700+
size_t i;
701+
702+
(void)dfs_continue;
703+
704+
/* Not a node type we are interested in */
705+
if (node->nodetype != LYS_LEAF && node->nodetype != LYS_LEAFLIST) {
706+
return LY_SUCCESS;
707+
}
708+
709+
/* Fetch leafrefs targets for comparison against our match node. Even if we
710+
* are going to throw them away, we still need a count to know if this has
711+
* valid leafref targets*/
712+
ret = lysc_node_find_lref_targets(node, &set);
713+
if (ret == LY_ENOTFOUND) {
714+
return LY_SUCCESS;
715+
} else if (ret != LY_SUCCESS) {
716+
goto cleanup;
717+
}
718+
719+
/* If set contains no entries, don't add node */
720+
if (set == NULL || set->count == 0) {
721+
goto cleanup;
722+
}
723+
724+
/* If we're not requiring a match of a target node, just add this node to
725+
* the returned set */
726+
if (bldata->match_node == NULL) {
727+
ly_set_add(bldata->set, node, 1, NULL);
728+
goto cleanup;
729+
}
730+
731+
/* We are doing target matching, scan to see if this node should be added */
732+
for (i=0; i<set->count; i++) {
733+
if (bldata->match_ancestors) {
734+
if (!lysc_node_has_ancestor(set->snodes[i], bldata->match_node)) {
735+
continue;
736+
}
737+
} else {
738+
if (set->snodes[i] != bldata->match_node) {
739+
continue;
740+
}
741+
}
742+
743+
/* Found a match, add self */
744+
ly_set_add(bldata->set, node, 1, NULL);
745+
goto cleanup;
746+
}
747+
748+
749+
cleanup:
750+
ly_set_free(set, NULL);
751+
return ret;
752+
}
753+
754+
LIBYANG_API_DEF LY_ERR
755+
lys_find_backlinks(const struct ly_ctx *ctx, const struct lysc_node *match_node, ly_bool match_ancestors, struct ly_set **set)
756+
{
757+
LY_ERR ret;
758+
uint32_t module_idx = 0;
759+
const struct lys_module *module;
760+
761+
LY_CHECK_ARG_RET(NULL, ctx, set, LY_EINVAL);
762+
763+
/* allocate return set */
764+
ret = ly_set_new(set);
765+
LY_CHECK_GOTO(ret, cleanup);
766+
767+
/* Iterate across all loaded modules */
768+
for (module_idx = 0; (module = ly_ctx_get_module_iter(ctx, &module_idx)) != NULL; ) {
769+
lys_find_backlinks_t data = { match_node, match_ancestors, *set };
770+
if (!module->compiled) {
771+
continue;
772+
}
773+
ret = lysc_module_dfs_full(module, lys_find_backlinks_clb, &data);
774+
LY_CHECK_GOTO(ret, cleanup);
775+
}
776+
777+
cleanup:
778+
if (ret != LY_SUCCESS || (*set)->count == 0) {
779+
if (ret != LY_SUCCESS) {
780+
ret = LY_ENOTFOUND;
781+
}
782+
ly_set_free(*set, NULL);
783+
*set = NULL;
784+
return ret;
785+
}
786+
787+
return ret;
788+
}
789+
790+
687791
char *
688792
lysc_path_until(const struct lysc_node *node, const struct lysc_node *parent, LYSC_PATH_TYPE pathtype, char *buffer,
689793
size_t buflen)

src/tree_schema.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,6 +1915,67 @@ LIBYANG_API_DECL struct lysc_when **lysc_node_when(const struct lysc_node *node)
19151915
*/
19161916
LIBYANG_API_DECL const struct lysc_node *lysc_node_lref_target(const struct lysc_node *node);
19171917

1918+
/**
1919+
* @brief Get the target node of a leafref type
1920+
*
1921+
* This is similar to lysc_node_lref_target() except it operates on a struct lysc_type
1922+
* object, which may be a member of another structure such as a union. The node
1923+
* owning the lysc_type must also be specified since the type object does not
1924+
* reference its parent.
1925+
*
1926+
* @param[in] node Node ownining the type object
1927+
* @param[in] type Type node which has a basetype of LY_TYPE_LEAFREF
1928+
* @return target schema node if found, otherwise NULL
1929+
*/
1930+
LIBYANG_API_DECL const struct lysc_node *lysc_type_lref_target(const struct lysc_node *node, const struct lysc_type *type);
1931+
1932+
/**
1933+
* @brief Determine if the node provided has an ancestor of the specified node.
1934+
*
1935+
* This will scan backwards in the tree using the parent node of each subsequent
1936+
* node to see if the ancestor matches. This will also match on self.
1937+
*
1938+
* @param[in] node Node to examine
1939+
* @param[in] ancestor node to match
1940+
* @return true if ancestor is found in tree, otherwise false
1941+
*/
1942+
LIBYANG_API_DECL ly_bool lysc_node_has_ancestor(const struct lysc_node *node, const struct lysc_node *ancestor);
1943+
1944+
/**
1945+
* @brief Fetch all leafref targets of the specified node
1946+
*
1947+
* This is an enhanced version of lysc_node_lref_target() which will return a
1948+
* set of leafref target nodes retrieved from the specified node. While
1949+
* lysc_node_lref_target() will only work on nodetype of LYS_LEAF and LYS_LEAFLIST
1950+
* this function will also evaluate other datatypes that may contain leafrefs
1951+
* such as LYS_UNION. This does not, however, search for children with leafref
1952+
* targets.
1953+
*
1954+
* @param[in] node node containing a leafref
1955+
* @param[out] set Set of found leafref targets (schema nodes).
1956+
* @return LY_ENOTFOUND if node specified does not contain lref targets, otherwise LY_SUCCESS
1957+
*
1958+
*/
1959+
LIBYANG_API_DECL LY_ERR lysc_node_find_lref_targets(const struct lysc_node *node, struct ly_set **set);
1960+
1961+
/**
1962+
* @brief Search entire schema for nodes that contain leafrefs and return as a set of schema nodes
1963+
*
1964+
* Perform a complete scan of the schema tree looking for nodes that contain leafref entries.
1965+
* When a node contains a leafref entry, and match_node is specified, determine if reference
1966+
* points to match_node, if so add the node to returned set. If no match_node is specified, the node
1967+
* containing the leafref is always added to the returned set. When match_ancestors is true,
1968+
* will evaluate if match_node is self or an ansestor of self.
1969+
*
1970+
* This does not return the leafref targets, but the actual node that contains a leafref.
1971+
*
1972+
* @param[in] ly_ctx
1973+
* @param[in] match_node Leafref target node to use for matching.
1974+
* @param[in] match_ancestors Whether match_node may be an anscestor instead of an exact node.
1975+
* @param[out] set Set of found nodes containing leafrefs
1976+
*/
1977+
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);
1978+
19181979
/**
19191980
* @brief Callback to be called for every schema node in a DFS traversal.
19201981
*

src/tree_schema_common.c

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,120 @@ lysc_node_lref_target(const struct lysc_node *node)
18341834
return target;
18351835
}
18361836

1837+
LIBYANG_API_DEF const struct lysc_node *
1838+
lysc_type_lref_target(const struct lysc_node *node, const struct lysc_type *type)
1839+
{
1840+
struct ly_set *set = NULL;
1841+
LY_ERR err;
1842+
const struct lysc_node *ret = NULL;
1843+
const struct lysc_type_leafref *lref;
1844+
1845+
if (node == NULL || type == NULL || type->basetype != LY_TYPE_LEAFREF) {
1846+
return NULL;
1847+
}
1848+
1849+
lref = (const struct lysc_type_leafref *)type;
1850+
1851+
err = lys_find_expr_atoms(node, node->module, lref->path, lref->prefixes, 0, &set);
1852+
if (err != LY_SUCCESS) {
1853+
return NULL;
1854+
}
1855+
1856+
if (set->count != 0) {
1857+
ret = set->snodes[set->count - 1];
1858+
}
1859+
ly_set_free(set, NULL);
1860+
return ret;
1861+
}
1862+
1863+
LIBYANG_API_DEF LY_ERR
1864+
lysc_node_find_lref_targets(const struct lysc_node *node, struct ly_set **set)
1865+
{
1866+
LY_ERR ret = LY_SUCCESS;
1867+
const struct lysc_type *type;
1868+
1869+
LY_CHECK_ARG_RET(NULL, node, set, LY_EINVAL);
1870+
1871+
/* Not a node type we are interested in */
1872+
if (node->nodetype != LYS_LEAF && node->nodetype != LYS_LEAFLIST) {
1873+
return LY_ENOTFOUND;
1874+
}
1875+
1876+
/* allocate return set */
1877+
ret = ly_set_new(set);
1878+
LY_CHECK_GOTO(ret, cleanup);
1879+
1880+
if (node->nodetype == LYS_LEAF) {
1881+
type = ((const struct lysc_node_leaf *)node)->type;
1882+
} else {
1883+
type = ((const struct lysc_node_leaflist *)node)->type;
1884+
}
1885+
1886+
if (type->basetype == LY_TYPE_UNION) {
1887+
/* Unions are a bit of a pain as they aren't represented by nodes,
1888+
* so we need to iterate across them to see if they contain any
1889+
* leafrefs */
1890+
const struct lysc_type_union *un = (const struct lysc_type_union *)type;
1891+
size_t i;
1892+
for (i=0; i<LY_ARRAY_COUNT(un->types); i++) {
1893+
const struct lysc_type *utype = un->types[i];
1894+
const struct lysc_node *target;
1895+
1896+
if (utype->basetype != LY_TYPE_LEAFREF) {
1897+
continue;
1898+
}
1899+
1900+
target = lysc_type_lref_target(node, utype);
1901+
if (target == NULL) {
1902+
continue;
1903+
}
1904+
1905+
ret = ly_set_add(*set, target, 1, NULL);
1906+
LY_CHECK_GOTO(ret, cleanup);
1907+
}
1908+
} else if (type->basetype == LY_TYPE_LEAFREF) {
1909+
const struct lysc_node *target = lysc_node_lref_target(node);
1910+
if (target == NULL) {
1911+
ret = LY_ENOTFOUND;
1912+
goto cleanup;
1913+
}
1914+
ret = ly_set_add(*set, target, 1, NULL);
1915+
LY_CHECK_GOTO(ret, cleanup);
1916+
} else {
1917+
/* Not a node type we're interested in */
1918+
ret = LY_ENOTFOUND;
1919+
goto cleanup;
1920+
}
1921+
1922+
cleanup:
1923+
if ((*set)->count == 0) {
1924+
ly_set_free(*set, NULL);
1925+
*set = NULL;
1926+
if (ret == LY_SUCCESS) {
1927+
ret = LY_ENOTFOUND;
1928+
}
1929+
}
1930+
return ret;
1931+
}
1932+
1933+
1934+
LIBYANG_API_DEF ly_bool
1935+
lysc_node_has_ancestor(const struct lysc_node *node, const struct lysc_node *ancestor)
1936+
{
1937+
const struct lysc_node *n;
1938+
1939+
if (node == NULL || ancestor == NULL) {
1940+
return 0;
1941+
}
1942+
1943+
for (n = node; n != NULL ; n = n->parent) {
1944+
if (n == ancestor) {
1945+
return 1;
1946+
}
1947+
}
1948+
return 0;
1949+
}
1950+
18371951
enum ly_stmt
18381952
lysp_match_kw(struct ly_in *in, uint64_t *indent)
18391953
{

0 commit comments

Comments
 (0)