From cf67a7e26577b0dda276324b40a602ae084e504e Mon Sep 17 00:00:00 2001 From: Christian Hopps Date: Sat, 6 Jan 2024 09:45:29 +0000 Subject: [PATCH] lib: mgmtd: implement full XPath 1.0 predicate functionality Allow user to specify full YANG compatible XPath 1.0 predicates. This allows for trimming results of generic queries using functions and other non-key predicates from XPath 1.0 Signed-off-by: Christian Hopps --- lib/mgmt_msg.c | 2 +- lib/northbound.c | 21 ++++++++ lib/northbound.h | 8 +++ lib/northbound_oper.c | 24 +++++++-- lib/yang.c | 105 ++++++++++++++++++++++++++++++++++++++++ lib/yang.h | 29 +++++++++++ mgmtd/mgmt_fe_adapter.c | 43 +++++++++++++--- mgmtd/mgmt_txn.c | 31 ++++++++---- mgmtd/mgmt_txn.h | 4 +- 9 files changed, 245 insertions(+), 22 deletions(-) diff --git a/lib/mgmt_msg.c b/lib/mgmt_msg.c index 99d000537ce9..782707b46314 100644 --- a/lib/mgmt_msg.c +++ b/lib/mgmt_msg.c @@ -207,7 +207,7 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms, } /** - * Write data from a onto the socket, using streams that have been queued for + * Write data onto the socket, using streams that have been queued for * sending by mgmt_msg_send_msg. This function should be reschedulable. * * Args: diff --git a/lib/northbound.c b/lib/northbound.c index 18d65e47f13e..03d252ee5210 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -6,6 +6,7 @@ #include +#include "darr.h" #include "libfrr.h" #include "log.h" #include "lib_errors.h" @@ -168,6 +169,26 @@ struct nb_node *nb_node_find(const char *path) return snode->priv; } +struct nb_node **nb_nodes_find(const char *xpath) +{ + struct lysc_node **snodes = NULL; + struct nb_node **nb_nodes = NULL; + bool simple; + LY_ERR err; + uint i; + + err = yang_resolve_snode_xpath(ly_native_ctx, xpath, &snodes, &simple); + if (err) + return NULL; + + darr_ensure_i(nb_nodes, darr_lasti(snodes)); + darr_foreach_i (snodes, i) + nb_nodes[i] = snodes[i]->priv; + darr_free(snodes); + return nb_nodes; +} + + void nb_node_set_dependency_cbs(const char *dependency_xpath, const char *dependant_xpath, struct nb_dependency_callbacks *cbs) diff --git a/lib/northbound.h b/lib/northbound.h index 018d09fac716..37b7055c10df 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -813,6 +813,14 @@ void nb_nodes_delete(void); */ extern struct nb_node *nb_node_find(const char *xpath); +/** + * nb_nodes_find() - find the NB nodes corresponding to complex xpath. + * @xpath: XPath to search for (with or without predicates). + * + * Return: a dynamic array (darr) of `struct nb_node *`s. + */ +extern struct nb_node **nb_nodes_find(const char *xpath); + extern void nb_node_set_dependency_cbs(const char *dependency_xpath, const char *dependant_xpath, struct nb_dependency_callbacks *cbs); diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c index bd6d870ebcaf..4e131154e779 100644 --- a/lib/northbound_oper.c +++ b/lib/northbound_oper.c @@ -72,6 +72,7 @@ struct nb_op_node_info { * @schema_path: the schema nodes for each node 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 @@ -85,6 +86,7 @@ struct nb_op_yield_state { 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; @@ -158,6 +160,7 @@ static inline void nb_op_free_yield_state(struct nb_op_yield_state *ys, 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); @@ -1142,18 +1145,23 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume) 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->node, NULL, ys->query_tokens[at_clevel], NULL, 0, &node); if (!err) - /* predicate resolved to specific node */ is_specific_node = true; + else if (err == LY_EVALID) + ys->non_specific_predicate[at_clevel] = true; else { - flog_warn(EC_LIB_NB_OPERATIONAL_DATA, - "%s: unable to create node for specific query string: %s", + flog_err(EC_LIB_NB_OPERATIONAL_DATA, + "%s: unable to create node for specific query string: %s: %s", __func__, - ys->query_tokens[at_clevel]); + ys->query_tokens[at_clevel], + yang_ly_strerrcode(err)); + ret = NB_ERR; + goto done; } } @@ -1570,6 +1578,7 @@ static enum nb_error nb_op_yield(struct nb_op_yield_state *ys) 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; @@ -1587,6 +1596,11 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys, * 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); @@ -1614,6 +1628,7 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys, /* 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; @@ -1675,6 +1690,7 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys, darr_free(ys->query_tokstr); darr_free(ys->schema_path); darr_free(ys->query_tokens); + darr_free(ys->non_specific_predicate); return NB_ERR; } diff --git a/lib/yang.c b/lib/yang.c index 18d2ac58d347..b2cc71b30918 100644 --- a/lib/yang.c +++ b/lib/yang.c @@ -251,6 +251,38 @@ void yang_snode_get_path(const struct lysc_node *snode, } } +LY_ERR yang_resolve_snode_xpath(struct ly_ctx *ly_ctx, const char *xpath, + struct lysc_node ***snodes, bool *simple) +{ + struct lysc_node *snode; + struct ly_set *set; + LY_ERR err; + + /* lys_find_path will not resolve complex xpaths */ + snode = (struct lysc_node *)lys_find_path(ly_ctx, NULL, xpath, 0); + if (snode) { + *darr_append(*snodes) = snode; + *simple = true; + return LY_SUCCESS; + } + + /* Try again to catch complex query cases */ + err = lys_find_xpath(ly_native_ctx, NULL, xpath, 0, &set); + if (err) + return err; + if (!set->count) { + ly_set_free(set, NULL); + return LY_ENOTFOUND; + } + + *simple = false; + darr_ensure_i(*snodes, set->count - 1); + memcpy(*snodes, set->snodes, set->count * sizeof(set->snodes[0])); + ly_set_free(set, NULL); + return LY_SUCCESS; +} + + struct lysc_node *yang_find_snode(struct ly_ctx *ly_ctx, const char *xpath, uint32_t options) { @@ -1019,3 +1051,76 @@ LY_ERR yang_lyd_new_list(struct lyd_node_inner *parent, /*NOTREACHED*/ return LY_EINVAL; } + + +int yang_trim_tree(struct lyd_node *root, const char *xpath) +{ + enum nb_error ret = NB_OK; + LY_ERR err; +#if 0 + err = lyd_trim_xpath(&root, xpath, NULL); + if (err) { + flog_err_sys(EC_LIB_LIBYANG, + "cannot obtain specific result for xpath \"%s\"", + xpath); + return NB_ERR; + } + return NB_OK; +#else + struct lyd_node *node; + struct lyd_node **remove = NULL; + struct ly_set *set = NULL; + uint32_t i; + + err = lyd_find_xpath3(NULL, root, xpath, NULL, &set); + if (err) { + flog_err_sys(EC_LIB_LIBYANG, + "cannot obtain specific result for xpath \"%s\"", + xpath); + ret = NB_ERR; + goto done; + } + /* + * Mark keepers and sweep deleting non-keepers. + * + * NOTE: We assume the data-nodes have NULL priv pointers and use that + * for our mark. + */ + + /* Mark */ + for (i = 0; i < set->count; i++) { + for (node = set->dnodes[i]; node; node = &node->parent->node) { + if (node->priv) + break; + if (node == set->dnodes[i]) + node->priv = (void *)2; + else + node->priv = (void *)1; + } + } + + darr_ensure_cap(remove, 128); + LYD_TREE_DFS_BEGIN (root, node) { + /* + * If this is a direct matching node then include it's subtree + * which won't be marked and would otherwise be removed. + */ + if (node->priv == (void *)2) + LYD_TREE_DFS_continue = 1; + else if (!node->priv) { + LYD_TREE_DFS_continue = 1; + *darr_append(remove) = node; + } + LYD_TREE_DFS_END(root, node); + } + darr_foreach_i (remove, i) + lyd_free_tree(remove[i]); + darr_free(remove); + +done: + if (set) + ly_set_free(set, NULL); + + return ret; +#endif +} diff --git a/lib/yang.h b/lib/yang.h index 3ce584b347c3..75dcab2d2af8 100644 --- a/lib/yang.h +++ b/lib/yang.h @@ -724,6 +724,35 @@ extern LY_ERR yang_lyd_new_list(struct lyd_node_inner *parent, const struct lysc_node *snode, const struct yang_list_keys *keys, struct lyd_node_inner **node); +/** + * yang_resolve_snodes() - Resolve an XPath to matching schema nodes. + * @ly_ctx: libyang context to operate on. + * @xpath: the path or XPath to resolve. + * @snodes: [OUT] pointer for resulting dynamic array (darr) of schema node + * pointers. + * @simple: [OUT] indicates if @xpath was resolvable simply or not. Non-simple + * means that the @xpath is not a simple path and utilizes XPath 1.0 + * functionality beyond simple key predicates. + * + * This function can be used to find the schema node (or nodes) that correspond + * to a given @xpath. If the @xpath includes non-key predicates (e.g., using + * functions) then @simple will be set to false, and @snodes may contain more + * than a single schema node. + * + * Return: a libyang error or LY_SUCCESS. + */ +extern LY_ERR yang_resolve_snode_xpath(struct ly_ctx *ly_ctx, const char *xpath, + struct lysc_node ***snodes, bool *simple); + +/** + * yang_trim_tree() - trim the data tree to the given xpath + * @root: the data tree + * @xpath: the xpath to trim @root to. + * + * Return: enum nb_error.. + */ +extern int yang_trim_tree(struct lyd_node *root, const char *xpath); + #ifdef __cplusplus } #endif diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c index 5f17b89c5c61..b7c7a0fff147 100644 --- a/mgmtd/mgmt_fe_adapter.c +++ b/mgmtd/mgmt_fe_adapter.c @@ -1132,14 +1132,21 @@ static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session, } /** - * Handle a get-tree message from the client. + * fe_adapter_handle_get_tree() - Handle a get-tree message from a FE client. + * @session: the client session. + * @msg_raw: the message data. + * @msg_len: the length of the message data. */ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session, - void *data, size_t len) + void *__msg, size_t msg_len) { - struct mgmt_msg_get_tree *msg = data; + struct mgmt_msg_get_tree *msg = __msg; + struct lysc_node **snodes = NULL; + char *xpath_resolved = NULL; uint64_t req_id = msg->req_id; uint64_t clients; + bool simple_xpath; + LY_ERR err; int ret; MGMTD_FE_ADAPTER_DBG("Received get-tree request from client %s for session-id %" PRIu64 @@ -1147,14 +1154,32 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session, session->adapter->name, session->session_id, msg->req_id); + if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) { + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Invalid message rcvd from session-id: %" PRIu64, + session->session_id); + goto done; + } + if (session->txn_id != MGMTD_TXN_ID_NONE) { fe_adapter_send_error(session, req_id, false, -EINPROGRESS, "Transaction in progress txn-id: %" PRIu64 " for session-id: %" PRIu64, session->txn_id, session->session_id); - return; + goto done; } + + err = yang_resolve_snode_xpath(ly_native_ctx, msg->xpath, &snodes, + &simple_xpath); + if (err) { + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "XPath doesn't resolve for session-id: %" PRIu64, + session->session_id); + goto done; + } + darr_free(snodes); + clients = mgmt_be_interested_clients(msg->xpath, false); if (!clients) { MGMTD_FE_ADAPTER_DBG("No backends provide xpath: %s for txn-id: %" PRIu64 @@ -1164,7 +1189,7 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session, fe_adapter_send_tree_data(session, req_id, false, msg->result_type, NULL, 0); - return; + goto done; } /* Start a SHOW Transaction */ @@ -1173,7 +1198,7 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session, if (session->txn_id == MGMTD_SESSION_ID_NONE) { fe_adapter_send_error(session, req_id, false, -EINPROGRESS, "failed to create a 'show' txn"); - return; + goto done; } MGMTD_FE_ADAPTER_DBG("Created new show txn-id: %" PRIu64 @@ -1182,13 +1207,17 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session, /* Create a GET-TREE request under the transaction */ ret = mgmt_txn_send_get_tree_oper(session->txn_id, req_id, clients, - msg->result_type, msg->xpath); + msg->result_type, simple_xpath, + msg->xpath); if (ret) { /* destroy the just created txn */ mgmt_destroy_txn(&session->txn_id); fe_adapter_send_error(session, req_id, false, -EINPROGRESS, "failed to create a 'show' txn"); } +done: + darr_free(snodes); + darr_free(xpath_resolved); } /** diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index ea3236e80b96..1c6444563144 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -173,10 +173,11 @@ struct mgmt_get_data_req { struct txn_req_get_tree { char *xpath; /* xpath of tree to get */ - uint8_t result_type; /* LYD_FORMAT for results */ uint64_t sent_clients; /* Bitmask of clients sent req to */ uint64_t recv_clients; /* Bitmask of clients recv reply from */ int32_t partial_error; /* an error while gather results */ + uint8_t result_type; /* LYD_FORMAT for results */ + uint8_t simple_xpath; /* if xpath is simple */ struct lyd_node *client_results; /* result tree from clients */ }; @@ -1268,22 +1269,33 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, { struct txn_req_get_tree *get_tree = txn_req->req.get_tree; uint64_t req_id = txn_req->req_id; - int ret = 0; + int ret = NB_OK; /* cancel timer and send reply onward */ EVENT_OFF(txn->get_tree_timeout); - ret = mgmt_fe_adapter_send_tree_data(txn->session_id, txn->txn_id, - txn_req->req_id, - get_tree->result_type, - get_tree->client_results, - get_tree->partial_error, false); + if (!get_tree->simple_xpath && get_tree->client_results) { + /* + * We have a complex query so Filter results by the xpath query. + */ + ret = yang_trim_tree(get_tree->client_results, + txn_req->req.get_tree->xpath); + } + + if (ret == NB_OK) + ret = mgmt_fe_adapter_send_tree_data(txn->session_id, + txn->txn_id, + txn_req->req_id, + get_tree->result_type, + get_tree->client_results, + get_tree->partial_error, + false); /* we're done with the request */ mgmt_txn_req_free(&txn_req); if (ret) { - MGMTD_TXN_ERR("Error saving the results of GETTREE for txn-id %" PRIu64 + MGMTD_TXN_ERR("Error sending the results of GETTREE for txn-id %" PRIu64 " req_id %" PRIu64 " to requested type %u", txn->txn_id, req_id, get_tree->result_type); @@ -2352,7 +2364,7 @@ int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, */ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, uint64_t clients, LYD_FORMAT result_type, - const char *xpath) + bool simple_xpath, const char *xpath) { struct mgmt_msg_get_tree *msg; struct mgmt_txn_ctx *txn; @@ -2370,6 +2382,7 @@ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETTREE); get_tree = txn_req->req.get_tree; get_tree->result_type = result_type; + get_tree->simple_xpath = simple_xpath; get_tree->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, slen + 1, diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h index 4aa067775558..39d8cde16953 100644 --- a/mgmtd/mgmt_txn.h +++ b/mgmtd/mgmt_txn.h @@ -203,13 +203,15 @@ extern int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, * req_id: FE client request identifier. * clients: Bitmask of clients to send get-tree to. * result_type: LYD_FORMAT result format. + * simple_xpath: true if xpath is simple (only key predicates). * xpath: The xpath to get the tree from. + * * Return: * 0 on success. */ extern int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, uint64_t clients, LYD_FORMAT result_type, - const char *xpath); + bool simple_xpath, const char *xpath); /* * Notifiy backend adapter on connection.