diff --git a/.editorconfig b/.editorconfig index b757fc37..572c8dff 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,3 +3,4 @@ root = true [*.{c,h}] indent_size = 4 indent_style = space + diff --git a/CHANGELOG.md b/CHANGELOG.md index 27cb1f34..772a93f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,19 @@ ## 6.0.0 Expected: End of 2022 +### New features + +* Confirmed-commit capability + * Standards + * RFC 4741 "NETCONF Configuration Protocol": Section 8.4 + * RFC 6241 "Network Configuration Protocol (NETCONF)": Section 8.4 + * Features + * `:confirmed-commit:1.0` capability + * `:confirmed-commit:1.1` capability + * Added support for relevant arguments to CLI commit + * See [example_cli.cli](https://github.com/clicon/clixon/blob/master/example/main/example_cli.cli) + * See [Netconf Confirmed Commit Capability](https://github.com/clicon/clixon/issues/255) + ### API changes on existing protocol/config features Users may have to change how they access the system @@ -53,9 +66,29 @@ Developers may need to change their code * C API changes * Added `defaults` parameter to `clicon_rpc_get_pageable_list()` + * `clicon_rpc_commit()` and `cli_commit` + * Added `confirmed`, `cancel`, `timeout`, `persist-id`, and `persist-id-val` parameters to + * `clicon_rpc_commit()` and `clicon_rpc_validate` + * Added three-value return. + * Code need to be changed from: checking for `<0` to `<1` to keep same semantics + +### Minor features + +* [Code formatting: Change indentation style to space](https://github.com/clicon/clixon/issues/379) + * Applies to all c/h/y/l/sh files and .editorconfig +* Added warning if modstate is not present in datastore if `CLICON_XMLDB_MODSTATE` is set. ### Corrected Bugs +* Fixed: [Non-obvious behavior of clixon_snmp after snmpset command when transaction validation returns an error](https://github.com/clicon/clixon/issues/375) + * Fixed by validating writes on ACTION instead of COMMIT since libnetsnmp seems not to accept commit errors +* Fixed: [YANG when condition evaluated as false combined with a mandatory leaf does not work](https://github.com/clicon/clixon/issues/380) +* Fixed: [Trying to change the "read-only" node through snmpset](https://github.com/clicon/clixon/issues/376) +* Fixed: [Trying to change the "config false" node through snmpset](https://github.com/clicon/clixon/issues/377) + * Fixed by returning `SNMP_ERR_NOTWRITABLE` when trying to reserve object +* Fixed: [Non-obvious behavior of clixon_snmp after snmpset command when transaction validation returns an error](https://github.com/clicon/clixon/issues/375) +* Fixed: [clixon_snmp module crashes on snmpwalk command](https://github.com/clicon/clixon/issues/378) +* Fixed: [unneeded trailing zero character on SNMP strings](https://github.com/clicon/clixon/issues/367) * Fixed: [message-id present on netconf app "hello"](https://github.com/clicon/clixon/issues/369) * Fixed: [SNMP "smiv2" yang extension doesn't work on augmented nodes](https://github.com/clicon/clixon/issues/366) diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 9bfa8d92..c5c2f409 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -100,6 +100,7 @@ APPOBJ = $(APPSRC:.c=.o) LIBSRC = clixon_backend_transaction.c LIBSRC += clixon_backend_handle.c LIBSRC += backend_commit.c +LIBSRC += backend_confirm.c LIBSRC += backend_plugin.c LIBOBJ = $(LIBSRC:.c=.o) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 7164bca7..d434ffc6 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -78,13 +78,13 @@ */ static struct client_entry * ce_find_byid(struct client_entry *ce_list, - uint32_t id) + uint32_t id) { struct client_entry *ce; for (ce = ce_list; ce; ce = ce->ce_next) - if (ce->ce_id == id) - return ce; + if (ce->ce_id == id) + return ce; return NULL; } @@ -97,26 +97,26 @@ ce_find_byid(struct client_entry *ce_list, */ int ce_event_cb(clicon_handle h, - int op, - cxobj *event, - void *arg) + int op, + cxobj *event, + void *arg) { struct client_entry *ce = (struct client_entry *)arg; clicon_debug(1, "%s op:%d", __FUNCTION__, op); switch (op){ case 1: - /* Risk of recursion here */ - if (ce->ce_s) - backend_client_rm(h, ce); - break; + /* Risk of recursion here */ + if (ce->ce_s) + backend_client_rm(h, ce); + break; default: - if (send_msg_notify_xml(h, ce->ce_s, event) < 0){ - if (errno == ECONNRESET || errno == EPIPE){ - clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr); - } - break; - } + if (send_msg_notify_xml(h, ce->ce_s, event) < 0){ + if (errno == ECONNRESET || errno == EPIPE){ + clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr); + } + break; + } } return 0; } @@ -126,7 +126,7 @@ ce_event_cb(clicon_handle h, */ static int release_all_dbs(clicon_handle h, - uint32_t id) + uint32_t id) { int retval = -1; char **keys = NULL; @@ -136,21 +136,21 @@ release_all_dbs(clicon_handle h, /* get all db:s */ if (clicon_hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0) - goto done; + goto done; /* Identify the ones locked by client id */ for (i = 0; i < klen; i++) { - if ((de = clicon_db_elmnt_get(h, keys[i])) != NULL && - de->de_id == id){ - de->de_id = 0; /* unlock */ - clicon_db_elmnt_set(h, keys[i], de); - if (clixon_plugin_lockdb_all(h, keys[i], 0, id) < 0) - goto done; - } + if ((de = clicon_db_elmnt_get(h, keys[i])) != NULL && + de->de_id == id){ + de->de_id = 0; /* unlock */ + clicon_db_elmnt_set(h, keys[i], de); + if (clixon_plugin_lockdb_all(h, keys[i], 0, id) < 0) + goto done; + } } retval = 0; done: if (keys) - free(keys); + free(keys); return retval; } @@ -159,15 +159,44 @@ release_all_dbs(clicon_handle h, * Finally actually remove client struct in handle * @param[in] h Clicon handle * @param[in] ce Client handle + * @retval -1 Error (fatal) + * @retval 0 Ok * @see backend_client_delete for actual deallocation of client entry struct */ int backend_client_rm(clicon_handle h, - struct client_entry *ce) + struct client_entry *ce) { struct client_entry *c; struct client_entry *c0; struct client_entry **ce_prev; + uint32_t myid = ce->ce_id; + yang_stmt *yspec; + int retval = -1; + + /* If the confirmed-commit feature is enabled, rollback any ephemeral commit originated by this client */ + if ((yspec = clicon_dbspec_yang(h)) == NULL) { + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + + if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { + if (confirmed_commit_state_get(h) == EPHEMERAL) { + /* See if this client is the origin */ + clicon_debug(1, "session_id: %u, confirmed_commit.session_id: %u", ce->ce_id, confirmed_commit_session_id_get(h)); + + if (myid == confirmed_commit_session_id_get(h)) { + clicon_debug(1, "ok, rolling back"); + clicon_log(LOG_NOTICE, "a client with an active ephemeral confirmed-commit has disconnected; rolling back"); + + /* do_rollback errors are logged internally and there is no client to report errors to, so errors are + * ignored here. + */ + cancel_rollback_event(h); + do_rollback(h, NULL); + } + } + } clicon_debug(1, "%s", __FUNCTION__); /* for all streams: XXX better to do it top-level? */ @@ -175,19 +204,22 @@ backend_client_rm(clicon_handle h, c0 = backend_client_list(h); ce_prev = &c0; /* this points to stack and is not real backpointer */ for (c = *ce_prev; c; c = c->ce_next){ - if (c == ce){ - if (ce->ce_s){ - clixon_event_unreg_fd(ce->ce_s, from_client); - close(ce->ce_s); - ce->ce_s = 0; - if (release_all_dbs(h, ce->ce_id) < 0) - return -1; - } - break; - } - ce_prev = &c->ce_next; + if (c == ce){ + if (ce->ce_s){ + clixon_event_unreg_fd(ce->ce_s, from_client); + close(ce->ce_s); + ce->ce_s = 0; + if (release_all_dbs(h, ce->ce_id) < 0) + return -1; + } + break; + } + ce_prev = &c->ce_next; } - return backend_client_delete(h, ce); /* actually purge it */ + retval = backend_client_delete(h, ce); /* actually purge it */ + + done: + return retval; } /*! Get clixon per datastore stats @@ -199,8 +231,8 @@ backend_client_rm(clicon_handle h, */ static int clixon_stats_datastore_get(clicon_handle h, - char *dbname, - cbuf *cb) + char *dbname, + cbuf *cb) { int retval = -1; cxobj *xt = NULL; @@ -211,26 +243,26 @@ clixon_stats_datastore_get(clicon_handle h, /* This is the db cache */ if ((xt = xmldb_cache_get(h, dbname)) == NULL){ - /* Trigger cache if no exist */ - if (xmldb_get(h, dbname, NULL, "/", &xn) < 0) - goto done; - xt = xmldb_cache_get(h, dbname); + /* Trigger cache if no exist */ + if (xmldb_get(h, dbname, NULL, "/", &xn) < 0) + goto done; + xt = xmldb_cache_get(h, dbname); } if (xt == NULL){ - cprintf(cb, "%s00", - CLIXON_LIB_NS, dbname); + cprintf(cb, "%s00", + CLIXON_LIB_NS, dbname); } else{ - if (xml_stats(xt, &nr, &sz) < 0) - goto done; - cprintf(cb, "%s%" PRIu64 "" - "%zu", - CLIXON_LIB_NS, dbname, nr, sz); + if (xml_stats(xt, &nr, &sz) < 0) + goto done; + cprintf(cb, "%s%" PRIu64 "" + "%zu", + CLIXON_LIB_NS, dbname, nr, sz); } retval = 0; done: if (xn) - xml_free(xn); + xml_free(xn); return retval; } @@ -243,8 +275,8 @@ clixon_stats_datastore_get(clicon_handle h, */ static int clixon_stats_module_get(clicon_handle h, - yang_stmt *ys, - cbuf *cb) + yang_stmt *ys, + cbuf *cb) { int retval = -1; uint64_t nr = 0; @@ -252,16 +284,16 @@ clixon_stats_module_get(clicon_handle h, cxobj *xn = NULL; if (ys == NULL) - return 0; + return 0; if (yang_stats(ys, &nr, &sz) < 0) - goto done; + goto done; cprintf(cb, "%s%" PRIu64 "" - "%zu", - CLIXON_LIB_NS, yang_argument_get(ys), nr, sz); + "%zu", + CLIXON_LIB_NS, yang_argument_get(ys), nr, sz); retval = 0; done: if (xn) - xml_free(xn); + xml_free(xn); return retval; } @@ -285,10 +317,10 @@ clixon_stats_module_get(clicon_handle h, */ static int from_client_edit_config(clicon_handle h, - cxobj *xn, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xn, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; struct client_entry *ce = (struct client_entry *)arg; @@ -312,159 +344,187 @@ from_client_edit_config(clicon_handle h, username = clicon_username_get(h); if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec9"); - goto done; + clicon_err(OE_YANG, ENOENT, "No yang spec9"); + goto done; } if ((target = netconf_db_find(xn, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) - goto done; - goto ok; + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; + goto ok; } if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(target) < 0){ - cprintf(cbx, "No such database: %s", target); - if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) - goto done; - goto ok; + cprintf(cbx, "No such database: %s", target); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; + goto ok; } /* Check if target locked by other client */ iddb = xmldb_islocked(h, target); if (iddb && myid != iddb){ - cprintf(cbx, "%u", iddb); - if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) - goto done; - goto ok; + cprintf(cbx, "%u", iddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; + goto ok; } if (xml_nsctx_node(xn, &nsc) < 0) - goto done; + goto done; /* Get prefix of netconf base namespace in the incoming message */ if (xml_nsctx_get_prefix(nsc, NETCONF_BASE_NAMESPACE, &prefix) == 0){ - cprintf(cbx, "No appropriate prefix exists for: %s", NETCONF_BASE_NAMESPACE); - if (netconf_unknown_namespace(cbret, "protocol", xml_name(xn), cbuf_get(cbx)) < 0) - goto done; - goto ok; + cprintf(cbx, "No appropriate prefix exists for: %s", NETCONF_BASE_NAMESPACE); + if (netconf_unknown_namespace(cbret, "protocol", xml_name(xn), cbuf_get(cbx)) < 0) + goto done; + goto ok; } /* Get default-operation element */ if ((x = xpath_first(xn, nsc, "%s%sdefault-operation", prefix?prefix:"", prefix?":":"")) != NULL){ - if (xml_operation(xml_body(x), &operation) < 0){ - if (netconf_invalid_value(cbret, "protocol", "Wrong operation")< 0) - goto done; - goto ok; - } + if (xml_operation(xml_body(x), &operation) < 0){ + if (netconf_invalid_value(cbret, "protocol", "Wrong operation")< 0) + goto done; + goto ok; + } } /* Get config element */ if ((xc = xpath_first(xn, nsc, "%s%s%s", - prefix?prefix:"", - prefix?":":"", - NETCONF_INPUT_CONFIG)) == NULL){ - cprintf(cbx, "Element not found, or mismatching prefix %s for namespace %s", - prefix?prefix:"null", NETCONF_BASE_NAMESPACE); - if (netconf_missing_element(cbret, "protocol", NETCONF_INPUT_CONFIG, cbuf_get(cbx)) < 0) - goto done; - goto ok; + prefix?prefix:"", + prefix?":":"", + NETCONF_INPUT_CONFIG)) == NULL){ + cprintf(cbx, "Element not found, or mismatching prefix %s for namespace %s", + prefix?prefix:"null", NETCONF_BASE_NAMESPACE); + if (netconf_missing_element(cbret, "protocol", NETCONF_INPUT_CONFIG, cbuf_get(cbx)) < 0) + goto done; + goto ok; } /* yang spec may be set to anyxml by ingress yang check,...*/ if (xml_spec(xc) != NULL) - xml_spec_set(xc, NULL); + xml_spec_set(xc, NULL); /* Populate XML with Yang spec (why not do this in parser?) */ if ((ret = xml_bind_yang(xc, YB_MODULE, yspec, &xret)) < 0) - goto done; + goto done; if (ret == 0){ - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - goto ok; + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + goto ok; } /* (Mark all nodes that are not configure data and) set return */ if ((ret = xml_non_config_data(xc, &xret)) < 0) - goto done; + goto done; if (ret == 0){ - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - goto ok; + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + goto ok; } if (non_config){ - if (netconf_invalid_value(cbret, "protocol", "State data not allowed")< 0) - goto done; - goto ok; + if (netconf_invalid_value(cbret, "protocol", "State data not allowed")< 0) + goto done; + goto ok; } /* Limited validation of incoming payload */ if ((ret = xml_yang_minmax_recurse(xc, &xret)) < 0) - goto done; + goto done; /* xmldb_put (difflist handling) requires list keys */ if (ret==1 && (ret = xml_yang_validate_list_key_only(xc, &xret)) < 0) - goto done; + goto done; if (ret == 0){ - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - goto ok; + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + goto ok; } /* Cant do this earlier since we dont have a yang spec to * the upper part of the tree, until we get the "config" tree. */ if (xml_sort_recurse(xc) < 0) - goto done; + goto done; if ((ret = xmldb_put(h, target, operation, xc, username, cbret)) < 0){ - if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) - goto done; - goto ok; + if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) + goto done; + goto ok; } if (ret == 0) - goto ok; + goto ok; xmldb_modified_set(h, target, 1); /* mark as dirty */ /* Clixon extension: autocommit */ if ((attr = xml_find_value(xn, "autocommit")) != NULL && - strcmp(attr,"true")==0) - autocommit = 1; + strcmp(attr,"true")==0) + autocommit = 1; /* If autocommit option is set or requested by client */ if (clicon_autocommit(h) || autocommit) { - if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; - xmldb_copy(h, "running", "candidate"); - goto ok; - } - if (ret == 0){ /* discard */ - if (xmldb_copy(h, "running", "candidate") < 0){ - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; - goto ok; - } - goto ok; - } + /* if this is from a restconf client ... + * and, if there is an existing ephemeral commit, set is_valid_confirming_commit=1 such that + * candidate_commit will apply the configuration per RFC 8040 1.4: + * If a confirmed commit procedure is + * in progress by any NETCONF client, then any new commit will act as + * the confirming commit. + * and, if there is an existing persistent commit, netconf_operation_failed with "in-use", so + * that the restconf server will return "409 Conflict" per RFC 8040 1.4: + * If the NETCONF server is expecting a + * "persist-id" parameter to complete the confirmed commit procedure, + * then the RESTCONF edit operation MUST fail with a "409 Conflict" + * status-line. The error-tag "in-use" is used in this case. + */ + if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { + switch (confirmed_commit_state_get(h)){ + case INACTIVE: + break; + case PERSISTENT: + if (netconf_in_use(cbret, "application", "Persistent commit is ongoing")< 0) + goto done; + goto ok; + break; + case EPHEMERAL: + case ROLLBACK: + cancel_confirmed_commit(h); + break; + } + } + if ((ret = candidate_commit(h, NULL, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + xmldb_copy(h, "running", "candidate"); + goto ok; + } + if (ret == 0){ /* discard */ + if (xmldb_copy(h, "running", "candidate") < 0){ + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto ok; + } + goto ok; + } } /* Clixon extension: copy */ if ((attr = xml_find_value(xn, "copystartup")) != NULL && - strcmp(attr,"true") == 0){ - if (xmldb_copy(h, "running", "startup") < 0){ - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; - goto ok; - } + strcmp(attr,"true") == 0){ + if (xmldb_copy(h, "running", "startup") < 0){ + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto ok; + } } if (cbuf_len(cbret) != 0){ - clicon_err(OE_NETCONF, EINVAL, "Internal error: cbret is not empty"); - goto done; + clicon_err(OE_NETCONF, EINVAL, "Internal error: cbret is not empty"); + goto done; } cprintf(cbret, ""); ok: retval = 0; done: if (nsc) - cvec_free(nsc); + cvec_free(nsc); if (xret) - xml_free(xret); + xml_free(xret); if (cbx) - cbuf_free(cbx); - clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret)); + cbuf_free(cbx); + clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret)); return retval; } /* from_client_edit_config */ @@ -484,10 +544,10 @@ from_client_edit_config(clicon_handle h, */ static int from_client_copy_config(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; struct client_entry *ce = (struct client_entry *)arg; @@ -499,48 +559,48 @@ from_client_copy_config(clicon_handle h, cbuf *cbmsg = NULL; if ((source = netconf_db_find(xe, "source")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) - goto done; - goto ok; + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + goto done; + goto ok; } if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(source) < 0){ - cprintf(cbx, "No such database: %s", source); - if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) - goto done; - goto ok; + cprintf(cbx, "No such database: %s", source); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; + goto ok; } if ((target = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) - goto done; - goto ok; + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; + goto ok; } if (xmldb_validate_db(target) < 0){ - cprintf(cbx, "No such database: %s", target); - if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) - goto done; - goto ok; + cprintf(cbx, "No such database: %s", target); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; + goto ok; } /* Check if target locked by other client */ iddb = xmldb_islocked(h, target); if (iddb && myid != iddb){ - cprintf(cbx, "%u", iddb); - if (netconf_lock_denied(cbret, cbuf_get(cbx), "Copy failed, lock is already held") < 0) - goto done; - goto ok; + cprintf(cbx, "%u", iddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Copy failed, lock is already held") < 0) + goto done; + goto ok; } if (xmldb_copy(h, source, target) < 0){ - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbmsg, "Copy %s datastore to %s: %s", source, target, clicon_err_reason); - if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg))< 0) - goto done; - goto ok; + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Copy %s datastore to %s: %s", source, target, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg))< 0) + goto done; + goto ok; } xmldb_modified_set(h, target, 1); /* mark as dirty */ cprintf(cbret, "", NETCONF_BASE_NAMESPACE); @@ -548,9 +608,9 @@ from_client_copy_config(clicon_handle h, retval = 0; done: if (cbmsg) - cbuf_free(cbmsg); + cbuf_free(cbmsg); if (cbx) - cbuf_free(cbx); + cbuf_free(cbx); return retval; } @@ -565,10 +625,10 @@ from_client_copy_config(clicon_handle h, */ static int from_client_delete_config(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; struct client_entry *ce = (struct client_entry *)arg; @@ -580,48 +640,48 @@ from_client_delete_config(clicon_handle h, /* XXX should use prefix cf edit_config */ if ((target = netconf_db_find(xe, "target")) == NULL || - strcmp(target, "running")==0){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) - goto done; - goto ok; + strcmp(target, "running")==0){ + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; + goto ok; } if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(target) < 0){ - cprintf(cbx, "No such database: %s", target); - if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) - goto done; - goto ok; + cprintf(cbx, "No such database: %s", target); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; + goto ok; } /* Check if target locked by other client */ iddb = xmldb_islocked(h, target); if (iddb && myid != iddb){ - cprintf(cbx, "%u", iddb); - if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) - goto done; - goto ok; + cprintf(cbx, "%u", iddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; + goto ok; } if (xmldb_delete(h, target) < 0){ - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbmsg, "Delete %s datastore: %s", target, clicon_err_reason); - if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0) - goto done; - goto ok; + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Delete %s datastore: %s", target, clicon_err_reason); + if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0) + goto done; + goto ok; } if (xmldb_create(h, target) < 0){ - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbmsg, "Create %s datastore: %s", target, clicon_err_reason); - if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0) - goto done; - goto ok; + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Create %s datastore: %s", target, clicon_err_reason); + if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0) + goto done; + goto ok; } xmldb_modified_set(h, target, 1); /* mark as dirty */ cprintf(cbret, "", NETCONF_BASE_NAMESPACE); @@ -629,9 +689,9 @@ from_client_delete_config(clicon_handle h, retval = 0; done: if (cbmsg) - cbuf_free(cbmsg); + cbuf_free(cbmsg); if (cbx) - cbuf_free(cbx); + cbuf_free(cbx); return retval; } @@ -647,64 +707,84 @@ from_client_delete_config(clicon_handle h, */ static int from_client_lock(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; struct client_entry *ce = (struct client_entry *)arg; uint32_t id = ce->ce_id; uint32_t iddb; + uint32_t otherid; char *db; cbuf *cbx = NULL; /* Assist cbuf */ - + yang_stmt *yspec; + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec9"); + goto done; + } if ((db = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) - goto done; - goto ok; + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; + goto ok; } if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(db) < 0){ - cprintf(cbx, "No such database: %s", db); - if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) - goto done; - goto ok; + cprintf(cbx, "No such database: %s", db); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; + goto ok; } /* * A lock MUST not be granted if either of the following conditions is true: * 1) A lock is already held by any NETCONF session or another entity. */ if ((iddb = xmldb_islocked(h, db)) != 0){ - cprintf(cbx, "%u", iddb); - if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) - goto done; - goto ok; + cprintf(cbx, "%u", iddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; + goto ok; } /* 2) The target configuration is , it has already been modified, and * these changes have not been committed or rolled back. */ if (strcmp(db, "candidate") == 0 && - xmldb_modified_get(h, db)){ - if (netconf_lock_denied(cbret, "0", - "Operation failed, candidate has already been modified and the changes have not been committed or rolled back (RFC 6241 7.5)") < 0) - goto done; - goto ok; + xmldb_modified_get(h, db)){ + if (netconf_lock_denied(cbret, "0", + "Operation failed, candidate has already been modified and the changes have not been committed or rolled back (RFC 6241 7.5)") < 0) + goto done; + goto ok; + } + /* 3) The target configuration is , and another NETCONF + * session has an ongoing confirmed commi + */ + if (strcmp(db, "running") == 0 && + if_feature(yspec, "ietf-netconf", "confirmed-commit") && + confirmed_commit_state_get(h) != INACTIVE){ + if ((otherid = confirmed_commit_session_id_get(h)) != 0){ + cprintf(cbx, "%u", otherid); + if (netconf_lock_denied(cbret, cbuf_get(cbx), + "Operation failed, another session has an ongoing confirmed commit") < 0) + goto done; + goto ok; + } } if (xmldb_lock(h, db, id) < 0) - goto done; + goto done; /* user callback */ if (clixon_plugin_lockdb_all(h, db, 1, id) < 0) - goto done; + goto done; cprintf(cbret, "", NETCONF_BASE_NAMESPACE); ok: retval = 0; done: if (cbx) - cbuf_free(cbx); + cbuf_free(cbx); return retval; } @@ -720,10 +800,10 @@ from_client_lock(clicon_handle h, */ static int from_client_unlock(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; struct client_entry *ce = (struct client_entry *)arg; @@ -733,19 +813,19 @@ from_client_unlock(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) - goto done; - goto ok; + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; + goto ok; } if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(db) < 0){ - cprintf(cbx, "No such database: %s", db); - if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) - goto done; - goto ok; + cprintf(cbx, "No such database: %s", db); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; + goto ok; } iddb = xmldb_islocked(h, db); /* @@ -754,33 +834,33 @@ from_client_unlock(clicon_handle h, * 1) the specified lock is not currently active */ if (iddb == 0){ - cprintf(cbx, "0"); - if (netconf_lock_denied(cbret, cbuf_get(cbx), "Unlock failed, lock is not currently active") < 0) - goto done; - goto ok; + cprintf(cbx, "0"); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Unlock failed, lock is not currently active") < 0) + goto done; + goto ok; } /* 2) the session issuing the operation is not the same * session that obtained the lock */ else if (iddb != id){ - cprintf(cbx, "%u", iddb); - if (netconf_lock_denied(cbret, cbuf_get(cbx), "Unlock failed, lock held by other session") < 0) - goto done; - goto ok; + cprintf(cbx, "%u", iddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Unlock failed, lock held by other session") < 0) + goto done; + goto ok; } else{ - xmldb_unlock(h, db); - /* user callback */ - if (clixon_plugin_lockdb_all(h, db, 0, id) < 0) - goto done; - if (cprintf(cbret, "", NETCONF_BASE_NAMESPACE) < 0) - goto done; + xmldb_unlock(h, db); + /* user callback */ + if (clixon_plugin_lockdb_all(h, db, 0, id) < 0) + goto done; + if (cprintf(cbret, "", NETCONF_BASE_NAMESPACE) < 0) + goto done; } ok: retval = 0; done: if (cbx) - cbuf_free(cbx); + cbuf_free(cbx); return retval; } @@ -795,16 +875,16 @@ from_client_unlock(clicon_handle h, */ static int from_client_close_session(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { struct client_entry *ce = (struct client_entry *)arg; uint32_t id = ce->ce_id; if (release_all_dbs(h, id) < 0) - return -1; + return -1; stream_ss_delete_all(h, ce_event_cb, (void*)ce); cprintf(cbret, "", NETCONF_BASE_NAMESPACE); return 0; @@ -822,10 +902,10 @@ from_client_close_session(clicon_handle h, */ static int from_client_kill_session(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; uint32_t id; /* session id */ @@ -837,33 +917,33 @@ from_client_kill_session(clicon_handle h, char *reason = NULL; if ((x = xml_find(xe, "session-id")) == NULL || - (str = xml_find_value(x, "body")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) - goto done; - goto ok; + (str = xml_find_value(x, "body")) == NULL){ + if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) + goto done; + goto ok; } if ((ret = netconf_parse_uint32("session-id", str, NULL, 0, cbret, &id)) < 0) - goto done; + goto done; if (ret == 0) - goto ok; + goto ok; /* may or may not be in active client list, probably not */ if ((ce = ce_find_byid(backend_client_list(h), id)) != NULL){ - if (release_all_dbs(h, id) < 0) - goto done; - backend_client_rm(h, ce); /* Removes client struct */ + if (release_all_dbs(h, id) < 0) + goto done; + backend_client_rm(h, ce); /* Removes client struct */ } if (xmldb_islocked(h, db) == id){ - xmldb_unlock(h, db); - /* user callback */ - if (clixon_plugin_lockdb_all(h, db, 0, id) < 0) - goto done; + xmldb_unlock(h, db); + /* user callback */ + if (clixon_plugin_lockdb_all(h, db, 0, id) < 0) + goto done; } cprintf(cbret, "", NETCONF_BASE_NAMESPACE); ok: retval = 0; done: if (reason) - free(reason); + free(reason); return retval; } @@ -886,10 +966,10 @@ from_client_kill_session(clicon_handle h, */ static int from_client_create_subscription(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; struct client_entry *ce = (struct client_entry *)arg; @@ -906,47 +986,47 @@ from_client_create_subscription(clicon_handle h, /* XXX should use prefix cf edit_config */ if ((nsc = xml_nsctx_init(NULL, EVENT_RFC5277_NAMESPACE)) == NULL) - goto done; + goto done; if ((x = xpath_first(xe, nsc, "//stream")) != NULL) - stream = xml_find_value(x, "body"); + stream = xml_find_value(x, "body"); if ((x = xpath_first(xe, nsc, "//stopTime")) != NULL){ - if ((stoptime = xml_find_value(x, "body")) != NULL && - str2time(stoptime, &stop) < 0){ - if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0) - goto done; - goto ok; - } + if ((stoptime = xml_find_value(x, "body")) != NULL && + str2time(stoptime, &stop) < 0){ + if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0) + goto done; + goto ok; + } } if ((x = xpath_first(xe, nsc, "//startTime")) != NULL){ - if ((starttime = xml_find_value(x, "body")) != NULL && - str2time(starttime, &start) < 0){ - if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0) - goto done; - goto ok; - } + if ((starttime = xml_find_value(x, "body")) != NULL && + str2time(starttime, &start) < 0){ + if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0) + goto done; + goto ok; + } } if ((xfilter = xpath_first(xe, nsc, "//filter")) != NULL){ - if ((ftype = xml_find_value(xfilter, "type")) != NULL){ - /* Only accept xpath as filter type */ - if (strcmp(ftype, "xpath") != 0){ - if (netconf_operation_failed(cbret, "application", "Only xpath filter type supported")< 0) - goto done; - goto ok; - } - if ((selector = xml_find_value(xfilter, "select")) == NULL) - goto done; - } + if ((ftype = xml_find_value(xfilter, "type")) != NULL){ + /* Only accept xpath as filter type */ + if (strcmp(ftype, "xpath") != 0){ + if (netconf_operation_failed(cbret, "application", "Only xpath filter type supported")< 0) + goto done; + goto ok; + } + if ((selector = xml_find_value(xfilter, "select")) == NULL) + goto done; + } } if ((stream_find(h, stream)) == NULL){ - if (netconf_invalid_value(cbret, "application", "No such stream") < 0) - goto done; - goto ok; + if (netconf_invalid_value(cbret, "application", "No such stream") < 0) + goto done; + goto ok; } /* Add subscriber to stream - to make notifications for this client */ if (stream_ss_add(h, stream, selector, - starttime?&start:NULL, stoptime?&stop:NULL, - ce_event_cb, (void*)ce) < 0) - goto done; + starttime?&start:NULL, stoptime?&stop:NULL, + ce_event_cb, (void*)ce) < 0) + goto done; /* Replay of this stream to specific subscription according to start and * stop (if present). * RFC 5277: If is not present, this is not a replay @@ -954,15 +1034,15 @@ from_client_create_subscription(clicon_handle h, * Schedule the replay to occur right after this RPC completes, eg "now" */ if (starttime){ - if (stream_replay_trigger(h, stream, ce_event_cb, (void*)ce) < 0) - goto done; + if (stream_replay_trigger(h, stream, ce_event_cb, (void*)ce) < 0) + goto done; } cprintf(cbret, "", NETCONF_BASE_NAMESPACE); ok: retval = 0; done: if (nsc) - xml_nsctx_free(nsc); + xml_nsctx_free(nsc); return retval; } @@ -977,19 +1057,19 @@ from_client_create_subscription(clicon_handle h, */ static int from_client_debug(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; uint32_t level; char *valstr; if ((valstr = xml_find_body(xe, "level")) == NULL){ - if (netconf_missing_element(cbret, "application", "level", NULL) < 0) - goto done; - goto ok; + if (netconf_missing_element(cbret, "application", "level", NULL) < 0) + goto done; + goto ok; } level = atoi(valstr); @@ -1014,10 +1094,10 @@ from_client_debug(clicon_handle h, */ static int from_client_ping(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { cprintf(cbret, "", NETCONF_BASE_NAMESPACE); return 0; @@ -1034,10 +1114,10 @@ from_client_ping(clicon_handle h, */ static int from_client_stats(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; uint64_t nr; @@ -1054,25 +1134,25 @@ from_client_stats(clicon_handle h, cprintf(cbret, "%" PRIu64 "", nr); cprintf(cbret, ""); if (clixon_stats_datastore_get(h, "running", cbret) < 0) - goto done; + goto done; if (clixon_stats_datastore_get(h, "candidate", cbret) < 0) - goto done; + goto done; if (clixon_stats_datastore_get(h, "startup", cbret) < 0) - goto done; + goto done; ym = NULL; while ((ym = yn_each(clicon_config_yang(h), ym)) != NULL) { - if (clixon_stats_module_get(h, ym, cbret) < 0) - goto done; + if (clixon_stats_module_get(h, ym, cbret) < 0) + goto done; } ym = NULL; while ((ym = yn_each(clicon_dbspec_yang(h), ym)) != NULL) { - if (clixon_stats_module_get(h, ym, cbret) < 0) - goto done; + if (clixon_stats_module_get(h, ym, cbret) < 0) + goto done; } ym = NULL; while ((ym = yn_each(clicon_nacm_ext_yang(h), ym)) != NULL) { - if (clixon_stats_module_get(h, ym, cbret) < 0) - goto done; + if (clixon_stats_module_get(h, ym, cbret) < 0) + goto done; } cprintf(cbret, ""); retval = 0; @@ -1091,10 +1171,10 @@ from_client_stats(clicon_handle h, */ static int from_client_restart_plugin(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; char *name; @@ -1105,25 +1185,25 @@ from_client_restart_plugin(clicon_handle h, int ret; if (xpath_vec(xe, NULL, "plugin", &vec, &veclen) < 0) - goto done; + goto done; for (i=0; i", NETCONF_BASE_NAMESPACE); ok: retval = 0; done: if (vec) - free(vec); + free(vec); return retval; } @@ -1138,10 +1218,10 @@ from_client_restart_plugin(clicon_handle h, */ static int from_client_process_control(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; cxobj *x; @@ -1150,21 +1230,21 @@ from_client_process_control(clicon_handle h, proc_operation op = PROC_OP_NONE; if ((x = xml_find_type(xe, NULL, "name", CX_ELMNT)) != NULL) - name = xml_body(x); + name = xml_body(x); if ((x = xml_find_type(xe, NULL, "operation", CX_ELMNT)) != NULL){ - opstr = xml_body(x); - op = clixon_process_op_str2int(opstr); + opstr = xml_body(x); + op = clixon_process_op_str2int(opstr); } /* Make the actual process operation (with wrap function enabled) */ if (op == PROC_OP_STATUS){ - if (clixon_process_status(h, name, cbret) < 0) - goto done; + if (clixon_process_status(h, name, cbret) < 0) + goto done; } else{ - if (clixon_process_operation(h, name, op, 1) < 0) - goto done; - cprintf(cbret, "", - NETCONF_BASE_NAMESPACE, CLIXON_LIB_NS); + if (clixon_process_operation(h, name, op, 1) < 0) + goto done; + cprintf(cbret, "", + NETCONF_BASE_NAMESPACE, CLIXON_LIB_NS); } retval = 0; done: @@ -1177,21 +1257,21 @@ from_client_process_control(clicon_handle h, */ static int from_client_hello(clicon_handle h, - cxobj *x, - struct client_entry *ce, - cbuf *cbret) + cxobj *x, + struct client_entry *ce, + cbuf *cbret) { int retval = -1; uint32_t id; if (clicon_session_id_get(h, &id) < 0){ - clicon_err(OE_NETCONF, ENOENT, "session_id not set"); - goto done; + clicon_err(OE_NETCONF, ENOENT, "session_id not set"); + goto done; } id++; clicon_session_id_set(h, id); cprintf(cbret, "%u", - NETCONF_BASE_NAMESPACE, id); + NETCONF_BASE_NAMESPACE, id); retval = 0; done: return retval; @@ -1207,8 +1287,8 @@ from_client_hello(clicon_handle h, */ static int from_client_msg(clicon_handle h, - struct client_entry *ce, - struct clicon_msg *msg) + struct client_entry *ce, + struct clicon_msg *msg) { int retval = -1; cxobj *xt = NULL; @@ -1237,191 +1317,191 @@ from_client_msg(clicon_handle h, * as wither rpc-error or by positive response. */ if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; } /* Decode msg from client -> xml top (ct) and session id * Bind is a part of the decode function */ if ((ret = clicon_msg_decode(msg, yspec, &id, &xt, &xret)) < 0){ - if (netconf_malformed_message(cbret, "XML parse error") < 0) - goto done; - goto reply; + if (netconf_malformed_message(cbret, "XML parse error") < 0) + goto done; + goto reply; } if (ret == 0){ - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - goto reply; + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + goto reply; } /* Check for empty frame (no mesaages), return empty message, not clear from RFC what to do */ if (xml_child_nr_type(xt, CX_ELMNT) == 0){ - if (netconf_malformed_message(cbret, "Empty message in netconf rpc frame")< 0) - goto done; - goto reply; + if (netconf_malformed_message(cbret, "Empty message in netconf rpc frame")< 0) + goto done; + goto reply; } /* Check for multi-messages in frame */ if (xml_child_nr_type(xt, CX_ELMNT) != 1){ - if (netconf_malformed_message(cbret, "More than one message in netconf rpc frame")< 0) - goto done; - goto reply; + if (netconf_malformed_message(cbret, "More than one message in netconf rpc frame")< 0) + goto done; + goto reply; } if ((x = xml_child_i_type(xt, 0, CX_ELMNT)) == NULL){ /* Shouldnt happen */ - clicon_err(OE_XML, EFAULT, "No xml req (shouldnt happen)"); - goto done; + clicon_err(OE_XML, EFAULT, "No xml req (shouldnt happen)"); + goto done; } rpcname = xml_name(x); rpcprefix = xml_prefix(x); /* Note that this validation is also made in xml_yang_validate_rpc, but not for hello */ if (xml2ns(x, rpcprefix, &namespace) < 0) - goto done; + goto done; /* Only accept resolved NETCONF base namespace */ if (namespace == NULL){ - if (netconf_bad_element(cbret, "protocol", rpcname, "No namespace associated with prefix") < 0) - goto done; - goto reply; + if (netconf_bad_element(cbret, "protocol", rpcname, "No namespace associated with prefix") < 0) + goto done; + goto reply; } else if (strcmp(namespace, NETCONF_BASE_NAMESPACE) != 0){ - cbuf *cbmsg = NULL; - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbmsg, "No appropriate namespace found for: %s %s", rpcprefix, rpcname); - if (netconf_unknown_namespace(cbret, "protocol", namespace, cbuf_get(cbmsg)) < 0) - goto done; - cbuf_free(cbmsg); - goto reply; + cbuf *cbmsg = NULL; + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "No appropriate namespace found for: %s %s", rpcprefix, rpcname); + if (netconf_unknown_namespace(cbret, "protocol", namespace, cbuf_get(cbmsg)) < 0) + goto done; + cbuf_free(cbmsg); + goto reply; } if (strcmp(rpcname, "rpc") == 0){ - ; /* continue below */ + ; /* continue below */ } else if (strcmp(rpcname, "hello") == 0){ - if ((ret = from_client_hello(h, x, ce, cbret)) <0) - goto done; - goto reply; + if ((ret = from_client_hello(h, x, ce, cbret)) <0) + goto done; + goto reply; } else{ - if (netconf_unknown_element(cbret, "protocol", rpcname, "Unrecognized netconf operation")< 0) - goto done; - goto reply; + if (netconf_unknown_element(cbret, "protocol", rpcname, "Unrecognized netconf operation")< 0) + goto done; + goto reply; } ce->ce_id = id; if ((ret = xml_yang_validate_rpc(h, x, &xret)) < 0) - goto done; + goto done; if (ret == 0){ - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - goto reply; + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + goto reply; } xe = NULL; username = xml_find_value(x, "username"); /* May be used by callbacks, etc */ clicon_username_set(h, username); while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { - rpc = xml_name(xe); - if ((ye = xml_spec(xe)) == NULL){ - if (netconf_operation_not_supported(cbret, "protocol", rpc) < 0) - goto done; - goto reply; - } - if ((ymod = ys_module(ye)) == NULL){ - clicon_err(OE_XML, ENOENT, "rpc yang does not have module"); - goto done; - } - module = yang_argument_get(ymod); - clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc); - /* Pre-NACM access step */ - xnacm = NULL; + rpc = xml_name(xe); + if ((ye = xml_spec(xe)) == NULL){ + if (netconf_operation_not_supported(cbret, "protocol", rpc) < 0) + goto done; + goto reply; + } + if ((ymod = ys_module(ye)) == NULL){ + clicon_err(OE_XML, ENOENT, "rpc yang does not have module"); + goto done; + } + module = yang_argument_get(ymod); + clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc); + /* Pre-NACM access step */ + xnacm = NULL; - /* NACM intial pre- access control enforcements. Retval: - * 0: Use NACM validation and xnacm is set. - * 1: Permit, skip NACM - * Therefore, xnacm=NULL means no NACM checks needed. - */ - if ((ret = nacm_access_pre(h, ce->ce_username, username, &xnacm)) < 0) - goto done; - /* Cache XML NACM tree here. Use with caution, only valid on from_client_msg stack - */ - if (clicon_nacm_cache_set(h, xnacm) < 0) - goto done; - if (ret == 0){ /* Do NACM RPC validation */ - creds = clicon_nacm_credentials(h); - if ((ret = verify_nacm_user(h, creds, ce->ce_username, username, cbret)) < 0) - goto done; - if (ret == 0) /* credentials fail */ - goto reply; - /* NACM rpc operation exec validation */ - if ((ret = nacm_rpc(rpc, module, username, xnacm, cbret)) < 0) - goto done; - if (ret == 0) /* Not permitted and cbret set */ - goto reply; - } - clicon_err_reset(); - if ((ret = rpc_callback_call(h, xe, ce, &nr, cbret)) < 0){ - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; - clicon_log(LOG_NOTICE, "%s Error in rpc_callback_call:%s", __FUNCTION__, xml_name(xe)); - goto reply; /* Dont quit here on user callbacks */ - } - if (ret == 0) - goto reply; - if (nr == 0){ /* not handled by callback */ - if (netconf_operation_not_supported(cbret, "application", "RPC operation not supported")< 0) - goto done; - goto reply; - } - if (xnacm){ - xml_free(xnacm); - xnacm = NULL; - if (clicon_nacm_cache_set(h, NULL) < 0) - goto done; - } + /* NACM intial pre- access control enforcements. Retval: + * 0: Use NACM validation and xnacm is set. + * 1: Permit, skip NACM + * Therefore, xnacm=NULL means no NACM checks needed. + */ + if ((ret = nacm_access_pre(h, ce->ce_username, username, &xnacm)) < 0) + goto done; + /* Cache XML NACM tree here. Use with caution, only valid on from_client_msg stack + */ + if (clicon_nacm_cache_set(h, xnacm) < 0) + goto done; + if (ret == 0){ /* Do NACM RPC validation */ + creds = clicon_nacm_credentials(h); + if ((ret = verify_nacm_user(h, creds, ce->ce_username, username, cbret)) < 0) + goto done; + if (ret == 0) /* credentials fail */ + goto reply; + /* NACM rpc operation exec validation */ + if ((ret = nacm_rpc(rpc, module, username, xnacm, cbret)) < 0) + goto done; + if (ret == 0) /* Not permitted and cbret set */ + goto reply; + } + clicon_err_reset(); + if ((ret = rpc_callback_call(h, xe, ce, &nr, cbret)) < 0){ + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + clicon_log(LOG_NOTICE, "%s Error in rpc_callback_call:%s", __FUNCTION__, xml_name(xe)); + goto reply; /* Dont quit here on user callbacks */ + } + if (ret == 0) + goto reply; + if (nr == 0){ /* not handled by callback */ + if (netconf_operation_not_supported(cbret, "application", "RPC operation not supported")< 0) + goto done; + goto reply; + } + if (xnacm){ + xml_free(xnacm); + xnacm = NULL; + if (clicon_nacm_cache_set(h, NULL) < 0) + goto done; + } } /* while */ reply: if (cbuf_len(cbret) == 0) - if (netconf_operation_failed(cbret, "application", clicon_errno?clicon_err_reason:"unknown")< 0) - goto done; + if (netconf_operation_failed(cbret, "application", clicon_errno?clicon_err_reason:"unknown")< 0) + goto done; clicon_debug(1, "%s cbret:%s", __FUNCTION__, cbuf_get(cbret)); /* XXX problem here is that cbret has not been parsed so may contain parse errors */ if (send_msg_reply(ce->ce_s, cbuf_get(cbret), cbuf_len(cbret)+1) < 0){ - switch (errno){ - case EPIPE: - /* man (2) write: - * EPIPE fd is connected to a pipe or socket whose reading end is - * closed. When this happens the writing process will also receive - * a SIGPIPE signal. - * In Clixon this means a client, eg restconf, netconf or cli closes - * the (UNIX domain) socket. - */ - case ECONNRESET: - clicon_log(LOG_WARNING, "client rpc reset"); - break; - default: - goto done; - } + switch (errno){ + case EPIPE: + /* man (2) write: + * EPIPE fd is connected to a pipe or socket whose reading end is + * closed. When this happens the writing process will also receive + * a SIGPIPE signal. + * In Clixon this means a client, eg restconf, netconf or cli closes + * the (UNIX domain) socket. + */ + case ECONNRESET: + clicon_log(LOG_WARNING, "client rpc reset"); + break; + default: + goto done; + } } // ok: retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xnacm){ - xml_free(xnacm); - if (clicon_nacm_cache_set(h, NULL) < 0) - goto done; + xml_free(xnacm); + if (clicon_nacm_cache_set(h, NULL) < 0) + goto done; } if (xret) - xml_free(xret); + xml_free(xret); if (xt) - xml_free(xt); + xml_free(xt); if (cbret) - cbuf_free(cbret); + cbuf_free(cbret); /* Sanity: log if clicon_err() is not called ! */ if (retval < 0 && clicon_errno < 0) - clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on RPC error (message: %s)", - __FUNCTION__, rpc?rpc:""); + clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on RPC error (message: %s)", + __FUNCTION__, rpc?rpc:""); // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval;// -1 here terminates backend } @@ -1435,7 +1515,7 @@ from_client_msg(clicon_handle h, */ int from_client(int s, - void* arg) + void* arg) { int retval = -1; struct clicon_msg *msg = NULL; @@ -1445,21 +1525,21 @@ from_client(int s, clicon_debug(1, "%s", __FUNCTION__); if (s != ce->ce_s){ - clicon_err(OE_NETCONF, EINVAL, "Internal error: s != ce->ce_s"); - goto done; + clicon_err(OE_NETCONF, EINVAL, "Internal error: s != ce->ce_s"); + goto done; } if (clicon_msg_rcv(ce->ce_s, &msg, &eof) < 0) - goto done; + goto done; if (eof) - backend_client_rm(h, ce); + backend_client_rm(h, ce); else - if (from_client_msg(h, ce, msg) < 0) - goto done; + if (from_client_msg(h, ce, msg) < 0) + goto done; retval = 0; done: clicon_debug(1, "%s retval=%d", __FUNCTION__, retval); if (msg) - free(msg); + free(msg); return retval; /* -1 here terminates backend */ } @@ -1476,70 +1556,70 @@ backend_rpc_init(clicon_handle h) /* In backend_client.? RFC 6241 */ if (rpc_callback_register(h, from_client_get_config, NULL, - NETCONF_BASE_NAMESPACE, "get-config") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "get-config") < 0) + goto done; if (rpc_callback_register(h, from_client_edit_config, NULL, - NETCONF_BASE_NAMESPACE, "edit-config") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "edit-config") < 0) + goto done; if (rpc_callback_register(h, from_client_copy_config, NULL, - NETCONF_BASE_NAMESPACE, "copy-config") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "copy-config") < 0) + goto done; if (rpc_callback_register(h, from_client_delete_config, NULL, - NETCONF_BASE_NAMESPACE, "delete-config") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "delete-config") < 0) + goto done; if (rpc_callback_register(h, from_client_lock, NULL, - NETCONF_BASE_NAMESPACE, "lock") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "lock") < 0) + goto done; if (rpc_callback_register(h, from_client_unlock, NULL, - NETCONF_BASE_NAMESPACE, "unlock") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "unlock") < 0) + goto done; if (rpc_callback_register(h, from_client_get, NULL, - NETCONF_BASE_NAMESPACE, "get") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "get") < 0) + goto done; if (rpc_callback_register(h, from_client_close_session, NULL, - NETCONF_BASE_NAMESPACE, "close-session") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "close-session") < 0) + goto done; if (rpc_callback_register(h, from_client_kill_session, NULL, - NETCONF_BASE_NAMESPACE, "kill-session") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "kill-session") < 0) + goto done; if (rpc_callback_register(h, action_callback_call, NULL, - YANG_XML_NAMESPACE, "action") < 0) - goto done; + YANG_XML_NAMESPACE, "action") < 0) + goto done; /* In backend_commit.? */ if (rpc_callback_register(h, from_client_commit, NULL, - NETCONF_BASE_NAMESPACE, "commit") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "commit") < 0) + goto done; if (rpc_callback_register(h, from_client_discard_changes, NULL, - NETCONF_BASE_NAMESPACE, "discard-changes") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "discard-changes") < 0) + goto done; /* if-feature confirmed-commit */ if (rpc_callback_register(h, from_client_cancel_commit, NULL, - NETCONF_BASE_NAMESPACE, "cancel-commit") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "cancel-commit") < 0) + goto done; /* if-feature validate */ if (rpc_callback_register(h, from_client_validate, NULL, - NETCONF_BASE_NAMESPACE, "validate") < 0) - goto done; + NETCONF_BASE_NAMESPACE, "validate") < 0) + goto done; /* In backend_client.? RPC from RFC 5277 */ if (rpc_callback_register(h, from_client_create_subscription, NULL, - EVENT_RFC5277_NAMESPACE, "create-subscription") < 0) - goto done; + EVENT_RFC5277_NAMESPACE, "create-subscription") < 0) + goto done; /* Clixon RPC */ if (rpc_callback_register(h, from_client_debug, NULL, - CLIXON_LIB_NS, "debug") < 0) - goto done; + CLIXON_LIB_NS, "debug") < 0) + goto done; if (rpc_callback_register(h, from_client_ping, NULL, - CLIXON_LIB_NS, "ping") < 0) - goto done; + CLIXON_LIB_NS, "ping") < 0) + goto done; if (rpc_callback_register(h, from_client_stats, NULL, - CLIXON_LIB_NS, "stats") < 0) - goto done; + CLIXON_LIB_NS, "stats") < 0) + goto done; if (rpc_callback_register(h, from_client_restart_plugin, NULL, - CLIXON_LIB_NS, "restart-plugin") < 0) - goto done; + CLIXON_LIB_NS, "restart-plugin") < 0) + goto done; if (rpc_callback_register(h, from_client_process_control, NULL, - CLIXON_LIB_NS, "process-control") < 0) - goto done; + CLIXON_LIB_NS, "process-control") < 0) + goto done; retval =0; done: return retval; diff --git a/apps/backend/backend_client.h b/apps/backend/backend_client.h index 8aaf2244..18251e69 100644 --- a/apps/backend/backend_client.h +++ b/apps/backend/backend_client.h @@ -51,7 +51,7 @@ struct client_entry{ int ce_nr; /* Client number (for dbg/tracing) */ int ce_stat_in; /* Nr of received msgs from client */ int ce_stat_out;/* Nr of sent msgs to client */ - int ce_id; /* Session id */ + uint32_t ce_id; /* Session id, accessor functions: clicon_session_id_get/set */ char *ce_username;/* Translated from peer user cred */ clicon_handle ce_handle; /* clicon config handle (all clients have same?) */ }; diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 969ba75f..7af3470e 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -32,7 +32,7 @@ the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** - + Commit and validate */ #ifdef HAVE_CONFIG_H @@ -89,9 +89,9 @@ */ static int generic_validate(clicon_handle h, - yang_stmt *yspec, - transaction_data_t *td, - cxobj **xret) + yang_stmt *yspec, + transaction_data_t *td, + cxobj **xret) { int retval = -1; cxobj *x2; @@ -101,31 +101,31 @@ generic_validate(clicon_handle h, /* All entries */ if ((ret = xml_yang_validate_all_top(h, td->td_target, xret)) < 0) - goto done; + goto done; if (ret == 0) - goto fail; + goto fail; /* changed entries */ for (i=0; itd_clen; i++){ - x2 = td->td_tcvec[i]; /* target changed */ - /* Should this be recursive? */ - if ((ret = xml_yang_validate_add(h, x2, xret)) < 0) - goto done; - if (ret == 0) - goto fail; + x2 = td->td_tcvec[i]; /* target changed */ + /* Should this be recursive? */ + if ((ret = xml_yang_validate_add(h, x2, xret)) < 0) + goto done; + if (ret == 0) + goto fail; } /* added entries */ for (i=0; itd_alen; i++){ - x2 = td->td_avec[i]; - if ((ret = xml_yang_validate_add(h, x2, xret)) < 0) - goto done; - if (ret == 0) - goto fail; + x2 = td->td_avec[i]; + if ((ret = xml_yang_validate_add(h, x2, xret)) < 0) + goto done; + if (ret == 0) + goto fail; } // ok: retval = 1; done: if (cb) - cbuf_free(cb); + cbuf_free(cb); return retval; fail: retval = 0; @@ -156,9 +156,9 @@ generic_validate(clicon_handle h, */ static int startup_common(clicon_handle h, - char *db, - transaction_data_t *td, - cbuf *cbret) + char *db, + transaction_data_t *td, + cbuf *cbret) { int retval = -1; yang_stmt *yspec; @@ -173,149 +173,154 @@ startup_common(clicon_handle h, * potentially non-matching module-state in msdiff */ if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE")) - if ((msdiff = modstate_diff_new()) == NULL) - goto done; + if ((msdiff = modstate_diff_new()) == NULL) + goto done; clicon_debug(1, "Reading startup config from %s", db); /* Get the startup datastore WITHOUT binding to YANG, sorting and default setting. * It is done below, later in this function */ if (clicon_option_bool(h, "CLICON_XMLDB_UPGRADE_CHECKOLD")){ - if ((ret = xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &xt, msdiff, &xerr)) < 0) - goto done; - if (ret == 0){ /* ret should not be 0 */ - /* Print upgraded db: -q backend switch for debugging/ showing upgraded config only */ - if (clicon_quit_upgrade_get(h) == 1){ - xml_print(stderr, xerr); - clicon_err(OE_XML, 0, "invalid configuration before upgrade"); - exit(0); /* This is fairly abrupt , but need to avoid side-effects of rewinding - * See similar clause below - */ - } - if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) - goto done; - goto fail; - } + if ((ret = xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &xt, msdiff, &xerr)) < 0) + goto done; + if (ret == 0){ /* ret should not be 0 */ + /* Print upgraded db: -q backend switch for debugging/ showing upgraded config only */ + if (clicon_quit_upgrade_get(h) == 1){ + xml_print(stderr, xerr); + clicon_err(OE_XML, 0, "invalid configuration before upgrade"); + exit(0); /* This is fairly abrupt , but need to avoid side-effects of rewinding + * See similar clause below + */ + } + if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) + goto done; + goto fail; + } } else { - if (xmldb_get0(h, db, YB_NONE, NULL, "/", 0, &xt, msdiff, &xerr) < 0) - goto done; + if (xmldb_get0(h, db, YB_NONE, NULL, "/", 0, &xt, msdiff, &xerr) < 0) + goto done; + } + if (msdiff && msdiff->md_status == 0){ // Possibly check for CLICON_XMLDB_MODSTATE + clicon_log(LOG_WARNING, "Modstate expected in startup datastore but not found\n" + "This may indicate that the datastore is not initialized corrrectly, such as copy/pasted.\n" + "It may also be normal bootstrapping since module state will be written on next datastore save"); } if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, 0, "Yang spec not set"); - goto done; + clicon_err(OE_YANG, 0, "Yang spec not set"); + goto done; } clicon_debug(1, "Reading startup config done"); /* Clear flags xpath for get */ xml_apply0(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, - (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); + (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); /* Here xt is old syntax */ /* General purpose datastore upgrade */ if (clixon_plugin_datastore_upgrade_all(h, db, xt, msdiff) < 0) goto done; /* Module-specific upgrade callbacks */ if (msdiff){ - if ((ret = clixon_module_upgrade(h, xt, msdiff, cbret)) < 0) - goto done; - if (ret == 0) - goto fail; + if ((ret = clixon_module_upgrade(h, xt, msdiff, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; } /* Print upgraded db: -q backend switch for debugging/ showing upgraded config only */ if (clicon_quit_upgrade_get(h) == 1){ - /* bind yang */ - if ((ret = xml_bind_yang(xt, YB_MODULE, yspec, &xret)) < 1){ - if (ret == 0){ - /* invalid */ - clicon_err(OE_XML, EFAULT, "invalid configuration"); - } - else { - /* error */ - xml_print(stderr, xret); - clicon_err(OE_XML, 0, "%s: YANG binding error", __func__); - } - } /* sort yang */ - else if (xml_sort_recurse(xt) < 0) { - clicon_err(OE_XML, EFAULT, "Yang sort error"); + /* bind yang */ + if ((ret = xml_bind_yang(xt, YB_MODULE, yspec, &xret)) < 1){ + if (ret == 0){ + /* invalid */ + clicon_err(OE_XML, EFAULT, "invalid configuration"); + } + else { + /* error */ + xml_print(stderr, xret); + clicon_err(OE_XML, 0, "%s: YANG binding error", __func__); + } + } /* sort yang */ + else if (xml_sort_recurse(xt) < 0) { + clicon_err(OE_XML, EFAULT, "Yang sort error"); } - if (xmldb_dump(h, stdout, xt) < 0) - goto done; - exit(0); /* This is fairly abrupt , but need to avoid side-effects of rewinding - stack. Alternative is to make a separate function stack for this. */ + if (xmldb_dump(h, stdout, xt) < 0) + goto done; + exit(0); /* This is fairly abrupt , but need to avoid side-effects of rewinding + stack. Alternative is to make a separate function stack for this. */ } /* If empty skip. Note upgrading can add children, so it may be empty before that. */ if (xml_child_nr(xt) == 0){ - td->td_target = xt; - xt = NULL; - goto ok; + td->td_target = xt; + xt = NULL; + goto ok; } /* After upgrading, XML tree needs to be sorted and yang spec populated */ if ((ret = xml_bind_yang(xt, YB_MODULE, yspec, &xret)) < 0) - goto done; + goto done; if (ret == 0){ - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - goto fail; + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + goto fail; } /* After upgrade check no state data */ if ((ret = xml_non_config_data(xt, &xret)) < 0) - goto done; + goto done; if (ret == 0){ - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - goto fail; + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + goto fail; } /* Sort xml */ if (xml_sort_recurse(xt) < 0) - goto done; + goto done; /* Add global defaults. */ if (xml_global_defaults(h, xt, NULL, NULL, yspec, 0) < 0) - goto done; + goto done; /* Apply default values (removed in clear function) */ if (xml_default_recurse(xt, 0) < 0) - goto done; + goto done; /* Handcraft transition with with only add tree */ td->td_target = xt; xt = NULL; x = NULL; while ((x = xml_child_each(td->td_target, x, CX_ELMNT)) != NULL){ - xml_flag_set(x, XML_FLAG_ADD); /* Also down */ - xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD); - if (cxvec_append(x, &td->td_avec, &td->td_alen) < 0) - goto done; + xml_flag_set(x, XML_FLAG_ADD); /* Also down */ + xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD); + if (cxvec_append(x, &td->td_avec, &td->td_alen) < 0) + goto done; } /* 4. Call plugin transaction start callbacks */ if (plugin_transaction_begin_all(h, td) < 0) - goto done; + goto done; /* 5. Make generic validation on all new or changed data. Note this is only call that uses 3-values */ clicon_debug(1, "Validating startup %s", db); if ((ret = generic_validate(h, yspec, td, &xret)) < 0) - goto done; + goto done; if (ret == 0){ - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - goto fail; /* STARTUP_INVALID */ + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + goto fail; /* STARTUP_INVALID */ } /* 6. Call plugin transaction validate callbacks */ if (plugin_transaction_validate_all(h, td) < 0) - goto done; + goto done; /* 7. Call plugin transaction complete callbacks */ if (plugin_transaction_complete_all(h, td) < 0) - goto done; + goto done; ok: retval = 1; done: if (xerr) - xml_free(xerr); + xml_free(xerr); if (xret) - xml_free(xret); + xml_free(xret); if (xt) - xml_free(xt); + xml_free(xt); if (msdiff) - modstate_diff_free(msdiff); + modstate_diff_free(msdiff); return retval; fail: retval = 0; @@ -334,9 +339,9 @@ startup_common(clicon_handle h, */ int startup_validate(clicon_handle h, - char *db, - cxobj **xtr, - cbuf *cbret) + char *db, + cxobj **xtr, + cbuf *cbret) { int retval = -1; int ret; @@ -344,28 +349,28 @@ startup_validate(clicon_handle h, /* Handcraft a transition with only target and add trees */ if ((td = transaction_new()) == NULL) - goto done; + goto done; if ((ret = startup_common(h, db, td, cbret)) < 0){ - plugin_transaction_abort_all(h, td); - goto done; + plugin_transaction_abort_all(h, td); + goto done; } if (ret == 0){ - plugin_transaction_abort_all(h, td); - goto fail; + plugin_transaction_abort_all(h, td); + goto fail; } plugin_transaction_end_all(h, td); /* Clear cached trees from default values and marking */ if (xmldb_get0_clear(h, td->td_target) < 0) - goto done; + goto done; if (xtr){ - *xtr = td->td_target; - td->td_target = NULL; + *xtr = td->td_target; + td->td_target = NULL; } retval = 1; done: if (td){ - xmldb_get0_free(h, &td->td_target); - transaction_free(td); + xmldb_get0_free(h, &td->td_target); + transaction_free(td); } return retval; fail: /* cbret should be set */ @@ -385,64 +390,64 @@ startup_validate(clicon_handle h, */ int startup_commit(clicon_handle h, - char *db, - cbuf *cbret) + char *db, + cbuf *cbret) { int retval = -1; int ret; transaction_data_t *td = NULL; if (strcmp(db,"running")==0){ - clicon_err(OE_FATAL, 0, "Invalid startup db: %s", db); - goto done; + clicon_err(OE_FATAL, 0, "Invalid startup db: %s", db); + goto done; } /* Handcraft a transition with only target and add trees */ if ((td = transaction_new()) == NULL) - goto done; + goto done; if ((ret = startup_common(h, db, td, cbret)) < 0) - goto done; + goto done; if (ret == 0) - goto fail; + goto fail; /* 8. Call plugin transaction commit callbacks */ if (plugin_transaction_commit_all(h, td) < 0) - goto done; + goto done; /* After commit, make a post-commit call (sure that all plugins have committed) */ if (plugin_transaction_commit_done_all(h, td) < 0) - goto done; + goto done; /* Clear cached trees from default values and marking */ if (xmldb_get0_clear(h, td->td_target) < 0) - goto done; + goto done; /* [Delete and] create running db */ if (xmldb_exists(h, "running") == 1){ - if (xmldb_delete(h, "running") != 0 && errno != ENOENT) - goto done;; + if (xmldb_delete(h, "running") != 0 && errno != ENOENT) + goto done;; } if (xmldb_create(h, "running") < 0) - goto done; + goto done; /* 9, write (potentially modified) tree to running * XXX note here startup is copied to candidate, which may confuse everything * XXX default values are overwritten */ if (td->td_target) - /* target is datastore, but is here transformed to mimic an incoming - * edit-config - */ - xml_name_set(td->td_target, NETCONF_INPUT_CONFIG); + /* target is datastore, but is here transformed to mimic an incoming + * edit-config + */ + xml_name_set(td->td_target, NETCONF_INPUT_CONFIG); if ((ret = xmldb_put(h, "running", OP_REPLACE, td->td_target, - clicon_username_get(h), cbret)) < 0) - goto done; + clicon_username_get(h), cbret)) < 0) + goto done; if (ret == 0) - goto fail; + goto fail; /* 10. Call plugin transaction end callbacks */ plugin_transaction_end_all(h, td); retval = 1; done: if (td){ - if (retval < 1) - plugin_transaction_abort_all(h, td); - xmldb_get0_free(h, &td->td_target); - transaction_free(td); + if (retval < 1) + plugin_transaction_abort_all(h, td); + xmldb_get0_free(h, &td->td_target); + transaction_free(td); } return retval; fail: /* cbret should be set */ @@ -465,9 +470,9 @@ startup_commit(clicon_handle h, */ static int validate_common(clicon_handle h, - char *db, - transaction_data_t *td, - cxobj **xret) + char *db, + transaction_data_t *td, + cxobj **xret) { int retval = -1; yang_stmt *yspec; @@ -476,79 +481,79 @@ validate_common(clicon_handle h, int ret; if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_FATAL, 0, "No DB_SPEC"); - goto done; - } + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } /* This is the state we are going to */ if ((ret = xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, xret)) < 0) - goto done; + goto done; if (ret == 0) - goto fail; + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_target, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, - (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); + (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); /* 2. Parse xml trees * This is the state we are going from */ if ((ret = xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, xret)) < 0) - goto done; + goto done; if (ret == 0) - goto fail; + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, - (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); + (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); /* 3. Compute differences */ if (xml_diff(yspec, - td->td_src, - td->td_target, - &td->td_dvec, /* removed: only in running */ - &td->td_dlen, - &td->td_avec, /* added: only in candidate */ - &td->td_alen, - &td->td_scvec, /* changed: original values */ - &td->td_tcvec, /* changed: wanted values */ - &td->td_clen) < 0) - goto done; + td->td_src, + td->td_target, + &td->td_dvec, /* removed: only in running */ + &td->td_dlen, + &td->td_avec, /* added: only in candidate */ + &td->td_alen, + &td->td_scvec, /* changed: original values */ + &td->td_tcvec, /* changed: wanted values */ + &td->td_clen) < 0) + goto done; if (clicon_debug_get()>1) - transaction_print(stderr, td); + transaction_print(stderr, td); /* Mark as changed in tree */ for (i=0; itd_dlen; i++){ /* Also down */ - xn = td->td_dvec[i]; - xml_flag_set(xn, XML_FLAG_DEL); - xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_DEL); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + xn = td->td_dvec[i]; + xml_flag_set(xn, XML_FLAG_DEL); + xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_DEL); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); } for (i=0; itd_alen; i++){ /* Also down */ - xn = td->td_avec[i]; - xml_flag_set(xn, XML_FLAG_ADD); - xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + xn = td->td_avec[i]; + xml_flag_set(xn, XML_FLAG_ADD); + xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); } for (i=0; itd_clen; i++){ /* Also up */ - xn = td->td_scvec[i]; - xml_flag_set(xn, XML_FLAG_CHANGE); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); - xn = td->td_tcvec[i]; - xml_flag_set(xn, XML_FLAG_CHANGE); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + xn = td->td_scvec[i]; + xml_flag_set(xn, XML_FLAG_CHANGE); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + xn = td->td_tcvec[i]; + xml_flag_set(xn, XML_FLAG_CHANGE); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); } /* 4. Call plugin transaction start callbacks */ if (plugin_transaction_begin_all(h, td) < 0) - goto done; + goto done; /* 5. Make generic validation on all new or changed data. Note this is only call that uses 3-values */ if ((ret = generic_validate(h, yspec, td, xret)) < 0) - goto done; + goto done; if (ret == 0) - goto fail; + goto fail; /* 6. Call plugin transaction validate callbacks */ if (plugin_transaction_validate_all(h, td) < 0) - goto done; + goto done; /* 7. Call plugin transaction complete callbacks */ if (plugin_transaction_complete_all(h, td) < 0) - goto done; + goto done; retval = 1; done: return retval; @@ -567,8 +572,8 @@ validate_common(clicon_handle h, */ int candidate_validate(clicon_handle h, - char *db, - cbuf *cbret) + char *db, + cbuf *cbret) { int retval = -1; transaction_data_t *td = NULL; @@ -577,51 +582,51 @@ candidate_validate(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); if (db == NULL || cbret == NULL){ - clicon_err(OE_CFG, EINVAL, "db or cbret is NULL"); - goto done; + clicon_err(OE_CFG, EINVAL, "db or cbret is NULL"); + goto done; } /* 1. Start transaction */ if ((td = transaction_new()) == NULL) - goto done; + goto done; /* Common steps (with commit) */ if ((ret = validate_common(h, db, td, &xret)) < 0){ - /* A little complex due to several sources of validation fails or errors. - * (1) xerr is set -> translate to cbret; (2) cbret set use that; otherwise - * use clicon_err. - * TODO: -1 return should be fatal error, not failed validation - */ - if (!cbuf_len(cbret) && - netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; - goto fail; + /* A little complex due to several sources of validation fails or errors. + * (1) xerr is set -> translate to cbret; (2) cbret set use that; otherwise + * use clicon_err. + * TODO: -1 return should be fatal error, not failed validation + */ + if (!cbuf_len(cbret) && + netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto fail; } if (ret == 0){ - if (xret == NULL){ - clicon_err(OE_CFG, EINVAL, "xret is NULL"); - goto done; - } - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - if (!cbuf_len(cbret) && - netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; - goto fail; + if (xret == NULL){ + clicon_err(OE_CFG, EINVAL, "xret is NULL"); + goto done; + } + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + if (!cbuf_len(cbret) && + netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto fail; } if (xmldb_get0_clear(h, td->td_src) < 0 || - xmldb_get0_clear(h, td->td_target) < 0) - goto done; + xmldb_get0_clear(h, td->td_target) < 0) + goto done; plugin_transaction_end_all(h, td); retval = 1; done: if (xret) - xml_free(xret); + xml_free(xret); if (td){ - if (retval < 1) - plugin_transaction_abort_all(h, td); - xmldb_get0_free(h, &td->td_target); - xmldb_get0_free(h, &td->td_src); - transaction_free(td); + if (retval < 1) + plugin_transaction_abort_all(h, td); + xmldb_get0_free(h, &td->td_target); + xmldb_get0_free(h, &td->td_src); + transaction_free(td); } return retval; fail: @@ -629,69 +634,97 @@ candidate_validate(clicon_handle h, goto done; } + /*! Do a diff between candidate and running, then start a commit transaction * * The code reverts changes if the commit fails. But if the revert * fails, we just ignore the errors and proceed. Maybe we should * do something more drastic? - * @param[in] h Clicon handle - * @param[in] db A candidate database, not necessarily "candidate" - * @retval -1 Error - or validation failed - * @retval 0 Validation failed (with cbret set) - * @retval 1 Validation OK + * @param[in] h Clicon handle + * @param[in] xe Request: (or NULL) + * @param[in] session_id Client session id, only if xe + * @param[in] db A candidate database, not necessarily "candidate" + * @param[out] cbret Return xml tree, eg ..., td_target) < 0) - goto done; + goto done; if (xmldb_get0_clear(h, td->td_src) < 0) - goto done; + goto done; /* 8. Success: Copy candidate to running */ if (xmldb_copy(h, db, "running") < 0) - goto done; + goto done; xmldb_modified_set(h, db, 0); /* reset dirty bit */ /* Here pointers to old (source) tree are obsolete */ if (td->td_dvec){ - td->td_dlen = 0; - free(td->td_dvec); - td->td_dvec = NULL; + td->td_dlen = 0; + free(td->td_dvec); + td->td_dvec = NULL; } if (td->td_scvec){ - free(td->td_scvec); - td->td_scvec = NULL; + free(td->td_scvec); + td->td_scvec = NULL; } /* 9. Call plugin transaction end callbacks */ @@ -701,20 +734,20 @@ candidate_commit(clicon_handle h, done: /* In case of failure (or error), call plugin transaction termination callbacks */ if (td){ - if (retval < 1) - plugin_transaction_abort_all(h, td); - xmldb_get0_free(h, &td->td_target); - xmldb_get0_free(h, &td->td_src); - transaction_free(td); + if (retval < 1) + plugin_transaction_abort_all(h, td); + xmldb_get0_free(h, &td->td_target); + xmldb_get0_free(h, &td->td_src); + transaction_free(td); } if (xret) - xml_free(xret); + xml_free(xret); return retval; fail: retval = 0; goto done; } - + /*! Commit the candidate configuration as the device's new current configuration * * @param[in] h Clicon handle @@ -728,13 +761,26 @@ candidate_commit(clicon_handle h, * configuration datastore that are actually different and only check * "create", "update", and "delete" access permissions for this set of * nodes, which could be empty. + * + * Handling of the first phase of confirmed-commit: + * First, it must be determined if the given RPC constitutes a "confirming-commit", roughly meaning: + * 1) it was issued in the same session as a prior confirmed-commit + * 2) it bears a element matching the element that accompanied the prior confirmed-commit + * + * If it is a valid "confirming-commit" and this RPC does not bear another element, then the + * confirmed-commit is complete, the rollback event can be cancelled and the rollback database deleted. + * + * No further action is necessary as the candidate configuration was already copied to the running configuration. + * + * If the RPC does bear another element, that will be handled in phase two, from within the + * candidate_commit() method. */ int from_client_commit(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; struct client_entry *ce = (struct client_entry *)arg; @@ -742,33 +788,45 @@ from_client_commit(clicon_handle h, uint32_t iddb; cbuf *cbx = NULL; /* Assist cbuf */ int ret; + yang_stmt *yspec; + + if ((yspec = clicon_dbspec_yang(h)) == NULL) { + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + + if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { + if ((ret = from_client_confirmed_commit(h, xe, myid, cbret)) < 0) + goto done; + if (ret == 0) + goto ok; + } /* Check if target locked by other client */ iddb = xmldb_islocked(h, "running"); if (iddb && myid != iddb){ - if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - cprintf(cbx, "%u", iddb); - if (netconf_in_use(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) - goto done; - goto ok; + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if (netconf_in_use(cbret, "protocol", "Operation failed, lock is already held") < 0) + goto done; + goto ok; } - if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ - clicon_debug(1, "Commit candidate failed"); - if (ret < 0) - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; + if ((ret = candidate_commit(h, xe, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ + clicon_debug(1, "Commit candidate failed"); + if (ret < 0) + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; goto ok; } if (ret == 1) - cprintf(cbret, "", NETCONF_BASE_NAMESPACE); + cprintf(cbret, "", NETCONF_BASE_NAMESPACE); ok: retval = 0; done: if (cbx) - cbuf_free(cbx); + cbuf_free(cbx); return retval; /* may be zero if we ignoring errors from commit */ } /* from_client_commit */ @@ -786,10 +844,10 @@ from_client_commit(clicon_handle h, */ int from_client_discard_changes(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; struct client_entry *ce = (struct client_entry *)arg; @@ -800,19 +858,19 @@ from_client_discard_changes(clicon_handle h, /* Check if target locked by other client */ iddb = xmldb_islocked(h, "candidate"); if (iddb && myid != iddb){ - if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - cprintf(cbx, "%u", iddb); - if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) - goto done; - goto ok; + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbx, "%u", iddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; + goto ok; } if (xmldb_copy(h, "running", "candidate") < 0){ - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; - goto ok; + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto ok; } xmldb_modified_set(h, "candidate", 0); /* reset dirty bit */ cprintf(cbret, "", NETCONF_BASE_NAMESPACE); @@ -820,37 +878,10 @@ from_client_discard_changes(clicon_handle h, retval = 0; done: if (cbx) - cbuf_free(cbx); + cbuf_free(cbx); return retval; /* may be zero if we ignoring errors from commit */ } -/*! Cancel an ongoing confirmed commit. - * If the confirmed commit is persistent, the parameter 'persist-id' must be - * given, and it must match the value of the 'persist' parameter. - * - * @param[in] h Clicon handle - * @param[in] xe Request: - * @param[out] cbret Return xml tree, eg ..., @@ -863,10 +894,10 @@ from_client_cancel_commit(clicon_handle h, */ int from_client_validate(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; int ret; @@ -874,14 +905,14 @@ from_client_validate(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); if ((db = netconf_db_find(xe, "source")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) - goto done; - goto ok; + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + goto done; + goto ok; } if ((ret = candidate_validate(h, db, cbret)) < 0) - goto done; + goto done; if (ret == 1) - cprintf(cbret, "", NETCONF_BASE_NAMESPACE); + cprintf(cbret, "", NETCONF_BASE_NAMESPACE); ok: retval = 0; done: @@ -894,8 +925,8 @@ from_client_validate(clicon_handle h, */ int from_client_restart_one(clicon_handle h, - clixon_plugin_t *cp, - cbuf *cbret) + clixon_plugin_t *cp, + cbuf *cbret) { int retval = -1; char *db = "tmp"; @@ -910,100 +941,100 @@ from_client_restart_one(clicon_handle h, yspec = clicon_dbspec_yang(h); if (xmldb_db_reset(h, db) < 0) - goto done; + goto done; /* Application may define extra xml in its reset function*/ if ((resetfn = clixon_plugin_api_get(cp)->ca_reset) != NULL){ - wh = NULL; - if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0) - goto done; - if ((retval = resetfn(h, db)) < 0) { - clicon_debug(1, "plugin_start() failed"); - goto done; - } - if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0) - goto done; + wh = NULL; + if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0) + goto done; + if ((retval = resetfn(h, db)) < 0) { + clicon_debug(1, "plugin_start() failed"); + goto done; + } + if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0) + goto done; } /* 1. Start transaction */ if ((td = transaction_new()) == NULL) - goto done; + goto done; /* This is the state we are going to */ if (xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_target, NULL, NULL) < 0) - goto done; + goto done; if ((ret = xml_yang_validate_all_top(h, td->td_target, &xerr)) < 0) - goto done; + goto done; if (ret == 0){ - if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) - goto done; - goto fail; + if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) + goto done; + goto fail; } /* This is the state we are going from */ if (xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_src, NULL, NULL) < 0) - goto done; + goto done; /* 3. Compute differences */ if (xml_diff(yspec, - td->td_src, - td->td_target, - &td->td_dvec, /* removed: only in running */ - &td->td_dlen, - &td->td_avec, /* added: only in candidate */ - &td->td_alen, - &td->td_scvec, /* changed: original values */ - &td->td_tcvec, /* changed: wanted values */ - &td->td_clen) < 0) - goto done; + td->td_src, + td->td_target, + &td->td_dvec, /* removed: only in running */ + &td->td_dlen, + &td->td_avec, /* added: only in candidate */ + &td->td_alen, + &td->td_scvec, /* changed: original values */ + &td->td_tcvec, /* changed: wanted values */ + &td->td_clen) < 0) + goto done; /* Mark as changed in tree */ for (i=0; itd_dlen; i++){ /* Also down */ - xn = td->td_dvec[i]; - xml_flag_set(xn, XML_FLAG_DEL); - xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_DEL); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + xn = td->td_dvec[i]; + xml_flag_set(xn, XML_FLAG_DEL); + xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_DEL); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); } for (i=0; itd_alen; i++){ /* Also down */ - xn = td->td_avec[i]; - xml_flag_set(xn, XML_FLAG_ADD); - xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + xn = td->td_avec[i]; + xml_flag_set(xn, XML_FLAG_ADD); + xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); } for (i=0; itd_clen; i++){ /* Also up */ - xn = td->td_scvec[i]; - xml_flag_set(xn, XML_FLAG_CHANGE); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); - xn = td->td_tcvec[i]; - xml_flag_set(xn, XML_FLAG_CHANGE); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + xn = td->td_scvec[i]; + xml_flag_set(xn, XML_FLAG_CHANGE); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + xn = td->td_tcvec[i]; + xml_flag_set(xn, XML_FLAG_CHANGE); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); } /* Call plugin transaction start callbacks */ if (plugin_transaction_begin_one(cp, h, td) < 0) - goto fail; + goto fail; /* Make generic validation on all new or changed data. Note this is only call that uses 3-values */ if ((ret = generic_validate(h, yspec, td, &xerr)) < 0) - goto done; + goto done; if (ret == 0){ - if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) - goto done; - goto fail; + if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) + goto done; + goto fail; } /* Call validate callback in this plugin */ if (plugin_transaction_validate_one(cp, h, td) < 0) - goto fail; + goto fail; if (plugin_transaction_complete_one(cp, h, td) < 0) - goto fail; + goto fail; /* Call commit callback in this plugin */ if (plugin_transaction_commit_one(cp, h, td) < 0) - goto fail; + goto fail; if (plugin_transaction_commit_done_one(cp, h, td) < 0) - goto fail; + goto fail; /* Finalize */ if (plugin_transaction_end_one(cp, h, td) < 0) - goto fail; + goto fail; retval = 1; done: if (td){ - xmldb_get0_free(h, &td->td_target); - transaction_free(td); + xmldb_get0_free(h, &td->td_target); + transaction_free(td); } return retval; fail: @@ -1011,3 +1042,58 @@ from_client_restart_one(clicon_handle h, goto done; } +/*! Reset running and start in failsafe mode. If no failsafe then quit. + * + * param[in] h Clixon handle + * param[in] phase Debug string + Typically done when startup status is not OK so + +failsafe ----------------------+ + reset \ commit +running ----|-------+---------------> RUNNING FAILSAFE + \ +tmp |----------------------> + */ +int +load_failsafe(clicon_handle h, + char *phase) +{ + int retval = -1; + int ret; + char *db = "failsafe"; + cbuf *cbret = NULL; + + phase = phase == NULL ? "(unknown)" : phase; + + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if ((ret = xmldb_exists(h, db)) < 0) + goto done; + if (ret == 0){ /* No it does not exist, fail */ + clicon_err(OE_DB, 0, "%s failed and no Failsafe database found, exiting", phase); + goto done; + } + /* Copy original running to tmp as backup (restore if error) */ + if (xmldb_copy(h, "running", "tmp") < 0) + goto done; + if (xmldb_db_reset(h, "running") < 0) + goto done; + ret = candidate_commit(h, NULL, db, cbret); + if (ret != 1) + if (xmldb_copy(h, "tmp", "running") < 0) + goto done; + if (ret < 0) + goto done; + if (ret == 0){ + clicon_err(OE_DB, 0, "%s failed, Failsafe database validation failed %s", phase, cbuf_get(cbret)); + goto done; + } + clicon_log(LOG_NOTICE, "%s failed, Failsafe database loaded ", phase); + retval = 0; + done: + if (cbret) + cbuf_free(cbret); + return retval; +} diff --git a/apps/backend/backend_confirm.c b/apps/backend/backend_confirm.c new file mode 100644 index 00000000..5a94268d --- /dev/null +++ b/apps/backend/backend_confirm.c @@ -0,0 +1,809 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + Commit-confirm + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "clixon_backend_transaction.h" +#include "clixon_backend_plugin.h" +#include "backend_handle.h" +#include "clixon_backend_commit.h" +#include "backend_client.h" + +/* + * Local types + */ +/* A struct to store the information necessary for tracking the status and relevant details of + * one or more overlapping confirmed-commit events. + */ +struct confirmed_commit { + enum confirmed_commit_state cc_state; + char *cc_persist_id; /* a value given by a client in the confirmed-commit */ + uint32_t cc_session_id; /* the session_id of the client that gave no value */ + int (*cc_fn)(int, void*); /* function pointer for rollback event (rollback_fn()) */ + void *cc_arg; /* clicon_handle that will be passed to rollback_fn() */ +}; + +int +confirmed_commit_init(clicon_handle h) +{ + int retval = -1; + struct confirmed_commit *cc = NULL; + + if ((cc = calloc(1, sizeof(*cc))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + cc->cc_state = INACTIVE; + if (clicon_ptr_set(h, "confirmed-commit-struct", cc) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! If confirm commit persist-id exists, free it + * @param[in] h Clixon handle + * @retval 0 OK + */ +int +confirmed_commit_free(clicon_handle h) +{ + struct confirmed_commit *cc = NULL; + + clicon_ptr_get(h, "confirmed-commit-struct", (void**)&cc); + if (cc != NULL){ + if (cc->cc_persist_id != NULL) + free (cc->cc_persist_id); + free(cc); + } + clicon_ptr_del(h, "confirmed-commit-struct"); + return 0; +} + +/* + * Accessor functions + */ +enum confirmed_commit_state +confirmed_commit_state_get(clicon_handle h) +{ + struct confirmed_commit *cc = NULL; + + clicon_ptr_get(h, "confirmed-commit-struct", (void**)&cc); + return cc->cc_state; +} + +static int +confirmed_commit_state_set(clicon_handle h, + enum confirmed_commit_state state) +{ + struct confirmed_commit *cc = NULL; + + clicon_ptr_get(h, "confirmed-commit-struct", (void**)&cc); + cc->cc_state = state; + return 0; +} + +char * +confirmed_commit_persist_id_get(clicon_handle h) +{ + struct confirmed_commit *cc = NULL; + + clicon_ptr_get(h, "confirmed-commit-struct", (void**)&cc); + return cc->cc_persist_id; +} + +static int +confirmed_commit_persist_id_set(clicon_handle h, + char *persist_id) +{ + struct confirmed_commit *cc = NULL; + + clicon_ptr_get(h, "confirmed-commit-struct", (void**)&cc); + if (cc->cc_persist_id) + free(cc->cc_persist_id); + if (persist_id){ + if ((cc->cc_persist_id = strdup4(persist_id)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup4"); + return -1; + } + } + else + cc->cc_persist_id = NULL; + return 0; +} + +uint32_t +confirmed_commit_session_id_get(clicon_handle h) +{ + struct confirmed_commit *cc = NULL; + + clicon_ptr_get(h, "confirmed-commit-struct", (void**)&cc); + return cc->cc_session_id; +} + +static int +confirmed_commit_session_id_set(clicon_handle h, + uint32_t session_id) +{ + struct confirmed_commit *cc = NULL; + + clicon_ptr_get(h, "confirmed-commit-struct", (void**)&cc); + cc->cc_session_id = session_id; + return 0; +} + +static int +confirmed_commit_fn_arg_get(clicon_handle h, + int (**fn)(int, void*), + void **arg) +{ + struct confirmed_commit *cc = NULL; + + clicon_ptr_get(h, "confirmed-commit-struct", (void**)&cc); + *fn = cc->cc_fn; + *arg = cc->cc_arg; + return 0; +} + +static int +confirmed_commit_fn_arg_set(clicon_handle h, + int (*fn)(int, void*), + void *arg) +{ + struct confirmed_commit *cc = NULL; + + clicon_ptr_get(h, "confirmed-commit-struct", (void**)&cc); + cc->cc_fn = fn; + cc->cc_arg = arg; + return 0; +} + +/*! Return if confirmed tag found + * @param[in] xe Commit rpc xml + * @retval 1 Confirmed tag exists + * @retval 0 Confirmed tag does not exist + */ +static int +xe_confirmed(cxobj *xe) +{ + return (xml_find_type(xe, NULL, "confirmed", CX_ELMNT) != NULL) ? 1 : 0; +} + +/*! Return if persist exists and its string value field + * @param[in] xe Commit rpc xml + * @param[out] str Pointer to persist + * @retval 1 Persist field exists + * @retval 0 Persist field does not exist + */ +static int +xe_persist(cxobj *xe, + char **str) +{ + cxobj *xml; + + if ((xml = xml_find_type(xe, NULL, "persist", CX_ELMNT)) != NULL){ + *str = xml_body(xml); + return 1; + } + *str = NULL; + return 0; +} + +/*! Return if persist-id exists and its string value + * + * @param[in] xe Commit rpc xml + * @param[out] str Pointer to persist-id + * @retval 1 Persist-id exists + * @retval 0 Persist-id does not exist + */ +static int +xe_persist_id(cxobj *xe, + char **str) +{ + cxobj *xml; + + if ((xml = xml_find_type(xe, NULL, "persist-id", CX_ELMNT)) != NULL){ + *str = xml_body(xml); + return 1; + } + *str = NULL; + return 0; +} + +/*! Return timeout + * @param[in] xe Commit rpc xml + * @retval sec Timeout in seconds, can be 0 if no timeout exists or is zero + */ +static unsigned int +xe_timeout(cxobj *xe) +{ + cxobj *xml; + char *str; + + if ((xml = xml_find_type(xe, NULL, "confirm-timeout", CX_ELMNT)) != NULL && + (str = xml_body(xml)) != NULL) + return strtoul(str, NULL, 10); + return 0; +} + +/*! Cancel a scheduled rollback as previously registered by schedule_rollback_event() + * + * @param[in] h Clixon handle + * @retval 0 Rollback event successfully cancelled + * @retval -1 No Rollback event was found + */ +int +cancel_rollback_event(clicon_handle h) +{ + int retval; + int (*fn)(int, void*) = NULL; + void *arg = NULL; + + confirmed_commit_fn_arg_get(h, &fn, &arg); + if ((retval = clixon_event_unreg_timeout(fn, arg)) == 0) { + clicon_log(LOG_INFO, "a scheduled rollback event has been cancelled"); + } else { + clicon_log(LOG_WARNING, "the specified scheduled rollback event was not found"); + } + + return retval; +} + +/*! Apply the rollback configuration upon expiration of the confirm-timeout + * + * @param[in] fd a dummy argument per the event callback semantics + * @param[in] arg a void pointer to a clicon_handle + * @retval 0 the rollback was successful + * @retval -1 the rollback failed + * @see do_rollback() + */ +static int +rollback_fn(int fd, + void *arg) +{ + clicon_handle h = arg; + + clicon_log(LOG_CRIT, "a confirming-commit was not received before the confirm-timeout expired; rolling back"); + + return do_rollback(h, NULL); +} + +/*! Schedule a rollback in case no confirming-commit is received before the confirm-timeout + * + * @param[in] h a clicon handle + * @param[in] timeout a uint32 representing the number of seconds before the rollback event should fire + * + * @retval 0 Rollback event successfully scheduled + * @retval -1 Rollback event was not scheduled + */ +static int +schedule_rollback_event(clicon_handle h, + uint32_t timeout) +{ + int retval = -1; + + // register a new scheduled event + struct timeval t, t1; + if (gettimeofday(&t, NULL) < 0) { + clicon_err(OE_UNIX, 0, "failed to get time of day: %s", strerror(errno)); + goto done; + }; + t1.tv_sec = timeout; t1.tv_usec = 0; + timeradd(&t, &t1, &t); + + /* The confirmed-commit is either: + * - ephemeral, and the client requesting the new confirmed-commit is on the same session, OR + * - persistent, and the client provided the persist-id in the new confirmed-commit + */ + + /* remember the function pointer and args so the confirming-commit can cancel the rollback */ + confirmed_commit_fn_arg_set(h, rollback_fn, h); + if (clixon_event_reg_timeout(t, rollback_fn, h, "rollback after timeout") < 0) { + /* error is logged in called function */ + goto done; + }; + + retval = 0; + + done: + return retval; +} + +/*! Cancel a confirming commit by removing rollback, and free state + * @param[in] h + * @param[out] cbret + * @retval 0 OK + */ +int +cancel_confirmed_commit(clicon_handle h) +{ + cancel_rollback_event(h); + + if (confirmed_commit_state_get(h) == PERSISTENT && + confirmed_commit_persist_id_get(h) != NULL) { + confirmed_commit_persist_id_set(h, NULL); + } + + confirmed_commit_state_set(h, INACTIVE); + + if (xmldb_delete(h, "rollback") < 0) + clicon_err(OE_DB, 0, "Error deleting the rollback configuration"); + return 0; +} + +/*! Determine if the present commit RPC invocation constitutes a valid "confirming-commit". + * + * To be considered a valid confirming-commit, the must either: + * 1) be presented without a value, and on the same session as a prior confirmed-commit that itself was + * without a value, OR + * 2) be presented with a value that matches the value accompanying the prior confirmed-commit + * + * @param[in] h Clicon handle + * @param[in] xe Request: + * @param[in] myid current client session-id + * @retval 1 The confirming-commit is valid + * @retval 0 The confirming-commit is not valid + * @retval -1 Error + */ +static int +check_valid_confirming_commit(clicon_handle h, + cxobj *xe, + uint32_t myid) +{ + int retval = -1; + char *persist_id = NULL; + + if (xe == NULL){ + clicon_err(OE_CFG, EINVAL, "xe is NULL"); + goto done; + } + switch (confirmed_commit_state_get(h)) { + case PERSISTENT: + if (xe_persist_id(xe, &persist_id)) { + if (clicon_strcmp(persist_id, confirmed_commit_persist_id_get(h)) == 0) { + /* the RPC included a matching the prior confirming-commit's */ + break; // valid + } + else { + clicon_log(LOG_INFO, + "a persistent confirmed-commit is in progress but the client issued a " + "confirming-commit with an incorrect persist-id"); + goto invalid; + } + } else { + clicon_log(LOG_INFO, + "a persistent confirmed-commit is in progress but the client issued a confirming-commit" + "without a persist-id"); + goto invalid; + } + case EPHEMERAL: + if (myid == confirmed_commit_session_id_get(h)) { + /* the RPC lacked a , the prior confirming-commit lacked , and both were issued + * on the same session. + */ + break; // valid + } + clicon_log(LOG_DEBUG, "an ephemeral confirmed-commit is in progress, but there confirming-commit was" + "not issued on the same session as the confirmed-commit"); + goto invalid; + default: + clicon_debug(1, "commit-confirmed state !? %d", confirmed_commit_state_get(h)); + goto invalid; + } + retval = 1; // valid + done: + return retval; + invalid: + retval = 0; + goto done; +} + +/*! Handle the second phase of confirmed-commit processing. + * + * In the first phase, the proper action was taken in the case of a valid confirming-commit, but no subsequent + * confirmed-commit. + * + * In the second phase, the action taken is to handle both confirming- and confirmed-commit by creating the + * rollback database as required, then deleting it once the sequence is complete. + * + * @param[in] h Clicon handle + * @param[in] xe Commit rpc xml or NULL + * @retval 0 OK + * @retval -1 Error + */ +int +handle_confirmed_commit(clicon_handle h, + cxobj *xe) +{ + int retval = -1; + uint32_t session_id; + char *persist; + unsigned long confirm_timeout = 0L; + int cc_valid; + int db_exists; + + if (xe == NULL){ + clicon_err(OE_CFG, EINVAL, "xe is NULL"); + goto done; + } + if (clicon_session_id_get(h, &session_id) < 0) { + clicon_err(OE_DAEMON, 0, + "an ephemeral confirmed-commit was issued, but the session-id could not be determined"); + goto done; + }; + /* The case of a valid confirming-commit is also handled in the first phase, but only if there is no subsequent + * confirmed-commit. It is tested again here as the case of a valid confirming-commit *with* a subsequent + * confirmed-commit must be handled once the transaction has begun and after all the plugins' validate callbacks + * have been called. + */ + cc_valid = check_valid_confirming_commit(h, xe, session_id); + if (cc_valid) { + if (cancel_rollback_event(h) < 0) { + clicon_err(OE_DAEMON, 0, "A valid confirming-commit was received, but the corresponding rollback event was not found"); + } + + if (confirmed_commit_state_get(h) == PERSISTENT && + confirmed_commit_persist_id_get(h) != NULL) { + confirmed_commit_persist_id_set(h, NULL); + } + + confirmed_commit_state_set(h, INACTIVE); + } + + /* Now, determine if there is a subsequent confirmed-commit */ + if (xe_confirmed(xe)){ + /* There is, get it's confirm-timeout value, which will default per the yang schema if not client-specified */ + /* Clixon also pre-validates input according to the schema, so bounds checking here is redundant */ + confirm_timeout = xe_timeout(xe); + if (xe_persist(xe, &persist)){ + if (persist == NULL) { + confirmed_commit_persist_id_set(h, NULL); + } + else if (confirmed_commit_persist_id_set(h, persist) < 0){ + goto done; + } + + /* The client has passed ; the confirming-commit MUST now be accompanied by a matching + * + */ + confirmed_commit_state_set(h, PERSISTENT); + clicon_log(LOG_INFO, + "a persistent confirmed-commit has been requested with persist id of '%s' and a timeout of %lu seconds", + confirmed_commit_persist_id_get(h), confirm_timeout); + } + + else { + /* The client did not pass a value for and therefore any subsequent confirming-commit must be + * issued within the same session. + */ + confirmed_commit_session_id_set(h, session_id); + confirmed_commit_state_set(h, EPHEMERAL); + + clicon_log(LOG_INFO, + "an ephemeral confirmed-commit has been requested by session-id %u and a timeout of %lu seconds", + confirmed_commit_session_id_get(h), + confirm_timeout); + } + + /* The confirmed-commits and confirming-commits can overlap; the rollback database is created at the beginning + * of such a sequence and deleted at the end; hence its absence implies this is the first of a sequence. ** + * + * + * | edit + * | | confirmed-commit + * | | copy t=0 running to rollback + * | | | edit + * | | | | both + * | | | | | edit + * | | | | | | both + * | | | | | | | confirming-commit + * | | | | | | | | delete rollback + * +----|-|-|-|-|-|-|-|--------------- + * t=0 1 2 3 4 5 6 7 8 + * + * edit = edit of the candidate configuration + * both = both a confirmed-commit and confirming-commit in the same RPC + * + * As shown, the rollback database created at t=2 is comprised of the running database from t=0 + * Thus, if there is a rollback event at t=7, the t=0 configuration will be committed. + * + * ** the rollback database may be present at system startup if there was a crash during a confirmed-commit; + * in the case the system is configured to startup from running and the rollback database is present, the + * rollback database will be committed to running and then deleted. If the system is configured to use a + * startup configuration instead, any present rollback database will be deleted. + * + */ + + db_exists = xmldb_exists(h, "rollback"); + if (db_exists == -1) { + clicon_err(OE_DAEMON, 0, "there was an error while checking existence of the rollback database"); + goto done; + } else if (db_exists == 0) { + // db does not yet exists + if (xmldb_copy(h, "running", "rollback") < 0) { + clicon_err(OE_DAEMON, 0, "there was an error while copying the running configuration to rollback database."); + goto done; + }; + } + + if (schedule_rollback_event(h, confirm_timeout) < 0) { + clicon_err(OE_DAEMON, 0, "the rollback event could not be scheduled"); + goto done; + }; + + } + else { + /* There was no subsequent confirmed-commit, meaning this is the end of the confirmed/confirming sequence; + * The new configuration is already committed to running and the rollback database can now be deleted + */ + if (xmldb_delete(h, "rollback") < 0) { + clicon_err(OE_DB, 0, "Error deleting the rollback configuration"); + goto done; + } + } + retval = 0; + done: + return retval; +} + +/*! Do a rollback of the running configuration to the state prior to initiation of a confirmed-commit + * + * The "running" configuration prior to the first confirmed-commit was stored in another database named "rollback". + * Here, it is committed as if it is the candidate configuration. + * + * Execution has arrived here because do_rollback() was called by one of: + * 1. backend_client_rm() (client disconnected and confirmed-commit is ephemeral) + * 2. from_client_cancel_commit() (invoked either by netconf client, or CLI) + * 3. rollback_fn() (invoked by expiration of the rollback event timer) + * + * @param[in] h Clicon handle + * @retval -1 Error + * @retval 0 Success + * @see backend_client_rm() + * @see from_client_cancel_commit() + * @see rollback_fn() + */ +int +do_rollback(clicon_handle h, + uint8_t *errs) +{ + int retval = -1; + uint8_t errstate = 0; + cbuf *cbret; + + if ((cbret = cbuf_new()) == NULL) { + clicon_err(OE_DAEMON, 0, "rollback was not performed. (cbuf_new: %s)", strerror(errno)); + /* the rollback_db won't be deleted, so one can try recovery by: + * load rollback running + * restart the backend, which will try to load the rollback_db, and delete it if successful + * (otherwise it will load the failsafe) + */ + clicon_log(LOG_CRIT, "An error occurred during rollback and the rollback_db wasn't deleted."); + errstate |= ROLLBACK_NOT_APPLIED | ROLLBACK_DB_NOT_DELETED; + goto done; + } + + if (confirmed_commit_state_get(h) == PERSISTENT && + confirmed_commit_persist_id_get(h) != NULL) { + confirmed_commit_persist_id_set(h, NULL); + } + confirmed_commit_state_set(h, ROLLBACK); + if (candidate_commit(h, NULL, "rollback", cbret) < 0) { /* Assume validation fail, nofatal */ + /* theoretically, this should never error, since the rollback database was previously active and therefore + * had itself been previously and successfully committed. + */ + clicon_log(LOG_CRIT, "An error occurred committing the rollback database."); + errstate |= ROLLBACK_NOT_APPLIED; + + /* Rename the errored rollback database */ + if (xmldb_rename(h, "rollback", NULL, ".error") < 0) { + clicon_log(LOG_CRIT, "An error occurred renaming the rollback database."); + errstate |= ROLLBACK_DB_NOT_DELETED; + } + + /* Attempt to load the failsafe config */ + + if (load_failsafe(h, "Rollback") < 0) { + clicon_log(LOG_CRIT, "An error occurred committing the failsafe database. Exiting."); + /* Invoke our own signal handler to exit */ + raise(SIGINT); + + /* should never make it here */ + } + + errstate |= ROLLBACK_FAILSAFE_APPLIED; + goto done; + } + cbuf_free(cbret); + + if (xmldb_delete(h, "rollback") < 0) { + clicon_log(LOG_WARNING, "A rollback occurred but the rollback_db wasn't deleted."); + errstate |= ROLLBACK_DB_NOT_DELETED; + goto done; + }; + retval = 0; + done: + confirmed_commit_state_set(h, INACTIVE); + if (errs) + *errs = errstate; + return retval; +} + +/*! Cancel an ongoing confirmed commit. + * If the confirmed commit is persistent, the parameter 'persist-id' must be + * given, and it must match the value of the 'persist' parameter. + * If the confirmed-commit is ephemeral, the 'persist-id' must not be given and both the confirmed-commit and the + * cancel-commit must originate from the same session. + * + * @param[in] h Clicon handle + * @param[in] xe Request: + * @param[out] cbret Return xml tree, eg ..., ", NETCONF_BASE_NAMESPACE); + clicon_log(LOG_INFO, "a confirmed-commit has been cancelled by client request"); + } + retval = 0; + done: + return retval; +} + +/*! Incoming commit handler for confirmed commit + * @param[in] h Clicon handle + * @param[in] xe Request: + * @param[in] myid Client-id + * @param[out] cbret Return xml tree + * @retval 1 OK + * @retval 0 OK, dont proceed with commit + * @retval -1 Error + */ +int +from_client_confirmed_commit(clicon_handle h, + cxobj *xe, + uint32_t myid, + cbuf *cbret) +{ + int retval = -1; + int cc_valid; + + if ((cc_valid = check_valid_confirming_commit(h, xe, myid)) < 0) + goto done; + /* If is *not* present, this will conclude the confirmed-commit, so cancel the rollback. */ + if (!xe_confirmed(xe) && cc_valid) { + cancel_confirmed_commit(h); + cprintf(cbret, "", NETCONF_BASE_NAMESPACE); + goto dontcommit; + } + retval = 1; + done: + return retval; + dontcommit: + retval = 0; + goto done; +} diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c index c60e19dd..c2996bc3 100644 --- a/apps/backend/backend_get.c +++ b/apps/backend/backend_get.c @@ -84,21 +84,21 @@ */ static int client_get_capabilities(clicon_handle h, - yang_stmt *yspec, - char *xpath, - cxobj **xret) + yang_stmt *yspec, + char *xpath, + cxobj **xret) { int retval = -1; cxobj *xrstate = NULL; /* xml restconf-state node */ cbuf *cb = NULL; if ((xrstate = xpath_first(*xret, NULL, "restconf-state")) == NULL){ - clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node"); - goto done; + clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node"); + goto done; } if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; } cprintf(cb, ""); cprintf(cb, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit"); @@ -106,11 +106,11 @@ client_get_capabilities(clicon_handle h, cprintf(cb, "urn:ietf:params:restconf:capability:with-defaults:1.0"); cprintf(cb, ""); if (clixon_xml_parse_string(cbuf_get(cb), YB_PARENT, NULL, &xrstate, NULL) < 0) - goto done; + goto done; retval = 0; done: if (cb) - cbuf_free(cb); + cbuf_free(cb); return retval; } @@ -127,11 +127,11 @@ client_get_capabilities(clicon_handle h, */ static int client_get_streams(clicon_handle h, - yang_stmt *yspec, - char *xpath, - yang_stmt *ymod, - char *top, - cxobj **xret) + yang_stmt *yspec, + char *xpath, + yang_stmt *ymod, + char *top, + cxobj **xret) { int retval = -1; yang_stmt *yns = NULL; /* yang namespace */ @@ -140,36 +140,36 @@ client_get_streams(clicon_handle h, int ret; if ((yns = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL){ - clicon_err(OE_YANG, 0, "%s yang namespace not found", yang_argument_get(ymod)); - goto done; + clicon_err(OE_YANG, 0, "%s yang namespace not found", yang_argument_get(ymod)); + goto done; } if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; } cprintf(cb, "<%s xmlns=\"%s\">", top, yang_argument_get(yns)); /* Second argument is a hack to have the same function for the * RFC5277 and 8040 stream cases */ if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0) - goto done; + goto done; cprintf(cb,"", top); if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) - goto done; - goto fail; + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + goto done; + goto fail; } if ((ret = netconf_trymerge(x, yspec, xret)) < 0) - goto done; + goto done; if (ret == 0) - goto fail; + goto fail; retval = 1; done: if (cb) - cbuf_free(cb); + cbuf_free(cb); if (x) - xml_free(x); + xml_free(x); return retval; fail: retval = 0; @@ -187,9 +187,9 @@ client_get_streams(clicon_handle h, */ static int get_client_statedata(clicon_handle h, - char *xpath, - cvec *nsc, - cxobj **xret) + char *xpath, + cvec *nsc, + cxobj **xret) { int retval = -1; yang_stmt *yspec; @@ -200,73 +200,73 @@ get_client_statedata(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; } if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; } if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){ - if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){ - clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found"); - goto done; - } - if ((namespace = yang_find_mynamespace(ymod)) == NULL){ - clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found"); - goto done; - } - cprintf(cb, "", namespace); - if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0) - goto done; - if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0) - goto done; - if (ret == 0) - goto fail; + if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){ + clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found"); + goto done; + } + if ((namespace = yang_find_mynamespace(ymod)) == NULL){ + clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found"); + goto done; + } + cprintf(cb, "", namespace); + if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0) + goto done; + if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0) + goto done; + if (ret == 0) + goto fail; } if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")){ - if ((ymod = yang_find_module_by_name(yspec, "ietf-restconf-monitoring")) == NULL){ - clicon_err(OE_YANG, ENOENT, "yang module ietf-restconf-monitoring not found"); - goto done; - } - if ((namespace = yang_find_mynamespace(ymod)) == NULL){ - clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found"); - goto done; - } - cbuf_reset(cb); - /* XXX This code does not filter state data with xpath */ - cprintf(cb, "", namespace); - if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0) - goto done; - if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0) - goto done; - if (ret == 0) - goto fail; - if ((ret = client_get_capabilities(h, yspec, xpath, xret)) < 0) - goto done; + if ((ymod = yang_find_module_by_name(yspec, "ietf-restconf-monitoring")) == NULL){ + clicon_err(OE_YANG, ENOENT, "yang module ietf-restconf-monitoring not found"); + goto done; + } + if ((namespace = yang_find_mynamespace(ymod)) == NULL){ + clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found"); + goto done; + } + cbuf_reset(cb); + /* XXX This code does not filter state data with xpath */ + cprintf(cb, "", namespace); + if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0) + goto done; + if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0) + goto done; + if (ret == 0) + goto fail; + if ((ret = client_get_capabilities(h, yspec, xpath, xret)) < 0) + goto done; } if (clicon_option_bool(h, "CLICON_YANG_LIBRARY")){ - if ((ret = yang_modules_state_get(h, yspec, xpath, nsc, 0, xret)) < 0) - goto done; - if (ret == 0) - goto fail; + if ((ret = yang_modules_state_get(h, yspec, xpath, nsc, 0, xret)) < 0) + goto done; + if (ret == 0) + goto fail; } /* Use plugin state callbacks */ if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath, xret)) < 0) - goto done; + goto done; if (ret == 0) - goto fail; + goto fail; /* Add default state to config if present */ if (xml_default_recurse(*xret, 1) < 0) - goto done; + goto done; /* Add default global state */ if (xml_global_defaults(h, *xret, nsc, xpath, yspec, 1) < 0) - goto done; + goto done; retval = 1; /* OK */ done: clicon_debug(1, "%s %d", __FUNCTION__, retval); if (cb) - cbuf_free(cb); + cbuf_free(cb); return retval; fail: retval = 0; @@ -294,35 +294,35 @@ get_client_statedata(clicon_handle h, */ static int filter_xpath_again(clicon_handle h, - yang_stmt *yspec, - cxobj *xret, - cxobj **xvec, - size_t xlen, - char *xpath, - cvec *nsc) + yang_stmt *yspec, + cxobj *xret, + cxobj **xvec, + size_t xlen, + char *xpath, + cvec *nsc) { int retval = -1; int i; if (xret == NULL){ - clicon_err(OE_PLUGIN, EINVAL, "xret is NULL"); - goto done; + clicon_err(OE_PLUGIN, EINVAL, "xret is NULL"); + goto done; } /* If vectors are specified then mark the nodes found and * then filter out everything else, * otherwise return complete tree. */ if (xvec != NULL){ - for (i=0; i", NETCONF_BASE_NAMESPACE); /* OK */ if (xret==NULL) - cprintf(cbret, ""); + cprintf(cbret, ""); else{ - if (xml_name_set(xret, NETCONF_OUTPUT_DATA) < 0) - goto done; - /* Top level is data, so add 1 to depth if significant */ - if (clixon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth, 0) < 0) - goto done; + if (xml_name_set(xret, NETCONF_OUTPUT_DATA) < 0) + goto done; + /* Top level is data, so add 1 to depth if significant */ + if (clixon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth, 0) < 0) + goto done; } cprintf(cbret, ""); retval = 0; @@ -393,52 +393,52 @@ get_nacm_and_reply(clicon_handle h, */ static int element2value(clicon_handle h, - cxobj *xe, - char *name, - char *defaultstr, - cbuf *cbret, - uint32_t *value) + cxobj *xe, + char *name, + char *defaultstr, + cbuf *cbret, + uint32_t *value) { char *valstr; cxobj *x; *value = 0; if ((x = xml_find_type(xe, NULL, name, CX_ELMNT)) != NULL && - (valstr = xml_body(x)) != NULL){ - return netconf_parse_uint32(name, valstr, defaultstr, 0, cbret, value); + (valstr = xml_body(x)) != NULL){ + return netconf_parse_uint32(name, valstr, defaultstr, 0, cbret, value); } return 1; } /*! Set flag on node having schema default value. - * @param[in] x XML node - * @param[in] flag Flag to be used - * @retval 0 OK + * @param[in] x XML node + * @param[in] flag Flag to be used + * @retval 0 OK */ static int xml_flag_default_value(cxobj *x, - uint16_t flag) + uint16_t flag) { - yang_stmt *y; - cg_var *cv; - char *yv; - char *xv; + yang_stmt *y; + cg_var *cv; + char *yv; + char *xv; xml_flag_reset(x, flag); /* Assume not default value */ if ((xv = xml_body(x)) == NULL) - goto done; + goto done; if ((y = xml_spec(x)) == NULL) - goto done; + goto done; if ((cv = yang_cv_get(y)) == NULL) - goto done; + goto done; if ((cv = yang_cv_get(y)) == NULL) - goto done; + goto done; if (cv_name_get(cv) == NULL) - goto done; + goto done; if ((yv = cv2str_dup(cv)) == NULL) - goto done; + goto done; if (strcmp(xv, yv) == 0) - xml_flag_set(x, flag); /* Actual value same as default value */ + xml_flag_set(x, flag); /* Actual value same as default value */ free(yv); done: @@ -446,26 +446,26 @@ xml_flag_default_value(cxobj *x, } /*! Add default attribute to node with default value. - * @param[in] x XML node - * @param[in] flags Flags indicatiing default nodes - * @retval 0 OK - * @retval -1 Error + * @param[in] x XML node + * @param[in] flags Flags indicatiing default nodes + * @retval 0 OK + * @retval -1 Error */ static int xml_add_default_tag(cxobj *x, - uint16_t flags) + uint16_t flags) { int retval = -1; cxobj *xattr; if (xml_flag(x, flags)) { - /* set default attribute */ - if ((xattr = xml_new("default", x, CX_ATTR)) == NULL) - goto done; - if (xml_value_set(xattr, "true") < 0) - goto done; - if (xml_prefix_set(xattr, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX) < 0) - goto done; + /* set default attribute */ + if ((xattr = xml_new("default", x, CX_ATTR)) == NULL) + goto done; + if (xml_value_set(xattr, "true") < 0) + goto done; + if (xml_prefix_set(xattr, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX) < 0) + goto done; } retval = 0; done: @@ -475,12 +475,12 @@ xml_add_default_tag(cxobj *x, /*! Update XML return tree according to RFC6243: with-defaults * @param[in] xe Request: * @param[in] xret XML return tree to be updated - * @retval 0 OK - * @retval -1 Error + * @retval 0 OK + * @retval -1 Error */ static int with_defaults(cxobj *xe, - cxobj *xret) + cxobj *xret) { int retval = -1; cxobj *xfind; @@ -488,56 +488,56 @@ with_defaults(cxobj *xe, cxobj *x; if ((xfind = xml_find(xe, "with-defaults")) != NULL) { - if ((mode = xml_find_value(xfind, "body")) == NULL) - goto done; + if ((mode = xml_find_value(xfind, "body")) == NULL) + goto done; - if (strcmp(mode, "explicit") == 0) { - /* Clear marked nodes */ - if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*) xml_flag_reset,(void*) XML_FLAG_MARK) < 0) - goto done; - /* Mark state nodes */ - if (xml_non_config_data(xret, NULL) < 0) - goto done; - /* Remove default configuration nodes */ - if (xml_tree_prune_flags(xret, XML_FLAG_DEFAULT, XML_FLAG_MARK | XML_FLAG_DEFAULT) < 0) - goto done; - /* Remove empty containers */ - if (xml_defaults_nopresence(xret, 1) < 0) - goto done; - goto ok; - } - else if (strcmp(mode, "trim") == 0) { - /* Remove default nodes from XML */ - if (xml_tree_prune_flags(xret, XML_FLAG_DEFAULT, XML_FLAG_DEFAULT) < 0) - goto done; - /* Mark and remove nodes having schema default values */ - if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*) xml_flag_default_value, (void*) XML_FLAG_MARK) < 0) - goto done; - if (xml_tree_prune_flags(xret, XML_FLAG_MARK, XML_FLAG_MARK) - < 0) - goto done; - /* Remove empty containers */ - if (xml_defaults_nopresence(xret, 1) < 0) - goto done; - goto ok; - } - else if (strcmp(mode, "report-all-tagged") == 0) { - x = NULL; - while ((x = xml_child_each(xret, x, CX_ELMNT)) != NULL) - if (xmlns_set(x, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, IETF_NETCONF_WITH_DEFAULTS_ATTR_NAMESPACE) < 0) - goto done; - /* Mark nodes having default schema values */ - if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*) xml_flag_default_value, (void*) XML_FLAG_MARK) < 0) - goto done; - /* Add tag attributes to default nodes */ - if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*) xml_add_default_tag, (void*) (XML_FLAG_DEFAULT | XML_FLAG_MARK)) < 0) - goto done; - goto ok; - } - else if (strcmp(mode, "report-all") == 0) { - /* Accept mode, do nothing */ - goto ok; - } + if (strcmp(mode, "explicit") == 0) { + /* Clear marked nodes */ + if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*) xml_flag_reset,(void*) XML_FLAG_MARK) < 0) + goto done; + /* Mark state nodes */ + if (xml_non_config_data(xret, NULL) < 0) + goto done; + /* Remove default configuration nodes */ + if (xml_tree_prune_flags(xret, XML_FLAG_DEFAULT, XML_FLAG_MARK | XML_FLAG_DEFAULT) < 0) + goto done; + /* Remove empty containers */ + if (xml_defaults_nopresence(xret, 1) < 0) + goto done; + goto ok; + } + else if (strcmp(mode, "trim") == 0) { + /* Remove default nodes from XML */ + if (xml_tree_prune_flags(xret, XML_FLAG_DEFAULT, XML_FLAG_DEFAULT) < 0) + goto done; + /* Mark and remove nodes having schema default values */ + if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*) xml_flag_default_value, (void*) XML_FLAG_MARK) < 0) + goto done; + if (xml_tree_prune_flags(xret, XML_FLAG_MARK, XML_FLAG_MARK) + < 0) + goto done; + /* Remove empty containers */ + if (xml_defaults_nopresence(xret, 1) < 0) + goto done; + goto ok; + } + else if (strcmp(mode, "report-all-tagged") == 0) { + x = NULL; + while ((x = xml_child_each(xret, x, CX_ELMNT)) != NULL) + if (xmlns_set(x, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, IETF_NETCONF_WITH_DEFAULTS_ATTR_NAMESPACE) < 0) + goto done; + /* Mark nodes having default schema values */ + if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*) xml_flag_default_value, (void*) XML_FLAG_MARK) < 0) + goto done; + /* Add tag attributes to default nodes */ + if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*) xml_add_default_tag, (void*) (XML_FLAG_DEFAULT | XML_FLAG_MARK)) < 0) + goto done; + goto ok; + } + else if (strcmp(mode, "report-all") == 0) { + /* Accept mode, do nothing */ + goto ok; + } } ok: retval = 0; @@ -565,17 +565,17 @@ with_defaults(cxobj *xe, */ static int get_list_pagination(clicon_handle h, - struct client_entry *ce, - cxobj *xe, - netconf_content content, - char *db, - int32_t depth, - yang_stmt *yspec, - char *xpath, - cvec *nsc, - char *username, - cbuf *cbret - ) + struct client_entry *ce, + cxobj *xe, + netconf_content content, + char *db, + int32_t depth, + yang_stmt *yspec, + char *xpath, + cvec *nsc, + char *username, + cbuf *cbret + ) { int retval = -1; uint32_t offset = 0; @@ -602,219 +602,219 @@ get_list_pagination(clicon_handle h, /* Check if list/leaf-list */ if (yang_path_arg(yspec, xpath?xpath:"/", &ylist) < 0) - goto done; + goto done; if (ylist == NULL){ - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* error reason should be in clicon_err_reason */ - cprintf(cbmsg, "Netconf get list-pagination: \"%s\" not found", xpath); - if (netconf_invalid_value(cbret, "application", cbuf_get(cbmsg)) < 0) - goto done; - goto ok; + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* error reason should be in clicon_err_reason */ + cprintf(cbmsg, "Netconf get list-pagination: \"%s\" not found", xpath); + if (netconf_invalid_value(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; } if (yang_keyword_get(ylist) != Y_LIST && - yang_keyword_get(ylist) != Y_LEAF_LIST){ - if (netconf_invalid_value(cbret, "application", "list-pagination is enabled but target is not list or leaf-list") < 0) - goto done; - goto ok; + yang_keyword_get(ylist) != Y_LEAF_LIST){ + if (netconf_invalid_value(cbret, "application", "list-pagination is enabled but target is not list or leaf-list") < 0) + goto done; + goto ok; } /* Sanity checks on state/config */ if ((list_config = yang_config_ancestor(ylist)) != 0){ /* config list */ - if (content == CONTENT_NONCONFIG){ - if (netconf_invalid_value(cbret, "application", "list-pagination targets a config list but content request is nonconfig") < 0) - goto done; - goto ok; - } + if (content == CONTENT_NONCONFIG){ + if (netconf_invalid_value(cbret, "application", "list-pagination targets a config list but content request is nonconfig") < 0) + goto done; + goto ok; + } } else { /* state list */ - if (content == CONTENT_CONFIG){ - if (netconf_invalid_value(cbret, "application", "list-pagination targets a state list but content request is config") < 0) - goto done; - goto ok; - } + if (content == CONTENT_CONFIG){ + if (netconf_invalid_value(cbret, "application", "list-pagination targets a state list but content request is config") < 0) + goto done; + goto ok; + } } /* offset */ if ((ret = element2value(h, xe, "offset", "none", cbret, &offset)) < 0) - goto done; + goto done; /* limit */ if (ret && (ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0) - goto done; + goto done; #ifdef NOTYET /* direction */ if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){ - direction = xml_body(x); - if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){ - if (netconf_bad_attribute(cbret, "application", - "direction", "Unrecognized value of direction attribute") < 0) - goto done; - goto ok; - } + direction = xml_body(x); + if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){ + if (netconf_bad_attribute(cbret, "application", + "direction", "Unrecognized value of direction attribute") < 0) + goto done; + goto ok; + } } /* sort */ if (ret && (x = xml_find_type(xe, NULL, "sort-by", CX_ELMNT)) != NULL) - sort = xml_body(x); + sort = xml_body(x); if (sort) { - /* XXX: nothing yet */ + /* XXX: nothing yet */ } /* where */ if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL) - where = xml_body(x); + where = xml_body(x); #endif /* Read config */ switch (content){ case CONTENT_CONFIG: /* config data only */ case CONTENT_ALL: /* both config and state */ - /* Build a "predicate" cbuf - * This solution uses xpath predicates to translate "limit" and "offset" to - * relational operators <>. - */ - if ((cbpath = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* This uses xpath. Maybe limit should use parameters */ - if (xpath) - cprintf(cbpath, "%s", xpath); - else - cprintf(cbpath, "/"); + /* Build a "predicate" cbuf + * This solution uses xpath predicates to translate "limit" and "offset" to + * relational operators <>. + */ + if ((cbpath = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* This uses xpath. Maybe limit should use parameters */ + if (xpath) + cprintf(cbpath, "%s", xpath); + else + cprintf(cbpath, "/"); #ifdef NOTYET - if (where) - cprintf(cbpath, "[%s]", where); + if (where) + cprintf(cbpath, "[%s]", where); #endif - if (offset){ - cprintf(cbpath, "[%u <= position()", offset); - if (limit) - cprintf(cbpath, " and position() < %u", limit+offset); - cprintf(cbpath, "]"); - } - else if (limit) - cprintf(cbpath, "[position() < %u]", limit); + if (offset){ + cprintf(cbpath, "[%u <= position()", offset); + if (limit) + cprintf(cbpath, " and position() < %u", limit+offset); + cprintf(cbpath, "]"); + } + else if (limit) + cprintf(cbpath, "[position() < %u]", limit); - /* Append predicate to original xpath and replace it */ - xpath2 = cbuf_get(cbpath); - /* specific xpath */ - if (xmldb_get0(h, db, YB_MODULE, nsc, xpath2?xpath2:"/", 1, &xret, NULL, NULL) < 0) { - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); - if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) - goto done; - goto ok; - } - break; + /* Append predicate to original xpath and replace it */ + xpath2 = cbuf_get(cbpath); + /* specific xpath */ + if (xmldb_get0(h, db, YB_MODULE, nsc, xpath2?xpath2:"/", 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + break; case CONTENT_NONCONFIG: /* state data only */ - if ((xret = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)/* Only top tree */ - goto done; - break; + if ((xret = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)/* Only top tree */ + goto done; + break; }/* switch content */ if (list_config){ #ifdef LIST_PAGINATION_REMAINING - /* Get total/remaining - * XXX: Works only for cache - */ - if ((xcache = xmldb_cache_get(h, db)) != NULL){ - if (xpath_count(xcache, nsc, xpath, &total) < 0) - goto done; - if (total >= (offset + limit)) - remaining = total - (offset + limit); - } + /* Get total/remaining + * XXX: Works only for cache + */ + if ((xcache = xmldb_cache_get(h, db)) != NULL){ + if (xpath_count(xcache, nsc, xpath, &total) < 0) + goto done; + if (total >= (offset + limit)) + remaining = total - (offset + limit); + } #endif } else {/* Check if running locked (by this session) */ - if ((iddb = xmldb_islocked(h, "running")) != 0 && - iddb == ce->ce_id) - locked = 1; - else - locked = 0; - if ((ret = clixon_pagination_cb_call(h, xpath, locked, - offset, limit, - xret)) < 0) - goto done; - if (ret == 0){ - if ((cberr = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* error reason should be in clicon_err_reason */ - cprintf(cberr, "Internal error, pagination state callback invalid return : %s", - clicon_err_reason); - if (netconf_operation_failed_xml(&xerr, "application", cbuf_get(cberr)) < 0) - goto done; - if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) - goto done; - goto ok; - } + if ((iddb = xmldb_islocked(h, "running")) != 0 && + iddb == ce->ce_id) + locked = 1; + else + locked = 0; + if ((ret = clixon_pagination_cb_call(h, xpath, locked, + offset, limit, + xret)) < 0) + goto done; + if (ret == 0){ + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* error reason should be in clicon_err_reason */ + cprintf(cberr, "Internal error, pagination state callback invalid return : %s", + clicon_err_reason); + if (netconf_operation_failed_xml(&xerr, "application", cbuf_get(cberr)) < 0) + goto done; + if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) + goto done; + goto ok; + } - /* System makes the binding */ - if ((ret = xml_bind_yang(xret, YB_MODULE, yspec, &xerr)) < 0) - goto done; - if (ret == 0){ - if (clicon_debug_get() && xret) - clicon_log_xml(LOG_DEBUG, xret, "Yang bind pagination state"); - if (clixon_netconf_internal_error(xerr, - ". Internal error, state callback returned invalid XML", - NULL) < 0) - goto done; - if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) - goto done; - goto ok; - } + /* System makes the binding */ + if ((ret = xml_bind_yang(xret, YB_MODULE, yspec, &xerr)) < 0) + goto done; + if (ret == 0){ + if (clicon_debug_get() && xret) + clicon_log_xml(LOG_DEBUG, xret, "Yang bind pagination state"); + if (clixon_netconf_internal_error(xerr, + ". Internal error, state callback returned invalid XML", + NULL) < 0) + goto done; + if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) + goto done; + goto ok; + } } if (with_defaults(xml_parent(xe), xret) < 0) - goto done; + goto done; if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) - goto done; + goto done; /* Help function to filter out anything that is outside of xpath */ if (filter_xpath_again(h, yspec, xret, xvec, xlen, xpath, nsc) < 0) - goto done; + goto done; #ifdef LIST_PAGINATION_REMAINING /* Add remaining attribute Sec 3.1.5: Any list or leaf-list that is limited includes, on the first element in the result set, a metadata value [RFC7952] called "remaining"*/ if (limit && x1){ - cxobj *xa; - cbuf *cba = NULL; + cxobj *xa; + cbuf *cba = NULL; - /* Add remaining attribute */ - if ((xa = xml_new("remaining", x1, CX_ATTR)) == NULL) - goto done; - if ((cba = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cba, "%u", remaining); - if (xml_value_set(xa, cbuf_get(cba)) < 0) - goto done; - if (xml_prefix_set(xa, "cp") < 0) - goto done; - if (xmlns_set(x1, "cp", "http://clicon.org/clixon-netconf-list-pagination") < 0) - goto done; - if (cba) - cbuf_free(cba); + /* Add remaining attribute */ + if ((xa = xml_new("remaining", x1, CX_ATTR)) == NULL) + goto done; + if ((cba = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cba, "%u", remaining); + if (xml_value_set(xa, cbuf_get(cba)) < 0) + goto done; + if (xml_prefix_set(xa, "cp") < 0) + goto done; + if (xmlns_set(x1, "cp", "http://clicon.org/clixon-netconf-list-pagination") < 0) + goto done; + if (cba) + cbuf_free(cba); } #endif /* LIST_PAGINATION_REMAINING */ if (get_nacm_and_reply(h, xret, xvec, xlen, xpath, nsc, username, depth, cbret) < 0) - goto done; + goto done; ok: retval = 0; done: if (xvec) - free(xvec); + free(xvec); if (cbmsg) - cbuf_free(cbmsg); + cbuf_free(cbmsg); if (cbpath) - cbuf_free(cbpath); + cbuf_free(cbpath); if (xerr) - xml_free(xerr); + xml_free(xerr); if (cberr) - cbuf_free(cberr); + cbuf_free(cberr); if (xret) - xml_free(xret); + xml_free(xret); return retval; } @@ -833,12 +833,12 @@ get_list_pagination(clicon_handle h, */ static int get_common(clicon_handle h, - struct client_entry *ce, - cxobj *xe, - netconf_content content, - char *db, - cbuf *cbret - ) + struct client_entry *ce, + cxobj *xe, + netconf_content content, + char *db, + cbuf *cbret + ) { int retval = -1; cxobj *xfilter; @@ -864,103 +864,103 @@ get_common(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); username = clicon_username_get(h); if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec9"); - goto done; + clicon_err(OE_YANG, ENOENT, "No yang spec9"); + goto done; } if ((xfilter = xml_find(xe, "filter")) != NULL){ - if ((xpath0 = xml_find_value(xfilter, "select"))==NULL) - xpath0 = "/"; - /* Create namespace context for xpath from - * The set of namespace declarations are those in scope on the - * element. - */ - else - if (xml_nsctx_node(xfilter, &nsc0) < 0) - goto done; - if ((ret = xpath2canonical(xpath0, nsc0, yspec, &xpath, &nsc, &cbreason)) < 0) - goto done; - if (ret == 0){ - if (netconf_bad_attribute(cbret, "application", - "select", cbuf_get(cbreason)) < 0) - goto done; - goto ok; - } + if ((xpath0 = xml_find_value(xfilter, "select"))==NULL) + xpath0 = "/"; + /* Create namespace context for xpath from + * The set of namespace declarations are those in scope on the + * element. + */ + else + if (xml_nsctx_node(xfilter, &nsc0) < 0) + goto done; + if ((ret = xpath2canonical(xpath0, nsc0, yspec, &xpath, &nsc, &cbreason)) < 0) + goto done; + if (ret == 0){ + if (netconf_bad_attribute(cbret, "application", + "select", cbuf_get(cbreason)) < 0) + goto done; + goto ok; + } } /* Clixon extensions: depth */ if ((attr = xml_find_value(xe, "depth")) != NULL){ - if ((ret = parse_int32(attr, &depth, &reason)) < 0){ - clicon_err(OE_XML, errno, "parse_int32"); - goto done; - } - if (ret == 0){ - if (netconf_bad_attribute(cbret, "application", - "depth", "Unrecognized value of depth attribute") < 0) - goto done; - goto ok; - } + if ((ret = parse_int32(attr, &depth, &reason)) < 0){ + clicon_err(OE_XML, errno, "parse_int32"); + goto done; + } + if (ret == 0){ + if (netconf_bad_attribute(cbret, "application", + "depth", "Unrecognized value of depth attribute") < 0) + goto done; + goto ok; + } } /* Check if list pagination */ if ((xfind = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL) - list_pagination = 1; + list_pagination = 1; /* Sanity check for list pagination: path must be a list/leaf-list, if it is, * check config/state */ if (list_pagination){ - if (get_list_pagination(h, ce, - xfind, - content, db, - depth, yspec, xpath, nsc, username, - cbret) < 0) - goto done; - goto ok; + if (get_list_pagination(h, ce, + xfind, + content, db, + depth, yspec, xpath, nsc, username, + cbret) < 0) + goto done; + goto ok; } /* Read configuration */ switch (content){ case CONTENT_CONFIG: /* config data only */ - /* specific xpath */ - if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) { - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); - if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) - goto done; - goto ok; - } - break; + /* specific xpath */ + if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + break; case CONTENT_ALL: /* both config and state */ case CONTENT_NONCONFIG: /* state data only */ - if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ - /* Whole config tree, for validate debug */ - if (xmldb_get0(h, "running", YB_MODULE, nsc, NULL, 1, &xret, NULL, NULL) < 0) { - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); - if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) - goto done; - goto ok; - } - } - else if (content == CONTENT_ALL){ - /* specific xpath */ - if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) { - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); - if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) - goto done; - goto ok; - } - } - /* CONTENT_NONCONFIG */ - else if ((xret = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)/* Only top tree */ - goto done; - break; + if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ + /* Whole config tree, for validate debug */ + if (xmldb_get0(h, "running", YB_MODULE, nsc, NULL, 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + } + else if (content == CONTENT_ALL){ + /* specific xpath */ + if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + } + /* CONTENT_NONCONFIG */ + else if ((xret = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)/* Only top tree */ + goto done; + break; }/* switch content */ /* If not only config, * get state data from plugins as defined by plugin_statedata(), if any @@ -968,83 +968,83 @@ get_common(clicon_handle h, /* Read state */ switch (content){ case CONTENT_CONFIG: /* config data only */ - break; + break; case CONTENT_ALL: /* both config and state */ case CONTENT_NONCONFIG: /* state data only */ - if ((ret = get_client_statedata(h, xpath?xpath:"/", nsc, &xret)) < 0) - goto done; - if (ret == 0){ /* Error from callback (error in xret) */ - if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) - goto done; - goto ok; - } - break; + if ((ret = get_client_statedata(h, xpath?xpath:"/", nsc, &xret)) < 0) + goto done; + if (ret == 0){ /* Error from callback (error in xret) */ + if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) + goto done; + goto ok; + } + break; } if (with_defaults(xe, xret) < 0) - goto done; + goto done; if (content != CONTENT_CONFIG && - clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ - /* Check XML by validating it. return internal error with error cause - * Primarily intended for user-supplied state-data. - * The whole config tree must be present in case the state data references config data - */ - if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0) - goto done; - if (ret > 0 && - (ret = xml_yang_validate_add(h, xret, &xerr)) < 0) - goto done; - if (ret == 0){ - if (clicon_debug_get()) - clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE"); - if (clixon_netconf_internal_error(xerr, - ". Internal error, state callback returned invalid XML", - NULL) < 0) - goto done; - if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) - goto done; - goto ok; - } + clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ + /* Check XML by validating it. return internal error with error cause + * Primarily intended for user-supplied state-data. + * The whole config tree must be present in case the state data references config data + */ + if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0) + goto done; + if (ret > 0 && + (ret = xml_yang_validate_add(h, xret, &xerr)) < 0) + goto done; + if (ret == 0){ + if (clicon_debug_get()) + clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE"); + if (clixon_netconf_internal_error(xerr, + ". Internal error, state callback returned invalid XML", + NULL) < 0) + goto done; + if (clixon_xml2cbuf(cbret, xerr, 0, 0, -1, 0) < 0) + goto done; + goto ok; + } } /* CLICON_VALIDATE_STATE_XML */ if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */ - /* Keep state data only, remove everything that is config. Note that state data - * may be a sub-part in a config tree, we need to traverse to find all - */ - if (xml_non_config_data(xret, NULL) < 0) - goto done; - if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0) - goto done; - if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) - goto done; + /* Keep state data only, remove everything that is config. Note that state data + * may be a sub-part in a config tree, we need to traverse to find all + */ + if (xml_non_config_data(xret, NULL) < 0) + goto done; + if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0) + goto done; + if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) + goto done; } if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) - goto done; + goto done; if (filter_xpath_again(h, yspec, xret, xvec, xlen, xpath, nsc) < 0) - goto done; + goto done; if (get_nacm_and_reply(h, xret, xvec, xlen, xpath, nsc, username, depth, cbret) < 0) - goto done; + goto done; ok: retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xvec) - free(xvec); + free(xvec); if (xret) - xml_free(xret); + xml_free(xret); if (cbreason) - cbuf_free(cbreason); + cbuf_free(cbreason); if (nsc0) - xml_nsctx_free(nsc0); + xml_nsctx_free(nsc0); if (nsc) - xml_nsctx_free(nsc); + xml_nsctx_free(nsc); if (cbmsg) - cbuf_free(cbmsg); + cbuf_free(cbmsg); if (reason) - free(reason); + free(reason); if (xerr) - xml_free(xerr); + xml_free(xerr); if (xpath) - free(xpath); + free(xpath); return retval; } @@ -1061,18 +1061,18 @@ get_common(clicon_handle h, */ int from_client_get_config(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { int retval = -1; char *db; struct client_entry *ce = (struct client_entry *)arg; if ((db = netconf_db_find(xe, "source")) == NULL){ - clicon_err(OE_XML, 0, "db not found"); - goto done; + clicon_err(OE_XML, 0, "db not found"); + goto done; } retval = get_common(h, ce, xe, CONTENT_CONFIG, db, cbret); done: @@ -1093,10 +1093,10 @@ from_client_get_config(clicon_handle h, */ int from_client_get(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { netconf_content content = CONTENT_ALL; char *attr; @@ -1104,6 +1104,6 @@ from_client_get(clicon_handle h, /* Clixon extensions: content */ if ((attr = xml_find_value(xe, "content")) != NULL) - content = netconf_content_str2int(attr); + content = netconf_content_str2int(attr); return get_common(h, ce, xe, content, "running", cbret); } diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ac8bc69d..7de8ac41 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -101,29 +101,30 @@ backend_terminate(clicon_handle h) clicon_debug(1, "%s", __FUNCTION__); if ((ss = clicon_socket_get(h)) != -1) - close(ss); + close(ss); /* Disconnect datastore */ xmldb_disconnect(h); /* Clear module state caches */ if ((x = clicon_modst_cache_get(h, 0)) != NULL) - xml_free(x); + xml_free(x); if ((x = clicon_modst_cache_get(h, 1)) != NULL) - xml_free(x); + xml_free(x); /* Free changelog */ if ((x = clicon_xml_changelog_get(h)) != NULL) - xml_free(x); + xml_free(x); if ((yspec = clicon_dbspec_yang(h)) != NULL) - ys_free(yspec); + ys_free(yspec); if ((yspec = clicon_config_yang(h)) != NULL) - ys_free(yspec); + ys_free(yspec); if ((yspec = clicon_nacm_ext_yang(h)) != NULL) - ys_free(yspec); + ys_free(yspec); if ((nsctx = clicon_nsctx_global_get(h)) != NULL) - cvec_free(nsctx); + cvec_free(nsctx); if ((x = clicon_nacm_ext(h)) != NULL) - xml_free(x); + xml_free(x); if ((x = clicon_conf_xml(h)) != NULL) - xml_free(x); + xml_free(x); + confirmed_commit_free(h); stream_publish_exit(); /* Delete all plugins, RPC callbacks, and upgrade callbacks */ clixon_plugin_module_exit(h); @@ -133,9 +134,9 @@ backend_terminate(clicon_handle h) xpath_optimize_exit(); clixon_pagination_free(h); if (pidfile) - unlink(pidfile); + unlink(pidfile); if (sockfamily==AF_UNIX && lstat(sockpath, &st) == 0) - unlink(sockpath); + unlink(sockpath); backend_handle_exit(h); /* Also deletes streams. Cannot use h after this. */ clixon_event_exit(); clicon_debug(1, "%s done", __FUNCTION__); @@ -152,8 +153,8 @@ backend_sig_term(int arg) static int i=0; if (i++ == 0) - clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d", - __PROGRAM__, __FUNCTION__, getpid(), arg); + clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d", + __PROGRAM__, __FUNCTION__, getpid(), arg); clixon_exit_set(1); /* checked in clixon_event_loop() */ } @@ -180,12 +181,12 @@ backend_server_socket(clicon_handle h) /* Open control socket */ if ((ss = backend_socket_init(h)) < 0) - return -1; + return -1; /* ss is a server socket that the clients connect to. The callback therefore accepts clients on ss */ if (clixon_event_reg_fd(ss, backend_accept_client, h, "server socket") < 0) { - close(ss); - return -1; + close(ss); + return -1; } return ss; } @@ -204,63 +205,63 @@ nacm_load_external(clicon_handle h) filename = clicon_option_str(h, "CLICON_NACM_FILE"); if (filename == NULL || strlen(filename)==0){ - clicon_err(OE_UNIX, errno, "CLICON_NACM_FILE not set in NACM external mode"); - goto done; + clicon_err(OE_UNIX, errno, "CLICON_NACM_FILE not set in NACM external mode"); + goto done; } if (stat(filename, &st) < 0){ - clicon_err(OE_UNIX, errno, "%s", filename); - goto done; + clicon_err(OE_UNIX, errno, "%s", filename); + goto done; } if (!S_ISREG(st.st_mode)){ - clicon_err(OE_UNIX, 0, "%s is not a regular file", filename); - goto done; + clicon_err(OE_UNIX, 0, "%s is not a regular file", filename); + goto done; } if ((f = fopen(filename, "r")) == NULL) { - clicon_err(OE_UNIX, errno, "configure file: %s", filename); - return -1; + clicon_err(OE_UNIX, errno, "configure file: %s", filename); + return -1; } if ((yspec = yspec_new()) == NULL) - goto done; + goto done; if (yang_spec_parse_module(h, "ietf-netconf-acm", NULL, yspec) < 0) - goto done; + goto done; /* Read configfile */ if (clixon_xml_parse_file(f, YB_MODULE, yspec, &xt, NULL) < 0) - goto done; + goto done; if (xt == NULL){ - clicon_err(OE_XML, 0, "No xml tree in %s", filename); - goto done; + clicon_err(OE_XML, 0, "No xml tree in %s", filename); + goto done; } if (clicon_nacm_ext_yang_set(h, yspec) < 0) - goto done; + goto done; if (clicon_nacm_ext_set(h, xt) < 0) - goto done; + goto done; retval = 0; done: if (f) - fclose(f); + fclose(f); return retval; } static int xmldb_drop_priv(clicon_handle h, - const char *db, - uid_t uid, - gid_t gid) + const char *db, + uid_t uid, + gid_t gid) { int retval = -1; char *filename = NULL; if (xmldb_db2file(h, db, &filename) < 0) - goto done; + goto done; if (chown(filename, uid, gid) < 0){ - clicon_err(OE_UNIX, errno, "chown"); - goto done; + clicon_err(OE_UNIX, errno, "chown"); + goto done; } retval = 0; done: if (filename) - free(filename); + free(filename); return retval; } @@ -279,7 +280,8 @@ xmldb_drop_priv(clicon_handle h, */ static int check_drop_priv(clicon_handle h, - gid_t gid) + gid_t gid, + yang_stmt *yspec) { int retval = -1; uid_t uid; @@ -287,69 +289,76 @@ check_drop_priv(clicon_handle h, enum priv_mode_t priv_mode = PM_NONE; char *backend_user = NULL; - /* Get privileges mode (for dropping privileges) */ if ((priv_mode = clicon_backend_privileges_mode(h)) == PM_NONE) - goto ok; + goto ok; /* From here, drop privileges */ /* Check backend user exists */ if ((backend_user = clicon_backend_user(h)) == NULL){ - clicon_err(OE_DAEMON, EPERM, "Privileges cannot be dropped without specifying CLICON_BACKEND_USER\n"); - goto done; + clicon_err(OE_DAEMON, EPERM, "Privileges cannot be dropped without specifying CLICON_BACKEND_USER\n"); + goto done; } /* Get (wanted) new backend user id */ if (name2uid(backend_user, &newuid) < 0){ - clicon_err(OE_DAEMON, errno, "'%s' is not a valid user .\n", backend_user); - goto done; + clicon_err(OE_DAEMON, errno, "'%s' is not a valid user .\n", backend_user); + goto done; } /* get current backend userid, if already at this level OK */ if ((uid = getuid()) == newuid) - goto ok; + goto ok; if (uid != 0){ - clicon_err(OE_DAEMON, EPERM, "Privileges can only be dropped from root user (uid is %u)\n", uid); - goto done; + clicon_err(OE_DAEMON, EPERM, "Privileges can only be dropped from root user (uid is %u)\n", uid); + goto done; } /* When dropping privileges, datastores are created if they do not exist. * But when drops are not made, datastores are created on demand. * XXX: move the creation to top-level so they are always created at init? */ if (xmldb_exists(h, "running") != 1) - if (xmldb_create(h, "running") < 0) - goto done; + if (xmldb_create(h, "running") < 0) + goto done; if (xmldb_drop_priv(h, "running", newuid, gid) < 0) - goto done; + goto done; if (xmldb_exists(h, "candidate") != 1) - if (xmldb_create(h, "candidate") < 0) - goto done; + if (xmldb_create(h, "candidate") < 0) + goto done; if (xmldb_drop_priv(h, "candidate", newuid, gid) < 0) - goto done; - if (xmldb_exists(h, "startup") != 1) - if (xmldb_create(h, "startup") < 0) - goto done; - if (xmldb_drop_priv(h, "startup", newuid, gid) < 0) - goto done; - + goto done; + if (if_feature(yspec, "ietf-netconf", "startup")) { + if (xmldb_exists(h, "startup") != 1) + if (xmldb_create(h, "startup") < 0) + goto done; + if (xmldb_drop_priv(h, "startup", newuid, gid) < 0) + goto done; + } + if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { + if (xmldb_exists(h, "rollback") != 1) + if (xmldb_create(h, "rollback") < 0) + goto done; + if (xmldb_drop_priv(h, "rollback", newuid, gid) < 0) + goto done; + } if (setgid(gid) == -1) { - clicon_err(OE_DAEMON, errno, "setgid %d", gid); - goto done; + clicon_err(OE_DAEMON, errno, "setgid %d", gid); + goto done; } switch (priv_mode){ case PM_DROP_PERM: - if (drop_priv_perm(newuid) < 0) - goto done; - /* Verify you cannot regain root privileges */ - if (setuid(0) != -1){ - clicon_err(OE_DAEMON, EPERM, "Could regain root privilieges"); - goto done; - } - break; + if (drop_priv_perm(newuid) < 0) + goto done; + /* Verify you cannot regain root privileges */ + if (setuid(0) != -1){ + clicon_err(OE_DAEMON, EPERM, "Could regain root privilieges"); + goto done; + } + break; case PM_DROP_TEMP: - if (drop_priv_temp(newuid) < 0) - goto done; - break; + if (drop_priv_temp(newuid) < 0) + goto done; + break; case PM_NONE: - break; /* catched above */ + break; /* catched above */ } ok: retval = 0; @@ -374,25 +383,25 @@ check_drop_priv(clicon_handle h, */ static int ret2status(int ret, - enum startup_status *status) + enum startup_status *status) { int retval = -1; switch (ret){ case -1: - if (clicon_suberrno != XMLPARSE_ERRNO) - goto done; - clicon_err_reset(); - *status = STARTUP_ERR; - break; + if (clicon_suberrno != XMLPARSE_ERRNO) + goto done; + clicon_err_reset(); + *status = STARTUP_ERR; + break; case 0: - *status = STARTUP_INVALID; - break; + *status = STARTUP_INVALID; + break; case 1: - *status = STARTUP_OK; - break; + *status = STARTUP_OK; + break; default: - clicon_err(OE_CFG, EINVAL, "No such retval %d", retval); + clicon_err(OE_CFG, EINVAL, "No such retval %d", retval); } /* switch */ retval = 0; done: @@ -403,7 +412,7 @@ ret2status(int ret, /* Debug timer */ int backend_timer_setup(int fd, - void *arg) + void *arg) { int retval = -1; clicon_handle h = (clicon_handle)arg; @@ -421,10 +430,10 @@ backend_timer_setup(int fd, /* Initiate new timer */ timeradd(&now, &t1, &t); if (clixon_event_reg_timeout(t, - backend_timer_setup, /* this function */ - h, /* clicon handle */ - "backend timer setup") < 0) - goto done; + backend_timer_setup, /* this function */ + h, /* clicon handle */ + "backend timer setup") < 0) + goto done; retval = 0; done: return retval; @@ -443,35 +452,35 @@ usage(clicon_handle h, char *group = clicon_sock_group(h); fprintf(stderr, "usage:%s *\n" - "where options are\n" + "where options are\n" "\t-h\t\tHelp\n" - "\t-D \tDebug level\n" - "\t-f \tClixon config file\n" - "\t-E \tExtra configuration file directory\n" - "\t-l > \tLog on (s)yslog, std(e)rr, std(o)ut, (n)one or (f)ile (syslog is default)\n" - "\t-d \tSpecify backend plugin directory (default: %s)\n" - "\t-p \tAdd Yang directory path (see CLICON_YANG_DIR)\n" - "\t-b \tSpecify datastore directory\n" - "\t-F\t\tRun in foreground, do not run as daemon\n" - "\t-z\t\tKill other backend daemon and exit\n" - "\t-a UNIX|IPv4|IPv6 Internal backend socket family\n" - "\t-u \tInternal socket domain path or IP addr (see -a)(default: %s)\n" - "\t-P \tPid filename (default: %s)\n" - "\t-1\t\tRun once and then quit (dont wait for events)\n" - "\t-s \tSpecify backend startup mode: none|startup|running|init)\n" - "\t-c \tLoad extra XML configuration file, but do not commit.\n" - "\t-q \t\tQuit startup directly after upgrading and print result on stdout\n" - "\t-U \tRun backend daemon as this user AND drop privileges permanently\n" - "\t-g \tClient membership required to this group (default: %s)\n" + "\t-D \tDebug level\n" + "\t-f \tClixon config file\n" + "\t-E \tExtra configuration file directory\n" + "\t-l > \tLog on (s)yslog, std(e)rr, std(o)ut, (n)one or (f)ile (syslog is default)\n" + "\t-d \tSpecify backend plugin directory (default: %s)\n" + "\t-p \tAdd Yang directory path (see CLICON_YANG_DIR)\n" + "\t-b \tSpecify datastore directory\n" + "\t-F\t\tRun in foreground, do not run as daemon\n" + "\t-z\t\tKill other backend daemon and exit\n" + "\t-a UNIX|IPv4|IPv6 Internal backend socket family\n" + "\t-u \tInternal socket domain path or IP addr (see -a)(default: %s)\n" + "\t-P \tPid filename (default: %s)\n" + "\t-1\t\tRun once and then quit (dont wait for events)\n" + "\t-s \tSpecify backend startup mode: none|startup|running|init)\n" + "\t-c \tLoad extra XML configuration file, but do not commit.\n" + "\t-q \t\tQuit startup directly after upgrading and print result on stdout\n" + "\t-U \tRun backend daemon as this user AND drop privileges permanently\n" + "\t-g \tClient membership required to this group (default: %s)\n" - "\t-y \tLoad yang spec file (override yang main module)\n" - "\t-o \"