diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 04:24:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 04:24:32 +0000 |
commit | 35cadacd2bb9383686753731e31bd7e145fb2506 (patch) | |
tree | 4489adbde75a837989533837185b2b8369a0bf68 /lib/northbound_oper.c | |
parent | Adding debian version 9.1-0.1. (diff) | |
download | frr-35cadacd2bb9383686753731e31bd7e145fb2506.tar.xz frr-35cadacd2bb9383686753731e31bd7e145fb2506.zip |
Merging upstream version 10.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/northbound_oper.c')
-rw-r--r-- | lib/northbound_oper.c | 1857 |
1 files changed, 1857 insertions, 0 deletions
diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c new file mode 100644 index 0000000..2394b5e --- /dev/null +++ b/lib/northbound_oper.c @@ -0,0 +1,1857 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * October 14 2023, Christian Hopps <chopps@labn.net> + * + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + * Copyright (c) 2023, LabN Consulting, L.L.C. + * + */ + +#include <zebra.h> +#include "darr.h" +#include "debug.h" +#include "frrevent.h" +#include "frrstr.h" +#include "lib_errors.h" +#include "monotime.h" +#include "northbound.h" + +/* + * YANG model yielding design restrictions: + * + * In order to be able to yield and guarantee we have a valid data tree at the + * point of yielding we must know that each parent has all it's siblings + * collected to represent a complete element. + * + * Basically, there should be a only single branch in the schema tree that + * supports yielding. In practice this means: + * + * list node schema with lookup next: + * - must not have any lookup-next list node sibling schema + * - must not have any list or container node siblings with lookup-next descendants. + * - any parent list nodes must also be lookup-next list nodes + * + * We must also process containers with lookup-next descendants last. + */ + +DEFINE_MTYPE_STATIC(LIB, NB_YIELD_STATE, "NB Yield State"); +DEFINE_MTYPE_STATIC(LIB, NB_NODE_INFOS, "NB Node Infos"); + +/* Amount of time allowed to spend constructing oper-state prior to yielding */ +#define NB_OP_WALK_INTERVAL_MS 50 +#define NB_OP_WALK_INTERVAL_US (NB_OP_WALK_INTERVAL_MS * 1000) + +/* ---------- */ +/* Data Types */ +/* ---------- */ +PREDECL_LIST(nb_op_walks); + +/* + * This is our information about a node on the branch we are looking at + */ +struct nb_op_node_info { + struct lyd_node *inner; + const struct lysc_node *schema; /* inner schema in case we rm inner */ + struct yang_list_keys keys; /* if list, keys to locate element */ + const void *list_entry; /* opaque entry from user or NULL */ + uint xpath_len; /* length of the xpath string for this node */ + uint niters; /* # list elems create this iteration */ + uint nents; /* # list elems create so far */ + bool query_specific_entry : 1; /* this info is specific specified */ + bool has_lookup_next : 1; /* if this node support lookup next */ + bool lookup_next_ok : 1; /* if this and all previous support */ +}; + +/** + * struct nb_op_yield_state - tracking required state for yielding. + * + * @xpath: current xpath representing the node_info stack. + * @xpath_orig: the original query string from the user + * @node_infos: the container stack for the walk from root to current + * @schema_path: the schema nodes along the path indicated by the query string. + * this will include the choice and case nodes which are not + * present in the query string. + * @query_tokstr: the query string tokenized with NUL bytes. + * @query_tokens: the string pointers to each query token (node). + * @non_specific_predicate: tracks if a query_token is non-specific predicate. + * @walk_root_level: The topmost specific node, +1 is where we start walking. + * @walk_start_level: @walk_root_level + 1. + * @query_base_level: the level the query string stops at and full walks + * commence below that. + */ +struct nb_op_yield_state { + /* Walking state */ + char *xpath; + char *xpath_orig; + struct nb_op_node_info *node_infos; + const struct lysc_node **schema_path; + char *query_tokstr; + char **query_tokens; + uint8_t *non_specific_predicate; + int walk_root_level; + int walk_start_level; + int query_base_level; + bool query_list_entry; /* XXX query was for a specific list entry */ + + /* Yielding state */ + bool query_did_entry; /* currently processing the entry */ + bool should_batch; + struct timeval start_time; + struct yang_translator *translator; + uint32_t flags; + nb_oper_data_cb cb; + void *cb_arg; + nb_oper_data_finish_cb finish; + void *finish_arg; + struct event *walk_ev; + struct nb_op_walks_item link; +}; + +DECLARE_LIST(nb_op_walks, struct nb_op_yield_state, link); + +/* ---------------- */ +/* Global Variables */ +/* ---------------- */ + +static struct event_loop *event_loop; +static struct nb_op_walks_head nb_op_walks; + +/* --------------------- */ +/* Function Declarations */ +/* --------------------- */ + +static enum nb_error nb_op_yield(struct nb_op_yield_state *ys); +static struct lyd_node *ys_root_node(struct nb_op_yield_state *ys); + +/* -------------------- */ +/* Function Definitions */ +/* -------------------- */ + +static inline struct nb_op_yield_state * +nb_op_create_yield_state(const char *xpath, struct yang_translator *translator, + uint32_t flags, bool should_batch, nb_oper_data_cb cb, + void *cb_arg, nb_oper_data_finish_cb finish, + void *finish_arg) +{ + struct nb_op_yield_state *ys; + + ys = XCALLOC(MTYPE_NB_YIELD_STATE, sizeof(*ys)); + ys->xpath = darr_strdup_cap(xpath, (size_t)XPATH_MAXLEN); + ys->xpath_orig = darr_strdup(xpath); + ys->translator = translator; + ys->flags = flags; + ys->should_batch = should_batch; + ys->cb = cb; + ys->cb_arg = cb_arg; + ys->finish = finish; + ys->finish_arg = finish_arg; + + nb_op_walks_add_tail(&nb_op_walks, ys); + + return ys; +} + +static inline void nb_op_free_yield_state(struct nb_op_yield_state *ys, + bool nofree_tree) +{ + if (ys) { + EVENT_OFF(ys->walk_ev); + nb_op_walks_del(&nb_op_walks, ys); + /* if we have a branch then free up it's libyang tree */ + if (!nofree_tree && ys_root_node(ys)) + lyd_free_all(ys_root_node(ys)); + darr_free(ys->query_tokens); + darr_free(ys->non_specific_predicate); + darr_free(ys->query_tokstr); + darr_free(ys->schema_path); + darr_free(ys->node_infos); + darr_free(ys->xpath_orig); + darr_free(ys->xpath); + XFREE(MTYPE_NB_YIELD_STATE, ys); + } +} + +static const struct lysc_node *ys_get_walk_stem_tip(struct nb_op_yield_state *ys) +{ + if (ys->walk_start_level <= 0) + return NULL; + return ys->node_infos[ys->walk_start_level - 1].schema; +} + +static struct lyd_node *ys_root_node(struct nb_op_yield_state *ys) +{ + if (!darr_len(ys->node_infos)) + return NULL; + return ys->node_infos[0].inner; +} + +static void ys_trim_xpath(struct nb_op_yield_state *ys) +{ + uint len = darr_len(ys->node_infos); + + if (len == 0) + darr_setlen(ys->xpath, 1); + else + darr_setlen(ys->xpath, darr_last(ys->node_infos)->xpath_len + 1); + ys->xpath[darr_len(ys->xpath) - 1] = 0; +} + +static void ys_pop_inner(struct nb_op_yield_state *ys) +{ + uint len = darr_len(ys->node_infos); + + assert(len); + darr_setlen(ys->node_infos, len - 1); + ys_trim_xpath(ys); +} + +static void ys_free_inner(struct nb_op_yield_state *ys, + struct nb_op_node_info *ni) +{ + if (!CHECK_FLAG(ni->schema->nodetype, LYS_CASE | LYS_CHOICE)) + lyd_free_tree(ni->inner); + ni->inner = NULL; +} + +static void nb_op_get_keys(struct lyd_node_inner *list_node, + struct yang_list_keys *keys) +{ + struct lyd_node *child; + uint n = 0; + + keys->num = 0; + LY_LIST_FOR (list_node->child, child) { + if (!lysc_is_key(child->schema)) + break; + strlcpy(keys->key[n], yang_dnode_get_string(child, NULL), + sizeof(keys->key[n])); + n++; + } + + keys->num = n; +} + +/** + * __move_back_to_next() - move back to the next lookup-next schema + */ +static bool __move_back_to_next(struct nb_op_yield_state *ys, int i) +{ + struct nb_op_node_info *ni; + int j; + + /* + * We will free the subtree we are trimming back to, or we will be done + * with the walk and will free the root on cleanup. + */ + + /* pop any node_info we dropped below on entry */ + for (j = darr_ilen(ys->node_infos) - 1; j > i; j--) + ys_pop_inner(ys); + + for (; i >= ys->walk_root_level; i--) { + if (ys->node_infos[i].has_lookup_next) + break; + ys_pop_inner(ys); + } + + if (i < ys->walk_root_level) + return false; + + ni = &ys->node_infos[i]; + + /* + * The i'th node has been lost after a yield so trim it from the tree + * now. + */ + ys_free_inner(ys, ni); + ni->list_entry = NULL; + + /* + * Leave the empty-of-data node_info on top, __walk will deal with + * this, by doing a lookup-next with the keys which we still have. + */ + + return true; +} + +static void nb_op_resume_data_tree(struct nb_op_yield_state *ys) +{ + struct nb_op_node_info *ni; + struct nb_node *nn; + const void *parent_entry; + const void *list_entry; + uint i; + + /* + * IMPORTANT: On yielding: we always yield during list iteration and + * after the initial list element has been created and handled, so the + * top of the yield stack will always point at a list node. + * + * Additionally, that list node has been processed and was in the + * process of being "get_next"d when we yielded. We process the + * lookup-next list node last so all the rest of the data (to the left) + * has been gotten. NOTE: To keep this simple we will require only a + * single lookup-next sibling in any parents list of children. + * + * Walk the rightmost branch (the node info stack) from base to tip + * verifying all list nodes are still present. If not we backup to the + * node which has a lookup next, and we prune the branch to this node. + * If the list node that went away is the topmost we will be using + * lookup_next, but if it's a parent then the list_entry will have been + * restored. + */ + darr_foreach_i (ys->node_infos, i) { + ni = &ys->node_infos[i]; + nn = ni->schema->priv; + + if (!CHECK_FLAG(ni->schema->nodetype, LYS_LIST)) + continue; + + assert(ni->list_entry != NULL || + ni == darr_last(ys->node_infos)); + + /* Verify the entry is still present */ + parent_entry = (i == 0 ? NULL : ni[-1].list_entry); + list_entry = nb_callback_lookup_entry(nn, parent_entry, + &ni->keys); + if (!list_entry || list_entry != ni->list_entry) { + /* May be NULL or a different pointer + * move back to first of + * container with last lookup_next list node + * (which may be this one) and get next. + */ + if (!__move_back_to_next(ys, i)) + DEBUGD(&nb_dbg_events, + "%s: Nothing to resume after delete during walk (yield)", + __func__); + return; + } + } +} + +/* + * Can only yield if all list nodes to root have lookup_next() callbacks + * + * In order to support lookup_next() the list_node get_next() callback + * needs to return ordered (i.e., sorted) results. + */ + +/* ======================= */ +/* Start of walk init code */ +/* ======================= */ + +/** + * __xpath_pop_node() - remove the last node from xpath string + * @xpath: an xpath string + * + * Return: NB_OK or NB_ERR_NOT_FOUND if nothing left to pop. + */ +static int __xpath_pop_node(char *xpath) +{ + int len = strlen(xpath); + bool abs = xpath[0] == '/'; + char *slash; + + /* "//" or "/" => NULL */ + if (abs && (len == 1 || (len == 2 && xpath[1] == '/'))) + return NB_ERR_NOT_FOUND; + + slash = (char *)frrstr_back_to_char(xpath, '/'); + /* "/foo/bar/" or "/foo/bar//" => "/foo " */ + if (slash && slash == &xpath[len - 1]) { + xpath[--len] = 0; + slash = (char *)frrstr_back_to_char(xpath, '/'); + if (slash && slash == &xpath[len - 1]) { + xpath[--len] = 0; + slash = (char *)frrstr_back_to_char(xpath, '/'); + } + } + if (!slash) + return NB_ERR_NOT_FOUND; + *slash = 0; + return NB_OK; +} + +/** + * nb_op_xpath_to_trunk() - generate a lyd_node tree (trunk) using an xpath. + * @xpath_in: xpath query string to build trunk from. + * @dnode: resulting tree (trunk) + * + * Use the longest prefix of @xpath_in as possible to resolve to a tree (trunk). + * This is logically as if we walked along the xpath string resolving each + * nodename reference (in particular list nodes) until we could not. + * + * Return: error if any, if no error then @dnode contains the tree (trunk). + */ +static enum nb_error nb_op_xpath_to_trunk(const char *xpath_in, + struct lyd_node **trunk) +{ + char *xpath = NULL; + enum nb_error ret = NB_OK; + LY_ERR err; + + darr_in_strdup(xpath, xpath_in); + for (;;) { + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, + LYD_NEW_PATH_UPDATE, NULL, trunk); + if (err == LY_SUCCESS) + break; + + ret = __xpath_pop_node(xpath); + if (ret != NB_OK) + break; + } + darr_free(xpath); + return ret; +} + +/* + * Finish initializing the node info based on the xpath string, and previous + * node_infos on the stack. If this node is a list node, obtain the specific + * list-entry object. + */ +static enum nb_error nb_op_ys_finalize_node_info(struct nb_op_yield_state *ys, + uint index) +{ + struct nb_op_node_info *ni = &ys->node_infos[index]; + struct lyd_node *inner = ni->inner; + struct nb_node *nn = ni->schema->priv; + bool yield_ok = ys->finish != NULL; + + ni->has_lookup_next = nn->cbs.lookup_next != NULL; + + /* track the last list_entry until updated by new list node */ + ni->list_entry = index == 0 ? NULL : ni[-1].list_entry; + + /* Assert that we are walking the rightmost branch */ + assert(!inner->parent || inner == inner->parent->child->prev); + + if (CHECK_FLAG(inner->schema->nodetype, + LYS_CASE | LYS_CHOICE | LYS_CONTAINER)) { + /* containers have only zero or one child on a branch of a tree */ + inner = ((struct lyd_node_inner *)inner)->child; + assert(!inner || inner->prev == inner); + ni->lookup_next_ok = yield_ok && + (index == 0 || ni[-1].lookup_next_ok); + return NB_OK; + } + + assert(CHECK_FLAG(inner->schema->nodetype, LYS_LIST)); + + ni->lookup_next_ok = yield_ok && ni->has_lookup_next && + (index == 0 || ni[-1].lookup_next_ok); + + nb_op_get_keys((struct lyd_node_inner *)inner, &ni->keys); + + /* A list entry cannot be present in a tree w/o it's keys */ + assert(ni->keys.num == yang_snode_num_keys(inner->schema)); + + /* + * Get this nodes opaque list_entry object + */ + + if (!nn->cbs.lookup_entry) { + flog_warn(EC_LIB_NB_OPERATIONAL_DATA, + "%s: data path doesn't support iteration over operational data: %s", + __func__, ys->xpath); + return NB_ERR_NOT_FOUND; + } + + /* ni->list_entry starts as the parent entry of this node */ + ni->list_entry = nb_callback_lookup_entry(nn, ni->list_entry, &ni->keys); + if (ni->list_entry == NULL) { + flog_warn(EC_LIB_NB_OPERATIONAL_DATA, + "%s: list entry lookup failed", __func__); + return NB_ERR_NOT_FOUND; + } + + /* + * By definition any list element we can get a specific list_entry for + * is specific. + */ + ni->query_specific_entry = true; + + return NB_OK; +} + +/** + * nb_op_ys_init_node_infos() - initialize the node info stack from the query. + * @ys: the yield state for this tree walk. + * + * On starting a walk we initialize the node_info stack as deeply as possible + * based on specific node references in the query string. We will stop at the + * point in the query string that is not specific (e.g., a list element without + * it's keys predicate) + * + * Return: northbound return value (enum nb_error) + */ +static enum nb_error nb_op_ys_init_node_infos(struct nb_op_yield_state *ys) +{ + struct nb_op_node_info *ni; + struct lyd_node *inner; + struct lyd_node *node = NULL; + enum nb_error ret; + uint i, len; + char *tmp; + + /* + * Obtain the trunk of the data node tree of the query. + * + * These are the nodes from the root that could be specifically + * identified with the query string. The trunk ends when a no specific + * node could be identified (e.g., a list-node name with no keys). + */ + + ret = nb_op_xpath_to_trunk(ys->xpath, &node); + if (ret || !node) { + flog_warn(EC_LIB_LIBYANG, + "%s: can't instantiate concrete path using xpath: %s", + __func__, ys->xpath); + if (!ret) + ret = NB_ERR_NOT_FOUND; + return ret; + } + + /* Move up to the container if on a leaf currently. */ + if (node && + !CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST)) { + struct lyd_node *leaf = node; + + node = &node->parent->node; + + /* + * If the leaf is not a key, delete it, because it has a wrong + * empty value. + */ + if (!lysc_is_key(leaf->schema)) + lyd_free_tree(leaf); + } + assert(!node || + CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST)); + if (!node) + return NB_ERR_NOT_FOUND; + + inner = node; + for (len = 1; inner->parent; len++) + inner = &inner->parent->node; + + darr_append_nz_mt(ys->node_infos, len, MTYPE_NB_NODE_INFOS); + + /* + * For each node find the prefix of the xpath query that identified it + * -- save the prefix length. + */ + inner = node; + for (i = len; i > 0; i--, inner = &inner->parent->node) { + ni = &ys->node_infos[i - 1]; + ni->inner = inner; + ni->schema = inner->schema; + /* + * NOTE: we could build this by hand with a litte more effort, + * but this simple implementation works and won't be expensive + * since the number of nodes is small and only done once per + * query. + */ + tmp = yang_dnode_get_path(inner, NULL, 0); + ni->xpath_len = strlen(tmp); + + /* Replace users supplied xpath with the libyang returned value */ + if (i == len) + darr_in_strdup(ys->xpath, tmp); + + /* The prefix must match the prefix of the stored xpath */ + assert(!strncmp(tmp, ys->xpath, ni->xpath_len)); + free(tmp); + } + + /* + * Obtain the specific list-entry objects for each list node on the + * trunk and finish initializing the node_info structs. + */ + + darr_foreach_i (ys->node_infos, i) { + ret = nb_op_ys_finalize_node_info(ys, i); + if (ret != NB_OK) { + if (ys->node_infos[0].inner) + lyd_free_all(ys->node_infos[0].inner); + darr_free(ys->node_infos); + return ret; + } + } + + ys->walk_start_level = darr_len(ys->node_infos); + + ys->walk_root_level = (int)ys->walk_start_level - 1; + + return NB_OK; +} + +/* ================ */ +/* End of init code */ +/* ================ */ + +/** + * nb_op_add_leaf() - Add leaf data to the get tree results + * @ys - the yield state for this tree walk. + * @nb_node - the northbound node representing this leaf. + * @xpath - the xpath (with key predicates) to this leaf value. + * + * Return: northbound return value (enum nb_error) + */ +static enum nb_error nb_op_iter_leaf(struct nb_op_yield_state *ys, + const struct nb_node *nb_node, + const char *xpath) +{ + const struct lysc_node *snode = nb_node->snode; + struct nb_op_node_info *ni = darr_last(ys->node_infos); + struct yang_data *data; + enum nb_error ret = NB_OK; + LY_ERR err; + + if (CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return NB_OK; + + /* Ignore list keys. */ + if (lysc_is_key(snode)) + return NB_OK; + + data = nb_callback_get_elem(nb_node, xpath, ni->list_entry); + if (data == NULL) + return NB_OK; + + /* Add a dnode to our tree */ + err = lyd_new_term(ni->inner, snode->module, snode->name, data->value, + false, NULL); + if (err) { + yang_data_free(data); + return NB_ERR_RESOURCE; + } + + if (ys->cb) + ret = (*ys->cb)(nb_node->snode, ys->translator, data, + ys->cb_arg); + yang_data_free(data); + + return ret; +} + +static enum nb_error nb_op_iter_leaflist(struct nb_op_yield_state *ys, + const struct nb_node *nb_node, + const char *xpath) +{ + const struct lysc_node *snode = nb_node->snode; + struct nb_op_node_info *ni = darr_last(ys->node_infos); + const void *list_entry = NULL; + enum nb_error ret = NB_OK; + LY_ERR err; + + if (CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return NB_OK; + + do { + struct yang_data *data; + + list_entry = nb_callback_get_next(nb_node, ni->list_entry, + list_entry); + if (!list_entry) + /* End of the list. */ + break; + + data = nb_callback_get_elem(nb_node, xpath, list_entry); + if (data == NULL) + continue; + + /* Add a dnode to our tree */ + err = lyd_new_term(ni->inner, snode->module, snode->name, + data->value, false, NULL); + if (err) { + yang_data_free(data); + return NB_ERR_RESOURCE; + } + + if (ys->cb) + ret = (*ys->cb)(nb_node->snode, ys->translator, data, + ys->cb_arg); + yang_data_free(data); + } while (ret == NB_OK && list_entry); + + return ret; +} + + +static bool nb_op_schema_path_has_predicate(struct nb_op_yield_state *ys, + int level) +{ + if (level > darr_lasti(ys->query_tokens)) + return false; + return strchr(ys->query_tokens[level], '[') != NULL; +} + +/** + * nb_op_empty_container_ok() - determine if should keep empty container node. + * + * Return: true if the empty container should be kept. + */ +static bool nb_op_empty_container_ok(const struct lysc_node *snode, + const char *xpath, const void *list_entry) +{ + struct nb_node *nn = snode->priv; + struct yang_data *data; + + if (!CHECK_FLAG(snode->flags, LYS_PRESENCE)) + return false; + + if (!nn->cbs.get_elem) + return false; + + data = nb_callback_get_elem(nn, xpath, list_entry); + if (data) { + yang_data_free(data); + return true; + } + return false; +} + +/** + * nb_op_get_child_path() - add child node name to the xpath. + * @xpath_parent - a darr string for the parent node. + * @schild - the child schema node. + * @xpath_child - a previous return value from this function to reuse. + */ +static char *nb_op_get_child_path(const char *xpath_parent, + const struct lysc_node *schild, + char *xpath_child) +{ + /* "/childname" */ + uint space, extra = strlen(schild->name) + 1; + bool new_mod = (!schild->parent || + schild->parent->module != schild->module); + int n; + + if (new_mod) + /* "modulename:" */ + extra += strlen(schild->module->name) + 1; + space = darr_len(xpath_parent) + extra; + + if (xpath_parent == xpath_child) + darr_ensure_cap(xpath_child, space); + else + darr_in_strdup_cap(xpath_child, xpath_parent, space); + if (new_mod) + n = snprintf(darr_strnul(xpath_child), extra + 1, "/%s:%s", + schild->module->name, schild->name); + else + n = snprintf(darr_strnul(xpath_child), extra + 1, "/%s", + schild->name); + assert(n == (int)extra); + _darr_len(xpath_child) += extra; + return xpath_child; +} + +static bool __is_yielding_node(const struct lysc_node *snode) +{ + struct nb_node *nn = snode->priv; + + return nn->cbs.lookup_next != NULL; +} + +static const struct lysc_node *__sib_next(bool yn, const struct lysc_node *sib) +{ + for (; sib; sib = sib->next) { + /* Always skip keys. */ + if (lysc_is_key(sib)) + continue; + if (yn == __is_yielding_node(sib)) + return sib; + } + return NULL; +} + +/** + * nb_op_sib_next() - Return the next sibling to walk to + * @ys: the yield state for this tree walk. + * @sib: the currently being visited sibling + * + * Return: the next sibling to walk to, walking non-yielding before yielding. + */ +static const struct lysc_node *nb_op_sib_next(struct nb_op_yield_state *ys, + const struct lysc_node *sib) +{ + struct lysc_node *parent = sib->parent; + bool yn = __is_yielding_node(sib); + + /* + * If the node info stack is shorter than the schema path then we are + * doign specific query still on the node from the schema path (should + * match) so just return NULL (i.e., don't process siblings) + */ + if (darr_len(ys->schema_path) > darr_len(ys->node_infos)) + return NULL; + /* + * If sib is on top of the node info stack then + * 1) it's a container node -or- + * 2) it's a list node that we were walking and we've reach the last entry + * 3) if sib is a list and the list was empty we never would have + * pushed sib on the stack so the top of the stack is the parent + * + * If the query string included this node then we do not process any + * siblings as we are not walking all the parent's children just this + * specified one give by the query string. + */ + if (sib == darr_last(ys->node_infos)->schema && + darr_len(ys->schema_path) >= darr_len(ys->node_infos)) + return NULL; + /* case (3) */ + else if (sib->nodetype == LYS_LIST && + parent == darr_last(ys->node_infos)->schema && + darr_len(ys->schema_path) > darr_len(ys->node_infos)) + return NULL; + + sib = __sib_next(yn, sib->next); + if (sib) + return sib; + if (yn) + return NULL; + return __sib_next(true, lysc_node_child(parent)); +} +/* + * sib_walk((struct lyd_node *)ni->inner->node.parent->parent->parent->parent->parent->parent->parent) + */ + +/** + * nb_op_sib_first() - obtain the first child to walk to + * @ys: the yield state for this tree walk. + * @parent: the parent whose child we seek + * @skip_keys: if should skip over keys + * + * Return: the first child to continue the walk to, starting with non-yielding + * siblings then yielding ones. There should be no more than 1 yielding sibling. + */ +static const struct lysc_node *nb_op_sib_first(struct nb_op_yield_state *ys, + const struct lysc_node *parent) +{ + const struct lysc_node *sib = lysc_node_child(parent); + const struct lysc_node *first_sib; + + /* + * NOTE: when we want to handle root level walks we will need to use + * lys_getnext() to walk root level of each module and + * ly_ctx_get_module_iter() to walk the modules. + */ + assert(darr_len(ys->node_infos) > 0); + + /* + * The top of the node stack points at @parent. + * + * If the schema path (original query) is longer than our current node + * info stack (current xpath location), we are building back up to the + * base of the user query, return the next schema node from the query + * string (schema_path). + */ + if (darr_last(ys->node_infos) != NULL && + !CHECK_FLAG(darr_last(ys->node_infos)->schema->nodetype, + LYS_CASE | LYS_CHOICE)) + assert(darr_last(ys->node_infos)->schema == parent); + if (darr_lasti(ys->node_infos) < ys->query_base_level) + return ys->schema_path[darr_lasti(ys->node_infos) + 1]; + + /* We always skip keys. */ + while (sib && lysc_is_key(sib)) + sib = sib->next; + if (!sib) + return NULL; + + /* Return non-yielding node's first */ + first_sib = sib; + if (__is_yielding_node(sib)) { + sib = __sib_next(false, sib); + if (sib) + return sib; + } + return first_sib; +} + +/* + * "3-dimensional" walk from base of the tree to the tip in-order. + * + * The actual tree is only 2-dimensional as list nodes are organized as adjacent + * siblings under a common parent perhaps with other siblings to each side; + * however, using 3d view here is easier to diagram. + * + * - A list node is yielding if it has a lookup_next callback. + * - All other node types are not yielding. + * - There's only one yielding node in a list of children (i.e., siblings). + * + * We visit all non-yielding children prior to the yielding child. + * That way we have the fullest tree possible even when something is deleted + * during a yield. + * --- child/parent descendant poinilnters + * ... next/prev sibling pointers + * o.o list entries pointers + * ~~~ diagram extension connector + * 1 + * / \ + * / \ o~~~~12 + * / \ . / \ + * 2.......5 o~~~9 13...14 + * / \ | . / \ + * 3...4 6 10...11 Cont Nodes: 1,2,5 + * / \ List Nodes: 6,9,12 + * 7...8 Leaf Nodes: 3,4,7,8,10,11,13,14 + * Schema Leaf A: 3 + * Schema Leaf B: 4 + * Schema Leaf C: 7,10,13 + * Schema Leaf D: 8,11,14 + */ +static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume) +{ + const struct lysc_node *walk_stem_tip = ys_get_walk_stem_tip(ys); + const struct lysc_node *sib; + const void *parent_list_entry = NULL; + const void *list_entry = NULL; + struct nb_op_node_info *ni, *pni; + struct lyd_node *node; + struct nb_node *nn; + char *xpath_child = NULL; + // bool at_query_base; + bool at_root_level, list_start, is_specific_node; + enum nb_error ret = NB_OK; + LY_ERR err; + int at_clevel; + uint len; + + + monotime(&ys->start_time); + + /* Don't currently support walking all root nodes */ + if (!walk_stem_tip) + return NB_ERR_NOT_FOUND; + + if (ys->schema_path[0]->nodetype == LYS_CHOICE) { + flog_err(EC_LIB_NB_OPERATIONAL_DATA, + "%s: unable to walk root level choice node from module: %s", + __func__, ys->schema_path[0]->module->name); + return NB_ERR; + } + + /* + * If we are resuming then start with the list container on top. + * Otherwise get the first child of the container we are walking, + * starting with non-yielding children. + */ + if (is_resume) + sib = darr_last(ys->node_infos)->schema; + else { + /* + * Start with non-yielding children first. + * + * When adding root level walks, the sibling list are the root + * level nodes of all modules + */ + sib = nb_op_sib_first(ys, walk_stem_tip); + if (!sib) + return NB_ERR_NOT_FOUND; + } + + + while (true) { + /* Grab the top container/list node info on the stack */ + at_clevel = darr_lasti(ys->node_infos); + ni = &ys->node_infos[at_clevel]; + + /* + * This is the level of the last specific node at init + * time. +1 would be the first non-specific list or + * non-container if present in the container node. + */ + at_root_level = at_clevel == ys->walk_root_level; + + if (!sib) { + /* + * We've reached the end of the siblings inside a + * containing node; either a container, case, choice, or + * a specific list node entry. + * + * We handle case/choice/container node inline; however, + * for lists we are only done with a specific entry and + * need to move to the next element on the list so we + * drop down into the switch for that case. + */ + + /* Grab the containing node. */ + sib = ni->schema; + + if (CHECK_FLAG(sib->nodetype, + LYS_CASE | LYS_CHOICE | LYS_CONTAINER)) { + /* If we added an empty container node (no + * children) and it's not a presence container + * or it's not backed by the get_elem callback, + * remove the node from the tree. + */ + if (sib->nodetype == LYS_CONTAINER && + !lyd_child(ni->inner) && + !nb_op_empty_container_ok(sib, ys->xpath, + ni->list_entry)) + ys_free_inner(ys, ni); + + /* If we have returned to our original walk base, + * then we are done with the walk. + */ + if (at_root_level) { + ret = NB_OK; + goto done; + } + /* + * Grab the sibling of the container we are + * about to pop, so we will be mid-walk on the + * parent containers children. + */ + sib = nb_op_sib_next(ys, sib); + + /* Pop container node to the parent container */ + ys_pop_inner(ys); + + /* + * If are were working on a user narrowed path + * then we are done with these siblings. + */ + if (darr_len(ys->schema_path) > + darr_len(ys->node_infos)) + sib = NULL; + + /* Start over */ + continue; + } + /* + * If we are here we have reached the end of the + * children of a list entry node. sib points + * at the list node info. + */ + } + + if (CHECK_FLAG(sib->nodetype, + LYS_LEAF | LYS_LEAFLIST | LYS_CONTAINER)) + xpath_child = nb_op_get_child_path(ys->xpath, sib, + xpath_child); + else if (CHECK_FLAG(sib->nodetype, LYS_CASE | LYS_CHOICE)) + darr_in_strdup(xpath_child, ys->xpath); + + nn = sib->priv; + + switch (sib->nodetype) { + case LYS_LEAF: + /* + * If we have a non-specific walk to a specific leaf + * (e.g., "..../route-entry/metric") and the leaf value + * is not present, then we are left with the data nodes + * of the stem of the branch to the missing leaf data. + * For containers this will get cleaned up by the + * container code above that looks for no children; + * however, this doesn't work for lists. + * + * (FN:A) We need a similar check for empty list + * elements. Empty list elements below the + * query_base_level (i.e., the schema path length) + * should be cleaned up as they don't support anything + * the user is querying for, if they are above the + * query_base_level then they are part of the walk and + * should be kept. + */ + ret = nb_op_iter_leaf(ys, nn, xpath_child); + if (ret != NB_OK) + goto done; + sib = nb_op_sib_next(ys, sib); + continue; + case LYS_LEAFLIST: + ret = nb_op_iter_leaflist(ys, nn, xpath_child); + if (ret != NB_OK) + goto done; + sib = nb_op_sib_next(ys, sib); + continue; + case LYS_CASE: + case LYS_CHOICE: + case LYS_CONTAINER: + if (CHECK_FLAG(nn->flags, F_NB_NODE_CONFIG_ONLY)) { + sib = nb_op_sib_next(ys, sib); + continue; + } + + if (sib->nodetype != LYS_CONTAINER) { + /* Case/choice use parent inner. */ + /* TODO: thus we don't support root level choice */ + node = ni->inner; + } else { + err = lyd_new_inner(ni->inner, sib->module, + sib->name, false, &node); + if (err) { + ret = NB_ERR_RESOURCE; + goto done; + } + } + + /* push this choice/container node on top of the stack */ + ni = darr_appendz(ys->node_infos); + ni->inner = node; + ni->schema = sib; + ni->lookup_next_ok = ni[-1].lookup_next_ok; + ni->list_entry = ni[-1].list_entry; + + darr_in_strdup(ys->xpath, xpath_child); + ni->xpath_len = darr_strlen(ys->xpath); + + sib = nb_op_sib_first(ys, sib); + continue; + case LYS_LIST: + + /* + * Notes: + * + * NOTE: ni->inner may be NULL here if we resumed and it + * was gone. ni->schema and ni->keys will still be + * valid. + * + * NOTE: At this point sib is never NULL; however, if it + * was NULL at the top of the loop, then we were done + * working on a list element's children and will be + * attempting to get the next list element here so sib + * == ni->schema (i.e., !list_start). + * + * (FN:A): Before doing this let's remove empty list + * elements that are "inside" the query string as they + * represent a stem which didn't lead to actual data + * being requested by the user -- for example, + * ".../route-entry/metric" if metric is not present we + * don't want to return an empty route-entry to the + * user. + */ + + node = NULL; + list_start = ni->schema != sib; + if (list_start) { + /* + * List iteration: First Element + * ----------------------------- + * + * Our node info wasn't on top (wasn't an entry + * for sib) so this is a new list iteration, we + * will push our node info below. The top is our + * parent. + */ + if (CHECK_FLAG(nn->flags, + F_NB_NODE_CONFIG_ONLY)) { + sib = nb_op_sib_next(ys, sib); + continue; + } + /* we are now at one level higher */ + at_clevel += 1; + pni = ni; + ni = NULL; + } else { + /* + * List iteration: Next Element + * ---------------------------- + * + * This is the case where `sib == NULL` at the + * top of the loop, so, we just completed the + * walking the children of a list entry, i.e., + * we are done with that list entry. + * + * `sib` was reset to point at the our list node + * at the top of node_infos. + * + * Within this node_info, `ys->xpath`, `inner`, + * `list_entry`, and `xpath_len` are for the + * previous list entry, and need to be updated. + */ + pni = darr_len(ys->node_infos) > 1 ? &ni[-1] + : NULL; + } + + parent_list_entry = pni ? pni->list_entry : NULL; + list_entry = ni ? ni->list_entry : NULL; + + /* + * Before yielding we check to see if we are doing a + * specific list entry instead of a full list iteration. + * We do not want to yield during specific list entry + * processing. + */ + + /* + * If we are at a list start check to see if the node + * has a predicate. If so we will try and fetch the data + * node now that we've built part of the tree, if the + * predicates are keys or only depend on the tree already + * built, it should create the element for us. + */ + is_specific_node = false; + if (list_start && + at_clevel <= darr_lasti(ys->query_tokens) && + !ys->non_specific_predicate[at_clevel] && + nb_op_schema_path_has_predicate(ys, at_clevel)) { + err = lyd_new_path(pni->inner, NULL, + ys->query_tokens[at_clevel], + NULL, 0, &node); + if (!err) + is_specific_node = true; + else if (err == LY_EVALID) + ys->non_specific_predicate[at_clevel] = true; + else { + flog_err(EC_LIB_NB_OPERATIONAL_DATA, + "%s: unable to create node for specific query string: %s: %s", + __func__, + ys->query_tokens[at_clevel], + yang_ly_strerrcode(err)); + ret = NB_ERR; + goto done; + } + } + + if (list_entry && ni->query_specific_entry) { + /* + * Ending specific list entry processing. + */ + assert(!list_start); + is_specific_node = true; + list_entry = NULL; + } + + /* + * Should we yield? + * + * Don't yield if we have a specific entry. + */ + if (!is_specific_node && ni && ni->lookup_next_ok && + // make sure we advance, if the interval is + // fast and we are very slow. + ((monotime_since(&ys->start_time, NULL) > + NB_OP_WALK_INTERVAL_US && + ni->niters) || + (ni->niters + 1) % 10000 == 0)) { + /* This is a yield supporting list node and + * we've been running at least our yield + * interval, so yield. + * + * NOTE: we never yield on list_start, and we + * are always about to be doing a get_next. + */ + DEBUGD(&nb_dbg_events, + "%s: yielding after %u iterations", + __func__, ni->niters); + + ni->niters = 0; + ret = NB_YIELD; + goto done; + } + + /* + * Now get the backend list_entry opaque object for + * this list entry from the backend. + */ + + if (is_specific_node) { + /* + * Specific List Entry: + * -------------------- + */ + if (list_start) { + list_entry = + nb_callback_lookup_node_entry( + node, parent_list_entry); + /* + * If the node we created from a + * specific predicate entry is not + * actually there we need to delete the + * node from our data tree + */ + if (!list_entry) { + lyd_free_tree(node); + node = NULL; + } + } + } else if (!list_start && !list_entry && + ni->has_lookup_next) { + /* + * After Yield: + * ------------ + * After a yield the list_entry may have become + * invalid, so use lookup_next callback with + * parent and keys instead to find next element. + */ + list_entry = + nb_callback_lookup_next(nn, + parent_list_entry, + &ni->keys); + } else { + /* + * Normal List Iteration: + * ---------------------- + * Start (list_entry == NULL) or continue + * (list_entry != NULL) the list iteration. + */ + /* Obtain [next] list entry. */ + list_entry = + nb_callback_get_next(nn, + parent_list_entry, + list_entry); + } + + /* + * (FN:A) Reap empty list element? Check to see if we + * should reap an empty list element. We do this if the + * empty list element exists at or below the query base + * (i.e., it's not part of the walk, but a failed find + * on a more specific query e.g., for below the + * `route-entry` element for a query + * `.../route-entry/metric` where the list element had + * no metric value. + * + * However, if the user query is for a key of a list + * element, then when we reach that list element it will + * have no non-key children, check for this condition + * and do not reap if true. + */ + if (!list_start && ni->inner && + !lyd_child_no_keys(ni->inner) && + /* not the top element with a key match */ + !((darr_ilen(ys->node_infos) == + darr_ilen(ys->schema_path) - 1) && + lysc_is_key((*darr_last(ys->schema_path)))) && + /* is this at or below the base? */ + darr_ilen(ys->node_infos) <= ys->query_base_level) + ys_free_inner(ys, ni); + + + if (!list_entry) { + /* + * List Iteration Done + * ------------------- + */ + + /* + * Grab next sibling of the list node + */ + if (is_specific_node) + sib = NULL; + else + sib = nb_op_sib_next(ys, sib); + + /* + * If we are at the walk root (base) level then + * that specifies a list and we are done iterating + * the list, so we are done with the walk entirely. + */ + if (!sib && at_clevel == ys->walk_root_level) { + ret = NB_OK; + goto done; + } + + /* + * Pop the our list node info back to our + * parent. + * + * We only do this if we've already pushed a + * node for the current list schema. For + * `list_start` this hasn't happened yet, as + * would have happened below. So when list_start + * is true but list_entry if NULL we + * are processing an empty list. + */ + if (!list_start) + ys_pop_inner(ys); + + /* + * We should never be below the walk root + */ + assert(darr_lasti(ys->node_infos) >= + ys->walk_root_level); + + /* Move on to the sibling of the list node */ + continue; + } + + /* + * From here on, we have selected a new top node_info + * list entry (either newly pushed or replacing the + * previous entry in the walk), and we are filling in + * the details. + */ + + if (list_start) { + /* + * Starting iteration of a list type or + * processing a specific entry, push the list + * node_info on stack. + */ + ni = darr_appendz(ys->node_infos); + pni = &ni[-1]; /* memory may have moved */ + ni->has_lookup_next = nn->cbs.lookup_next != + NULL; + ni->lookup_next_ok = ((!pni && ys->finish) || + pni->lookup_next_ok) && + ni->has_lookup_next; + ni->query_specific_entry = is_specific_node; + ni->niters = 0; + ni->nents = 0; + + /* this will be our predicate-less xpath */ + ys->xpath = nb_op_get_child_path(ys->xpath, sib, + ys->xpath); + } else { + /* + * Reset our xpath to the list node (i.e., + * remove the entry predicates) + */ + if (ni->query_specific_entry) { + flog_warn(EC_LIB_NB_OPERATIONAL_DATA, + "%s: unexpected state", + __func__); + } + assert(!ni->query_specific_entry); + len = strlen(sib->name) + 1; /* "/sibname" */ + if (pni) + len += pni->xpath_len; + darr_setlen(ys->xpath, len + 1); + ys->xpath[len] = 0; + ni->xpath_len = len; + } + + /* Need to get keys. */ + + if (!CHECK_FLAG(nn->flags, F_NB_NODE_KEYLESS_LIST)) { + ret = nb_callback_get_keys(nn, list_entry, + &ni->keys); + if (ret) { + darr_pop(ys->node_infos); + ret = NB_ERR_RESOURCE; + goto done; + } + } + /* + * Append predicates to xpath. + */ + len = darr_strlen(ys->xpath); + if (ni->keys.num) { + yang_get_key_preds(ys->xpath + len, sib, + &ni->keys, + darr_cap(ys->xpath) - len); + } else { + /* add a position predicate (1s based?) */ + darr_ensure_avail(ys->xpath, 10); + snprintf(ys->xpath + len, + darr_cap(ys->xpath) - len + 1, "[%u]", + ni->nents + 1); + } + darr_setlen(ys->xpath, + strlen(ys->xpath + len) + len + 1); + ni->xpath_len = darr_strlen(ys->xpath); + + /* + * Create the new list entry node. + */ + + if (!node) { + err = yang_lyd_new_list((struct lyd_node_inner *) + ni[-1] + .inner, + sib, &ni->keys, &node); + if (err) { + darr_pop(ys->node_infos); + ret = NB_ERR_RESOURCE; + goto done; + } + } + + /* + * Save the new list entry with the list node info + */ + ni->inner = node; + ni->schema = node->schema; + ni->list_entry = list_entry; + ni->niters += 1; + ni->nents += 1; + + /* Skip over the key children, they've been created. */ + sib = nb_op_sib_first(ys, sib); + continue; + + default: + /*FALLTHROUGH*/ + case LYS_ANYXML: + case LYS_ANYDATA: + /* These schema types are not currently handled */ + flog_warn(EC_LIB_NB_OPERATIONAL_DATA, + "%s: unsupported schema node type: %s", + __func__, lys_nodetype2str(sib->nodetype)); + sib = nb_op_sib_next(ys, sib); + continue; + } + } + +done: + darr_free(xpath_child); + return ret; +} + +static void nb_op_walk_continue(struct event *thread) +{ + struct nb_op_yield_state *ys = EVENT_ARG(thread); + enum nb_error ret = NB_OK; + + DEBUGD(&nb_dbg_cbs_state, "northbound oper-state: resuming %s", + ys->xpath); + + nb_op_resume_data_tree(ys); + + /* if we've popped past the walk start level we're done */ + if (darr_lasti(ys->node_infos) < ys->walk_root_level) + goto finish; + + /* otherwise we are at a resumable node */ + assert(darr_last(ys->node_infos)->has_lookup_next); + + ret = __walk(ys, true); + if (ret == NB_YIELD) { + if (nb_op_yield(ys) != NB_OK) { + if (ys->should_batch) + goto stopped; + else + goto finish; + } + return; + } +finish: + (*ys->finish)(ys_root_node(ys), ys->finish_arg, ret); +stopped: + nb_op_free_yield_state(ys, false); +} + +static void __free_siblings(struct lyd_node *this) +{ + struct lyd_node *next, *sib; + uint count = 0; + + LY_LIST_FOR_SAFE(lyd_first_sibling(this), next, sib) + { + if (lysc_is_key(sib->schema)) + continue; + if (sib == this) + continue; + lyd_free_tree(sib); + count++; + } + DEBUGD(&nb_dbg_events, "NB oper-state: deleted %u siblings", count); +} + +/* + * Trim Algorithm: + * + * Delete final lookup-next list node and subtree, leave stack slot with keys. + * + * Then walking up the stack, delete all siblings except: + * 1. right-most container or list node (must be lookup-next by design) + * 2. keys supporting existing parent list node. + * + * NOTE the topmost node on the stack will be the final lookup-nexxt list node, + * as we only yield on lookup-next list nodes. + * + */ +static void nb_op_trim_yield_state(struct nb_op_yield_state *ys) +{ + struct nb_op_node_info *ni; + int i = darr_lasti(ys->node_infos); + + assert(i >= 0); + + DEBUGD(&nb_dbg_events, "NB oper-state: start trimming: top: %d", i); + + ni = &ys->node_infos[i]; + assert(ni->has_lookup_next); + + DEBUGD(&nb_dbg_events, "NB oper-state: deleting tree at level %d", i); + __free_siblings(ni->inner); + ys_free_inner(ys, ni); + + while (--i > 0) { + DEBUGD(&nb_dbg_events, + "NB oper-state: deleting siblings at level: %d", i); + __free_siblings(ys->node_infos[i].inner); + } + DEBUGD(&nb_dbg_events, "NB oper-state: stop trimming: new top: %d", + (int)darr_lasti(ys->node_infos)); +} + +static enum nb_error nb_op_yield(struct nb_op_yield_state *ys) +{ + enum nb_error ret; + unsigned long min_us = MAX(1, NB_OP_WALK_INTERVAL_US / 50000); + struct timeval tv = { .tv_sec = 0, .tv_usec = min_us }; + + DEBUGD(&nb_dbg_events, "NB oper-state: yielding %s for %lus (should_batch %d)", + ys->xpath, tv.tv_usec, ys->should_batch); + + if (ys->should_batch) { + /* + * TODO: add ability of finish to influence the timer. + * This will allow, for example, flow control based on how long + * it takes finish to process the batch. + */ + ret = (*ys->finish)(ys_root_node(ys), ys->finish_arg, NB_YIELD); + if (ret != NB_OK) + return ret; + /* now trim out that data we just "finished" */ + nb_op_trim_yield_state(ys); + + } + + event_add_timer_tv(event_loop, nb_op_walk_continue, ys, &tv, + &ys->walk_ev); + return NB_OK; +} + +static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys, + struct nb_node **last) +{ + struct nb_node **nb_nodes = NULL; + const struct lysc_node *sn; + struct nb_node *nblast; + char *s, *s2; + int count; + uint i; + + /* + * Get the schema node stack for the entire query string + * + * The user might pass in something like "//metric" which may resolve to + * more than one schema node ("trunks"). nb_node_find() returns a single + * node though. We should expand the functionality to get the set of + * nodes that matches the xpath (not path) query and save that set in + * the yield state. Then we should do a walk using the users query + * string over each schema trunk in the set. + */ + nblast = nb_node_find(ys->xpath); + if (!nblast) { + nb_nodes = nb_nodes_find(ys->xpath); + nblast = darr_len(nb_nodes) ? nb_nodes[0] : NULL; + darr_free(nb_nodes); + } + if (!nblast) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, ys->xpath); + return NB_ERR; + } + *last = nblast; + + /* + * Create a stack of schema nodes one element per node in the query + * path, only the top (last) element may be a non-container type. + * + * NOTE: appears to be a bug in nb_node linkage where parent can be NULL, + * or I'm misunderstanding the code, in any case we use the libyang + * linkage to walk which works fine. + * + * XXX: we don't actually support choice/case yet, they are container + * types in the libyang schema, but won't be in data so our length + * checking gets messed up. + */ + for (sn = nblast->snode, count = 0; sn; count++, sn = sn->parent) + if (sn != nblast->snode) + assert(CHECK_FLAG(sn->nodetype, + LYS_CONTAINER | LYS_LIST | + LYS_CHOICE | LYS_CASE)); + /* create our arrays */ + darr_append_n(ys->schema_path, count); + darr_append_n(ys->query_tokens, count); + darr_append_nz(ys->non_specific_predicate, count); + for (sn = nblast->snode; sn; sn = sn->parent) + ys->schema_path[--count] = sn; + + /* + * Now tokenize the query string and get pointers to each token + */ + + /* Get copy of query string start after initial '/'s */ + s = ys->xpath; + while (*s && *s == '/') + s++; + ys->query_tokstr = darr_strdup(s); + s = ys->query_tokstr; + + darr_foreach_i (ys->schema_path, i) { + const char *modname = ys->schema_path[i]->module->name; + const char *name = ys->schema_path[i]->name; + int nlen = strlen(name); + int mnlen = 0; + + /* + * Technically the query_token for choice/case should probably be pointing at + * the child (leaf) rather than the parent (container), however, + * we only use these for processing list nodes so KISS. + */ + if (CHECK_FLAG(ys->schema_path[i]->nodetype, + LYS_CASE | LYS_CHOICE)) { + ys->query_tokens[i] = ys->query_tokens[i - 1]; + continue; + } + + while (true) { + s2 = strstr(s, name); + if (!s2) + goto error; + + if (s2[-1] == ':') { + mnlen = strlen(modname) + 1; + if (ys->query_tokstr > s2 - mnlen || + strncmp(s2 - mnlen, modname, mnlen - 1)) + goto error; + s2 -= mnlen; + nlen += mnlen; + } + + s = s2; + if ((i == 0 || s[-1] == '/') && + (s[nlen] == 0 || s[nlen] == '[' || s[nlen] == '/')) + break; + /* + * Advance past the incorrect match, must have been + * part of previous predicate. + */ + s += nlen; + } + + /* NUL terminate previous token and save this one */ + if (i > 0) + s[-1] = 0; + ys->query_tokens[i] = s; + s += nlen; + } + + /* NOTE: need to subtract choice/case nodes when these are supported */ + ys->query_base_level = darr_lasti(ys->schema_path); + + return NB_OK; + +error: + darr_free(ys->query_tokstr); + darr_free(ys->schema_path); + darr_free(ys->query_tokens); + darr_free(ys->non_specific_predicate); + return NB_ERR; +} + + +/** + * nb_op_walk_start() - Start walking oper-state directed by query string. + * @ys: partially initialized yield state for this walk. + * + */ +static enum nb_error nb_op_walk_start(struct nb_op_yield_state *ys) +{ + struct nb_node *nblast; + enum nb_error ret; + + /* + * Get nb_node path (stack) corresponding to the xpath query + */ + ret = nb_op_ys_init_schema_path(ys, &nblast); + if (ret != NB_OK) + return ret; + + + /* + * Get the node_info path (stack) corresponding to the uniquely + * resolvable data nodes from the beginning of the xpath query. + */ + ret = nb_op_ys_init_node_infos(ys); + if (ret != NB_OK) + return ret; + + return __walk(ys, false); +} + + +void *nb_oper_walk(const char *xpath, struct yang_translator *translator, + uint32_t flags, bool should_batch, nb_oper_data_cb cb, + void *cb_arg, nb_oper_data_finish_cb finish, void *finish_arg) +{ + struct nb_op_yield_state *ys; + enum nb_error ret; + + ys = nb_op_create_yield_state(xpath, translator, flags, should_batch, + cb, cb_arg, finish, finish_arg); + + ret = nb_op_walk_start(ys); + if (ret == NB_YIELD) { + if (nb_op_yield(ys) != NB_OK) { + if (ys->should_batch) + goto stopped; + else + goto finish; + } + return ys; + } +finish: + (void)(*ys->finish)(ys_root_node(ys), ys->finish_arg, ret); +stopped: + nb_op_free_yield_state(ys, false); + return NULL; +} + + +void nb_oper_cancel_walk(void *walk) +{ + if (walk) + nb_op_free_yield_state(walk, false); +} + + +void nb_oper_cancel_all_walks(void) +{ + struct nb_op_yield_state *ys; + + frr_each_safe (nb_op_walks, &nb_op_walks, ys) + nb_oper_cancel_walk(ys); +} + + +/* + * The old API -- remove when we've update the users to yielding. + */ +enum nb_error nb_oper_iterate_legacy(const char *xpath, + struct yang_translator *translator, + uint32_t flags, nb_oper_data_cb cb, + void *cb_arg, struct lyd_node **tree) +{ + struct nb_op_yield_state *ys; + enum nb_error ret; + + ys = nb_op_create_yield_state(xpath, translator, flags, false, cb, + cb_arg, NULL, NULL); + + ret = nb_op_walk_start(ys); + assert(ret != NB_YIELD); + + if (tree && ret == NB_OK) + *tree = ys_root_node(ys); + else { + if (ys_root_node(ys)) + yang_dnode_free(ys_root_node(ys)); + if (tree) + *tree = NULL; + } + + nb_op_free_yield_state(ys, true); + return ret; +} + +void nb_oper_init(struct event_loop *loop) +{ + event_loop = loop; + nb_op_walks_init(&nb_op_walks); +} + +void nb_oper_terminate(void) +{ + nb_oper_cancel_all_walks(); +} |