diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 9a2526c9..1ba9744f 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -711,8 +711,14 @@ from_client_edit_config(clixon_handle h, } } if ((ret = candidate_commit(h, NULL, "candidate", myid, 0, cbret)) < 0){ /* Assume validation fail, nofatal */ - if (netconf_operation_failed(cbret, "application", clixon_err_reason())< 0) - goto done; + if (clixon_err_category()) { + if (netconf_operation_failed(cbret, "application", + clixon_err_reason()) < 0) + goto done; + } else if (plugin_rpc_err_set()) { + if (netconf_gen_rpc_err(cbret) < 0) + goto done; + } xmldb_copy(h, "running", "candidate"); goto ok; } diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 1c9115d2..cba65457 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -596,6 +596,138 @@ validate_common(clixon_handle h, goto done; } +static char *_plugin_rpc_err_ns; +static char *_plugin_rpc_err_type; +static char *_plugin_rpc_err_tag; +static char *_plugin_rpc_err_info; +static char *_plugin_rpc_err_severity; +static cbuf *_plugin_rpc_err_message; + +/*! Save an RPC error to be reported by clixon. + * + * @param[in] h Clixon + * @param[in] ns Namespace. If NULL the netconf base ns will be used + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] severity Severity: "error" or "warning" + * @param[in] tag Error tag, like "invalid-value" or "unknown-attribute" + * @param[in] info bad-attribute or bad-element xml. If NULL not included. + * @param[in] fmt Error message format. May be NULL. + * @retval 0 Success + * @retval -1 Error + */ +int +plugin_rpc_err(clixon_handle h, const char *ns, + const char *type, const char *tag, const char *info, + const char *severity, + const char *fmt, ...) +{ + char *n_ns = NULL, *n_type = NULL, *n_tag = NULL; + char *n_info = NULL, *n_severity = NULL; + cbuf *n_message = NULL; + int err; + + if (ns) { + n_ns = strdup(ns); + if (!n_ns) + goto out_nomem; + } + n_type = strdup(type); + if (!n_type) + goto out_nomem; + n_tag = strdup(tag); + if (!n_tag) + goto out_nomem; + if (ns) { + n_ns = strdup(ns); + if (!n_ns) + goto out_nomem; + } + if (info) { + n_info = strdup(info); + if (!n_info) + goto out_nomem; + } + n_severity = strdup(severity); + if (!n_severity) + goto out_nomem; + if (fmt) { + va_list args; + n_message = cbuf_new(); + if (!n_message) + goto out_nomem; + va_start(args, fmt); + err = vcprintf(n_message, fmt, args); + va_end(args); + if (err < 0) + goto out_nomem; + } + /* + * Everything is allocated, we cannot fail from here, so clean up the + * old stuff. + */ + if (_plugin_rpc_err_ns) + free(_plugin_rpc_err_ns); + if (_plugin_rpc_err_type) + free(_plugin_rpc_err_type); + if (_plugin_rpc_err_tag) + free(_plugin_rpc_err_tag); + if (_plugin_rpc_err_info) + free(_plugin_rpc_err_info); + if (_plugin_rpc_err_severity) + free(_plugin_rpc_err_severity); + if (_plugin_rpc_err_message) + cbuf_free(_plugin_rpc_err_message); + + _plugin_rpc_err_ns = n_ns; + _plugin_rpc_err_type = n_type; + _plugin_rpc_err_tag = n_tag; + _plugin_rpc_err_info = n_info; + _plugin_rpc_err_severity = n_severity; + _plugin_rpc_err_message = n_message; + return 0; + + out_nomem: + if (n_ns) + free(n_ns); + if (n_type) + free(n_type); + if (n_tag) + free(n_tag); + if (n_info) + free(n_info); + if (n_severity) + free(n_severity); + if (n_message) + cbuf_free(n_message); + return -1; +} + +int +plugin_rpc_err_set(void) +{ + return _plugin_rpc_err_type != NULL; +} + +int +netconf_gen_rpc_err(cbuf *cbret) +{ + char *type = _plugin_rpc_err_type; + int ret; + + /* Type marks it as set, clear it before handling. */ + _plugin_rpc_err_type = NULL; + ret = netconf_common_rpc_err(cbret, _plugin_rpc_err_ns, + type, + _plugin_rpc_err_tag, + _plugin_rpc_err_severity, + _plugin_rpc_err_info, + cbuf_get(_plugin_rpc_err_message)); + free(type); + + return ret; +} + + /*! Start a validate transaction * * @param[in] h Clixon handle @@ -630,9 +762,16 @@ candidate_validate(clixon_handle h, * use clixon_err. * TODO: -1 return should be fatal error, not failed validation */ - if (!cbuf_len(cbret) && - netconf_operation_failed(cbret, "application", clixon_err_reason())< 0) - goto done; + if (!cbuf_len(cbret)) + goto done; + if (clixon_err_category()) { + if (netconf_operation_failed(cbret, "application", + clixon_err_reason()) < 0) + goto done; + } else if (plugin_rpc_err_set()) { + if (netconf_gen_rpc_err(cbret) < 0) + goto done; + } goto fail; } if (ret == 0){ @@ -844,9 +983,16 @@ from_client_commit(clixon_handle h, } if ((ret = candidate_commit(h, xe, "candidate", myid, 0, cbret)) < 0){ /* Assume validation fail, nofatal */ clixon_debug(CLIXON_DBG_BACKEND, "Commit candidate failed"); - if (ret < 0) - if (netconf_operation_failed(cbret, "application", clixon_err_reason())< 0) - goto done; + if (ret < 0) { + if (clixon_err_category()) { + if (netconf_operation_failed(cbret, "application", + clixon_err_reason()) < 0) + goto done; + } else if (plugin_rpc_err_set()) { + if (netconf_gen_rpc_err(cbret) < 0) + goto done; + } + } goto ok; } if (clicon_option_bool(h, "CLICON_AUTOLOCK")) diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 1d34e42c..a0f0ecc6 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -629,8 +629,9 @@ plugin_transaction_call_one(clixon_handle h, if (clixon_resource_check(h, &wh, clixon_plugin_name_get(cp), fnname) < 0) goto done; if (rv < 0) { - if (!clixon_err_category()) /* sanity: log if clixon_err() is not called ! */ - clixon_log(h, LOG_NOTICE, "%s: Plugin '%s' callback does not make clixon_err call on error", + if (!plugin_rpc_err_set() && !clixon_err_category()) + /* sanity: log if err is not called ! */ + clixon_log(h, LOG_NOTICE, "%s: Plugin '%s' callback does not make clixon_err or plugin_rpc_err call on error", fnname, clixon_plugin_name_get(cp)); goto done; } diff --git a/apps/backend/clixon_backend_plugin.h b/apps/backend/clixon_backend_plugin.h index 5d4c0da9..769efe5c 100644 --- a/apps/backend/clixon_backend_plugin.h +++ b/apps/backend/clixon_backend_plugin.h @@ -133,4 +133,10 @@ int plugin_transaction_end_all(clixon_handle h, transaction_data_t *td); int plugin_transaction_abort_one(clixon_plugin_t *cp, clixon_handle h, transaction_data_t *td); int plugin_transaction_abort_all(clixon_handle h, transaction_data_t *td); +int plugin_rpc_err(clixon_handle h, const char *ns, + const char *type, const char *tag, const char *info, + const char *severity, const char *fmt, ...); +int plugin_rpc_err_set(void); +int netconf_gen_rpc_err(cbuf *cbret); + #endif /* _CLIXON_BACKEND_PLUGIN_H_ */ diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index fdcceb50..70ff76ef 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -161,6 +161,8 @@ int netconf_missing_attribute_xml(cxobj **xret, char *type, char *info, char *me int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_bad_attribute_xml(cxobj **xret, char *type, char *info, char *message); int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message); +int netconf_common_rpc_err(cbuf *cb, char *ns, char *type, char *tag, char *info, + char *severity, char *message); int netconf_missing_element(cbuf *cb, char *type, char *element, char *message); int netconf_missing_yang_xml(cxobj **xret, char *path, char *app_tag, char *info, char *message); int netconf_missing_element_xml(cxobj **xret, char *type, char *element, char *message); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 95ec244c..51bb5ffd 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -487,6 +487,62 @@ netconf_unknown_attribute(cbuf *cb, goto done; } +/*! Create Netconf rpc-error XML tree with all parameters available. + * + * An unexpected attribute is present. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] ns Namespace. If NULL the netconf base ns will be used + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] severity Severity: "error" or "warning" + * @param[in] tag Error tag, like "invalid-value" or "unknown-attribute" + * @param[in] info bad-attribute or bad-element xml. If NULL not included. + * @param[in] message Error message. May be NULL. + * @retval 0 OK + * @retval -1 Error + */ +int +netconf_common_rpc_err(cbuf *cb, + char *ns, + char *type, + char *tag, + char *severity, + char *info, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (!ns) + ns = NETCONF_BASE_NAMESPACE; + + if (cprintf(cb, "" + "%s" + "%s" + "%s", + ns, type, tag, severity) < 0) + goto err; + if (info){ + if (cprintf(cb, "%s", info) < 0) + goto err; + } + if (message){ + if (xml_chardata_encode(&encstr, 0, "%s", message) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") < 0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clixon_err(OE_XML, errno, "cprintf"); + goto done; +} + /*! Common Netconf element XML tree according to RFC 6241 App A * * @param[out] xret Error XML tree. Free with xml_free after use