From f0c82017625312ea099b0a6604339591509cc6f6 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 3 Oct 2022 10:29:28 +0200 Subject: [PATCH 01/22] Added warning if modstate is not present in datastore if is set --- CHANGELOG.md | 4 ++++ apps/backend/backend_commit.c | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27cb1f34..d7379f90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,10 @@ Developers may need to change their code * C API changes * Added `defaults` parameter to `clicon_rpc_get_pageable_list()` +### Minor features + +* Added warning if modstate is not present in datastore if `CLICON_XMLDB_MODSTATE` is set. + ### Corrected Bugs * Fixed: [message-id present on netconf app "hello"](https://github.com/clicon/clixon/issues/369) diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 969ba75f..d6326d3c 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -200,6 +200,11 @@ startup_common(clicon_handle h, 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; From 4a5e0c1832a499902ae75c4a56280ab94ef1ff54 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 3 Oct 2022 10:33:07 +0200 Subject: [PATCH 02/22] C API: Exposed clicon_log_str() --- lib/clixon/clixon_log.h | 1 + lib/src/clixon_log.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/clixon/clixon_log.h b/lib/clixon/clixon_log.h index a8494810..9b587b55 100644 --- a/lib/clixon/clixon_log.h +++ b/lib/clixon/clixon_log.h @@ -58,6 +58,7 @@ int clicon_log_file(char *filename); int clicon_log_string_limit_set(size_t sz); size_t clicon_log_string_limit_get(void); int clicon_get_logflags(void); +int clicon_log_str(int level, char *msg); int clicon_log(int level, const char *format, ...) __attribute__ ((format (printf, 2, 3))); int clicon_debug(int dbglevel, const char *format, ...) __attribute__ ((format (printf, 2, 3))); int clicon_debug_init(int dbglevel, FILE *f); diff --git a/lib/src/clixon_log.c b/lib/src/clixon_log.c index 19444b33..63bddb01 100644 --- a/lib/src/clixon_log.c +++ b/lib/src/clixon_log.c @@ -238,7 +238,7 @@ slogtime(void) * @note syslog makes its own filtering, but if log to stderr we do it here * @see clicon_debug */ -static int +int clicon_log_str(int level, char *msg) { From 954e5d56fd6fa4c885e492019bd4992a2907af6d Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 3 Oct 2022 18:39:30 +0200 Subject: [PATCH 03/22] Fixed: [unneeded trailing zero character on SNMP strings](https://github.com/clicon/clixon/issues/367) --- CHANGELOG.md | 1 + apps/snmp/snmp_lib.c | 4 +++- test/test_snmp_ifmib.sh | 4 ++-- test/test_snmp_system.sh | 14 +++++++------- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7379f90..c2075914 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Developers may need to change their code ### Corrected Bugs +* 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/snmp/snmp_lib.c b/apps/snmp/snmp_lib.c index cbd2eabc..21aa107c 100644 --- a/apps/snmp/snmp_lib.c +++ b/apps/snmp/snmp_lib.c @@ -457,7 +457,9 @@ type_yang2asn1(yang_stmt *ys, if (yang_extension_value(yrp, "display-hint", IETF_YANG_SMIV2_NS, NULL, &display_hint) < 0) goto done; /* RFC2578/2579 but maybe all strings with display-hint should use this, eg exist>0? */ - if (display_hint && strcmp(display_hint, "255t")==0) + if (display_hint && + (strcmp(display_hint, "255a")==0 || + strcmp(display_hint, "255t")==0)) at = CLIXON_ASN_FIXED_STRING; } if (asn1_type) diff --git a/test/test_snmp_ifmib.sh b/test/test_snmp_ifmib.sh index 98ba9890..ee703589 100755 --- a/test/test_snmp_ifmib.sh +++ b/test/test_snmp_ifmib.sh @@ -370,8 +370,8 @@ expectpart "$($snmptable IF-MIB::ifTable)" 0 "Test 2" "1400" "1000" "11:22:33:44 new "Walk the walk..." expectpart "$($snmpwalk IF-MIB::ifTable)" 0 "IF-MIB::ifIndex.1 = INTEGER: 1" \ "IF-MIB::ifIndex.2 = INTEGER: 2" \ - "IF-MIB::ifDescr.1 = STRING: Test." \ - "IF-MIB::ifDescr.2 = STRING: Test 2." \ + "IF-MIB::ifDescr.1 = STRING: Test" \ + "IF-MIB::ifDescr.2 = STRING: Test 2" \ "IF-MIB::ifType.1 = INTEGER: ethernetCsmacd(6)" \ "IF-MIB::ifType.2 = INTEGER: ethernetCsmacd(6)" \ "IF-MIB::ifMtu.1 = INTEGER: 1500" \ diff --git a/test/test_snmp_system.sh b/test/test_snmp_system.sh index 922dbaf6..fdfdf440 100755 --- a/test/test_snmp_system.sh +++ b/test/test_snmp_system.sh @@ -217,19 +217,19 @@ expectpart "$($snmptable $OID_ORTABLE)" 0 ".*Entry 2 description.*" "IF-MIB::ifT expectpart "$($snmptable $NAME_ORTABLE)" 0 ".*Entry 1 description.*" "IP-MIB::ip" "1:7:10:33.44" expectpart "$($snmptable $NAME_ORTABLE)" 0 ".*Entry 2 description.*" "IF-MIB::ifTable" "129:20:58:31.11" -new "Walk the tabbles..." -expectpart "$($snmpwalkstr system)" 0 "SNMPv2-MIB::sysDescr = STRING: System description." \ +new "Walk the tables..." +expectpart "$($snmpwalkstr system)" 0 "SNMPv2-MIB::sysDescr = STRING: System description" \ "SNMPv2-MIB::sysUpTime = Timeticks: (11223344) 1 day, 7:10:33.44" \ - "SNMPv2-MIB::sysContact = STRING: clixon@clicon.com." \ - "SNMPv2-MIB::sysName = STRING: Test." \ - "SNMPv2-MIB::sysLocation = STRING: Clixon HQ." \ + "SNMPv2-MIB::sysContact = STRING: clixon@clicon.com" \ + "SNMPv2-MIB::sysName = STRING: Test" \ + "SNMPv2-MIB::sysLocation = STRING: Clixon HQ" \ "SNMPv2-MIB::sysServices = INTEGER: 72" \ "SNMPv2-MIB::sysORIndex.1 = INTEGER: 1" \ "SNMPv2-MIB::sysORIndex.2 = INTEGER: 2" \ "SNMPv2-MIB::sysORID.1 = OID: IP-MIB::ip" \ "SNMPv2-MIB::sysORID.2 = OID: IF-MIB::ifTable" \ - "SNMPv2-MIB::sysORDescr.1 = STRING: Entry 1 description." \ - "SNMPv2-MIB::sysORDescr.2 = STRING: Entry 2 description." \ + "SNMPv2-MIB::sysORDescr.1 = STRING: Entry 1 description" \ + "SNMPv2-MIB::sysORDescr.2 = STRING: Entry 2 description" \ "SNMPv2-MIB::sysORUpTime.1 = Timeticks: (11223344) 1 day, 7:10:33.44" \ "SNMPv2-MIB::sysORUpTime.2 = Timeticks: (1122111111) 129 days, 20:58:31.11" From 284316b6465af35aa05c47d953464b20054de275 Mon Sep 17 00:00:00 2001 From: Phil Heller Date: Fri, 30 Sep 2022 19:17:13 -0600 Subject: [PATCH 04/22] Initial implementation of NETCONF confirmed-commit --- apps/backend/Makefile.in | 8 +- apps/backend/backend_client.c | 49 ++- apps/backend/backend_commit.c | 571 ++++++++++++++++++++++++++- apps/backend/backend_failsafe.c | 105 +++++ apps/backend/backend_failsafe.h | 42 ++ apps/backend/backend_main.c | 7 + apps/backend/backend_startup.c | 41 +- apps/backend/clixon_backend_commit.h | 33 +- apps/cli/cli_common.c | 17 +- example/main/example.xml.in | 1 + example/main/example_cli.cli | 10 +- lib/clixon/clixon_datastore.h | 1 + lib/clixon/clixon_proto_client.h | 3 +- lib/src/clixon_datastore.c | 77 +++- lib/src/clixon_proto_client.c | 81 +++- test/test_confirmed_commit.sh | 355 +++++++++++++++++ 16 files changed, 1375 insertions(+), 26 deletions(-) create mode 100644 apps/backend/backend_failsafe.c create mode 100644 apps/backend/backend_failsafe.h create mode 100755 test/test_confirmed_commit.sh diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 9bfa8d92..5e3a782a 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -87,6 +87,10 @@ INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/inclu # Name of application APPL = clixon_backend +# Source / objects called from plugin and otherwise +COMMONSRC = backend_failsafe.c +COMMONOBJ = $(COMMONSRC:.c=.o) + # Not accessible from plugin APPSRC = backend_main.c APPSRC += backend_socket.c @@ -94,14 +98,14 @@ APPSRC += backend_client.c APPSRC += backend_get.c APPSRC += backend_plugin_restconf.c # Pseudo plugin for restconf daemon APPSRC += backend_startup.c -APPOBJ = $(APPSRC:.c=.o) +APPOBJ = $(APPSRC:.c=.o) $(COMMONOBJ) # Accessible from plugin LIBSRC = clixon_backend_transaction.c LIBSRC += clixon_backend_handle.c LIBSRC += backend_commit.c LIBSRC += backend_plugin.c -LIBOBJ = $(LIBSRC:.c=.o) +LIBOBJ = $(LIBSRC:.c=.o) $(COMMONOBJ) # Name of lib MYNAME = clixon_backend diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 7164bca7..94866a9b 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -159,6 +159,8 @@ 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 @@ -168,6 +170,33 @@ backend_client_rm(clicon_handle h, 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 == 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); + + if (myid == confirmed_commit.session_id) { + 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(); + do_rollback(h, NULL); + } + } + } clicon_debug(1, "%s", __FUNCTION__); /* for all streams: XXX better to do it top-level? */ @@ -187,7 +216,10 @@ backend_client_rm(clicon_handle h, } 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 @@ -423,7 +455,20 @@ from_client_edit_config(clicon_handle h, 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 */ + // TODO: 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 ((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"); diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index d6326d3c..658e5f72 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -70,6 +70,15 @@ #include "backend_handle.h" #include "clixon_backend_commit.h" #include "backend_client.h" +#include "backend_failsafe.h" + +/* a global instance of the confirmed_commit struct for reference throughout the procedure */ +struct confirmed_commit confirmed_commit = { + .state = INACTIVE, +}; + +/* flag to carry indication if an RPC bearing satisfies conditions to cancel the rollback timer */ +static int is_valid_confirming_commit = 0; /*! Key values are checked for validity independent of user-defined callbacks * @@ -634,6 +643,240 @@ candidate_validate(clicon_handle h, goto done; } +/*! Cancel a scheduled rollback as previously registered by schedule_rollback_event() + * + * @retval 0 Rollback event successfully cancelled + * @retval -1 No Rollback event was found + */ +int +cancel_rollback_event() +{ + int retval; + + if ((retval = clixon_event_unreg_timeout(confirmed_commit.fn, confirmed_commit.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_EMERG, "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 = rollback_fn; + confirmed_commit.arg = 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; +} + +/*! 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[out] cbret Return xml tree, eg ..., ; the confirming-commit MUST now be accompanied by a matching + * + */ + confirmed_commit.state = 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, confirm_timeout); + } else { + /* The client did not pass a value for and therefore any subsequent confirming-commit must be + * issued within the same session. + */ + if (clicon_session_id_get(h, &confirmed_commit.session_id) < 0) { + clicon_err(OE_DAEMON, 0, + "an ephemeral confirmed-commit was issued, but the session-id could not be determined"); + if (netconf_operation_failed(cbret, "application", + "there was an error while performing the confirmed-commit") < 0) + clicon_err(OE_DAEMON, 0, "there was an error sending a netconf response to the client"); + goto done; + }; + confirmed_commit.state = 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, 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. + * + */ + + int 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 diff between candidate and running, then start a commit transaction * * The code reverts changes if the commit fails. But if the revert @@ -654,6 +897,7 @@ candidate_commit(clicon_handle h, transaction_data_t *td = NULL; int ret; cxobj *xret = NULL; + yang_stmt *yspec; /* 1. Start transaction */ if ((td = transaction_new()) == NULL) @@ -664,6 +908,27 @@ candidate_commit(clicon_handle h, */ if ((ret = validate_common(h, db, td, &xret)) < 0) goto done; + + /* If the confirmed-commit feature is enabled, execute phase 2: + * - If a valid confirming-commit, cancel the rollback event + * - If a new confirmed-commit, schedule a new rollback event, otherwise + * - delete the rollback database + * + * Unless, however, this invocation of candidate_commit() was by way of a + * rollback event, in which case the timers are already cancelled and the + * caller will cleanup the rollback database. All that must be done here is + * to activate it. + */ + 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") + && confirmed_commit.state != ROLLBACK + && handle_confirmed_commit(h, cbret) < 0) + goto done; + if (ret == 0){ if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0) goto done; @@ -719,7 +984,178 @@ candidate_commit(clicon_handle h, retval = 0; goto done; } - + +/*! 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. + * + * @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) +{ + /* 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) + */ + uint8_t errstate = 0; + + int res = -1; + 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_EMERG, "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 == PERSISTENT && confirmed_commit.persist_id != NULL) { + free(confirmed_commit.persist_id); + confirmed_commit.persist_id = NULL; + } + + confirmed_commit.state = ROLLBACK; + if (candidate_commit(h, "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_EMERG, "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; + }; + + res = 0; + + done: + + confirmed_commit.state = INACTIVE; + if (errs) + *errs = errstate; + return res; +} + + +/*! 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] myid current client session-id + * @param[out] cbret Return xml tree, eg ..., matching the prior confirming-commit's */ + retval = 1; + break; + } else { + netconf_invalid_value(cbret, "protocol", "No such persist-id"); + clicon_log(LOG_INFO, + "a persistent confirmed-commit is in progress but the client issued a " + "confirming-commit with an incorrect persist-id"); + retval = 0; + break; + } + } else { + netconf_invalid_value(cbret, "protocol", "Persist-id not given"); + clicon_log(LOG_INFO, + "a persistent confirmed-commit is in progress but the client issued a confirming-commit" + "without a persist-id"); + retval = 0; + break; + } + case EPHEMERAL: + if (myid == confirmed_commit.session_id) { + /* the RPC lacked a , the prior confirming-commit lacked , and both were issued + * on the same session. + */ + retval = 1; + break; + } + + if (netconf_invalid_value(cbret, "protocol","confirming-commit not performed on originating session") < 0) { + clicon_err(OE_NETCONF, 0, "error sending response"); + break; + }; + + 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"); + retval = 0; + break; + + default: + clicon_debug(1, "commit-confirmed state !? %d", confirmed_commit.state); + retval = 0; + break; + } + + done: + return retval; +} + /*! Commit the candidate configuration as the device's new current configuration * * @param[in] h Clicon handle @@ -747,6 +1183,52 @@ from_client_commit(clicon_handle h, uint32_t iddb; cbuf *cbx = NULL; /* Assist cbuf */ int ret; + yang_stmt *yspec; + + /* Handle 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. + */ + 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")) { + confirmed_commit.xe = xe; + if ((is_valid_confirming_commit = check_valid_confirming_commit(h, myid, cbret)) < 0) + goto done; + + /* If is *not* present, this will conclude the confirmed-commit, so cancel the rollback. */ + if (xml_find_type(confirmed_commit.xe, NULL, "confirmed", CX_ELMNT) == NULL + && is_valid_confirming_commit) { + cancel_rollback_event(); + + if (confirmed_commit.state == PERSISTENT && confirmed_commit.persist_id != NULL) { + free(confirmed_commit.persist_id); + confirmed_commit.persist_id = NULL; + } + + confirmed_commit.state = INACTIVE; + + if (xmldb_delete(h, "rollback") < 0) + clicon_err(OE_DB, 0, "Error deleting the rollback configuration"); + + cprintf(cbret, "", NETCONF_BASE_NAMESPACE); + + goto ok; + } + } /* Check if target locked by other client */ iddb = xmldb_islocked(h, "running"); @@ -772,6 +1254,7 @@ from_client_commit(clicon_handle h, ok: retval = 0; done: + confirmed_commit.xe = NULL; if (cbx) cbuf_free(cbx); return retval; /* may be zero if we ignoring errors from commit */ @@ -832,6 +1315,8 @@ from_client_discard_changes(clicon_handle h, /*! 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: @@ -850,10 +1335,86 @@ from_client_cancel_commit(clicon_handle h, void *arg, void *regarg) { - int retval = -1; - retval = 0; - // done: - return retval; + cxobj *persist_id_xml; + char *persist_id = NULL; + uint32_t cur_session_id; + int retval = -1; + + if ((persist_id_xml = xml_find_type(xe, NULL, "persist-id", CX_ELMNT)) != NULL) { + /* persist == persist_id == NULL is legal */ + persist_id = xml_body(persist_id_xml); + } + + switch(confirmed_commit.state) { + case EPHEMERAL: + if (persist_id_xml != NULL) { + if (netconf_invalid_value(cbret, "protocol", "current confirmed-commit is not persistent") < 0) + goto netconf_response_error; + goto done; + } + + if (clicon_session_id_get(h, &cur_session_id) < 0) { + if (netconf_invalid_value(cbret, "application", "session-id was not set") < 0) + goto netconf_response_error; + goto done; + } + + if (cur_session_id != confirmed_commit.session_id) { + if (netconf_invalid_value(cbret, "protocol", "confirming-commit must be given within session that gave the confirmed-commit") < 0) + goto netconf_response_error; + goto done; + } + + goto rollback; + + case PERSISTENT: + if (persist_id_xml == NULL) { + if (netconf_invalid_value(cbret, "protocol", "persist-id is required") < 0) + goto netconf_response_error; + goto done; + } + + if (persist_id == confirmed_commit.persist_id || + (persist_id != NULL && confirmed_commit.persist_id != NULL + && strcmp(persist_id, confirmed_commit.persist_id) == 0)) { + goto rollback; + } + + if (netconf_invalid_value(cbret, "application", "a confirmed-commit with the given persist-id was not found") < 0) + goto netconf_response_error; + goto done; + + case INACTIVE: + if (netconf_invalid_value(cbret, "application", "no confirmed-commit is in progress") < 0) + goto netconf_response_error; + goto done; + + default: + clicon_err(OE_DAEMON, 0, "Unhandled confirmed-commit state"); + if (netconf_invalid_value(cbret, "application", "server error") < 0) + goto netconf_response_error; + goto done; + } + + /* all invalid conditions jump to done: and valid code paths jump to or fall through to here. */ + + rollback: + cancel_rollback_event(); + if ((retval = do_rollback(h, NULL)) < 0) { + if (netconf_operation_failed(cbret, "application", "rollback failed") < 0) + goto netconf_response_error; + } else { + cprintf(cbret, "", NETCONF_BASE_NAMESPACE); + retval = 0; + clicon_log(LOG_INFO, "a confirmed-commit has been cancelled by client request"); + } + goto done; + + netconf_response_error: + clicon_err(OE_DAEMON, 0, "failed to write netconf response"); + + done: + return retval; } /*! Validates the contents of the specified configuration. diff --git a/apps/backend/backend_failsafe.c b/apps/backend/backend_failsafe.c new file mode 100644 index 00000000..4c6f0966 --- /dev/null +++ b/apps/backend/backend_failsafe.c @@ -0,0 +1,105 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2021 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 ***** + + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include + +#include "clixon_backend_commit.h" +#include "backend_failsafe.h" + +/*! Reset running and start in failsafe mode. If no failsafe then quit. + 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, 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_failsafe.h b/apps/backend/backend_failsafe.h new file mode 100644 index 00000000..55e0dbf3 --- /dev/null +++ b/apps/backend/backend_failsafe.h @@ -0,0 +1,42 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2021 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 ***** + + */ + +#ifndef CLIXON_BACKEND_FAILSAFE_H +#define CLIXON_BACKEND_FAILSAFE_H +int load_failsafe(clicon_handle h, char *phase); + +#endif //CLIXON_BACKEND_FAILSAFE_H diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ac8bc69d..727a73c8 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -154,6 +154,10 @@ backend_sig_term(int arg) if (i++ == 0) clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d", __PROGRAM__, __FUNCTION__, getpid(), arg); + if (confirmed_commit.persist_id != NULL) { + free(confirmed_commit.persist_id); + confirmed_commit.persist_id = NULL; + } clixon_exit_set(1); /* checked in clixon_event_loop() */ } @@ -883,6 +887,9 @@ main(int argc, } switch (startup_mode){ case SM_INIT: /* Scratch running and start from empty */ + /* Delete any rollback database, if it exists */ + // TODO: xmldb_delete doesn't actually unlink; need to look at this + xmldb_delete(h, "rollback"); /* [Delete and] create running db */ if (xmldb_db_reset(h, "running") < 0) goto done; diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c index 0b065ae4..7f7a02c8 100644 --- a/apps/backend/backend_startup.c +++ b/apps/backend/backend_startup.c @@ -134,6 +134,7 @@ startup_mode_startup(clicon_handle h, { int retval = -1; int ret; + int db_exists; if (strcmp(db, "running")==0){ clicon_err(OE_FATAL, 0, "Invalid startup db: %s", db); @@ -144,7 +145,45 @@ startup_mode_startup(clicon_handle h, if (xmldb_create(h, db) < 0) /* diff */ return -1; } - if ((ret = startup_commit(h, db, cbret)) < 0) + + /* When a confirming-commit is issued, the confirmed-commit timeout + * callback is removed and then the rollback database is deleted. + * + * The presence of a rollback database means that before the rollback + * database was deleted, either clixon_backend crashed or the machine + * rebooted. + */ + yang_stmt *yspec = clicon_dbspec_yang(h); + if (if_feature(yspec, "ietf-netconf", "configmed-commit")) { + db_exists = xmldb_exists(h, "rollback"); + if (db_exists < 0) { + clicon_err(OE_DAEMON, 0, "Error checking for the existence of the rollback database"); + goto done; + } else if (db_exists == 1) { + ret = startup_commit(h, "rollback", cbret); + switch(ret) { + case -1: + case 0: + /* validation failed, cbret set */ + if ((ret = startup_commit(h, "failsafe", cbret)) < 0) + goto fail; + + /* Rename the errored rollback database so that it is not tried on a subsequent startup */ + xmldb_rename(h, db, NULL, ".error"); + + retval = 1; + goto done; + case 1: + /* validation ok */ + retval = 1; + xmldb_delete(h, "rollback"); + goto done; + default: + /* Unexpected response */ + goto fail; + } + } + } else if ((ret = startup_commit(h, db, cbret)) < 0) goto done; if (ret == 0) goto fail; diff --git a/apps/backend/clixon_backend_commit.h b/apps/backend/clixon_backend_commit.h index 8f630878..2cd1c1a6 100644 --- a/apps/backend/clixon_backend_commit.h +++ b/apps/backend/clixon_backend_commit.h @@ -39,9 +39,40 @@ #ifndef _CLIXON_BACKEND_COMMIT_H_ #define _CLIXON_BACKEND_COMMIT_H_ +#define ROLLBACK_NOT_APPLIED 1 +#define ROLLBACK_DB_NOT_DELETED 2 +#define ROLLBACK_FAILSAFE_APPLIED 4 + +#define COMMIT_NOT_CONFIRMED "Commit was not confirmed; automatic rollback complete." + +enum confirmed_commit_state { + INACTIVE, // a confirmed-commit is not in progress + PERSISTENT, // a confirmed-commit is in progress and a persist value was given + EPHEMERAL, // a confirmed-commit is in progress and a persist value was not given + ROLLBACK +}; + +/* 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 state; + char *persist_id; // a value given by a client in the confirmed-commit + uint32_t session_id; // the session_id of the client that gave no value + + cxobj *xe; // the commit confirmed request + int (*fn)(int, void*); // the function pointer for the rollback event (rollback_fn()) + void *arg; // the clicon_handle that will be passed to rollback_fn() +}; + +extern struct confirmed_commit confirmed_commit; + /* * Prototypes - */ + */ +int do_rollback(clicon_handle h, uint8_t *errs); +int cancel_rollback_event(); + int startup_validate(clicon_handle h, char *db, cxobj **xtr, cbuf *cbret); int startup_commit(clicon_handle h, char *db, cbuf *cbret); int candidate_validate(clicon_handle h, char *db, cbuf *cbret); diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 1fd41604..158921b6 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -648,8 +648,23 @@ cli_commit(clicon_handle h, cvec *argv) { int retval = -1; + uint32_t timeout = 0; /* any non-zero value means "confirmed-commit" */ + cg_var *timeout_var; + char *persist = NULL; + char *persist_id = NULL; + + int confirmed = (cvec_find_str(vars, "confirmed") != NULL); + int cancel = (cvec_find_str(vars, "cancel") != NULL); + + if ((timeout_var = cvec_find(vars, "timeout")) != NULL) { + timeout = cv_uint32_get(timeout_var); + clicon_debug(1, "commit confirmed with timeout %ul", timeout); + } + + persist = cvec_find_str(vars, "persist-val"); + persist_id = cvec_find_str(vars, "persist-id-val"); - if ((retval = clicon_rpc_commit(h)) < 0) + if ((retval = clicon_rpc_commit(h, confirmed, cancel, timeout, persist, persist_id)) < 0) goto done; retval = 0; done: diff --git a/example/main/example.xml.in b/example/main/example.xml.in index e03d82f1..4cb2a3e4 100644 --- a/example/main/example.xml.in +++ b/example/main/example.xml.in @@ -1,6 +1,7 @@ /usr/local/etc/example.xml ietf-netconf:startup + ietf-netconf:confirmed-commit clixon-restconf:allow-auth-none clixon-restconf:fcgi /usr/local/share/clixon diff --git a/example/main/example_cli.cli b/example/main/example_cli.cli index e06c2c92..0da1a6a1 100644 --- a/example/main/example_cli.cli +++ b/example/main/example_cli.cli @@ -48,7 +48,15 @@ delete("Delete a configuration item") { all("Delete whole candidate configuration"), delete_all("candidate"); } validate("Validate changes"), cli_validate(); -commit("Commit the changes"), cli_commit(); +commit("Commit the changes"), cli_commit(); { + [persist-id("Specify the 'persist' value of a previous confirmed-commit") ("The 'persist' value of the persistent confirmed-commit")], cli_commit(); { + ("Cancel an ongoing confirmed-commit"), cli_commit(); + ("Require a confirming commit") { + [persist("Make this confirmed-commit persistent") ("The value that must be provided as 'persist-id' in the confirming-commit or cancel-commit")] + [("The rollback timeout in seconds")], cli_commit(); + } + } +} quit("Quit"), cli_quit(); debug("Debugging parts of the system"){ diff --git a/lib/clixon/clixon_datastore.h b/lib/clixon/clixon_datastore.h index 680dfc12..954fb700 100644 --- a/lib/clixon/clixon_datastore.h +++ b/lib/clixon/clixon_datastore.h @@ -76,5 +76,6 @@ int xmldb_modified_set(clicon_handle h, const char *db, int value); int xmldb_empty_get(clicon_handle h, const char *db); int xmldb_dump(clicon_handle h, FILE *f, cxobj *xt); int xmldb_print(clicon_handle h, FILE *f); +int xmldb_rename(clicon_handle h, const char *db, const char *newdb, const char *suffix); #endif /* _CLIXON_DATASTORE_H */ diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index 76ff0fb9..644c8d81 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -1,4 +1,5 @@ /* + * t * ***** BEGIN LICENSE BLOCK ***** @@ -63,7 +64,7 @@ int clicon_rpc_get_pageable_list(clicon_handle h, char *datastore, char *xpath, int clicon_rpc_close_session(clicon_handle h); int clicon_rpc_kill_session(clicon_handle h, uint32_t session_id); int clicon_rpc_validate(clicon_handle h, char *db); -int clicon_rpc_commit(clicon_handle h); +int clicon_rpc_commit(clicon_handle h, int confirmed, int cancel, uint32_t timeout, char *persist, char *persist_id); int clicon_rpc_discard_changes(clicon_handle h); int clicon_rpc_create_subscription(clicon_handle h, char *stream, char *filter, int *s); int clicon_rpc_debug(clicon_handle h, int level); diff --git a/lib/src/clixon_datastore.c b/lib/src/clixon_datastore.c index bad333b0..8048f4b3 100644 --- a/lib/src/clixon_datastore.c +++ b/lib/src/clixon_datastore.c @@ -418,10 +418,18 @@ xmldb_delete(clicon_handle h, if (xmldb_db2file(h, db, &filename) < 0) goto done; if (lstat(filename, &sb) == 0) - if (truncate(filename, 0) < 0){ - clicon_err(OE_DB, errno, "truncate %s", filename); - goto done; - } + // TODO this had been changed from unlink to truncate some time ago. It was changed back for confirmed-commit + // as the presence of the rollback_db at startup triggers loading of the rollback rather than the startup + // configuration. It might not be sufficient to check for a truncated file. Needs more review, switching back + // to unlink temporarily. +// if (truncate(filename, 0) < 0){ +// clicon_err(OE_DB, errno, "truncate %s", filename); +// goto done; +// } + if (unlink(filename) < 0) { + clicon_err(OE_UNIX, errno, "unlink %s: %s", filename, strerror(errno)); + goto done; + } retval = 0; done: if (filename) @@ -594,3 +602,64 @@ xmldb_print(clicon_handle h, done: return retval; } + +/*! Rename an XML database + * @param[in] h Clicon handle + * @param[in] db Database name + * @param[in] newdb New Database name; if NULL, then same as new + * @param[in] suffix Suffix to append to new database name + * @retval -1 Error + * @retval 0 OK + * @note if newdb and suffix are null, OK is returned as it is a no-op + */ +int +xmldb_rename(clicon_handle h, + const char *db, + const char *newdb, + const char *suffix) +{ + char *old; + char *fname; + int retval = -1; + + if ((xmldb_db2file(h, db, &old)) < 0) { + goto done; + }; + + if (newdb == NULL && suffix == NULL) + // no-op + goto done; + + newdb = newdb == NULL ? old : newdb; + suffix = suffix == NULL ? "" : suffix; + + size_t size = strlen(newdb) + strlen(suffix); + + if ((fname = malloc(size + 1)) == NULL) { + clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); + goto done; + }; + + int actual = 0; + if ((actual = snprintf(fname, size, "%s%s", newdb, suffix)) < size) { + clicon_err(OE_UNIX, 0, "snprintf wrote fewer bytes (%d) than requested (%zu)", actual, size); + goto done; + }; + + if ((rename(old, fname)) < 0) { + clicon_err(OE_UNIX, errno, "rename: %s", strerror(errno)); + goto done; + }; + + + retval = 0; + + done: + if (old) + free(old); + + if (fname) + free(fname); + + return retval; +} diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 06afc117..ee724a44 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -83,6 +83,10 @@ #include "clixon_netconf_lib.h" #include "clixon_proto_client.h" +#define PERSIST_ID_XML_FMT "%s" +#define PERSIST_XML_FMT "%s" +#define TIMEOUT_XML_FMT "%u" + /*! Connect to internal netconf socket */ int @@ -1266,7 +1270,12 @@ clicon_rpc_validate(clicon_handle h, * @retval -1 Error and logged to syslog */ int -clicon_rpc_commit(clicon_handle h) +clicon_rpc_commit(clicon_handle h, + int confirmed, + int cancel, + uint32_t timeout, + char *persist, + char *persist_id) { int retval = -1; struct clicon_msg *msg = NULL; @@ -1274,15 +1283,65 @@ clicon_rpc_commit(clicon_handle h) cxobj *xerr; char *username; uint32_t session_id; - + char *persist_id_xml = NULL; + char *persist_xml = NULL; + char *timeout_xml = NULL; + + if (persist_id) { + if ((persist_id_xml = malloc(strlen(persist_id) + strlen(PERSIST_ID_XML_FMT) + 1)) == NULL) { + clicon_err(OE_UNIX, 0, "malloc: %s", strerror(errno)); + } + sprintf(persist_id_xml, PERSIST_ID_XML_FMT, persist_id); + } + + if (persist) { + if ((persist_xml = malloc(strlen(persist) + strlen(PERSIST_XML_FMT) + 1)) == NULL) { + clicon_err(OE_UNIX, 0, "malloc: %s", strerror(errno)); + }; + sprintf(persist_xml, PERSIST_XML_FMT, persist); + } + + if (timeout > 0) { + + /* timeout is a uint32_t, so max value is 2^32, a 10-digit number, we'll just always malloc for a string that + * may be that large rather than calculate the string length + */ + + if ((timeout_xml = malloc(10 + 1 + strlen(TIMEOUT_XML_FMT))) == NULL) { + clicon_err(OE_UNIX, 0, "malloc: %s", strerror(errno)); + }; + sprintf(timeout_xml, TIMEOUT_XML_FMT, timeout); + } + + if (session_id_check(h, &session_id) < 0) goto done; username = clicon_username_get(h); - if ((msg = clicon_msg_encode(session_id, - "", - NETCONF_BASE_NAMESPACE, - username?username:"", - NETCONF_MESSAGE_ID_ATTR)) == NULL) + if (cancel) { + msg = clicon_msg_encode(session_id, + "%s", + NETCONF_BASE_NAMESPACE, + username ? username : "", + NETCONF_MESSAGE_ID_ATTR, + persist_id ? persist_id_xml : ""); + } else if (confirmed) { + msg = clicon_msg_encode(session_id, + "%s%s%s", + NETCONF_BASE_NAMESPACE, + username ? username : "", + NETCONF_MESSAGE_ID_ATTR, + timeout ? timeout_xml : "", + persist_id ? persist_id_xml : "", + persist ? persist_xml : ""); + } else { + msg = clicon_msg_encode(session_id, + "%s", + NETCONF_BASE_NAMESPACE, + username ? username : "", + NETCONF_MESSAGE_ID_ATTR, + persist ? persist_xml : ""); + } + if (msg == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; @@ -1296,6 +1355,12 @@ clicon_rpc_commit(clicon_handle h) xml_free(xret); if (msg) free(msg); + if (persist_id_xml) + free(persist_id_xml); + if (persist_xml) + free(persist_xml); + if (timeout_xml) + free(timeout_xml); return retval; } @@ -1479,7 +1544,7 @@ clicon_rpc_restconf_debug(clicon_handle h, clicon_err(OE_XML, 0, "rpc error"); /* XXX extract info from rpc-error */ goto done; } - if ((retval = clicon_rpc_commit(h)) < 0) + if ((retval = clicon_rpc_commit(h, 0, 0, 0, NULL, NULL)) < 0) goto done; done: if (msg) diff --git a/test/test_confirmed_commit.sh b/test/test_confirmed_commit.sh new file mode 100755 index 00000000..b5ee69f1 --- /dev/null +++ b/test/test_confirmed_commit.sh @@ -0,0 +1,355 @@ +#!/usr/bin/env bash +# Basic Netconf functionality +# Mainly default/null prefix, but also xx: prefix +# XXX: could add tests for dual prefixes xx and xy with doppelganger names, ie xy:filter that is +# syntactic correct but wrong + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf_yang.xml +tmp=$dir/tmp.x +fyang=$dir/clixon-example.yang + +# Use yang in example + +cat < $cfg + + $cfg + ietf-netconf:startup + ietf-netconf:confirmed-commit + 42 + ${YANG_INSTALLDIR} + $IETFRFC + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + example_backend.so$ + /usr/local/lib/$APPNAME/netconf + false + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + $dir/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + +EOF + +cat < $fyang +module clixon-example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + import ietf-interfaces { + prefix if; + } + import ietf-ip { + prefix ip; + } + /* Example interface type for tests, local callbacks, etc */ + identity eth { + base if:interface-type; + } + /* Generic config data */ + container table{ + list parameter{ + key name; + leaf name{ + type string; + } + } + } + /* State data (not config) for the example application*/ + container state { + config false; + description "state data for the example application (must be here for example get operation)"; + leaf-list op { + type string; + } + } + augment "/if:interfaces/if:interface" { + container my-status { + config false; + description "For testing augment+state"; + leaf int { + type int32; + } + leaf str { + type string; + } + } + } + rpc client-rpc { + description "Example local client-side RPC that is processed by the + the netconf/restconf and not sent to the backend. + This is a clixon implementation detail: some rpc:s + are better processed by the client for API or perf reasons"; + input { + leaf x { + type string; + } + } + output { + leaf x { + type string; + } + } + } + rpc empty { + description "Smallest possible RPC with no input or output sections"; + } + rpc example { + description "Some example input/output for testing RFC7950 7.14. + RPC simply echoes the input for debugging."; + input { + leaf x { + description + "If a leaf in the input tree has a 'mandatory' statement with + the value 'true', the leaf MUST be present in an RPC invocation."; + type string; + mandatory true; + } + leaf y { + description + "If a leaf in the input tree has a 'mandatory' statement with the + value 'true', the leaf MUST be present in an RPC invocation."; + type string; + default "42"; + } + } + output { + leaf x { + type string; + } + leaf y { + type string; + } + } + } + +} +EOF + +function data() { + if [[ "$1" == "" ]] + then + echo "" + else + echo "$1" + fi +} + +# Pipe stdin to command and also do chunked framing (netconf 1.1) +# Arguments: +# - Command +# - expected command return value (0 if OK) +# - stdin input1 This is NOT encoded, eg preamble/hello +# - stdin input2 This gets chunked encoding +# - expect1 stdout outcome, can be partial and contain regexps +# - expect2 stdout outcome This gets chunked encoding, must be complete netconf message +# Use this if you want regex eg ^foo$ + +function rpc() { + expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "$1" "" "$2" +} + +function commit() { + if [[ "$1" == "" ]] + then + rpc "" "" + else + rpc "$1" "" + fi +} + +function edit_config() { + TARGET="$1" + CONFIG="$2" + rpc "<$TARGET/>$CONFIG" "" +} + +function assert_config_equals() { + TARGET="$1" + EXPECTED="$2" + rpc "<$TARGET/>" "$(data "$EXPECTED")" +} + +function reset() { + rpc "none" "" + commit + assert_config_equals "candidate" "" + assert_config_equals "running" "" +} + +CANDIDATE_PATH="/usr/local/var/$APPNAME/candidate_db" +RUNNING_PATH="/usr/local/var/$APPNAME/running_db" +ROLLBACK_PATH="/usr/local/var/$APPNAME/rollback_db" +FAILSAFE_PATH="/usr/local/var/$APPNAME/failsafe_db" + +CONFIGB="eth0ex:ethtrue" +CONFIGC="eth1ex:ethtrue" +CONFIGBPLUSC="eth0ex:ethtrueeth1ex:ethtrue" +FAILSAFE_CFG="eth99ex:ethtrue" + +# TODO this test suite is somewhat brittle as it relies on the presence of the example configuration that one gets with +# make install-example in the Clixon distribution. It would be better if the dependencies were entirely self contained. + +new "test params: -f $cfg -- -s" +# Bring your own backend +if [ $BE -ne 0 ]; then + # kill old backend (if any) + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -- -s" + start_backend -s init -f $cfg -- -s +fi + +new "wait backend" +wait_backend + +new "netconf ephemeral confirmed-commit rolls back after disconnect" +reset +edit_config "candidate" "$CONFIGB" +assert_config_equals "candidate" "$CONFIGB" +commit "30" +assert_config_equals "running" "" + +new "netconf persistent confirmed-commit" +reset +edit_config "candidate" "$CONFIGB" +commit "a" +assert_config_equals "running" "$CONFIGB" +edit_config "candidate" "$CONFIGC" +commit "aba" +assert_config_equals "running" "$CONFIGBPLUSC" + +new "netconf cancel-commit with invalid persist-id" +rpc "abc" "applicationinvalid-valueerrora confirmed-commit with the given persist-id was not found" + +new "netconf cancel-commit with valid persist-id" +rpc "ab" "" + +new "netconf persistent confirmed-commit with timeout" +reset +edit_config "candidate" "$CONFIGB" +commit "2abcd" +assert_config_equals "running" "$CONFIGB" +sleep 2 +assert_config_equals "running" "" + +new "netconf persistent confirmed-commit with reset timeout" +reset +edit_config "candidate" "$CONFIGB" +commit "abcde5" +assert_config_equals "running" "$CONFIGB" +edit_config "candidate" "$CONFIGC" +commit "abcdeabcdef10" +# prove the new timeout is active by sleeping longer than first timeout. get config, assert == B+C +sleep 6 +assert_config_equals "running" "$CONFIGBPLUSC" +# now sleep long enough for rollback to happen; get config, assert == A +sleep 5 +assert_config_equals "running" "" + +new "netconf persistent confirming-commit to epehemeral confirmed-commit should rollback" +reset +edit_config "candidate" "$CONFIGB" +commit "10" +assert_config_equals "running" "$CONFIGB" +commit "" +assert_config_equals "running" "" + +new "netconf confirming-commit for persistent confirmed-commit with empty persist value" +reset +edit_config "candidate" "$CONFIGB" +commit "10" +assert_config_equals "running" "$CONFIGB" +commit "" +assert_config_equals "running" "$CONFIGB" + +# TODO the next two tests are broken. The whole idea of presence or absence of rollback_db indicating something might +# need reconsideration. see clixon_datastore.c#xmldb_delete() and backend_startup.c#startup_mode_startup() + +new "backend loads rollback if present at startup" +reset +edit_config "candidate" "$CONFIGB" +commit "" +edit_config "candidate" "$CONFIGC" +commit "abcdefg" +assert_config_equals "running" "$CONFIGBPLUSC" +stop_backend -f $cfg # kill backend and restart +[ -f "$ROLLBACK_PATH" ] || err "rollback_db doesn't exist!" # assert rollback_db exists +start_backend -s running -f $cfg -- -s +wait_backend +assert_config_equals "running" "$CONFIGB" +[ -f "ROLLBACK_PATH" ] && err "rollback_db still exists!" # assert rollback_db doesn't exist + +stop_backend -f $cfg +start_backend -s init -f $cfg -- -s + +new "backend loads failsafe at startup if rollback present but cannot be loaded" +reset + +sudo tee "$FAILSAFE_PATH" > /dev/null << EOF # create a failsafe database +$FAILSAFE_CFG +EOF + +edit_config "candidate" "$CONFIGC" +commit "foobar" +assert_config_equals "running" "$CONFIGC" +stop_backend -f $cfg # kill the backend +sudo rm $ROLLBACK_PATH # modify rollback_db so it won't commit successfully +sudo tee "$ROLLBACK_PATH" > /dev/null << EOF + + + + + +EOF +start_backend -s running -f $cfg -- -s +wait_backend +assert_config_equals "running" "$FAILSAFE_CFG" + + +# TODO this test is now broken too, but not sure why; suspicion that the initial confirmed-commit session is not kept alive as intended +stop_backend -f $cfg +start_backend -s init -f $cfg -lf/tmp/clixon.log -D1 -- -s +wait_backend +new "ephemeral confirmed-commit survives unrelated ephemeral session disconnect" +reset +edit_config "candidate" "$CONFIGB" +# start a new ephemeral confirmed commit, but keep the confirmed-commit session alive (need to put it in the background) +sleep 60 | cat <(echo "$DEFAULTHELLO60]]>]]>") -| $clixon_netconf -qf $cfg >> /dev/null & +PIDS=($(jobs -l % | cut -c 6- | awk '{print $1}')) +assert_config_equals "running" "$CONFIGB" # assert config twice to prove it surives disconnect +assert_config_equals "running" "$CONFIGB" # of ephemeral sessions + +kill -9 ${PIDS[0]} # kill the while loop above to close STDIN on 1st + # ephemeral session and cause rollback +assert_config_equals "running" "" + + +# TODO test same cli methods as tested for netconf +# TODO test restconf receives "409 conflict" when there is a persistent confirmed-commit active +# TODO test restconf causes confirming-commit for ephemeral confirmed-commit + + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +new "endtest" +endtest From 8abcda6f85658cae96bb01174385f07687aa4c50 Mon Sep 17 00:00:00 2001 From: Phil Heller Date: Mon, 3 Oct 2022 21:42:01 -0600 Subject: [PATCH 05/22] confirmed-commit fixes, tests - fixed typo preventing evaluation of confirmed-commit logic in backend_startup - fixed uninitialized variable warnings - added details to CHANGELOG.MD - added capabilities advertisement for confirmed-commit 1.0 and 1.1 - added xml hello message that uses only eom framing, for simplicity in asynch tests - add stty restore after wait_restconf to fix console corruption in tests - adjust test_confirmed_commit to drop perms and run as the invoking user. This will require running user to have permissions on /usr/local/var/example - added CLI tests --- CHANGELOG.md | 15 ++++ apps/backend/backend_commit.c | 6 +- apps/backend/backend_main.c | 1 - apps/backend/backend_startup.c | 4 +- lib/src/clixon_datastore.c | 5 +- lib/src/clixon_netconf_lib.c | 8 ++ test/lib.sh | 6 ++ test/site.sh | 1 + test/test_confirmed_commit.sh | 130 +++++++++++++++++++++++++++++++-- 9 files changed, 160 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2075914..9886f4f2 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,6 +66,8 @@ Developers may need to change their code * C API changes * Added `defaults` parameter to `clicon_rpc_get_pageable_list()` + * Added `confirmed`, `cancel`, `timeout`, `persist-id`, and `persist-id-val` parameters to + `cli_commit(...)` ### Minor features diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 658e5f72..4ce995d8 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -676,7 +676,7 @@ rollback_fn(int fd, { clicon_handle h = arg; - clicon_log(LOG_EMERG, "a confirming-commit was not received before the confirm-timeout expired; rolling back"); + clicon_log(LOG_CRIT, "a confirming-commit was not received before the confirm-timeout expired; rolling back"); return do_rollback(h, NULL); } @@ -1017,7 +1017,7 @@ do_rollback(clicon_handle h, uint8_t *errs) * 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_EMERG, "An error occurred during rollback and the rollback_db wasn't deleted."); + 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; } @@ -1044,7 +1044,7 @@ do_rollback(clicon_handle h, uint8_t *errs) /* Attempt to load the failsafe config */ if (load_failsafe(h, "Rollback") < 0) { - clicon_log(LOG_EMERG, "An error occurred committing the failsafe database. Exiting."); + clicon_log(LOG_CRIT, "An error occurred committing the failsafe database. Exiting."); /* Invoke our own signal handler to exit */ raise(SIGINT); diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 727a73c8..fa0ac8ef 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -888,7 +888,6 @@ main(int argc, switch (startup_mode){ case SM_INIT: /* Scratch running and start from empty */ /* Delete any rollback database, if it exists */ - // TODO: xmldb_delete doesn't actually unlink; need to look at this xmldb_delete(h, "rollback"); /* [Delete and] create running db */ if (xmldb_db_reset(h, "running") < 0) diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c index 7f7a02c8..0602d64f 100644 --- a/apps/backend/backend_startup.c +++ b/apps/backend/backend_startup.c @@ -133,7 +133,7 @@ startup_mode_startup(clicon_handle h, cbuf *cbret) { int retval = -1; - int ret; + int ret = 0; int db_exists; if (strcmp(db, "running")==0){ @@ -154,7 +154,7 @@ startup_mode_startup(clicon_handle h, * rebooted. */ yang_stmt *yspec = clicon_dbspec_yang(h); - if (if_feature(yspec, "ietf-netconf", "configmed-commit")) { + if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { db_exists = xmldb_exists(h, "rollback"); if (db_exists < 0) { clicon_err(OE_DAEMON, 0, "Error checking for the existence of the rollback database"); diff --git a/lib/src/clixon_datastore.c b/lib/src/clixon_datastore.c index 8048f4b3..bc5f09a4 100644 --- a/lib/src/clixon_datastore.c +++ b/lib/src/clixon_datastore.c @@ -418,7 +418,8 @@ xmldb_delete(clicon_handle h, if (xmldb_db2file(h, db, &filename) < 0) goto done; if (lstat(filename, &sb) == 0) - // TODO this had been changed from unlink to truncate some time ago. It was changed back for confirmed-commit + // TODO this had been changed from unlink to truncate some time ago, likely related to dropping privileges. + // It was changed back for confirmed-commit, and test_confirmed_commit.sh drops privileges. // as the presence of the rollback_db at startup triggers loading of the rollback rather than the startup // configuration. It might not be sufficient to check for a truncated file. Needs more review, switching back // to unlink temporarily. @@ -619,7 +620,7 @@ xmldb_rename(clicon_handle h, const char *suffix) { char *old; - char *fname; + char *fname = NULL; int retval = -1; if ((xmldb_db2file(h, db, &old)) < 0) { diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 6d7d795e..e0e92d6d 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1765,6 +1765,7 @@ netconf_hello_server(clicon_handle h, char *module_set_id; char *ietf_yang_library_revision; char *encstr = NULL; + yang_stmt *yspec = clicon_dbspec_yang(h); module_set_id = clicon_option_str(h, "CLICON_MODULE_SET_ID"); cprintf(cb, "", NETCONF_BASE_NAMESPACE); @@ -1799,6 +1800,13 @@ netconf_hello_server(clicon_handle h, cprintf(cb, ""); xml_chardata_cbuf_append(cb, "urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&also-supported=report-all,trim,report-all-tagged"); cprintf(cb, ""); + + /* rfc 4741 and 6241 confirmed-commit capabilities */ + if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { + cprintf(cb, "urn:ietf:params:netconf:capability:confirmed-commit:1.0"); + cprintf(cb, "urn:ietf:params:netconf:capability:confirmed-commit:1.1"); + } + cprintf(cb, ""); if (session_id) cprintf(cb, "%lu", (long unsigned int)session_id); diff --git a/test/lib.sh b/test/lib.sh index ac39a21d..cb5e8f98 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -98,6 +98,9 @@ DEFAULTNS="$DEFAULTONLY message-id=\"42\"" # Minimal hello message as a prelude to netconf rpcs DEFAULTHELLO="urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:base:1.1]]>]]>" +# Minimal hello message that excludes 1.1 capability, in the case EOM style framing is needed +HELLONO11="urn:ietf:params:netconf:base:1.0]]>]]>" + # XXX cannot get this to work for all combinations of nc/netcat fcgi/native # But leave it here for debugging where netcat works properly if [ -n "$(type netcat 2> /dev/null)" ]; then @@ -569,6 +572,7 @@ function wait_backend(){ # Start restconf daemon # @see wait_restconf function start_restconf(){ + STTYSETTINGS=`stty -g` # Start in background echo "sudo -u $wwwstartuser -s $clixon_restconf $RCLOG -D $DBG $*" sudo -u $wwwstartuser -s $clixon_restconf $RCLOG -D $DBG $* /dev/null & @@ -625,6 +629,8 @@ function wait_restconf(){ if [ $valgrindtest -eq 3 ]; then sleep 2 # some problems with valgrind fi + + stty $STTYSETTINGS } # Wait for restconf to stop diff --git a/test/site.sh b/test/site.sh index c01c7f26..480ef3bf 100644 --- a/test/site.sh +++ b/test/site.sh @@ -10,6 +10,7 @@ # The SKIPLIST has precedence over the 'pattern' variable that you can use to # specify included file when running the various test scripts such as "all.sh". #SKIPLIST="test_[a-t]*\.sh test_openconfig.sh test_yangmodels.sh" +SKIPLIST="test_http_data.sh test_netconf_ssh_callhome.sh test_privileges.sh test_restconf.sh test_yang_models_ieee.sh" # # Parse yang openconfig models from https://github.com/openconfig/public OPENCONFIG=/usr/local/share/openconfig/public diff --git a/test/test_confirmed_commit.sh b/test/test_confirmed_commit.sh index b5ee69f1..3f18fc86 100755 --- a/test/test_confirmed_commit.sh +++ b/test/test_confirmed_commit.sh @@ -35,6 +35,8 @@ cat < $cfg $dir/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile /usr/local/var/$APPNAME + $USER + drop_perm EOF @@ -213,6 +215,8 @@ fi new "wait backend" wait_backend +################################################################################ + new "netconf ephemeral confirmed-commit rolls back after disconnect" reset edit_config "candidate" "$CONFIGB" @@ -220,6 +224,8 @@ assert_config_equals "candidate" "$CONFIGB" commit "30" assert_config_equals "running" "" +################################################################################ + new "netconf persistent confirmed-commit" reset edit_config "candidate" "$CONFIGB" @@ -229,12 +235,18 @@ edit_config "candidate" "$CONFIGC" commit "aba" assert_config_equals "running" "$CONFIGBPLUSC" +################################################################################ + new "netconf cancel-commit with invalid persist-id" rpc "abc" "applicationinvalid-valueerrora confirmed-commit with the given persist-id was not found" +################################################################################ + new "netconf cancel-commit with valid persist-id" rpc "ab" "" +################################################################################ + new "netconf persistent confirmed-commit with timeout" reset edit_config "candidate" "$CONFIGB" @@ -243,6 +255,8 @@ assert_config_equals "running" "$CONFIGB" sleep 2 assert_config_equals "running" "" +################################################################################ + new "netconf persistent confirmed-commit with reset timeout" reset edit_config "candidate" "$CONFIGB" @@ -257,6 +271,8 @@ assert_config_equals "running" "$CONFIGBPLUSC" sleep 5 assert_config_equals "running" "" +################################################################################ + new "netconf persistent confirming-commit to epehemeral confirmed-commit should rollback" reset edit_config "candidate" "$CONFIGB" @@ -265,6 +281,8 @@ assert_config_equals "running" "$CONFIGB" commit "" assert_config_equals "running" "" +################################################################################ + new "netconf confirming-commit for persistent confirmed-commit with empty persist value" reset edit_config "candidate" "$CONFIGB" @@ -273,8 +291,10 @@ assert_config_equals "running" "$CONFIGB" commit "" assert_config_equals "running" "$CONFIGB" -# TODO the next two tests are broken. The whole idea of presence or absence of rollback_db indicating something might -# need reconsideration. see clixon_datastore.c#xmldb_delete() and backend_startup.c#startup_mode_startup() +################################################################################ + +# TODO reconsider logic around presence/absence of rollback_db as a signal as dropping permissions may impact ability +# to unlink and/or create that file. see clixon_datastore.c#xmldb_delete() and backend_startup.c#startup_mode_startup() new "backend loads rollback if present at startup" reset @@ -293,6 +313,8 @@ assert_config_equals "running" "$CONFIGB" stop_backend -f $cfg start_backend -s init -f $cfg -- -s +################################################################################ + new "backend loads failsafe at startup if rollback present but cannot be loaded" reset @@ -316,26 +338,118 @@ start_backend -s running -f $cfg -- -s wait_backend assert_config_equals "running" "$FAILSAFE_CFG" - -# TODO this test is now broken too, but not sure why; suspicion that the initial confirmed-commit session is not kept alive as intended stop_backend -f $cfg start_backend -s init -f $cfg -lf/tmp/clixon.log -D1 -- -s wait_backend + +################################################################################ + new "ephemeral confirmed-commit survives unrelated ephemeral session disconnect" reset edit_config "candidate" "$CONFIGB" +assert_config_equals "candidate" "$CONFIGB" # start a new ephemeral confirmed commit, but keep the confirmed-commit session alive (need to put it in the background) -sleep 60 | cat <(echo "$DEFAULTHELLO60]]>]]>") -| $clixon_netconf -qf $cfg >> /dev/null & +# use HELLONO11 which uses older EOM framing +sleep 60 | cat <(echo "$HELLONO1160]]>]]>") -| $clixon_netconf -qf $cfg >> /dev/null & PIDS=($(jobs -l % | cut -c 6- | awk '{print $1}')) assert_config_equals "running" "$CONFIGB" # assert config twice to prove it surives disconnect assert_config_equals "running" "$CONFIGB" # of ephemeral sessions kill -9 ${PIDS[0]} # kill the while loop above to close STDIN on 1st - # ephemeral session and cause rollback + +################################################################################ + +new "cli ephemeral confirmed-commit rolls back after disconnect" +reset + +tmppipe=$(mktemp -u) +mkfifo -m 600 "$tmppipe" +cat << EOF | clixon_cli -f $cfg >> /dev/null & +set interfaces interface eth0 type ex:eth +set interfaces interface eth0 enabled true +commit confirmed 60 +shell echo >> $tmppipe +shell cat $tmppipe +quit +EOF +cat $tmppipe >> /dev/null +assert_config_equals "running" "$CONFIGB" +echo >> $tmppipe +sleep 1 +assert_config_equals "running" "" +rm $tmppipe + +################################################################################ + +new "cli persistent confirmed-commit" +reset +cat << EOF | clixon_cli -f $cfg >> /dev/null +set interfaces interface eth0 type ex:eth +set interfaces interface eth0 enabled true +commit confirmed persist a +quit +EOF +assert_config_equals "running" "$CONFIGB" + +cat << EOF | clixon_cli -f $cfg >> /dev/null +set interfaces interface eth1 type ex:eth +set interfaces interface eth1 enabled true +commit persist-id a confirmed persist ab +quit +EOF +assert_config_equals "running" "$CONFIGBPLUSC" + +################################################################################ + +new "cli cancel-commit with invalid persist-id" +expectpart "$($clixon_cli -lo -1 -f $cfg commit persist-id abc cancel)" 255 "a confirmed-commit with the given persist-id was not found" + +################################################################################ + +new "cli cancel-commit with valid persist-id" +expectpart "$($clixon_cli -lo -1 -f $cfg commit persist-id ab cancel)" 0 "^$" +assert_config_equals "running" "" + +################################################################################ + +new "cli cancel-commit with no confirmed-commit in progress" +expectpart "$($clixon_cli -lo -1 -f $cfg commit persist-id ab cancel)" 255 "no confirmed-commit is in progress" + +################################################################################ + +new "cli persistent confirmed-commit with timeout" +reset +cat << EOF | clixon_cli -f $cfg >> /dev/null +set interfaces interface eth0 type ex:eth +set interfaces interface eth0 enabled true +commit confirmed persist abcd 2 +EOF +assert_config_equals "running" "$CONFIGB" +sleep 2 +assert_config_equals "running" "" + +################################################################################ + +new "cli persistent confirmed-commit with reset timeout" +reset +cat << EOF | clixon_cli -f $cfg >> /dev/null +set interfaces interface eth0 type ex:eth +set interfaces interface eth0 enabled true +commit confirmed persist abcd 5 +EOF +assert_config_equals "running" "$CONFIGB" +cat << EOF | clixon_cli -f $cfg >> /dev/null +set interfaces interface eth1 type ex:eth +set interfaces interface eth1 enabled true +commit persist-id abcd confirmed persist abcdef 10 +EOF +sleep 6 +assert_config_equals "running" "$CONFIGBPLUSC" +# now sleep long enough for rollback to happen; get config, assert == A +sleep 5 assert_config_equals "running" "" -# TODO test same cli methods as tested for netconf # TODO test restconf receives "409 conflict" when there is a persistent confirmed-commit active # TODO test restconf causes confirming-commit for ephemeral confirmed-commit @@ -343,7 +457,7 @@ assert_config_equals "running" "" if [ $BE -ne 0 ]; then new "Kill backend" # Check if premature kill - pid=$(pgrep -u root -f clixon_backend) + pid=$(pgrep -u ${USER} -f clixon_backend) if [ -z "$pid" ]; then err "backend already dead" fi From 11eccd5478eecebe8d428ce9bc86df72c1b64ea6 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 7 Oct 2022 10:02:07 +0200 Subject: [PATCH 06/22] Confirm commit: - Removed confirm-commit 1.0 capability (only 1.1 present) - Made startup capability conditional (as confirmed-commit) - Fixed startup error when rollback did not exist - Adjust snmp commit calls - Move failsafe to lib - Test: Minimized test application, test capability --- apps/backend/Makefile.in | 8 +- apps/backend/backend_commit.c | 56 ++++++++- apps/backend/backend_failsafe.c | 105 ---------------- apps/backend/backend_failsafe.h | 42 ------- apps/backend/backend_main.c | 2 +- apps/backend/backend_startup.c | 89 ++++---------- apps/backend/backend_startup.h | 1 - apps/backend/clixon_backend_commit.h | 1 + apps/snmp/snmp_handler.c | 4 +- lib/clixon/clixon_proto_client.h | 1 - lib/src/clixon_netconf_lib.c | 22 ++-- lib/src/clixon_proto_client.c | 10 +- test/site.sh | 1 - test/test_confirmed_commit.sh | 172 ++++++++++----------------- test/test_netconf_hello.sh | 10 +- test/test_netconf_ssh_callhome.sh | 2 +- 16 files changed, 177 insertions(+), 349 deletions(-) delete mode 100644 apps/backend/backend_failsafe.c delete mode 100644 apps/backend/backend_failsafe.h diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 5e3a782a..9bfa8d92 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -87,10 +87,6 @@ INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/inclu # Name of application APPL = clixon_backend -# Source / objects called from plugin and otherwise -COMMONSRC = backend_failsafe.c -COMMONOBJ = $(COMMONSRC:.c=.o) - # Not accessible from plugin APPSRC = backend_main.c APPSRC += backend_socket.c @@ -98,14 +94,14 @@ APPSRC += backend_client.c APPSRC += backend_get.c APPSRC += backend_plugin_restconf.c # Pseudo plugin for restconf daemon APPSRC += backend_startup.c -APPOBJ = $(APPSRC:.c=.o) $(COMMONOBJ) +APPOBJ = $(APPSRC:.c=.o) # Accessible from plugin LIBSRC = clixon_backend_transaction.c LIBSRC += clixon_backend_handle.c LIBSRC += backend_commit.c LIBSRC += backend_plugin.c -LIBOBJ = $(LIBSRC:.c=.o) $(COMMONOBJ) +LIBOBJ = $(LIBSRC:.c=.o) # Name of lib MYNAME = clixon_backend diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 4ce995d8..8e689620 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -70,7 +70,6 @@ #include "backend_handle.h" #include "clixon_backend_commit.h" #include "backend_client.h" -#include "backend_failsafe.h" /* a global instance of the confirmed_commit struct for reference throughout the procedure */ struct confirmed_commit confirmed_commit = { @@ -1577,3 +1576,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, 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_failsafe.c b/apps/backend/backend_failsafe.c deleted file mode 100644 index 4c6f0966..00000000 --- a/apps/backend/backend_failsafe.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren - Copyright (C) 2017-2019 Olof Hagsand - Copyright (C) 2020-2021 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 ***** - - */ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif - -#include -#include -#include - -/* cligen */ -#include - -/* clixon */ -#include - -#include "clixon_backend_commit.h" -#include "backend_failsafe.h" - -/*! Reset running and start in failsafe mode. If no failsafe then quit. - 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, 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_failsafe.h b/apps/backend/backend_failsafe.h deleted file mode 100644 index 55e0dbf3..00000000 --- a/apps/backend/backend_failsafe.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren - Copyright (C) 2017-2019 Olof Hagsand - Copyright (C) 2020-2021 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 ***** - - */ - -#ifndef CLIXON_BACKEND_FAILSAFE_H -#define CLIXON_BACKEND_FAILSAFE_H -int load_failsafe(clicon_handle h, char *phase); - -#endif //CLIXON_BACKEND_FAILSAFE_H diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index fa0ac8ef..0b9a24ea 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -962,7 +962,7 @@ main(int argc, if (status != STARTUP_OK){ if (cbuf_len(cbret)) clicon_log(LOG_NOTICE, "%s: %u %s", __PROGRAM__, getpid(), cbuf_get(cbret)); - if (startup_failsafe(h) < 0){ + if (load_failsafe(h, "Startup") < 0){ goto done; } status = STARTUP_OK; diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c index 0602d64f..c03f3b95 100644 --- a/apps/backend/backend_startup.c +++ b/apps/backend/backend_startup.c @@ -132,9 +132,10 @@ startup_mode_startup(clicon_handle h, char *db, cbuf *cbret) { - int retval = -1; - int ret = 0; - int db_exists; + int retval = -1; + int ret = 0; + int rollback_exists; + yang_stmt *yspec = clicon_dbspec_yang(h); if (strcmp(db, "running")==0){ clicon_err(OE_FATAL, 0, "Invalid startup db: %s", db); @@ -153,13 +154,12 @@ startup_mode_startup(clicon_handle h, * database was deleted, either clixon_backend crashed or the machine * rebooted. */ - yang_stmt *yspec = clicon_dbspec_yang(h); if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { - db_exists = xmldb_exists(h, "rollback"); - if (db_exists < 0) { + if ((rollback_exists = xmldb_exists(h, "rollback")) < 0) { clicon_err(OE_DAEMON, 0, "Error checking for the existence of the rollback database"); goto done; - } else if (db_exists == 1) { + } + if (rollback_exists == 1) { ret = startup_commit(h, "rollback", cbret); switch(ret) { case -1: @@ -170,23 +170,30 @@ startup_mode_startup(clicon_handle h, /* Rename the errored rollback database so that it is not tried on a subsequent startup */ xmldb_rename(h, db, NULL, ".error"); - - retval = 1; - goto done; + goto ok; case 1: /* validation ok */ - retval = 1; xmldb_delete(h, "rollback"); - goto done; + goto ok; default: /* Unexpected response */ goto fail; } } - } else if ((ret = startup_commit(h, db, cbret)) < 0) - goto done; - if (ret == 0) - goto fail; + else { + if ((ret = startup_commit(h, db, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } + else { + if ((ret = startup_commit(h, db, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + ok: retval = 1; done: return retval; @@ -332,56 +339,6 @@ startup_extraxml(clicon_handle h, goto done; } -/*! Reset running and start in failsafe mode. If no failsafe then quit. - Typically done when startup status is not OK so - -failsafe ----------------------+ - reset \ commit -running ----|-------+---------------> RUNNING FAILSAFE - \ -tmp |----------------------> - */ -int -startup_failsafe(clicon_handle h) -{ - int retval = -1; - int ret; - char *db = "failsafe"; - cbuf *cbret = NULL; - - 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, "Startup failed and no Failsafe database found, exiting"); - 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, 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, "Startup failed, Failsafe database validation failed %s", cbuf_get(cbret)); - goto done; - } - clicon_log(LOG_NOTICE, "Startup failed, Failsafe database loaded "); - retval = 0; - done: - if (cbret) - cbuf_free(cbret); - return retval; -} - /*! Init modules state of the backend (server). To compare with startup XML * Set the modules state as setopt to the datastore module. * Only if CLICON_XMLDB_MODSTATE is enabled diff --git a/apps/backend/backend_startup.h b/apps/backend/backend_startup.h index 741bd2e0..7191f028 100644 --- a/apps/backend/backend_startup.h +++ b/apps/backend/backend_startup.h @@ -44,7 +44,6 @@ */ int startup_mode_startup(clicon_handle h, char *db, cbuf *cbret); int startup_extraxml(clicon_handle h, char *file, cbuf *cbret); -int startup_failsafe(clicon_handle h); int startup_module_state(clicon_handle h, yang_stmt *yspec); #endif /* _BACKEND_STARTUP_H_ */ diff --git a/apps/backend/clixon_backend_commit.h b/apps/backend/clixon_backend_commit.h index 2cd1c1a6..62785eef 100644 --- a/apps/backend/clixon_backend_commit.h +++ b/apps/backend/clixon_backend_commit.h @@ -83,5 +83,6 @@ int from_client_discard_changes(clicon_handle h, cxobj *xe, cbuf *cbret, void *a int from_client_cancel_commit(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); int from_client_validate(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); int from_client_restart_one(clicon_handle h, clixon_plugin_t *cp, cbuf *cbret); +int load_failsafe(clicon_handle h, char *phase); #endif /* _CLIXON_BACKEND_COMMIT_H_ */ diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index c113214d..fa981e41 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -750,7 +750,7 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, goto done; break; case MODE_SET_COMMIT: /* 3 */ - if (clicon_rpc_commit(sh->sh_h) < 0) + if (clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL) < 0) goto done; break; case MODE_SET_FREE: /* 4 */ @@ -1303,7 +1303,7 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, } break; case MODE_SET_COMMIT: // 3 - if (clicon_rpc_commit(sh->sh_h) < 0) + if (clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL) < 0) goto done; break; case MODE_SET_FREE: // 4 diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index 644c8d81..c13cafdb 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -1,5 +1,4 @@ /* - * t * ***** BEGIN LICENSE BLOCK ***** diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index e0e92d6d..2d15605a 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1752,7 +1752,8 @@ netconf_content_int2str(netconf_content nr) * backend, and backend may implement more modules - please consider if using * library routines for detecting capabilities here. In contrast, yang module * list (RFC7895) is processed by the backend. - * @note encode bodies, see xml_chardata_encode() + * @note If you add new, remember to encode bodies if needed, see xml_chardata_encode() + * @note * @see yang_modules_state_get * @see netconf_module_load */ @@ -1779,6 +1780,7 @@ netconf_hello_server(clicon_handle h, /* A peer MAY include capabilities for previous NETCONF versions, to indicate that it supports multiple protocol versions. */ cprintf(cb, "%s", NETCONF_BASE_CAPABILITY_1_0); + /* Check if RFC7895 loaded and revision found */ if ((ietf_yang_library_revision = yang_modules_revision(h)) != NULL){ if (xml_chardata_encode(&encstr, "urn:ietf:params:netconf:capability:yang-library:1.0?revision=%s&module-set-id=%s", @@ -1791,21 +1793,25 @@ netconf_hello_server(clicon_handle h, encstr = NULL; } } + /* RFC6241 Sec 8.3. Candidate Configuration Capability */ cprintf(cb, "urn:ietf:params:netconf:capability:candidate:1.0"); + /* RFC6241 Sec 8.6. Validate Capability */ cprintf(cb, "urn:ietf:params:netconf:capability:validate:1.1"); - cprintf(cb, "urn:ietf:params:netconf:capability:startup:1.0"); + /* rfc 6241 Sec 8.7 Distinct Startup Capability */ + if (if_feature(yspec, "ietf-netconf", "startup")) + cprintf(cb, "urn:ietf:params:netconf:capability:startup:1.0"); + /* RFC6241 Sec 8.9. XPath Capability */ cprintf(cb, "urn:ietf:params:netconf:capability:xpath:1.0"); - cprintf(cb, "urn:ietf:params:netconf:capability:notification:1.0"); /* rfc6243 with-defaults capability modes */ cprintf(cb, ""); xml_chardata_cbuf_append(cb, "urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&also-supported=report-all,trim,report-all-tagged"); cprintf(cb, ""); - - /* rfc 4741 and 6241 confirmed-commit capabilities */ - if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { - cprintf(cb, "urn:ietf:params:netconf:capability:confirmed-commit:1.0"); + /* RFC5277 Notification Capability */ + cprintf(cb, "urn:ietf:params:netconf:capability:notification:1.0"); + /* It is somewhat arbitrary why some features/capabilities are hardocded and why some are not + * rfc 6241 Sec 8.4 confirmed-commit capabilities */ + if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) cprintf(cb, "urn:ietf:params:netconf:capability:confirmed-commit:1.1"); - } cprintf(cb, ""); if (session_id) diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index ee724a44..4b2b1c9b 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -1266,8 +1266,14 @@ clicon_rpc_validate(clicon_handle h, /*! Commit changes send a commit request to backend daemon * @param[in] h CLICON handle - * @retval 0 OK - * @retval -1 Error and logged to syslog + * @param[in] confirmed If set, send commit/confirmed + * @param[in] cancel If set, send cancle-commit + * @param[in] timeout For confirmed, a timeout in seconds (default 600s) + * @param[in] persist A persist identifier to use + * @param[in] persist_id If cancel or confirmed, the persist id + * @retval 0 OK + * @retval -1 Error and logged to syslog + * @see rfc6241 Sec 8.4 Confirmed Commit Capability */ int clicon_rpc_commit(clicon_handle h, diff --git a/test/site.sh b/test/site.sh index 480ef3bf..c01c7f26 100644 --- a/test/site.sh +++ b/test/site.sh @@ -10,7 +10,6 @@ # The SKIPLIST has precedence over the 'pattern' variable that you can use to # specify included file when running the various test scripts such as "all.sh". #SKIPLIST="test_[a-t]*\.sh test_openconfig.sh test_yangmodels.sh" -SKIPLIST="test_http_data.sh test_netconf_ssh_callhome.sh test_privileges.sh test_restconf.sh test_yang_models_ieee.sh" # # Parse yang openconfig models from https://github.com/openconfig/public OPENCONFIG=/usr/local/share/openconfig/public diff --git a/test/test_confirmed_commit.sh b/test/test_confirmed_commit.sh index 3f18fc86..9d109328 100755 --- a/test/test_confirmed_commit.sh +++ b/test/test_confirmed_commit.sh @@ -1,8 +1,11 @@ #!/usr/bin/env bash -# Basic Netconf functionality -# Mainly default/null prefix, but also xx: prefix -# XXX: could add tests for dual prefixes xx and xy with doppelganger names, ie xy:filter that is -# syntactic correct but wrong +# Netconf confirm commit capability +# See RFC 6241 Section 8.4 +# Test uses privileges drop +# TODO: +# - privileges drop +# - restconf +# - lock check # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -13,13 +16,17 @@ cfg=$dir/conf_yang.xml tmp=$dir/tmp.x fyang=$dir/clixon-example.yang -# Use yang in example +# Backend user for priv drop, otherwise root +USER=root #${BUSER} + +# Define default restconfig config: RESTCONFIG +RESTCONFIG=$(restconf_config none false) cat < $cfg $cfg - ietf-netconf:startup ietf-netconf:confirmed-commit + clixon-restconf:allow-auth-none 42 ${YANG_INSTALLDIR} $IETFRFC @@ -37,6 +44,7 @@ cat < $cfg /usr/local/var/$APPNAME $USER drop_perm + $RESTCONFIG EOF @@ -45,16 +53,7 @@ module clixon-example{ yang-version 1.1; namespace "urn:example:clixon"; prefix ex; - import ietf-interfaces { - prefix if; - } - import ietf-ip { - prefix ip; - } - /* Example interface type for tests, local callbacks, etc */ - identity eth { - base if:interface-type; - } + /* Generic config data */ container table{ list parameter{ @@ -64,74 +63,6 @@ module clixon-example{ } } } - /* State data (not config) for the example application*/ - container state { - config false; - description "state data for the example application (must be here for example get operation)"; - leaf-list op { - type string; - } - } - augment "/if:interfaces/if:interface" { - container my-status { - config false; - description "For testing augment+state"; - leaf int { - type int32; - } - leaf str { - type string; - } - } - } - rpc client-rpc { - description "Example local client-side RPC that is processed by the - the netconf/restconf and not sent to the backend. - This is a clixon implementation detail: some rpc:s - are better processed by the client for API or perf reasons"; - input { - leaf x { - type string; - } - } - output { - leaf x { - type string; - } - } - } - rpc empty { - description "Smallest possible RPC with no input or output sections"; - } - rpc example { - description "Some example input/output for testing RFC7950 7.14. - RPC simply echoes the input for debugging."; - input { - leaf x { - description - "If a leaf in the input tree has a 'mandatory' statement with - the value 'true', the leaf MUST be present in an RPC invocation."; - type string; - mandatory true; - } - leaf y { - description - "If a leaf in the input tree has a 'mandatory' statement with the - value 'true', the leaf MUST be present in an RPC invocation."; - type string; - default "42"; - } - } - output { - leaf x { - type string; - } - leaf y { - type string; - } - } - } - } EOF @@ -176,6 +107,7 @@ function edit_config() { function assert_config_equals() { TARGET="$1" EXPECTED="$2" +# new "get-config: $TARGET" rpc "<$TARGET/>" "$(data "$EXPECTED")" } @@ -191,15 +123,12 @@ RUNNING_PATH="/usr/local/var/$APPNAME/running_db" ROLLBACK_PATH="/usr/local/var/$APPNAME/rollback_db" FAILSAFE_PATH="/usr/local/var/$APPNAME/failsafe_db" -CONFIGB="eth0ex:ethtrue" -CONFIGC="eth1ex:ethtrue" -CONFIGBPLUSC="eth0ex:ethtrueeth1ex:ethtrue" -FAILSAFE_CFG="eth99ex:ethtrue" +CONFIGB="eth0
" +CONFIGC="eth1
" +CONFIGBPLUSC="eth0eth1
" +FAILSAFE_CFG="eth99
" -# TODO this test suite is somewhat brittle as it relies on the presence of the example configuration that one gets with -# make install-example in the Clixon distribution. It would be better if the dependencies were entirely self contained. - -new "test params: -f $cfg -- -s" +new "test params: -f $cfg" # Bring your own backend if [ $BE -ne 0 ]; then # kill old backend (if any) @@ -208,15 +137,19 @@ if [ $BE -ne 0 ]; then if [ $? -ne 0 ]; then err fi - new "start backend -s init -f $cfg -- -s" - start_backend -s init -f $cfg -- -s + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg fi new "wait backend" wait_backend -################################################################################ + +new "Hello check confirm-commit capability" +expecteof "$clixon_netconf -f $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" "urn:ietf:params:netconf:capability:confirmed-commit:1.1" '^$' + +################################################################################ new "netconf ephemeral confirmed-commit rolls back after disconnect" reset edit_config "candidate" "$CONFIGB" @@ -305,13 +238,13 @@ commit "abcdefg" assert_config_equals "running" "$CONFIGBPLUSC" stop_backend -f $cfg # kill backend and restart [ -f "$ROLLBACK_PATH" ] || err "rollback_db doesn't exist!" # assert rollback_db exists -start_backend -s running -f $cfg -- -s +start_backend -s running -f $cfg wait_backend assert_config_equals "running" "$CONFIGB" [ -f "ROLLBACK_PATH" ] && err "rollback_db still exists!" # assert rollback_db doesn't exist stop_backend -f $cfg -start_backend -s init -f $cfg -- -s +start_backend -s init -f $cfg ################################################################################ @@ -334,12 +267,12 @@ sudo tee "$ROLLBACK_PATH" > /dev/null << EOF EOF -start_backend -s running -f $cfg -- -s +start_backend -s running -f $cfg wait_backend assert_config_equals "running" "$FAILSAFE_CFG" stop_backend -f $cfg -start_backend -s init -f $cfg -lf/tmp/clixon.log -D1 -- -s +start_backend -s init -f $cfg -lf/tmp/clixon.log -D1 wait_backend ################################################################################ @@ -364,14 +297,15 @@ reset tmppipe=$(mktemp -u) mkfifo -m 600 "$tmppipe" + cat << EOF | clixon_cli -f $cfg >> /dev/null & -set interfaces interface eth0 type ex:eth -set interfaces interface eth0 enabled true +set table parameter eth0 commit confirmed 60 shell echo >> $tmppipe shell cat $tmppipe quit EOF + cat $tmppipe >> /dev/null assert_config_equals "running" "$CONFIGB" echo >> $tmppipe @@ -383,17 +317,17 @@ rm $tmppipe new "cli persistent confirmed-commit" reset + cat << EOF | clixon_cli -f $cfg >> /dev/null -set interfaces interface eth0 type ex:eth -set interfaces interface eth0 enabled true +set table parameter eth0 commit confirmed persist a quit EOF + assert_config_equals "running" "$CONFIGB" cat << EOF | clixon_cli -f $cfg >> /dev/null -set interfaces interface eth1 type ex:eth -set interfaces interface eth1 enabled true +set table parameter eth1 commit persist-id a confirmed persist ab quit EOF @@ -420,8 +354,7 @@ expectpart "$($clixon_cli -lo -1 -f $cfg commit persist-id ab cancel)" 255 "no c new "cli persistent confirmed-commit with timeout" reset cat << EOF | clixon_cli -f $cfg >> /dev/null -set interfaces interface eth0 type ex:eth -set interfaces interface eth0 enabled true +set table parameter eth0 commit confirmed persist abcd 2 EOF assert_config_equals "running" "$CONFIGB" @@ -433,16 +366,16 @@ assert_config_equals "running" "" new "cli persistent confirmed-commit with reset timeout" reset cat << EOF | clixon_cli -f $cfg >> /dev/null -set interfaces interface eth0 type ex:eth -set interfaces interface eth0 enabled true +set table parameter eth0 commit confirmed persist abcd 5 EOF + assert_config_equals "running" "$CONFIGB" cat << EOF | clixon_cli -f $cfg >> /dev/null -set interfaces interface eth1 type ex:eth -set interfaces interface eth1 enabled true +set table parameter eth1 commit persist-id abcd confirmed persist abcdef 10 EOF + sleep 6 assert_config_equals "running" "$CONFIGBPLUSC" # now sleep long enough for rollback to happen; get config, assert == A @@ -452,7 +385,21 @@ assert_config_equals "running" "" # TODO test restconf receives "409 conflict" when there is a persistent confirmed-commit active # TODO test restconf causes confirming-commit for ephemeral confirmed-commit +if [ $RC -ne 0 ]; then + new "kill old restconf daemon" + stop_restconf_pre + new "start restconf daemon" + start_restconf -f $cfg +fi + +new "wait restconf" +wait_restconf + +if [ $RC -ne 0 ]; then + new "Kill restconf daemon" + stop_restconf +fi if [ $BE -ne 0 ]; then new "Kill backend" @@ -465,5 +412,8 @@ if [ $BE -ne 0 ]; then stop_backend -f $cfg fi +# Set by restconf_config +unset RESTCONFIG + new "endtest" endtest diff --git a/test/test_netconf_hello.sh b/test/test_netconf_hello.sh index 93e81177..61eb4447 100755 --- a/test/test_netconf_hello.sh +++ b/test/test_netconf_hello.sh @@ -106,7 +106,15 @@ new "Netconf snd hello with prefix" expecteof "$clixon_netconf -qef $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' new "netconf snd + rcv hello" -expecteof "$clixon_netconf -f $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" "^urn:ietf:params:netconf:base:1.1urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:capability:yang-library:1.0?revision=2019-01-04&module-set-id=42urn:ietf:params:netconf:capability:candidate:1.0urn:ietf:params:netconf:capability:validate:1.1urn:ietf:params:netconf:capability:startup:1.0urn:ietf:params:netconf:capability:xpath:1.0urn:ietf:params:netconf:capability:notification:1.0urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&also-supported=report-all,trim,report-all-tagged[0-9]*]]>]]>$" '^$' +expecteof "$clixon_netconf -f $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" "^urn:ietf:params:netconf:base:1.1urn:ietf:params:netconf:base:1.0 +urn:ietf:params:netconf:capability:yang-library:1.0?revision=2019-01-04&module-set-id=42 +urn:ietf:params:netconf:capability:candidate:1.0 +urn:ietf:params:netconf:capability:validate:1.1 +urn:ietf:params:netconf:capability:xpath:1.0 +urn:ietf:params:netconf:capability:notification:1.0 +urn:ietf:params:netconf:capability:startup:1.0 +urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&also-supported=report-all,trim,report-all-tagged +[0-9]*]]>]]>$" '^$' # Actually non-standard to reply on wrong hello with rpc-error, but may be useful new "Netconf snd hello with extra element" diff --git a/test/test_netconf_ssh_callhome.sh b/test/test_netconf_ssh_callhome.sh index 32a05415..8fc5469b 100755 --- a/test/test_netconf_ssh_callhome.sh +++ b/test/test_netconf_ssh_callhome.sh @@ -134,7 +134,7 @@ EOF new "Start Listener client" echo "ssh -s -F $sshcfg -v -i $key -o ProxyUseFdpass=yes -o ProxyCommand=\"clixon_netconf_ssh_callhome_client -a 127.0.0.1\" . netconf" #-F $sshcfg -expectpart "$(ssh -s -F $sshcfg -v -i $key -o ProxyUseFdpass=yes -o ProxyCommand="${clixon_netconf_ssh_callhome_client} -a 127.0.0.1" . netconf < $rpccmd)" 0 "urn:ietf:params:netconf:base:1.1urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:capability:yang-library:1.0?revision=2019-01-04&module-set-id=42urn:ietf:params:netconf:capability:candidate:1.0urn:ietf:params:netconf:capability:validate:1.1urn:ietf:params:netconf:capability:startup:1.0urn:ietf:params:netconf:capability:xpath:1.0urn:ietf:params:netconf:capability:notification:1.0urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&also-supported=report-all,trim,report-all-tagged2" "" +expectpart "$(ssh -s -F $sshcfg -v -i $key -o ProxyUseFdpass=yes -o ProxyCommand="${clixon_netconf_ssh_callhome_client} -a 127.0.0.1" . netconf < $rpccmd)" 0 "urn:ietf:params:netconf:base:1.1urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:capability:yang-library:1.0?revision=2019-01-04&module-set-id=42urn:ietf:params:netconf:capability:candidate:1.0urn:ietf:params:netconf:capability:validate:1.1urn:ietf:params:netconf:capability:xpath:1.0urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&also-supported=report-all,trim,report-all-taggedurn:ietf:params:netconf:capability:notification:1.02" "" # Wait wait From d29c81fce08c2c6161a307deba4b9cf4675aeab0 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 7 Oct 2022 17:53:25 +0200 Subject: [PATCH 07/22] Fixed: [clixon_snmp module crashes on snmpwalk command](https://github.com/clicon/clixon/issues/378) --- CHANGELOG.md | 1 + apps/snmp/snmp_handler.c | 11 ++++++----- apps/snmp/snmp_lib.c | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9886f4f2..c88f4001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ Developers may need to change their code ### Corrected Bugs +* 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/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index fa981e41..183e5515 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -146,13 +146,14 @@ snmp_scalar_return(cxobj *xs, char *reason = NULL; netsnmp_variable_list *requestvb = request->requestvb; int ret; + char *body = NULL; /* SMI default value, How is this different from yang defaults? */ if (yang_extension_value(ys, "defval", IETF_YANG_SMIV2_NS, NULL, &defaultval) < 0) goto done; - if (xs != NULL){ - if ((ret = type_xml2snmp_pre(xml_body(xs), ys, &xmlstr)) < 0) + if (xs != NULL && (body = xml_body(xs)) != NULL){ + if ((ret = type_xml2snmp_pre(body, ys, &xmlstr)) < 0) // XXX <--- goto done; if (ret == 0){ if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){ @@ -242,6 +243,7 @@ snmp_scalar_get(clicon_handle h, char *reason = NULL; netsnmp_variable_list *requestvb = request->requestvb; cxobj *xcache = NULL; + char *body = NULL; clicon_debug(1, "%s", __FUNCTION__); /* Prepare backend call by constructing namespace context */ @@ -270,9 +272,8 @@ snmp_scalar_get(clicon_handle h, * 1. From XML to SNMP string, there is a special case for enumeration, and for default value * 2. From SNMP string to SNMP binary value which invloves parsing */ - if (x != NULL){ - assert(xml_spec(x) == ys); - if ((ret = type_xml2snmp_pre(xml_body(x), ys, &xmlstr)) < 0) + if (x != NULL && (body = xml_body(x)) != NULL){ + if ((ret = type_xml2snmp_pre(body, ys, &xmlstr)) < 0) goto done; if (ret == 0){ if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){ diff --git a/apps/snmp/snmp_lib.c b/apps/snmp/snmp_lib.c index 21aa107c..5b979cd0 100644 --- a/apps/snmp/snmp_lib.c +++ b/apps/snmp/snmp_lib.c @@ -633,8 +633,8 @@ type_xml2snmp_pre(char *xmlstr0, char *str = NULL; int ret; - if (xmlstr1 == NULL){ - clicon_err(OE_UNIX, EINVAL, "xmlstr1"); + if (xmlstr0 == NULL || xmlstr1 == NULL){ + clicon_err(OE_UNIX, EINVAL, "xmlstr0/1 is NULL"); goto done; } /* Get yang type of leaf and trasnslate to ASN.1 */ From 575429caa100325d714809d90fd654298e3c7a52 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 8 Oct 2022 17:04:02 +0200 Subject: [PATCH 08/22] * Fixed: [Non-obvious behavior of clixon_snmp after snmpset command when transaction validation returns an error](https://github.com/clicon/clixon/issues/375) * Changed `clicon_rpc_commit()` and `clicon_rpc_validate`: Added three-value return. --- CHANGELOG.md | 10 ++++++--- apps/cli/cli_common.c | 4 ++-- apps/snmp/snmp_handler.c | 18 +++++++++++------ lib/src/clixon_proto_client.c | 20 ++++++++++-------- test/test_snmp_set.sh | 38 ++++++++++++++++++++++++++++++++++- 5 files changed, 70 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c88f4001..affb827d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,15 +66,19 @@ Developers may need to change their code * C API changes * Added `defaults` parameter to `clicon_rpc_get_pageable_list()` - * Added `confirmed`, `cancel`, `timeout`, `persist-id`, and `persist-id-val` parameters to - `cli_commit(...)` - + * `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 * 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: [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) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 158921b6..574eb9b4 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -664,7 +664,7 @@ cli_commit(clicon_handle h, persist = cvec_find_str(vars, "persist-val"); persist_id = cvec_find_str(vars, "persist-id-val"); - if ((retval = clicon_rpc_commit(h, confirmed, cancel, timeout, persist, persist_id)) < 0) + if (clicon_rpc_commit(h, confirmed, cancel, timeout, persist, persist_id) < 1) goto done; retval = 0; done: @@ -680,7 +680,7 @@ cli_validate(clicon_handle h, { int retval = -1; - if ((retval = clicon_rpc_validate(h, "candidate")) < 0) + if (clicon_rpc_validate(h, "candidate") < 1) goto done; retval = 0; done: diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index 183e5515..1f46dd64 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -44,7 +44,6 @@ #include #include #include -#include /* net-snmp */ #include @@ -730,7 +729,6 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, goto done; break; case MODE_GETNEXT: /* 161 */ - assert(0); // Not seen? break; case MODE_SET_RESERVE1: /* 0 */ /* Translate from YANG ys leaf type to SNMP asn1.1 type ids (not value), also cvtype */ @@ -751,8 +749,12 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, goto done; break; case MODE_SET_COMMIT: /* 3 */ - if (clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL) < 0) - goto done; + if ((ret = clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL)) < 0) + goto done; + if (ret == 0){ + clicon_rpc_discard_changes(sh->sh_h); + goto done; + } break; case MODE_SET_FREE: /* 4 */ break; @@ -1304,8 +1306,12 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, } break; case MODE_SET_COMMIT: // 3 - if (clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL) < 0) - goto done; + if ((ret = clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL)) < 0) + goto done; + if (ret == 0){ + clicon_rpc_discard_changes(sh->sh_h); + goto done; + } break; case MODE_SET_FREE: // 4 break; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 4b2b1c9b..fc6a20cc 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -1225,8 +1225,10 @@ clicon_rpc_kill_session(clicon_handle h, /*! Send validate request to backend daemon * @param[in] h CLICON handle * @param[in] db Name of database - * @retval 0 OK + * @retval 1 OK + * @retval 0 Invalid, netconf error return, and logged to syslog * @retval -1 Error and logged to syslog + * @note error returns are logged but not returned */ int clicon_rpc_validate(clicon_handle h, @@ -1253,9 +1255,10 @@ clicon_rpc_validate(clicon_handle h, goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clixon_netconf_error(xerr, CLIXON_ERRSTR_VALIDATE_FAILED, NULL); + retval = 0; goto done; } - retval = 0; + retval = 1; done: if (msg) free(msg); @@ -1271,9 +1274,11 @@ clicon_rpc_validate(clicon_handle h, * @param[in] timeout For confirmed, a timeout in seconds (default 600s) * @param[in] persist A persist identifier to use * @param[in] persist_id If cancel or confirmed, the persist id - * @retval 0 OK - * @retval -1 Error and logged to syslog + * @retval 1 OK + * @retval 0 Invalid, netconf error return, and logged to syslog + * @retval -1 Error * @see rfc6241 Sec 8.4 Confirmed Commit Capability + * @note error returns are logged but not returned */ int clicon_rpc_commit(clicon_handle h, @@ -1318,8 +1323,6 @@ clicon_rpc_commit(clicon_handle h, }; sprintf(timeout_xml, TIMEOUT_XML_FMT, timeout); } - - if (session_id_check(h, &session_id) < 0) goto done; username = clicon_username_get(h); @@ -1353,9 +1356,10 @@ clicon_rpc_commit(clicon_handle h, goto done; if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clixon_netconf_error(xerr, CLIXON_ERRSTR_COMMIT_FAILED, NULL); + retval = 0; goto done; } - retval = 0; + retval = 1; done: if (xret) xml_free(xret); @@ -1550,7 +1554,7 @@ clicon_rpc_restconf_debug(clicon_handle h, clicon_err(OE_XML, 0, "rpc error"); /* XXX extract info from rpc-error */ goto done; } - if ((retval = clicon_rpc_commit(h, 0, 0, 0, NULL, NULL)) < 0) + if ((retval = clicon_rpc_commit(h, 0, 0, 0, NULL, NULL)) < 1) goto done; done: if (msg) diff --git a/test/test_snmp_set.sh b/test/test_snmp_set.sh index f57ff676..54a13152 100755 --- a/test/test_snmp_set.sh +++ b/test/test_snmp_set.sh @@ -2,6 +2,7 @@ # snmpset. This requires deviation of MIB-YANG to make write operations # Get default value, set new value via SNMP and check it, set new value via NETCONF and check # Selected types from CLIXON/IF-MIB/ENTITY mib +# Also an incomplete commit failed test # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -33,6 +34,7 @@ cat < $cfg $fyang $dir/$APPNAME.sock /var/tmp/$APPNAME.pidfile + /usr/local/lib/$APPNAME/backend $dir unix:$SOCK CLIXON-TYPES-MIB @@ -195,9 +197,10 @@ function testrun() echo "$snmpset $oid $set_type $value" expectpart "$($snmpset $oid $set_type $value)" 0 "$type:" "$value" else - echo "$snmpset $oid $set_type $value" + echo "$snmpset $oid $set_type $value2" expectpart "$($snmpset $oid $set_type $value)" 0 "$type: $value2" fi + new "Check $name via SNMP" if [ "$type" == "STRING" ]; then expectpart "$($snmpget $oid)" 0 "$type:" "$value" @@ -211,6 +214,17 @@ function testrun() function testexit(){ stop_snmp + + if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + fi } new "SNMP tests" @@ -241,6 +255,28 @@ expectpart "$($snmpget $oid)" 0 "$type: active(1)" new "Check $name via CLI" expectpart "$($clixon_cli -1 -f $cfg show config xml)" 0 "<$name>active" +# restart backend with synthetic validation failure +# Incomplete commit failed test: the commit fails by logging but this is not actually checked +if [ $BE -ne 0 ]; then + # Kill old backend and start a new one + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err "Failed to start backend" + fi + + sudo pkill -f clixon_backend + + new "Starting backend" + start_backend -s startup -f $cfg -- -V CLIXON-TYPES-MIB/clixonExampleScalars/clixonExampleInteger +fi + +new "wait backend" +wait_backend + +new "set value with error" +expectpart "$($snmpset ${MIB}.1.1 i 4321)" 0 "INTEGER: 4321" + new "Cleaning up" testexit From dd1e5966fb781076c6339731c2684e1ad37f05fa Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 8 Oct 2022 17:19:24 +0200 Subject: [PATCH 09/22] Test: killed backend in snmp tests --- lib/src/clixon_validate_minmax.c | 1 - test/test_snmp_entity.sh | 11 +++++++++++ test/test_snmp_get.sh | 10 ++++++++++ test/test_snmp_ifmib.sh | 12 ++++++++++-- test/test_snmp_rowstatus.sh | 10 ++++++++++ test/test_snmp_system.sh | 10 ++++++++++ 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/src/clixon_validate_minmax.c b/lib/src/clixon_validate_minmax.c index 47c9a4f9..c87ce6ac 100644 --- a/lib/src/clixon_validate_minmax.c +++ b/lib/src/clixon_validate_minmax.c @@ -658,7 +658,6 @@ xml_yang_minmax_recurse(cxobj *xt, yt = xml_spec(xt); /* If yt == NULL, then no gap-analysis is done */ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - clicon_debug(1, "%s x:%s", __FUNCTION__, xml_name(x)); if ((y = xml_spec(x)) == NULL) continue; keyw = yang_keyword_get(y); diff --git a/test/test_snmp_entity.sh b/test/test_snmp_entity.sh index 7ebd7273..b780ab9e 100755 --- a/test/test_snmp_entity.sh +++ b/test/test_snmp_entity.sh @@ -136,6 +136,17 @@ function testinit(){ function testexit(){ stop_snmp + + if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + fi } ENTITY_OID=".1.3.6.1.2.1.47.1.1.1" diff --git a/test/test_snmp_get.sh b/test/test_snmp_get.sh index bb1a38d5..e8d10703 100755 --- a/test/test_snmp_get.sh +++ b/test/test_snmp_get.sh @@ -132,6 +132,16 @@ function testinit(){ function testexit(){ stop_snmp + if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + fi } new "SNMP tests" diff --git a/test/test_snmp_ifmib.sh b/test/test_snmp_ifmib.sh index ee703589..69e85241 100755 --- a/test/test_snmp_ifmib.sh +++ b/test/test_snmp_ifmib.sh @@ -195,13 +195,21 @@ function testinit(){ function testexit(){ stop_snmp + if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + fi } - new "SNMP tests" testinit - # IF-MIB::interfaces MIB=".1.3.6.1.2.1" for (( i=1; i<23; i++ )); do diff --git a/test/test_snmp_rowstatus.sh b/test/test_snmp_rowstatus.sh index e92776d5..87c4b28b 100755 --- a/test/test_snmp_rowstatus.sh +++ b/test/test_snmp_rowstatus.sh @@ -206,6 +206,16 @@ function testrun_removeRows() function testexit() { stop_snmp + if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + fi } new "SNMP tests" diff --git a/test/test_snmp_system.sh b/test/test_snmp_system.sh index fdfdf440..bbee27de 100755 --- a/test/test_snmp_system.sh +++ b/test/test_snmp_system.sh @@ -121,6 +121,16 @@ function testinit(){ function testexit(){ stop_snmp + if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + fi } new "SNMP tests" From d2c3b903f1238731d52c2c4e0d876be18cd32baa Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 9 Oct 2022 11:11:17 +0200 Subject: [PATCH 10/22] SNMP: set commit fail error --- apps/snmp/snmp_handler.c | 10 ++++++---- apps/snmp/snmp_main.c | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index 1f46dd64..6c509c8c 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -753,6 +753,7 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, goto done; if (ret == 0){ clicon_rpc_discard_changes(sh->sh_h); + retval = SNMP_ERR_COMMITFAILED; goto done; } break; @@ -1227,10 +1228,10 @@ snmp_table_getnext(clicon_handle h, /*! SNMP table operation handler * - * @param[in] handler Registered MIB handler structure - * @param[in] nhreg Root registration info. - * @param[in] reqinfo Agent transaction request structure - * @param[in] request The netsnmp request info structure. + * @param[in] handler Registered MIB handler structure + * @param[in] nhreg Root registration info. + * @param[in] reqinfo Agent transaction request structure + * @param[in] request The netsnmp request info structure. */ static int clixon_snmp_table_handler1(netsnmp_mib_handler *handler, @@ -1310,6 +1311,7 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, goto done; if (ret == 0){ clicon_rpc_discard_changes(sh->sh_h); + retval = SNMP_ERR_COMMITFAILED; goto done; } break; diff --git a/apps/snmp/snmp_main.c b/apps/snmp/snmp_main.c index 28fb9321..8c41e1cd 100644 --- a/apps/snmp/snmp_main.c +++ b/apps/snmp/snmp_main.c @@ -198,7 +198,7 @@ clixon_snmp_input_cb(int s, clicon_handle h = (clicon_handle)arg; int ret; - clicon_debug(1, "%s %d", __FUNCTION__, s); + clicon_debug(2, "%s %d", __FUNCTION__, s); FD_ZERO(&readfds); FD_SET(s, &readfds); (void)snmp_read(&readfds); From aff69127b61f6fcceacc4384b0bb85ee03d743b3 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 9 Oct 2022 11:59:26 +0200 Subject: [PATCH 11/22] 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 --- CHANGELOG.md | 2 ++ apps/snmp/snmp_handler.c | 8 ++++++++ test/test_snmp_get.sh | 3 +++ 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index affb827d..03547b42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,8 @@ Developers may need to change their code ### Corrected Bugs +* 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) diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index 6c509c8c..ddc86914 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -731,6 +731,10 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, case MODE_GETNEXT: /* 161 */ break; case MODE_SET_RESERVE1: /* 0 */ + if (!yang_config_ancestor(sh->sh_ys)){ + retval = SNMP_ERR_NOTWRITABLE; + goto done;; + } /* Translate from YANG ys leaf type to SNMP asn1.1 type ids (not value), also cvtype */ if (type_yang2asn1(sh->sh_ys, &asn1_type, 0) < 0) goto done; @@ -1289,6 +1293,10 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, } break; case MODE_SET_RESERVE1: // 0 + if (!yang_config_ancestor(sh->sh_ys)){ + retval = SNMP_ERR_NOTWRITABLE; + goto done;; + } // Check types: compare type in requestvb to yang type (or do later) break; case MODE_SET_RESERVE2: // 1 diff --git a/test/test_snmp_get.sh b/test/test_snmp_get.sh index e8d10703..e7fc9d2d 100755 --- a/test/test_snmp_get.sh +++ b/test/test_snmp_get.sh @@ -290,6 +290,9 @@ expectpart "$($snmpget $OID19)" 0 "$OID19 = Hex-STRING: 74 65 73 74 00" new "Test SNMP getnext netSnmpHostName" expectpart "$($snmpgetnext $OID19)" 0 "$OID20 = INTEGER: 1" +new "Negative test: Try to set object" +expectpart "$($snmpset $OID1 i 4 2> /dev/null)" 2 "^$" + new "Cleaning up" testexit From c4e69b76cfccd72acd4e211168cc9d10a2b02913 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 9 Oct 2022 12:10:12 +0200 Subject: [PATCH 12/22] Doxyfile for graphs --- doc/Doxyfile.graphs | 2 +- doc/Makefile.in | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/Doxyfile.graphs b/doc/Doxyfile.graphs index 7eaf3ef0..667604d9 100644 --- a/doc/Doxyfile.graphs +++ b/doc/Doxyfile.graphs @@ -743,7 +743,7 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/backend/ ../apps/restconf/ ../apps/netconf ../apps/dbctrl ../datastore ../datastore/text +INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/backend/ ../apps/restconf/ ../apps/netconf ../apps/snmp # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/doc/Makefile.in b/doc/Makefile.in index f0af1e9b..8e2ead5a 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -37,7 +37,6 @@ datarootdir = @datarootdir@ docdir = @docdir@ pdflatex = @PDFLATEX@ - SUBDIRS = .PHONY: clean all doc $(SUBDIRS) From 83663d4d15cc201095ae645510b1c20bab583329 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 9 Oct 2022 17:02:08 +0200 Subject: [PATCH 13/22] Fixed: [Trying to change the "read-only" node through snmpset](https://github.com/clicon/clixon/issues/376) --- CHANGELOG.md | 1 + apps/snmp/snmp_handler.c | 40 ++++++++++++++++++++++++++++++++-------- test/test_snmp_set.sh | 3 ++- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03547b42..5e2d57bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Developers may need to change their code ### Corrected Bugs +* 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) diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index ddc86914..216bb753 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -731,7 +731,8 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, case MODE_GETNEXT: /* 161 */ break; case MODE_SET_RESERVE1: /* 0 */ - if (!yang_config_ancestor(sh->sh_ys)){ + if (!yang_config_ancestor(sh->sh_ys) || + nhreg->modes == HANDLER_CAN_RONLY){ retval = SNMP_ERR_NOTWRITABLE; goto done;; } @@ -757,7 +758,7 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, goto done; if (ret == 0){ clicon_rpc_discard_changes(sh->sh_h); - retval = SNMP_ERR_COMMITFAILED; + netsnmp_request_set_error(request, SNMP_ERR_NOTWRITABLE); goto done; } break; @@ -929,9 +930,10 @@ snmp_table_get(clicon_handle h, * @param[in] oids OID of ultimate scalar value * @param[in] oidslen OID length of scalar * @param[in] reqinfo Agent transaction request structure - * @param[in] request The netsnmp request info structure. + * @param[in] request The netsnmp request info structure. + * @param[out] err Error code if failed (retval = 0) * @retval -1 Error - * @retval 0 Object not found + * @retval 0 Failed, err set * @retval 1 OK */ static int @@ -940,7 +942,8 @@ snmp_table_set(clicon_handle h, oid *oids, size_t oidslen, netsnmp_agent_request_info *reqinfo, - netsnmp_request_info *request) + netsnmp_request_info *request, + int *err) { int retval = -1; oid oidt[MAX_OID_LEN] = {0,}; /* Table / list oid */ @@ -1003,8 +1006,27 @@ snmp_table_set(clicon_handle h, } if (ys == NULL){ /* No leaf with matching OID */ + *err = SNMP_NOSUCHOBJECT; goto fail; } + if (!yang_config_ancestor(ys)){ + *err = SNMP_ERR_NOTWRITABLE; + goto fail; + } + { + char *modes_str = NULL; + int modes; + + if (yang_extension_value(ys, "max-access", IETF_YANG_SMIV2_NS, NULL, &modes_str) < 0) + goto done; + if (modes_str){ + modes = snmp_access_str2int(modes_str); + if (modes == HANDLER_CAN_RONLY){ + *err = SNMP_ERR_NOTWRITABLE; + goto fail; + } + } + } if (type_yang2asn1(ys, &asn1_type, 0) < 0) goto done; requestvb = request->requestvb; @@ -1041,6 +1063,7 @@ snmp_table_set(clicon_handle h, } if (oidilen != 0){ clicon_err(OE_YANG, 0, "Expected oidlen 0 but is %zu", oidilen); + *err = SNMP_NOSUCHOBJECT; goto fail; } if (yrowst){ @@ -1250,6 +1273,7 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, cbuf *cb = NULL; int ret; netsnmp_variable_list *requestvb; + int err = 0; clicon_debug(2, "%s", __FUNCTION__); if ((ret = snmp_common_handler(handler, nhreg, reqinfo, request, 1, &sh)) < 0) @@ -1304,10 +1328,10 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, case MODE_SET_ACTION: // 2 if ((ret = snmp_table_set(sh->sh_h, sh->sh_ys, requestvb->name, requestvb->name_length, - reqinfo, request)) < 0) + reqinfo, request, &err)) < 0) goto done; if (ret == 0){ - if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){ + if ((ret = netsnmp_request_set_error(request, err)) != SNMPERR_SUCCESS){ clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); goto done; } @@ -1319,7 +1343,7 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, goto done; if (ret == 0){ clicon_rpc_discard_changes(sh->sh_h); - retval = SNMP_ERR_COMMITFAILED; + netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED); goto done; } break; diff --git a/test/test_snmp_set.sh b/test/test_snmp_set.sh index 54a13152..9907941d 100755 --- a/test/test_snmp_set.sh +++ b/test/test_snmp_set.sh @@ -239,7 +239,8 @@ testrun clixonExampleSleeper INTEGER -1 -1 -1 ${MIB}.1.2 testrun clixonExampleString STRING foobar foobar foobar ${MIB}.1.3 testrun ifPromiscuousMode INTEGER 1 1 true ${MIB}.1.10 # boolean testrun ifIpAddr IPADDRESS 1.2.3.4 1.2.3.4 1.2.3.4 ${MIB}.1.13 # InetAddress -testrun ifPhysAddress STRING ff:ee:dd:cc:bb:aa ff:ee:dd:cc:bb:aa ff:ee:dd:cc:bb:aa ${IFMIB}.2.2.1.6.1 +# XXX It was supposed to test writing hardware address type, but it is also read-only +#testrun ifPhysAddress STRING ff:ee:dd:cc:bb:aa ff:ee:dd:cc:bb:aa ff:ee:dd:cc:bb:aa ${IFMIB}.2.2.1.6.1 # Inline testrun for rowstatus complicated logic name=ifStackStatus From 1eb78a78f842a12e793e93fe5d98ebbe4a81f99f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 17 Oct 2022 13:16:08 +0200 Subject: [PATCH 14/22] Fixed: [YANG when condition evaluated as false combined with a mandatory leaf does not work](https://github.com/clicon/clixon/issues/380) Replaced yang_mandatory() with yang_xml_mandatory() by extending existing it with when check --- CHANGELOG.md | 1 + lib/clixon/clixon_xml_map.h | 1 + lib/clixon/clixon_yang.h | 1 - lib/src/clixon_validate.c | 58 ++++--- lib/src/clixon_xml_map.c | 83 +++++++++- lib/src/clixon_yang.c | 59 ------- test/test_when_mandatory.sh | 309 ++++++++++++++++++++++++++++++++++++ 7 files changed, 424 insertions(+), 88 deletions(-) create mode 100755 test/test_when_mandatory.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e2d57bf..43f98a8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Developers may need to change their code ### Corrected Bugs +* 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 diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 400481bf..031b44f7 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -75,6 +75,7 @@ int yang_enum2valstr(yang_stmt *ytype, char *enumstr, char **valstr); int yang_enum_int_value(cxobj *node, int32_t *val); int xml_copy_marked(cxobj *x0, cxobj *x1); int yang_check_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp); +int yang_xml_mandatory(cxobj *xt, yang_stmt *ys); int xml_rpc_isaction(cxobj *xn); int xml_find_action(cxobj *xn, int top, cxobj **xap); int purge_tagged_nodes(cxobj *xn, char *ns, char *name, char *value, int keepnode); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index ce72361f..38b294a8 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -272,7 +272,6 @@ int yang_apply(yang_stmt *yn, enum rfc_6020 key, yang_applyfn_t fn, int f int yang_datanode(yang_stmt *ys); int yang_abs_schema_nodeid(yang_stmt *ys, char *schema_nodeid, yang_stmt **yres); int yang_desc_schema_nodeid(yang_stmt *yn, char *schema_nodeid, yang_stmt **yres); -int yang_mandatory(yang_stmt *ys); int yang_config(yang_stmt *ys); int yang_config_ancestor(yang_stmt *ys); int yang_features(clicon_handle h, yang_stmt *yt); diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 7c45390a..229e7413 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -613,16 +613,20 @@ check_list_key(cxobj *xt, * @retval -1 Error */ static int -choice_mandatory_check(yang_stmt *ycase, +choice_mandatory_check(cxobj *xt, + yang_stmt *ycase, cxobj **xret) { int retval = -1; yang_stmt *yc = NULL; cbuf *cb = NULL; int fail = 0; + int ret; while ((yc = yn_each(ycase, yc)) != NULL) { - if (yang_mandatory(yc)){ + if ((ret = yang_xml_mandatory(xt, yc)) < 0) + goto done; + if (ret == 1){ if (yang_flag_get(yc, YANG_FLAG_MARK)) yang_flag_reset(yc, YANG_FLAG_MARK); else if (fail == 0){ @@ -728,16 +732,20 @@ check_mandatory_case(cxobj *xt, if ((y = xml_spec(x)) != NULL && yang_ancestor_child(y, yc, &ym, &ycnew) != 0 && yang_keyword_get(ycnew) == Y_CASE){ - if (ym && yang_mandatory(ym)){ + if (ym){ + if ((ret = yang_xml_mandatory(xt, ym)) < 0) + goto done; + if (ret == 1){ if (yang_flag_get(ym, YANG_FLAG_MARK) != 0){ clicon_debug(1, "%s Already marked, shouldnt happen", __FUNCTION__); } yang_flag_set(ym, YANG_FLAG_MARK); + } } if (ycase != NULL){ if (ycnew != ycase){ /* End of case, new case */ /* Check and clear marked mandatory */ - if ((ret = choice_mandatory_check(ycase, xret)) < 0) + if ((ret = choice_mandatory_check(xt, ycase, xret)) < 0) goto done; if (ret == 0) goto fail; @@ -749,7 +757,7 @@ check_mandatory_case(cxobj *xt, } else if (ycase != NULL){ /* End of case */ /* Check and clear marked mandatory */ - if ((ret = choice_mandatory_check(ycase, xret)) < 0) + if ((ret = choice_mandatory_check(xt, ycase, xret)) < 0) goto done; if (ret == 0) goto fail; @@ -757,7 +765,7 @@ check_mandatory_case(cxobj *xt, } } if (ycase){ - if ((ret = choice_mandatory_check(ycase, xret)) < 0) + if ((ret = choice_mandatory_check(xt, ycase, xret)) < 0) goto done; if (ret == 0) goto fail; @@ -806,7 +814,7 @@ check_mandatory(cxobj *xt, while ((yc = yn_each(yt, yc)) != NULL) { /* Choice is more complex because of choice/case structure and possibly hierarchical */ if (yang_keyword_get(yc) == Y_CHOICE){ - if (yang_mandatory(yc)){ + if (yang_xml_mandatory(xt, yc)){ x = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { if ((y = xml_spec(x)) != NULL && @@ -829,7 +837,9 @@ check_mandatory(cxobj *xt, if (ret == 0) goto fail; } - if (!yang_mandatory(yc)) /* Rest of yangs are immediate children */ + if ((ret = yang_xml_mandatory(xt, yc)) < 0) /* Rest of yangs are immediate children */ + goto done; + if (ret == 0) continue; switch (yang_keyword_get(yc)){ case Y_CONTAINER: @@ -1130,6 +1140,22 @@ xml_yang_validate_all(clicon_handle h, goto fail; } if (yang_config(yt) != 0){ + if (yang_check_when_xpath(xt, xml_parent(xt), yt, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "Failed WHEN condition of %s in module %s (WHEN xpath is %s)", + xml_name(xt), + yang_argument_get(ys_module(yt)), + xpath); + if (xret && netconf_operation_failed_xml(xret, "application", + cbuf_get(cb)) < 0) + goto done; + goto fail; + } if ((ret = check_mandatory(xt, yt, xret)) < 0) goto done; if (ret == 0) @@ -1204,22 +1230,6 @@ xml_yang_validate_all(clicon_handle h, nsc = NULL; } } - if (yang_check_when_xpath(xt, xml_parent(xt), yt, &hit, &nr, &xpath) < 0) - goto done; - if (hit && nr == 0){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "Failed WHEN condition of %s in module %s (WHEN xpath is %s)", - xml_name(xt), - yang_argument_get(ys_module(yt)), - xpath); - if (xret && netconf_operation_failed_xml(xret, "application", - cbuf_get(cb)) < 0) - goto done; - goto fail; - } } x = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 729df365..98943745 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -2089,10 +2089,14 @@ xml_copy_marked(cxobj *x0, /*! Check when condition * - * @param[in] h Clixon handle - * @param[in] xn XML node, can be NULL, in which case it is added as dummy under xp - * @param[in] xp XML parent - * @param[in] ys Yang node + * @param[in] xn XML node, can be NULL, in which case it is added as dummy under xp + * @param[in] xp XML parent + * @param[in] yn Yang node + * @param[out] hit when statement found + * @param[out] nrp 1: when stmt evaluates to true + * @param[out] xpathp when stmts xpath + * @retval 0 OK + * @retval -1 Error * First variants of WHEN: Augmented and uses when using special info in node * Second variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ @@ -2155,6 +2159,77 @@ yang_check_when_xpath(cxobj *xn, return retval; } +/*! Check if node is (recursively) mandatory also checking when conditional + * + * @param[in] xn Optional XML node + * @param[in] xp XML parent + * @param[in] ys YANG node + * @retval 1 Recursively contains a mandatory node + * @retval 0 Does not contain a mandatory node + * @retval -1 Error + * @see RFC7950 Sec 3: + * o mandatory node: A mandatory node is one of: + * 1) A leaf, choice, anydata, or anyxml node with a "mandatory" + * statement with the value "true". + * 2) # see below + * 3) A container node without a "presence" statement and that has at + * least one mandatory node as a child. + */ +int +yang_xml_mandatory(cxobj *xt, + yang_stmt *ys) +{ + int retval = -1; + yang_stmt *ym; + cg_var *cv; + enum rfc_6020 keyw; + cxobj *xs = NULL; + int ret; + yang_stmt *yc; + int hit; + int nr; + + /* Create dummy xs if not exist */ + if ((xs = xml_new(yang_argument_get(ys), xt, CX_ELMNT)) == NULL) + goto done; + xml_spec_set(xs, ys); + if (yang_check_when_xpath(xs, xt, ys, &hit, &nr, NULL) < 0) + goto done; + if (hit && !nr){ + retval = 0; + goto done; + } + keyw = yang_keyword_get(ys); + if (keyw == Y_LEAF || keyw == Y_CHOICE || keyw == Y_ANYDATA || keyw == Y_ANYXML){ + if ((ym = yang_find(ys, Y_MANDATORY, NULL)) != NULL){ + if ((cv = yang_cv_get(ym)) != NULL){ /* shouldnt happen */ + retval = cv_bool_get(cv); + goto done; + } + } + } + /* 3) A container node without a "presence" statement and that has at + * least one mandatory node as a child. */ + else if (keyw == Y_CONTAINER && + yang_find(ys, Y_PRESENCE, NULL) == NULL){ + yc = NULL; + while ((yc = yn_each(ys, yc)) != NULL) { + if ((ret = yang_xml_mandatory(xs, yc)) < 0) + goto done; + if (ret == 1) + goto mandatory; + } + } + retval = 0; /* Does not contain mandatory node */ + done: + if (xs != NULL) + xml_purge(xs); + return retval; + mandatory: + retval = 1; + goto done; +} + /*! Is XML node (ie under ) an action, ie name action and belong to YANG_XML_NAMESPACE? * @param[in] xn XML node * @retval 1 Yes, an action diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 5e47ed26..e56bd91b 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -3213,65 +3213,6 @@ yang_desc_schema_nodeid(yang_stmt *yn, return retval; } -/*! Check if this leaf is mandatory or not - * Note: one can cache this value in ys_cvec instead of functionally evaluating it. - * @retval 1 yang statement is leaf and it has a mandatory sub-stmt with value true - * @retval 0 The negation of conditions for return value 1. - * @see RFC7950 Sec 3: - * o mandatory node: A mandatory node is one of: - * 1) A leaf, choice, anydata, or anyxml node with a "mandatory" - * statement with the value "true". - * 2) # see below - * 3) A container node without a "presence" statement and that has at - * least one mandatory node as a child. - * - * @note There is also this statement - * 2) A list or leaf-list node with a "min-elements" statement with a - * value greater than zero. - * which we ignore here since: - * (a) it does not consider the XML siblings and therefore returns false positives - * (b) where the actual check is catched by check_list_unique_minmax() - */ -int -yang_mandatory(yang_stmt *ys) -{ - yang_stmt *ym; - cg_var *cv; - - /* 1) A leaf, choice, anydata, or anyxml node with a "mandatory" - * statement with the value "true". */ - if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_CHOICE || - ys->ys_keyword == Y_ANYDATA || ys->ys_keyword == Y_ANYXML){ - if ((ym = yang_find(ys, Y_MANDATORY, NULL)) != NULL){ - if ((cv = yang_cv_get(ym)) != NULL) /* shouldnt happen */ - return cv_bool_get(cv); - } - } -#if 0 /* See note above */ - /* 2) A list or leaf-list node with a "min-elements" statement with a - * value greater than zero. */ - else if (ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST){ - if ((ym = yang_find(ys, Y_MIN_ELEMENTS, NULL)) != NULL){ - cv = yang_cv_get(ym); - return cv_uint32_get(cv) > 0; /* Not true if XML considered */ - } - } -#endif - /* 3) A container node without a "presence" statement and that has at - * least one mandatory node as a child. */ - else if (ys->ys_keyword == Y_CONTAINER && - yang_find(ys, Y_PRESENCE, NULL) == NULL){ - yang_stmt *yc; - int i; - for (i=0; iys_len; i++){ - yc = ys->ys_stmt[i]; - if (yang_mandatory(yc)) - return 1; - } - } - return 0; -} - /*! Return config state of this node * @param[in] ys Yang statement * @retval 0 If node has a config sub-statement and it is false diff --git a/test/test_when_mandatory.sh b/test/test_when_mandatory.sh new file mode 100755 index 00000000..3deac9cd --- /dev/null +++ b/test/test_when_mandatory.sh @@ -0,0 +1,309 @@ +#!/usr/bin/env bash +# Yang when conditional and mandatory +# Testing of validation phase. +# Variants: +# - One level and two levels of non-presence containers +# - when-condition true and false +# - empty vs extra leaf in non-presence container +# - mandatory leaf present or not + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang + +cat < $cfg + + $cfg + ${YANG_INSTALLDIR} + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + +EOF + +cat < $fyang +module $APPNAME{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + identity routing-protocol { + description + "Base identity from which routing protocol identities are + derived."; + } + identity direct { + base routing-protocol; + description + "Routing pseudo-protocol which provides routes to directly + connected networks."; + } + identity static { + base routing-protocol; + description + "Static routing pseudo-protocol."; + } + list x { + key "type"; + leaf type { + type identityref { + base routing-protocol; + } + } + container y { + when "../type='static'" { + description + "This container is only valid for the 'static' + routing protocol."; + } + leaf name{ + type string; + mandatory true; + } + leaf extra{ + type string; + } + } + } + list x2 { + key "type"; + leaf type { + type identityref { + base routing-protocol; + } + } + container y2 { + when "../type='static'" { + description + "This container is only valid for the 'static' + routing protocol."; + } + container y3 { + leaf name{ + type string; + mandatory true; + } + leaf extra{ + type string; + } + } + } + } + list x3 { + key "type"; + leaf type { + type identityref { + base routing-protocol; + } + } + container y2 { + container y3 { + when "../../type='static'" { + description + "This container is only valid for the 'static' + routing protocol."; + } + leaf name{ + type string; + mandatory true; + } + leaf extra{ + type string; + } + } + } + } +} +EOF + +new "test params: -f $cfg" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg +fi + +new "wait backend" +wait_backend + +new "First: have mandatory leaf under an empty when-conditioned ccntainer" + +new "when false + no name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "direct" "" "" + +new "validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when true + no name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "static" "" "" + +new "validate fail" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationmissing-elementyerrorMandatory variable of x in module example" + +new "when true + name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "statica" "" "" + +new "validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "Second: have mandatory leaf under a non-empty when-conditioned ccntainer" + +new "when false + no name + extra" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "directb" "" "" + +new "validate fail extra" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-failederrorFailed WHEN condition of y in module example (WHEN xpath is ../type='static')" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when true + no name + extra" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "staticb" "" "" + +new "validate fail" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationmissing-elementnameerrorMandatory variable of y in module example" + +new "when true + name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "staticab" "" "" + +new "validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "Third: have mandatory leaf under two empty when-conditioned ccntainer" +new "when false + no name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "direct" "" "" + +new "validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when true + no name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "static" "" "" + +new "validate fail" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationmissing-elementy2errorMandatory variable of x2 in module example" + +new "when true + name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "statica" "" "" + +new "validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "Fourth: have mandatory leaf under two non-empty when-conditioned ccntainer" + +new "when false + no name + extra" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "directb" "" "" + +new "validate fail extra" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-failederrorFailed WHEN condition of y2 in module example (WHEN xpath is ../type='static')" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when true + no name + extra" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "staticb" "" "" + +new "validate fail" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationmissing-elementnameerrorMandatory variable of y3 in module example" + +new "when true + name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "staticab" "" "" + +new "validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "Fifth: have mandatory leaf under one empty and one when-conditioned ccntainer" +new "when false + no name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "direct" "" "" + +new "validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when true + no name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "static" "" "" + +new "validate fail" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationmissing-elementy2errorMandatory variable of x3 in module example" + +new "when true + name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "statica" "" "" + +new "validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "Sixth: have mandatory leaf under one non-empty and one when-conditioned ccntainer" + +new "when false + no name + extra" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "directb" "" "" + +new "validate fail extra" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-failederrorFailed WHEN condition of y3 in module example (WHEN xpath is ../../type='static')" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when true + no name + extra" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "staticb" "" "" + +new "validate fail" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationmissing-elementnameerrorMandatory variable of y3 in module example" + +new "when true + name" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "staticab" "" "" + +new "validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "when: discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +rm -rf $dir + +new "endtest" +endtest From 6f0bd01a6a359a1873b21f7200e3c54648446242 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 18 Oct 2022 09:36:11 +0200 Subject: [PATCH 15/22] Confirm-commit RESTCONF support --- CHANGELOG.md | 3 ++ apps/backend/backend_client.c | 41 ++++++++++----- apps/backend/backend_commit.c | 40 ++++++++------ apps/backend/clixon_backend_commit.h | 5 +- test/test_confirmed_commit.sh | 64 +++++++++++++++++------ yang/clixon/clixon-config@2022-03-21.yang | 2 + 6 files changed, 108 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43f98a8b..e9d23ee7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,9 @@ Expected: End of 2022 * 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) + * Known issues + * Backend privileges drop + * Lock check, see RFC 6241 7.5 ### API changes on existing protocol/config features diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 94866a9b..95cfdda3 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -455,19 +455,34 @@ from_client_edit_config(clicon_handle h, autocommit = 1; /* If autocommit option is set or requested by client */ if (clicon_autocommit(h) || autocommit) { - // TODO: 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 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){ + 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, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) goto done; diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 8e689620..58af5c3c 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -648,7 +648,7 @@ candidate_validate(clicon_handle h, * @retval -1 No Rollback event was found */ int -cancel_rollback_event() +cancel_rollback_event(void) { int retval; @@ -723,6 +723,28 @@ schedule_rollback_event(clicon_handle h, 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(); + + if (confirmed_commit.state == PERSISTENT && confirmed_commit.persist_id != NULL) { + free(confirmed_commit.persist_id); + confirmed_commit.persist_id = NULL; + } + + confirmed_commit.state = INACTIVE; + + if (xmldb_delete(h, "rollback") < 0) + clicon_err(OE_DB, 0, "Error deleting the rollback configuration"); + return 0; +} + /*! 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 @@ -1211,20 +1233,8 @@ from_client_commit(clicon_handle h, /* If is *not* present, this will conclude the confirmed-commit, so cancel the rollback. */ if (xml_find_type(confirmed_commit.xe, NULL, "confirmed", CX_ELMNT) == NULL && is_valid_confirming_commit) { - cancel_rollback_event(); - - if (confirmed_commit.state == PERSISTENT && confirmed_commit.persist_id != NULL) { - free(confirmed_commit.persist_id); - confirmed_commit.persist_id = NULL; - } - - confirmed_commit.state = INACTIVE; - - if (xmldb_delete(h, "rollback") < 0) - clicon_err(OE_DB, 0, "Error deleting the rollback configuration"); - - cprintf(cbret, "", NETCONF_BASE_NAMESPACE); - + cancel_confirmed_commit(h); + cprintf(cbret, "", NETCONF_BASE_NAMESPACE); goto ok; } } diff --git a/apps/backend/clixon_backend_commit.h b/apps/backend/clixon_backend_commit.h index 62785eef..46a00a64 100644 --- a/apps/backend/clixon_backend_commit.h +++ b/apps/backend/clixon_backend_commit.h @@ -65,13 +65,14 @@ struct confirmed_commit { void *arg; // the clicon_handle that will be passed to rollback_fn() }; -extern struct confirmed_commit confirmed_commit; +extern struct confirmed_commit confirmed_commit; // XXX global /* * Prototypes */ int do_rollback(clicon_handle h, uint8_t *errs); -int cancel_rollback_event(); +int cancel_rollback_event(void); +int cancel_confirmed_commit(clicon_handle h); int startup_validate(clicon_handle h, char *db, cxobj **xtr, cbuf *cbret); int startup_commit(clicon_handle h, char *db, cbuf *cbret); diff --git a/test/test_confirmed_commit.sh b/test/test_confirmed_commit.sh index 9d109328..a805ce27 100755 --- a/test/test_confirmed_commit.sh +++ b/test/test_confirmed_commit.sh @@ -1,10 +1,8 @@ #!/usr/bin/env bash # Netconf confirm commit capability -# See RFC 6241 Section 8.4 -# Test uses privileges drop +# See RFC 6241 Section 8.4 and RFC 8040 Section 1.4 # TODO: # - privileges drop -# - restconf # - lock check # Magic line must be first in script (see README.md) @@ -24,19 +22,12 @@ RESTCONFIG=$(restconf_config none false) cat < $cfg - $cfg ietf-netconf:confirmed-commit clixon-restconf:allow-auth-none - 42 + $cfg ${YANG_INSTALLDIR} - $IETFRFC $fyang /usr/local/lib/$APPNAME/clispec - /usr/local/lib/$APPNAME/backend - example_backend.so$ - /usr/local/lib/$APPNAME/netconf - false - /usr/local/lib/$APPNAME/restconf /usr/local/lib/$APPNAME/cli $APPNAME $dir/$APPNAME.sock @@ -90,6 +81,7 @@ function rpc() { } function commit() { + new "commit $1" if [[ "$1" == "" ]] then rpc "" "" @@ -101,6 +93,7 @@ function commit() { function edit_config() { TARGET="$1" CONFIG="$2" + new "edit-config $1 $2" rpc "<$TARGET/>$CONFIG" "" } @@ -111,6 +104,7 @@ function assert_config_equals() { rpc "<$TARGET/>" "$(data "$EXPECTED")" } +# delete all function reset() { rpc "none" "" commit @@ -123,8 +117,10 @@ RUNNING_PATH="/usr/local/var/$APPNAME/running_db" ROLLBACK_PATH="/usr/local/var/$APPNAME/rollback_db" FAILSAFE_PATH="/usr/local/var/$APPNAME/failsafe_db" + CONFIGB="eth0
" CONFIGC="eth1
" +CONFIGCONLY="eth1" # restcpnf CONFIGBPLUSC="eth0eth1
" FAILSAFE_CFG="eth99
" @@ -144,8 +140,6 @@ fi new "wait backend" wait_backend - - new "Hello check confirm-commit capability" expecteof "$clixon_netconf -f $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" "urn:ietf:params:netconf:capability:confirmed-commit:1.1" '^$' @@ -225,7 +219,6 @@ commit "" assert_config_equals "running" "$CONFIGB" ################################################################################ - # TODO reconsider logic around presence/absence of rollback_db as a signal as dropping permissions may impact ability # to unlink and/or create that file. see clixon_datastore.c#xmldb_delete() and backend_startup.c#startup_mode_startup() @@ -247,14 +240,16 @@ stop_backend -f $cfg start_backend -s init -f $cfg ################################################################################ - new "backend loads failsafe at startup if rollback present but cannot be loaded" + +if [ ${valgrindtest} -eq 2 ]; then # backend valgrind + sleep 3 +fi reset sudo tee "$FAILSAFE_PATH" > /dev/null << EOF # create a failsafe database $FAILSAFE_CFG EOF - edit_config "candidate" "$CONFIGC" commit "foobar" assert_config_equals "running" "$CONFIGC" @@ -382,7 +377,6 @@ assert_config_equals "running" "$CONFIGBPLUSC" sleep 5 assert_config_equals "running" "" - # TODO test restconf receives "409 conflict" when there is a persistent confirmed-commit active # TODO test restconf causes confirming-commit for ephemeral confirmed-commit if [ $RC -ne 0 ]; then @@ -396,6 +390,42 @@ fi new "wait restconf" wait_restconf +new "restconf as confirmed commit" +reset +edit_config "candidate" "$CONFIGB" +assert_config_equals "candidate" "$CONFIGB" +# use HELLONO11 which uses older EOM framing +sleep 60 | cat <(echo "$HELLONO1160]]>]]>]]>]]>") -| $clixon_netconf -qf $cfg >> /dev/null & +PIDS=($(jobs -l % | cut -c 6- | awk '{print $1}')) +assert_config_equals "running" "$CONFIGB" # assert config twice to prove it surives disconnect + +new "restconf POST" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$CONFIGCONLY" $RCPROTO://localhost/restconf/data/clixon-example:table)" 0 "HTTP/$HVER 201" "location:" + +assert_config_equals "running" "$CONFIGBPLUSC" + +new "soft kill" +kill ${PIDS[0]} # kill the while loop above to close STDIN on 1st + +assert_config_equals "running" "$CONFIGBPLUSC" + +kill -9 ${PIDS[0]} 2> /dev/null # kill the while loop above to close STDIN on 1st + +assert_config_equals "running" "$CONFIGBPLUSC" + +################################################################################ + +new "restconf persistid expect fail" +reset +edit_config "candidate" "$CONFIGB" +commit "a" +assert_config_equals "running" "$CONFIGB" + +new "restconf POST" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$CONFIGCONLY" $RCPROTO://localhost/restconf/data/clixon-example:table)" 0 # "HTTP/$HVER 409" + +assert_config_equals "running" "$CONFIGB" + if [ $RC -ne 0 ]; then new "Kill restconf daemon" stop_restconf diff --git a/yang/clixon/clixon-config@2022-03-21.yang b/yang/clixon/clixon-config@2022-03-21.yang index 00441a8b..693b3be1 100644 --- a/yang/clixon/clixon-config@2022-03-21.yang +++ b/yang/clixon/clixon-config@2022-03-21.yang @@ -862,6 +862,8 @@ module clixon-config { description "Set if all configuration changes are committed automatically on every edit change. Explicit commit commands unnecessary + If confirm-commit, follow RESTCONF semantics: commit ephemeral but fail on + persistent confirming commit. (consider boolean)"; } leaf CLICON_XMLDB_DIR { From 62e652bbcf2783e5f8fa6187f7b25eaec8955201 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 19 Oct 2022 17:35:38 +0200 Subject: [PATCH 16/22] Confirm-commit refactoring Moved commit-confirm code to backend_confirm.c and removed (almost all) globals vars --- apps/backend/Makefile.in | 1 + apps/backend/backend_client.c | 12 +- apps/backend/backend_client.h | 2 +- apps/backend/backend_commit.c | 584 +------------------ apps/backend/backend_confirm.c | 834 +++++++++++++++++++++++++++ apps/backend/backend_main.c | 11 +- apps/backend/clixon_backend_commit.h | 31 +- lib/clixon/clixon_data.h | 1 + util/clixon_util_validate.c | 2 +- 9 files changed, 883 insertions(+), 595 deletions(-) create mode 100644 apps/backend/backend_confirm.c 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 95cfdda3..4edc7915 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -181,18 +181,18 @@ backend_client_rm(clicon_handle h, } if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { - if (confirmed_commit.state == EPHEMERAL) { + 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); + 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) { + 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(); + cancel_rollback_event(h); do_rollback(h, NULL); } } @@ -469,7 +469,7 @@ from_client_edit_config(clicon_handle h, * status-line. The error-tag "in-use" is used in this case. */ if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { - switch (confirmed_commit.state){ + switch (confirmed_commit_state_get(h)){ case INACTIVE: break; case PERSISTENT: @@ -483,7 +483,7 @@ from_client_edit_config(clicon_handle h, break; } } - if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ + 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"); 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 58af5c3c..f8337968 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 @@ -71,14 +71,6 @@ #include "clixon_backend_commit.h" #include "backend_client.h" -/* a global instance of the confirmed_commit struct for reference throughout the procedure */ -struct confirmed_commit confirmed_commit = { - .state = INACTIVE, -}; - -/* flag to carry indication if an RPC bearing satisfies conditions to cancel the rollback timer */ -static int is_valid_confirming_commit = 0; - /*! Key values are checked for validity independent of user-defined callbacks * * Key values are checked as follows: @@ -642,275 +634,24 @@ candidate_validate(clicon_handle h, goto done; } -/*! Cancel a scheduled rollback as previously registered by schedule_rollback_event() - * - * @retval 0 Rollback event successfully cancelled - * @retval -1 No Rollback event was found - */ -int -cancel_rollback_event(void) -{ - int retval; - - if ((retval = clixon_event_unreg_timeout(confirmed_commit.fn, confirmed_commit.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 = rollback_fn; - confirmed_commit.arg = 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(); - - if (confirmed_commit.state == PERSISTENT && confirmed_commit.persist_id != NULL) { - free(confirmed_commit.persist_id); - confirmed_commit.persist_id = NULL; - } - - confirmed_commit.state = INACTIVE; - - if (xmldb_delete(h, "rollback") < 0) - clicon_err(OE_DB, 0, "Error deleting the rollback configuration"); - return 0; -} - -/*! 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[out] cbret Return xml tree, eg ..., ; the confirming-commit MUST now be accompanied by a matching - * - */ - confirmed_commit.state = 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, confirm_timeout); - } else { - /* The client did not pass a value for and therefore any subsequent confirming-commit must be - * issued within the same session. - */ - if (clicon_session_id_get(h, &confirmed_commit.session_id) < 0) { - clicon_err(OE_DAEMON, 0, - "an ephemeral confirmed-commit was issued, but the session-id could not be determined"); - if (netconf_operation_failed(cbret, "application", - "there was an error while performing the confirmed-commit") < 0) - clicon_err(OE_DAEMON, 0, "there was an error sending a netconf response to the client"); - goto done; - }; - confirmed_commit.state = 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, 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. - * - */ - - int 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 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 ..., 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] myid current client session-id - * @param[out] cbret Return xml tree, eg ..., matching the prior confirming-commit's */ - retval = 1; - break; - } else { - netconf_invalid_value(cbret, "protocol", "No such persist-id"); - clicon_log(LOG_INFO, - "a persistent confirmed-commit is in progress but the client issued a " - "confirming-commit with an incorrect persist-id"); - retval = 0; - break; - } - } else { - netconf_invalid_value(cbret, "protocol", "Persist-id not given"); - clicon_log(LOG_INFO, - "a persistent confirmed-commit is in progress but the client issued a confirming-commit" - "without a persist-id"); - retval = 0; - break; - } - case EPHEMERAL: - if (myid == confirmed_commit.session_id) { - /* the RPC lacked a , the prior confirming-commit lacked , and both were issued - * on the same session. - */ - retval = 1; - break; - } - - if (netconf_invalid_value(cbret, "protocol","confirming-commit not performed on originating session") < 0) { - clicon_err(OE_NETCONF, 0, "error sending response"); - break; - }; - - 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"); - retval = 0; - break; - - default: - clicon_debug(1, "commit-confirmed state !? %d", confirmed_commit.state); - retval = 0; - break; - } - - done: - return retval; -} - /*! Commit the candidate configuration as the device's new current configuration * * @param[in] h Clicon handle @@ -1226,17 +797,10 @@ from_client_commit(clicon_handle h, } if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { - confirmed_commit.xe = xe; - if ((is_valid_confirming_commit = check_valid_confirming_commit(h, myid, cbret)) < 0) - goto done; - - /* If is *not* present, this will conclude the confirmed-commit, so cancel the rollback. */ - if (xml_find_type(confirmed_commit.xe, NULL, "confirmed", CX_ELMNT) == NULL - && is_valid_confirming_commit) { - cancel_confirmed_commit(h); - cprintf(cbret, "", NETCONF_BASE_NAMESPACE); - goto ok; - } + 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 */ @@ -1251,7 +815,7 @@ from_client_commit(clicon_handle h, goto done; goto ok; } - if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ + 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) @@ -1263,7 +827,6 @@ from_client_commit(clicon_handle h, ok: retval = 0; done: - confirmed_commit.xe = NULL; if (cbx) cbuf_free(cbx); return retval; /* may be zero if we ignoring errors from commit */ @@ -1321,111 +884,6 @@ from_client_discard_changes(clicon_handle h, 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. - * 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); - retval = 0; - clicon_log(LOG_INFO, "a confirmed-commit has been cancelled by client request"); - } - goto done; - - netconf_response_error: - clicon_err(OE_DAEMON, 0, "failed to write netconf response"); - - done: - return retval; -} - /*! Validates the contents of the specified configuration. * @param[in] h Clicon handle * @param[in] xe Request: @@ -1624,7 +1082,7 @@ load_failsafe(clicon_handle h, goto done; if (xmldb_db_reset(h, "running") < 0) goto done; - ret = candidate_commit(h, db, cbret); + ret = candidate_commit(h, NULL, db, cbret); if (ret != 1) if (xmldb_copy(h, "tmp", "running") < 0) goto done; diff --git a/apps/backend/backend_confirm.c b/apps/backend/backend_confirm.c new file mode 100644 index 00000000..b21b13bc --- /dev/null +++ b/apps/backend/backend_confirm.c @@ -0,0 +1,834 @@ +/* + * + ***** 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 constants + */ +/*! Use a global variable to : + * if an RPC bearing satisfies conditions to cancel the rollback timer + */ +#undef _GLOBAL_VALID_CONFIRMING_COMMIT + +/* + * 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() */ +}; + +#ifdef _GLOBAL_VALID_CONFIRMING_COMMIT +/* + * Local global variables + */ +/* if an RPC bearing satisfies conditions to cancel the rollback timer */ +static int _is_valid_confirming_commit = 0; +#endif + +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. + */ +#ifdef _GLOBAL_VALID_CONFIRMING_COMMIT + cc_valid = _is_valid_confirming_commit; + // assert(cc_valid == check_valid_confirming_commit(h, xe, session_id)); +#else + cc_valid = check_valid_confirming_commit(h, xe, session_id); +#endif + 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; +#ifdef _GLOBAL_VALID_CONFIRMING_COMMIT + _is_valid_confirming_commit = cc_valid; +#endif + + /* 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_main.c b/apps/backend/backend_main.c index 0b9a24ea..79b86ee2 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -124,6 +124,7 @@ backend_terminate(clicon_handle h) xml_free(x); if ((x = clicon_conf_xml(h)) != NULL) xml_free(x); + confirmed_commit_free(h); stream_publish_exit(); /* Delete all plugins, RPC callbacks, and upgrade callbacks */ clixon_plugin_module_exit(h); @@ -154,10 +155,6 @@ backend_sig_term(int arg) if (i++ == 0) clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d", __PROGRAM__, __FUNCTION__, getpid(), arg); - if (confirmed_commit.persist_id != NULL) { - free(confirmed_commit.persist_id); - confirmed_commit.persist_id = NULL; - } clixon_exit_set(1); /* checked in clixon_event_loop() */ } @@ -850,7 +847,11 @@ main(int argc, if (clicon_option_bool(h, "CLICON_XML_CHANGELOG")) if (clixon_xml_changelog_init(h) < 0) goto done; - + /* Init commit confirmed */ + if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) { + if (confirmed_commit_init(h) < 0) + goto done; + } /* Save modules state of the backend (server). Compare with startup XML */ if (startup_module_state(h, yspec) < 0) goto done; diff --git a/apps/backend/clixon_backend_commit.h b/apps/backend/clixon_backend_commit.h index 46a00a64..639ae80d 100644 --- a/apps/backend/clixon_backend_commit.h +++ b/apps/backend/clixon_backend_commit.h @@ -52,36 +52,29 @@ enum confirmed_commit_state { ROLLBACK }; -/* 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 state; - char *persist_id; // a value given by a client in the confirmed-commit - uint32_t session_id; // the session_id of the client that gave no value - - cxobj *xe; // the commit confirmed request - int (*fn)(int, void*); // the function pointer for the rollback event (rollback_fn()) - void *arg; // the clicon_handle that will be passed to rollback_fn() -}; - -extern struct confirmed_commit confirmed_commit; // XXX global - /* * Prototypes */ -int do_rollback(clicon_handle h, uint8_t *errs); -int cancel_rollback_event(void); +/* backend_confirm.c */ +int confirmed_commit_init(clicon_handle h); +int confirmed_commit_free(clicon_handle h); +enum confirmed_commit_state confirmed_commit_state_get(clicon_handle h); +uint32_t confirmed_commit_session_id_get(clicon_handle h); +int cancel_rollback_event(clicon_handle h); int cancel_confirmed_commit(clicon_handle h); +int handle_confirmed_commit(clicon_handle h, cxobj *xe); +int do_rollback(clicon_handle h, uint8_t *errs); +int from_client_cancel_commit(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); +int from_client_confirmed_commit(clicon_handle h, cxobj *xe, uint32_t myid, cbuf *cbret); +/* backend_commit.c */ int startup_validate(clicon_handle h, char *db, cxobj **xtr, cbuf *cbret); int startup_commit(clicon_handle h, char *db, cbuf *cbret); int candidate_validate(clicon_handle h, char *db, cbuf *cbret); -int candidate_commit(clicon_handle h, char *db, cbuf *cbret); +int candidate_commit(clicon_handle h, cxobj *xe, char *db, cbuf *cbret); int from_client_commit(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); int from_client_discard_changes(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); -int from_client_cancel_commit(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); int from_client_validate(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); int from_client_restart_one(clicon_handle h, clixon_plugin_t *cp, cbuf *cbret); int load_failsafe(clicon_handle h, char *phase); diff --git a/lib/clixon/clixon_data.h b/lib/clixon/clixon_data.h index 376d8d0b..d0ae7dcb 100644 --- a/lib/clixon/clixon_data.h +++ b/lib/clixon/clixon_data.h @@ -66,6 +66,7 @@ int clicon_data_get(clicon_handle h, const char *name, char **val); int clicon_data_set(clicon_handle h, const char *name, char *val); int clicon_data_del(clicon_handle h, const char *name); +/* Get generic clixon data on the form = where is void* */ int clicon_ptr_get(clicon_handle h, const char *name, void **ptr); int clicon_ptr_set(clicon_handle h, const char *name, void *ptr); int clicon_ptr_del(clicon_handle h, const char *name); diff --git a/util/clixon_util_validate.c b/util/clixon_util_validate.c index 5e9f2e5b..4ea77e60 100644 --- a/util/clixon_util_validate.c +++ b/util/clixon_util_validate.c @@ -227,7 +227,7 @@ main(int argc, goto done; } if (commit){ - if ((ret = candidate_commit(h, database, cb)) < 0) + if ((ret = candidate_commit(h, NULL, database, cb)) < 0) goto done; } else{ From 3a5d156690f01145cba2175683823c5d0f134058 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 24 Oct 2022 13:54:02 +0200 Subject: [PATCH 17/22] Docs: C struct style Tests: changed waiting->wait backend --- doc/DEVELOP.md | 27 ++++++++++++++++++++++--- test/test_api.sh | 2 +- test/test_augment_state.sh | 3 ++- test/test_augment_trans.sh | 3 ++- test/test_client.sh | 2 +- test/test_identity.sh | 2 +- test/test_leafref_uses_augment.sh | 2 +- test/test_nacm.sh | 2 +- test/test_nacm_credentials.sh | 2 +- test/test_nacm_datanode.sh | 2 +- test/test_nacm_datanode_paths.sh | 2 +- test/test_nacm_datanode_read.sh | 2 +- test/test_nacm_datanode_write.sh | 2 +- test/test_nacm_ext.sh | 2 +- test/test_nacm_module_read.sh | 2 +- test/test_nacm_protocol.sh | 2 +- test/test_netconf_filter.sh | 2 +- test/test_netconf_notifications.sh | 2 +- test/test_netconf_whitespace.sh | 2 +- test/test_perf_cli.sh | 2 +- test/test_perf_mem.sh | 2 +- test/test_perf_netconf.sh | 2 +- test/test_perf_restconf.sh | 2 +- test/test_perf_restconf_ssl.sh | 2 +- test/test_privileges.sh | 2 +- test/test_restconf_notifications.sh | 2 +- test/test_rpc.sh | 2 +- test/test_startup.sh | 2 +- test/test_upgrade_auto.sh | 2 +- test/test_upgrade_interfaces.sh | 2 +- test/test_upgrade_interfaces_rfc7895.sh | 2 +- test/test_upgrade_repair.sh | 2 +- test/test_upgrade_simple.sh | 6 +++--- 33 files changed, 60 insertions(+), 37 deletions(-) diff --git a/doc/DEVELOP.md b/doc/DEVELOP.md index 9d2c2d25..b358d1bb 100644 --- a/doc/DEVELOP.md +++ b/doc/DEVELOP.md @@ -47,15 +47,14 @@ myfn(int par1, ms = NULL; ``` Notes: -1. the return type of the function and all qualifers on first line (`static int`) -2. function name and first parameter on second line, thereafter each parameter on own line +1. The return type of the function and all qualifers on first line (`static int`) +2. Function name and first parameter on second line, thereafter each parameter on own line 3. Each parameter indented to match the "longest" (`my_structure`) 4. Pointer declarations written: `type *p`, not: `type* p`. 5. All local variables in a function declared at top of function, not inline with C-statements. 6. Local variables can be initialized with scalars or constants, not eg malloc or functions with return values that need to be checked for errors 7. There is a single empty line between local variable declarations and the first function statement. - Function signatures are declared in include files or in forward declaration using "one-line" syntax, unless very long: ``` static int myfn(int par1, my_structure *par2); @@ -123,6 +122,28 @@ files, there is only a single level of include file dependencies. The drawback is that the same include file may need to be repeated in many .c files. +### Structs + +Struct fields should have a prefix to distinguish them from other struct fields. The prefix should use an abbreviation of the struct name. + +Example: +``` +struct my_struct{ + int ms_foo; + char *ms_string[42]; +} +``` +where `ms_` is the prefix and is an abbreviation of `my_struct`. + +### Global variables + +Try to avoid global variables. + +If you absolutely need one, try to contain it as static within a +single C-file, ie do not declare it extern and use it elsewhere. + +Also, always prepend a global variable with `_`, underscore. + ## How to work in git Clixon uses semantic versioning (https://semver.org). diff --git a/test/test_api.sh b/test/test_api.sh index 69a03b26..1ea6847d 100755 --- a/test/test_api.sh +++ b/test/test_api.sh @@ -228,7 +228,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_augment_state.sh b/test/test_augment_state.sh index 582b71a6..70824e71 100755 --- a/test/test_augment_state.sh +++ b/test/test_augment_state.sh @@ -144,7 +144,8 @@ if [ $BE -ne 0 ]; then new "start backend -s init -f $cfg -- -sS $fstate" start_backend -s init -f $cfg -- -sS $fstate fi -new "waiting" + +new "wait backend" wait_backend #----------------------------- diff --git a/test/test_augment_trans.sh b/test/test_augment_trans.sh index a0aa53d2..df99b6cd 100755 --- a/test/test_augment_trans.sh +++ b/test/test_augment_trans.sh @@ -109,7 +109,8 @@ if [ $BE -ne 0 ]; then new "start backend -s init -f $cfg" start_backend -s init -f $cfg fi -new "waiting" + +new "wait backend" wait_backend new "get-config empty" diff --git a/test/test_client.sh b/test/test_client.sh index eb7efb32..77ab340d 100755 --- a/test/test_client.sh +++ b/test/test_client.sh @@ -138,7 +138,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_identity.sh b/test/test_identity.sh index 7e07e2ab..795680e4 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -197,7 +197,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_leafref_uses_augment.sh b/test/test_leafref_uses_augment.sh index bc11b24f..4ed1ad94 100755 --- a/test/test_leafref_uses_augment.sh +++ b/test/test_leafref_uses_augment.sh @@ -114,7 +114,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend new "cli set t1-con t1-con t1-a 123" diff --git a/test/test_nacm.sh b/test/test_nacm.sh index 963a5cd0..701bfb26 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -123,7 +123,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_nacm_credentials.sh b/test/test_nacm_credentials.sh index 937adc69..948c4929 100755 --- a/test/test_nacm_credentials.sh +++ b/test/test_nacm_credentials.sh @@ -135,7 +135,7 @@ EOF start_backend -s init -f $cfg fi - new "waiting" + new "wait backend" wait_backend # First push in nacm rules via regular means diff --git a/test/test_nacm_datanode.sh b/test/test_nacm_datanode.sh index 4ff4d6e8..44c62ca6 100755 --- a/test/test_nacm_datanode.sh +++ b/test/test_nacm_datanode.sh @@ -226,7 +226,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_nacm_datanode_paths.sh b/test/test_nacm_datanode_paths.sh index f64f0595..39f80785 100755 --- a/test/test_nacm_datanode_paths.sh +++ b/test/test_nacm_datanode_paths.sh @@ -100,7 +100,7 @@ if [ $BE -ne 0 ]; then start_backend -s startup -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_nacm_datanode_read.sh b/test/test_nacm_datanode_read.sh index 002e672c..791a0a33 100755 --- a/test/test_nacm_datanode_read.sh +++ b/test/test_nacm_datanode_read.sh @@ -231,7 +231,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_nacm_datanode_write.sh b/test/test_nacm_datanode_write.sh index be66222b..a34943a7 100755 --- a/test/test_nacm_datanode_write.sh +++ b/test/test_nacm_datanode_write.sh @@ -227,7 +227,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 470b8a2f..678ada8d 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -191,7 +191,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg -- -s fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh index 334f4a8f..18ae136a 100755 --- a/test/test_nacm_module_read.sh +++ b/test/test_nacm_module_read.sh @@ -184,7 +184,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg -- -s fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index f37d911f..60bd3fe3 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -145,7 +145,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_netconf_filter.sh b/test/test_netconf_filter.sh index 05346d1b..8929442e 100755 --- a/test/test_netconf_filter.sh +++ b/test/test_netconf_filter.sh @@ -56,7 +56,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend new "Add two entries" diff --git a/test/test_netconf_notifications.sh b/test/test_netconf_notifications.sh index 84c4c61c..116efa66 100755 --- a/test/test_netconf_notifications.sh +++ b/test/test_netconf_notifications.sh @@ -101,7 +101,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg -- -n # create example notification stream fi -new "waiting" +new "wait backend" wait_backend # diff --git a/test/test_netconf_whitespace.sh b/test/test_netconf_whitespace.sh index 76793223..59ee5549 100755 --- a/test/test_netconf_whitespace.sh +++ b/test/test_netconf_whitespace.sh @@ -73,7 +73,7 @@ if [ $BE -ne 0 ]; then start_backend -s startup -f $cfg fi -new "waiting" +new "wait backend" wait_backend new "get startup" diff --git a/test/test_perf_cli.sh b/test/test_perf_cli.sh index a0fd852a..88cdd6d8 100755 --- a/test/test_perf_cli.sh +++ b/test/test_perf_cli.sh @@ -83,7 +83,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg -- -s fi -new "waiting" +new "wait backend" wait_backend new "generate config with $perfnr list entries" diff --git a/test/test_perf_mem.sh b/test/test_perf_mem.sh index e6dd1ec2..567ce45e 100755 --- a/test/test_perf_mem.sh +++ b/test/test_perf_mem.sh @@ -85,7 +85,7 @@ function testrun(){ start_backend -s startup -f $cfg fi - new "waiting" + new "wait backend" wait_backend pid=$(cat $pidfile) diff --git a/test/test_perf_netconf.sh b/test/test_perf_netconf.sh index 7beb12b9..e4b8f669 100755 --- a/test/test_perf_netconf.sh +++ b/test/test_perf_netconf.sh @@ -87,7 +87,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg -- -s fi -new "waiting" +new "wait backend" wait_backend # Check this later with committed data diff --git a/test/test_perf_restconf.sh b/test/test_perf_restconf.sh index a1140eb0..b97716c1 100755 --- a/test/test_perf_restconf.sh +++ b/test/test_perf_restconf.sh @@ -106,7 +106,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg -- -s fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_perf_restconf_ssl.sh b/test/test_perf_restconf_ssl.sh index 0e230fd9..3670cc66 100755 --- a/test/test_perf_restconf_ssl.sh +++ b/test/test_perf_restconf_ssl.sh @@ -122,7 +122,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg -- -s fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_privileges.sh b/test/test_privileges.sh index 17c5af0e..7c9702b5 100755 --- a/test/test_privileges.sh +++ b/test/test_privileges.sh @@ -96,7 +96,7 @@ function testrun(){ err 1 $c fi - new "waiting" + new "wait backend" wait_backend if [ $expecterr -eq 1 ]; then diff --git a/test/test_restconf_notifications.sh b/test/test_restconf_notifications.sh index b7e671ec..35d2c177 100755 --- a/test/test_restconf_notifications.sh +++ b/test/test_restconf_notifications.sh @@ -149,7 +149,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg -- -n # create example notification stream fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_rpc.sh b/test/test_rpc.sh index ab7dd0a4..d77d2c48 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -196,7 +196,7 @@ if [ $BE -ne 0 ]; then start_backend -s init -f $cfg fi -new "waiting" +new "wait backend" wait_backend if [ $RC -ne 0 ]; then diff --git a/test/test_startup.sh b/test/test_startup.sh index a90e5198..cf89990b 100755 --- a/test/test_startup.sh +++ b/test/test_startup.sh @@ -124,7 +124,7 @@ function testrun(){ new "start backend -f $cfg -s $mode -c $dir/extra_db" start_backend -s $mode -f $cfg -c $dir/extra_db - new "waiting" + new "wait backend" wait_backend else new "Restart backend as eg follows: -Ff $cfg -s $mode -c $dir/extra_db # $BETIMEOUT s" diff --git a/test/test_upgrade_auto.sh b/test/test_upgrade_auto.sh index 69543002..7c522ba2 100755 --- a/test/test_upgrade_auto.sh +++ b/test/test_upgrade_auto.sh @@ -258,7 +258,7 @@ if [ $BE -ne 0 ]; then start_backend -s $mode -f $cfg fi -new "waiting" +new "wait backend" wait_backend new "Check running db content" diff --git a/test/test_upgrade_interfaces.sh b/test/test_upgrade_interfaces.sh index 58aa5d4c..26edf74d 100755 --- a/test/test_upgrade_interfaces.sh +++ b/test/test_upgrade_interfaces.sh @@ -271,7 +271,7 @@ function testrun(){ start_backend -s startup -f $cfg -- -u fi - new "waiting" + new "wait backend" wait_backend new "Check running db content" diff --git a/test/test_upgrade_interfaces_rfc7895.sh b/test/test_upgrade_interfaces_rfc7895.sh index 49b72302..cb4c9643 100755 --- a/test/test_upgrade_interfaces_rfc7895.sh +++ b/test/test_upgrade_interfaces_rfc7895.sh @@ -268,7 +268,7 @@ function testrun(){ start_backend -s startup -f $cfg -- -u fi - new "waiting" + new "wait backend" wait_backend new "Check running db content" diff --git a/test/test_upgrade_repair.sh b/test/test_upgrade_repair.sh index e83297ad..233d8f42 100755 --- a/test/test_upgrade_repair.sh +++ b/test/test_upgrade_repair.sh @@ -117,7 +117,7 @@ if [ $BE -ne 0 ]; then start_backend -s $mode -f $cfg fi -new "waiting" +new "wait backend" wait_backend new "Check running db content is failsafe" diff --git a/test/test_upgrade_simple.sh b/test/test_upgrade_simple.sh index d8682b0f..605c744e 100755 --- a/test/test_upgrade_simple.sh +++ b/test/test_upgrade_simple.sh @@ -111,11 +111,11 @@ EOF if [ $BE -ne 0 ]; then new "start backend -s running -f $cfg" start_backend -s running -f $cfg - - new "waiting" - wait_backend fi +new "wait backend" +wait_backend + new "netconf get config" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" From 05b31508a10a953423fdaa1187a11414690e61f9 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 25 Oct 2022 16:32:48 +0200 Subject: [PATCH 18/22] * 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 --- CHANGELOG.md | 2 ++ apps/snmp/snmp_handler.c | 37 ++++++++++++++++++++++++++++++++----- apps/snmp/snmp_main.c | 6 ++++-- test/test_snmp_set.sh | 2 +- test/test_snmp_system.sh | 12 +++++++----- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9d23ee7..da8a4225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,8 @@ Developers may need to change their code ### 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) diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index 216bb753..1917b4d4 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -733,8 +733,8 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, case MODE_SET_RESERVE1: /* 0 */ if (!yang_config_ancestor(sh->sh_ys) || nhreg->modes == HANDLER_CAN_RONLY){ - retval = SNMP_ERR_NOTWRITABLE; - goto done;; + netsnmp_request_set_error(request, SNMP_ERR_NOTWRITABLE); + goto done; } /* Translate from YANG ys leaf type to SNMP asn1.1 type ids (not value), also cvtype */ if (type_yang2asn1(sh->sh_ys, &asn1_type, 0) < 0) @@ -752,13 +752,28 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, case MODE_SET_ACTION: /* 2 */ if (snmp_scalar_set(sh->sh_h, sh->sh_ys, NULL, NULL, reqinfo, request) < 0) goto done; + /* + * There does not seem to be a separate validation action and commit does not + * return an error. + * Therefore validation is done here directly as well as discard if it fails. + */ + if ((ret = clicon_rpc_validate(sh->sh_h, "candidate")) < 0) + goto done; + if (ret == 0){ + clicon_rpc_discard_changes(sh->sh_h); + netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED); + goto done; + } break; case MODE_SET_COMMIT: /* 3 */ if ((ret = clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL)) < 0) goto done; if (ret == 0){ + /* Note that error given in commit is not propagated to the snmp client, + * therefore validation is in the ACTION instead + */ clicon_rpc_discard_changes(sh->sh_h); - netsnmp_request_set_error(request, SNMP_ERR_NOTWRITABLE); + netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED); goto done; } break; @@ -772,6 +787,7 @@ clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, ok: retval = SNMP_ERR_NOERROR; done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; } @@ -1307,7 +1323,6 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, reqinfo, request)) < 0) goto done; if (ret == 0){ - // if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHOBJECT)) != SNMPERR_SUCCESS){ if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHOBJECT)) != SNMPERR_SUCCESS){ clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); @@ -1318,7 +1333,7 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, break; case MODE_SET_RESERVE1: // 0 if (!yang_config_ancestor(sh->sh_ys)){ - retval = SNMP_ERR_NOTWRITABLE; + netsnmp_request_set_error(request, SNMP_ERR_NOTWRITABLE); goto done;; } // Check types: compare type in requestvb to yang type (or do later) @@ -1337,6 +1352,18 @@ clixon_snmp_table_handler1(netsnmp_mib_handler *handler, } clicon_debug(1, "%s Nosuchinstance", __FUNCTION__); } + /* + * There does not seem to be a separate validation action and commit does not + * return an error. + * Therefore validation is done here directly as well as discard if it fails. + */ + if ((ret = clicon_rpc_validate(sh->sh_h, "candidate")) < 0) + goto done; + if (ret == 0){ + clicon_rpc_discard_changes(sh->sh_h); + netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED); + goto done; + } break; case MODE_SET_COMMIT: // 3 if ((ret = clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL)) < 0) diff --git a/apps/snmp/snmp_main.c b/apps/snmp/snmp_main.c index 8c41e1cd..0e548636 100644 --- a/apps/snmp/snmp_main.c +++ b/apps/snmp/snmp_main.c @@ -309,7 +309,7 @@ usage(clicon_handle h, fprintf(stderr, "usage:%s\n" "where options are\n" "\t-h\t\tHelp\n" - "\t-D \tDebug level\n" + "\t-D \tDebug level (>1 for extensive libnetsnmp debug)\n" "\t-f \tConfiguration file (mandatory)\n" "\t-l (e|o|s|f) Log on std(e)rr, std(o)ut, (s)yslog(default), (f)ile\n" "\t-z\t\tKill other %s daemon and exit\n" @@ -384,7 +384,9 @@ main(int argc, */ clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst); clicon_debug_init(dbg, NULL); - + /* This is netsnmplib debugging which is quite extensive + only if compiled w debug */ + if (dbg > 1) + snmp_set_do_debugging(1); /* * Register error category and error/log callbacks for netsnmp special error handling */ diff --git a/test/test_snmp_set.sh b/test/test_snmp_set.sh index 9907941d..8bdcd7c5 100755 --- a/test/test_snmp_set.sh +++ b/test/test_snmp_set.sh @@ -276,7 +276,7 @@ new "wait backend" wait_backend new "set value with error" -expectpart "$($snmpset ${MIB}.1.1 i 4321)" 0 "INTEGER: 4321" +expectpart "$($snmpset ${MIB}.1.1 i 4321 2>&1)" 2 "commitFailed" new "Cleaning up" testexit diff --git a/test/test_snmp_system.sh b/test/test_snmp_system.sh index bbee27de..50618522 100755 --- a/test/test_snmp_system.sh +++ b/test/test_snmp_system.sh @@ -107,12 +107,14 @@ function testinit(){ wait_backend if [ $SN -ne 0 ]; then - # Kill old clixon_snmp, if any - new "Terminating any old clixon_snmp processes" - sudo killall -q clixon_snmp + # Kill old clixon_snmp, if any + new "Terminating any old clixon_snmp processes" + sudo killall -q clixon_snmp + + new "Starting clixon_snmp" + # XXX augmented objects seem to be registered twice: error: duplicate registration: MIB modules snmpSetSerialNo and AgentX subagent 52, session 0x562087a70e20, subsession 0x562087a820c0 (oid .1.3.6.1.6.3.1.1.6.1). - new "Starting clixon_snmp" - start_snmp $cfg & + start_snmp $cfg & fi new "wait snmp" From ba48521d990531c3da4509470d45fd7fcea68704 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 26 Oct 2022 11:00:44 +0200 Subject: [PATCH 19/22] Confirmed commit: removed is_valid_confirming_commit global variable --- apps/backend/backend_confirm.c | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/apps/backend/backend_confirm.c b/apps/backend/backend_confirm.c index b21b13bc..7c774ab8 100644 --- a/apps/backend/backend_confirm.c +++ b/apps/backend/backend_confirm.c @@ -71,14 +71,6 @@ #include "clixon_backend_commit.h" #include "backend_client.h" -/* - * Local constants - */ -/*! Use a global variable to : - * if an RPC bearing satisfies conditions to cancel the rollback timer - */ -#undef _GLOBAL_VALID_CONFIRMING_COMMIT - /* * Local types */ @@ -93,14 +85,6 @@ struct confirmed_commit { void *cc_arg; /* clicon_handle that will be passed to rollback_fn() */ }; -#ifdef _GLOBAL_VALID_CONFIRMING_COMMIT -/* - * Local global variables - */ -/* if an RPC bearing satisfies conditions to cancel the rollback timer */ -static int _is_valid_confirming_commit = 0; -#endif - int confirmed_commit_init(clicon_handle h) { @@ -514,12 +498,7 @@ handle_confirmed_commit(clicon_handle h, * confirmed-commit must be handled once the transaction has begun and after all the plugins' validate callbacks * have been called. */ -#ifdef _GLOBAL_VALID_CONFIRMING_COMMIT - cc_valid = _is_valid_confirming_commit; - // assert(cc_valid == check_valid_confirming_commit(h, xe, session_id)); -#else cc_valid = check_valid_confirming_commit(h, xe, session_id); -#endif 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"); @@ -815,10 +794,6 @@ from_client_confirmed_commit(clicon_handle h, if ((cc_valid = check_valid_confirming_commit(h, xe, myid)) < 0) goto done; -#ifdef _GLOBAL_VALID_CONFIRMING_COMMIT - _is_valid_confirming_commit = cc_valid; -#endif - /* If is *not* present, this will conclude the confirmed-commit, so cancel the rollback. */ if (!xe_confirmed(xe) && cc_valid) { cancel_confirmed_commit(h); From 7976303ef24666758378fd1956c5c2e8575c734e Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 26 Oct 2022 14:34:51 +0200 Subject: [PATCH 20/22] Confirmed-commit handle drop privileges Create and drop priv of rollback datastore on startup Reverted xmldb to truncate instead of deleting datastores due to privileges drop --- CHANGELOG.md | 1 - apps/backend/backend_main.c | 24 ++++++---- lib/src/clixon_datastore.c | 87 +++++++++++++++-------------------- test/test_confirmed_commit.sh | 45 ++++++++++++++---- 4 files changed, 89 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da8a4225..748923d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,6 @@ Expected: End of 2022 * 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) * Known issues - * Backend privileges drop * Lock check, see RFC 6241 7.5 ### API changes on existing protocol/config features diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 79b86ee2..aed5a80d 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -280,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; @@ -288,7 +289,6 @@ 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; @@ -325,12 +325,20 @@ check_drop_priv(clicon_handle h, 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) + 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 (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; @@ -1041,7 +1049,7 @@ main(int argc, clicon_option_dump(h, dbg); /* Depending on configure setting, privileges may be dropped here after * initializations */ - if (check_drop_priv(h, gid) < 0) + if (check_drop_priv(h, gid, yspec) < 0) goto done; /* Start session-id for clients */ diff --git a/lib/src/clixon_datastore.c b/lib/src/clixon_datastore.c index bc5f09a4..7540dbf6 100644 --- a/lib/src/clixon_datastore.c +++ b/lib/src/clixon_datastore.c @@ -199,6 +199,7 @@ xmldb_copy(clicon_handle h, cxobj *x1 = NULL; /* from */ cxobj *x2 = NULL; /* to */ + clicon_debug(1, "%s %s %s", __FUNCTION__, from, to); /* XXX lock */ if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){ /* Copy in-memory cache */ @@ -252,7 +253,6 @@ xmldb_copy(clicon_handle h, if (tofile) free(tofile); return retval; - } /*! Lock database @@ -350,12 +350,13 @@ xmldb_islocked(clicon_handle h, return de->de_id; } -/*! Check if db exists +/*! Check if db exists or is empty * @param[in] h Clicon handle * @param[in] db Database * @retval -1 Error * @retval 0 No it does not exist * @retval 1 Yes it exists + * @note An empty datastore is treated as not existent so that a backend after dropping priviliges can re-create it */ int xmldb_exists(clicon_handle h, @@ -365,12 +366,18 @@ xmldb_exists(clicon_handle h, char *filename = NULL; struct stat sb; + clicon_debug(2, "%s %s", __FUNCTION__, db); if (xmldb_db2file(h, db, &filename) < 0) goto done; if (lstat(filename, &sb) < 0) retval = 0; - else - retval = 1; + + else{ + if (sb.st_size == 0) + retval = 0; + else + retval = 1; + } done: if (filename) free(filename); @@ -404,6 +411,7 @@ xmldb_clear(clicon_handle h, * @param[in] db Database * @retval -1 Error * @retval 0 OK + * @note Datastore is not actually deleted so that a backend after dropping priviliges can re-create it */ int xmldb_delete(clicon_handle h, @@ -413,24 +421,16 @@ xmldb_delete(clicon_handle h, char *filename = NULL; struct stat sb; + clicon_debug(2, "%s %s", __FUNCTION__, db); if (xmldb_clear(h, db) < 0) goto done; if (xmldb_db2file(h, db, &filename) < 0) goto done; if (lstat(filename, &sb) == 0) - // TODO this had been changed from unlink to truncate some time ago, likely related to dropping privileges. - // It was changed back for confirmed-commit, and test_confirmed_commit.sh drops privileges. - // as the presence of the rollback_db at startup triggers loading of the rollback rather than the startup - // configuration. It might not be sufficient to check for a truncated file. Needs more review, switching back - // to unlink temporarily. -// if (truncate(filename, 0) < 0){ -// clicon_err(OE_DB, errno, "truncate %s", filename); -// goto done; -// } - if (unlink(filename) < 0) { - clicon_err(OE_UNIX, errno, "unlink %s: %s", filename, strerror(errno)); - goto done; - } + if (truncate(filename, 0) < 0){ + clicon_err(OE_DB, errno, "truncate %s", filename); + goto done; + } retval = 0; done: if (filename) @@ -454,6 +454,7 @@ xmldb_create(clicon_handle h, db_elmnt *de = NULL; cxobj *xt = NULL; + clicon_debug(2, "%s %s", __FUNCTION__, db); if ((de = clicon_db_elmnt_get(h, db)) != NULL){ if ((xt = de->de_xml) != NULL){ xml_free(xt); @@ -607,7 +608,7 @@ xmldb_print(clicon_handle h, /*! Rename an XML database * @param[in] h Clicon handle * @param[in] db Database name - * @param[in] newdb New Database name; if NULL, then same as new + * @param[in] newdb New Database name; if NULL, then same as old * @param[in] suffix Suffix to append to new database name * @retval -1 Error * @retval 0 OK @@ -619,48 +620,32 @@ xmldb_rename(clicon_handle h, const char *newdb, const char *suffix) { - char *old; - char *fname = NULL; - int retval = -1; + int retval = -1; + char *old; + char *fname = NULL; + cbuf *cb = NULL; - if ((xmldb_db2file(h, db, &old)) < 0) { + if ((xmldb_db2file(h, db, &old)) < 0) goto done; - }; - - if (newdb == NULL && suffix == NULL) - // no-op + if (newdb == NULL && suffix == NULL) // no-op goto done; - - newdb = newdb == NULL ? old : newdb; - suffix = suffix == NULL ? "" : suffix; - - size_t size = strlen(newdb) + strlen(suffix); - - if ((fname = malloc(size + 1)) == NULL) { - clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); - goto done; - }; - - int actual = 0; - if ((actual = snprintf(fname, size, "%s%s", newdb, suffix)) < size) { - clicon_err(OE_UNIX, 0, "snprintf wrote fewer bytes (%d) than requested (%zu)", actual, size); - goto done; - }; - + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "%s", newdb == NULL ? old : newdb); + if (suffix) + cprintf(cb, "%s", suffix); + fname = cbuf_get(cb); if ((rename(old, fname)) < 0) { clicon_err(OE_UNIX, errno, "rename: %s", strerror(errno)); goto done; }; - - retval = 0; - - done: + done: + if (cb) + cbuf_free(cb); if (old) free(old); - - if (fname) - free(fname); - return retval; } diff --git a/test/test_confirmed_commit.sh b/test/test_confirmed_commit.sh index a805ce27..4abcb8be 100755 --- a/test/test_confirmed_commit.sh +++ b/test/test_confirmed_commit.sh @@ -15,7 +15,7 @@ tmp=$dir/tmp.x fyang=$dir/clixon-example.yang # Backend user for priv drop, otherwise root -USER=root #${BUSER} +USER=${BUSER} # Define default restconfig config: RESTCONFIG RESTCONFIG=$(restconf_config none false) @@ -125,14 +125,13 @@ CONFIGBPLUSC="eth0eth99
" new "test params: -f $cfg" + # Bring your own backend if [ $BE -ne 0 ]; then # kill old backend (if any) new "kill old backend" - sudo clixon_backend -zf $cfg - if [ $? -ne 0 ]; then - err - fi + stop_backend -f $cfg + new "start backend -s init -f $cfg" start_backend -s init -f $cfg fi @@ -229,32 +228,52 @@ commit "" edit_config "candidate" "$CONFIGC" commit "abcdefg" assert_config_equals "running" "$CONFIGBPLUSC" + +new "kill old backend" stop_backend -f $cfg # kill backend and restart + +new "Check $ROLLBACK_PATH" [ -f "$ROLLBACK_PATH" ] || err "rollback_db doesn't exist!" # assert rollback_db exists + +new "start backend -s running -f $cfg" start_backend -s running -f $cfg + +new "wait backend" wait_backend + assert_config_equals "running" "$CONFIGB" + +new "Check $ROLLBACK_PATH removed" [ -f "ROLLBACK_PATH" ] && err "rollback_db still exists!" # assert rollback_db doesn't exist +new "kill old backend" stop_backend -f $cfg + +new "start backend -s init -f $cfg" start_backend -s init -f $cfg ################################################################################ new "backend loads failsafe at startup if rollback present but cannot be loaded" -if [ ${valgrindtest} -eq 2 ]; then # backend valgrind - sleep 3 -fi +new "wait backend" +wait_backend + reset sudo tee "$FAILSAFE_PATH" > /dev/null << EOF # create a failsafe database $FAILSAFE_CFG EOF edit_config "candidate" "$CONFIGC" + commit "foobar" + assert_config_equals "running" "$CONFIGC" + +new "kill old backend" stop_backend -f $cfg # kill the backend + sudo rm $ROLLBACK_PATH # modify rollback_db so it won't commit successfully + sudo tee "$ROLLBACK_PATH" > /dev/null << EOF @@ -262,12 +281,22 @@ sudo tee "$ROLLBACK_PATH" > /dev/null << EOF EOF + +new "start backend -s running -f $cfg" start_backend -s running -f $cfg + +new "wait backend" wait_backend + assert_config_equals "running" "$FAILSAFE_CFG" +new "kill old backend" stop_backend -f $cfg + +new "start backend -s init -f $cfg" start_backend -s init -f $cfg -lf/tmp/clixon.log -D1 + +new "wait backend" wait_backend ################################################################################ From a9d1ab006c465092874b6f8c81a9b47072ebb24f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 26 Oct 2022 20:18:57 +0200 Subject: [PATCH 21/22] Confirmed commit: lock check on running --- CHANGELOG.md | 2 -- apps/backend/backend_client.c | 22 +++++++++++++++++++++- apps/backend/backend_commit.c | 30 ++++++++++++++---------------- test/test_confirmed_commit.sh | 9 +++------ test/test_netconf.sh | 25 +++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 748923d8..a899c7e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,8 +52,6 @@ Expected: End of 2022 * 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) - * Known issues - * Lock check, see RFC 6241 7.5 ### API changes on existing protocol/config features diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 4edc7915..1e4aba09 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -716,9 +716,15 @@ from_client_lock(clicon_handle h, 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; @@ -754,6 +760,20 @@ from_client_lock(clicon_handle h, 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; /* user callback */ diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index f8337968..02edc316 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -761,6 +761,19 @@ 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, @@ -777,20 +790,6 @@ from_client_commit(clicon_handle h, int ret; yang_stmt *yspec; - /* Handle 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. - */ if ((yspec = clicon_dbspec_yang(h)) == NULL) { clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; @@ -810,8 +809,7 @@ from_client_commit(clicon_handle h, 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) + if (netconf_in_use(cbret, "protocol", "Operation failed, lock is already held") < 0) goto done; goto ok; } diff --git a/test/test_confirmed_commit.sh b/test/test_confirmed_commit.sh index 4abcb8be..1ddfe75f 100755 --- a/test/test_confirmed_commit.sh +++ b/test/test_confirmed_commit.sh @@ -312,7 +312,8 @@ PIDS=($(jobs -l % | cut -c 6- | awk '{print $1}')) assert_config_equals "running" "$CONFIGB" # assert config twice to prove it surives disconnect assert_config_equals "running" "$CONFIGB" # of ephemeral sessions -kill -9 ${PIDS[0]} # kill the while loop above to close STDIN on 1st +new "soft kill ${PIDS[0]}" +kill ${PIDS[0]} # kill the while loop above to close STDIN on 1st ################################################################################ @@ -433,15 +434,11 @@ expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml assert_config_equals "running" "$CONFIGBPLUSC" -new "soft kill" +new "soft kill ${PIDS[0]}" kill ${PIDS[0]} # kill the while loop above to close STDIN on 1st assert_config_equals "running" "$CONFIGBPLUSC" -kill -9 ${PIDS[0]} 2> /dev/null # kill the while loop above to close STDIN on 1st - -assert_config_equals "running" "$CONFIGBPLUSC" - ################################################################################ new "restconf persistid expect fail" diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 7c2c2f10..8e7ed5f1 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -19,6 +19,7 @@ cat < $cfg $cfg ietf-netconf:startup + ietf-netconf:confirmed-commit 42 $dir ${YANG_INSTALLDIR} @@ -356,6 +357,30 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " new "kill-session using prefix xx" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "44" "" "" +new "asynchronous lock running" +sleep 60 | cat <(echo "$HELLONO11]]>]]>") -| $clixon_netconf -qf $cfg >> /dev/null & + +PIDS=($(jobs -l % | cut -c 6- | awk '{print $1}')) + +new "try commit should fail" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "protocolin-useerrorOperation failed, lock is already held" + +new "soft kill ${PIDS[0]}" +kill ${PIDS[0]} # kill the while loop above to close STDIN on 1st + +new "asynchronous confirmed commit" +sleep 60 | cat <(echo "$HELLONO1160]]>]]>") -| $clixon_netconf -qf $cfg >> /dev/null & +PIDS=($(jobs -l % | cut -c 6- | awk '{print $1}')) + +new "try lock should fail" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "protocollock-denied[0-9]*errorOperation failed, another session has an ongoing confirmed commit" + +new "soft kill ${PIDS[0]}" +kill ${PIDS[0]} # kill the while loop above to close STDIN on 1st + +new "netconf discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + # modify candidate, then lock, should fail. new "netconf edit config" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "a
" "" "" From d84c529ff1fd940d30b447aad19ebf358dd69352 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 27 Oct 2022 14:21:17 +0200 Subject: [PATCH 22/22] [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 --- .editorconfig | 2 +- CHANGELOG.md | 2 + apps/backend/backend_client.c | 1208 ++++----- apps/backend/backend_commit.c | 670 ++--- apps/backend/backend_confirm.c | 170 +- apps/backend/backend_get.c | 1002 +++---- apps/backend/backend_main.c | 846 +++--- apps/backend/backend_plugin.c | 600 ++--- apps/backend/backend_plugin_restconf.c | 326 +-- apps/backend/backend_socket.c | 126 +- apps/backend/backend_startup.c | 128 +- apps/backend/clixon_backend_commit.h | 4 +- apps/backend/clixon_backend_handle.c | 48 +- apps/backend/clixon_backend_plugin.h | 6 +- apps/backend/clixon_backend_transaction.c | 68 +- apps/cli/cli_auto.c | 244 +- apps/cli/cli_autocli.c | 320 +-- apps/cli/cli_common.c | 884 +++---- apps/cli/cli_generate.c | 1252 ++++----- apps/cli/cli_handle.c | 30 +- apps/cli/cli_handle.h | 8 +- apps/cli/cli_main.c | 568 ++-- apps/cli/cli_plugin.c | 588 ++--- apps/cli/cli_show.c | 1092 ++++---- apps/cli/clixon_cli_api.h | 8 +- apps/netconf/README.md | 4 +- apps/netconf/clixon_netconf.h | 6 +- apps/netconf/netconf_filter.c | 92 +- apps/netconf/netconf_main.c | 922 +++---- apps/netconf/netconf_rpc.c | 470 ++-- apps/netconf/netconf_rpc.h | 6 +- apps/restconf/README.md | 12 +- apps/restconf/clixon_http1_parse.h | 2 +- apps/restconf/clixon_http1_parse.y | 200 +- apps/restconf/clixon_http_data.c | 282 +- apps/restconf/clixon_restconf.h | 4 +- apps/restconf/restconf_api_fcgi.c | 72 +- apps/restconf/restconf_api_native.c | 74 +- apps/restconf/restconf_err.c | 240 +- apps/restconf/restconf_handle.c | 32 +- apps/restconf/restconf_http1.c | 324 +-- apps/restconf/restconf_http1.h | 2 +- apps/restconf/restconf_lib.c | 426 +-- apps/restconf/restconf_main_fcgi.c | 552 ++-- apps/restconf/restconf_main_native.c | 798 +++--- apps/restconf/restconf_methods.c | 686 ++--- apps/restconf/restconf_methods.h | 22 +- apps/restconf/restconf_methods_get.c | 704 ++--- apps/restconf/restconf_methods_get.h | 8 +- apps/restconf/restconf_methods_patch.c | 562 ++-- apps/restconf/restconf_methods_patch.h | 8 +- apps/restconf/restconf_methods_post.c | 688 ++--- apps/restconf/restconf_methods_post.h | 12 +- apps/restconf/restconf_native.c | 1636 ++++++------ apps/restconf/restconf_native.h | 16 +- apps/restconf/restconf_nghttp2.c | 628 ++--- apps/restconf/restconf_root.c | 390 +-- apps/restconf/restconf_stream_fcgi.c | 340 +-- apps/snmp/snmp_handler.c | 1242 ++++----- apps/snmp/snmp_handler.h | 12 +- apps/snmp/snmp_lib.c | 816 +++--- apps/snmp/snmp_lib.h | 12 +- apps/snmp/snmp_main.c | 318 +-- apps/snmp/snmp_register.c | 332 +-- configure | 576 ++-- doc/CLI.md | 2 +- doc/DEVELOP.md | 2 +- doc/FAQ.md | 42 +- doc/INSTALL.md | 2 +- doc/README.md | 2 +- docker/main/README.md | 2 +- docker/main/startsystem_fcgi.sh | 26 +- example/main/README.md | 52 +- example/main/clixon-example@2020-12-01.yang | 326 +-- example/main/example_backend.c | 920 +++---- example/main/example_backend_nacm.c | 100 +- example/main/example_cli.c | 56 +- example/main/example_netconf.c | 30 +- example/main/example_restconf.c | 276 +- include/clixon_config.h.in | 3 + lib/clixon/clixon_datastore.h | 4 +- lib/clixon/clixon_event.h | 2 +- lib/clixon/clixon_file.h | 2 +- lib/clixon/clixon_hash.h | 18 +- lib/clixon/clixon_nacm.h | 6 +- lib/clixon/clixon_netconf_lib.h | 4 +- lib/clixon/clixon_path.h | 14 +- lib/clixon/clixon_plugin.h | 64 +- lib/clixon/clixon_proto.h | 10 +- lib/clixon/clixon_proto_client.h | 10 +- lib/clixon/clixon_queue.h | 72 +- lib/clixon/clixon_stream.h | 8 +- lib/clixon/clixon_string.h | 2 +- lib/clixon/clixon_xml.h | 24 +- lib/clixon/clixon_xml_io.h | 4 +- lib/clixon/clixon_xml_map.h | 8 +- lib/clixon/clixon_xml_nsctx.h | 2 +- lib/clixon/clixon_xml_sort.h | 4 +- lib/clixon/clixon_xpath.h | 2 +- lib/clixon/clixon_yang.h | 20 +- lib/clixon/clixon_yang_module.h | 2 +- lib/clixon/clixon_yang_parse_lib.h | 2 +- lib/clixon/clixon_yang_type.h | 12 +- lib/src/clixon_api_path_parse.h | 2 +- lib/src/clixon_api_path_parse.l | 4 +- lib/src/clixon_api_path_parse.y | 68 +- lib/src/clixon_client.c | 396 +-- lib/src/clixon_data.c | 176 +- lib/src/clixon_datastore.c | 278 +- lib/src/clixon_datastore_read.c | 928 +++---- lib/src/clixon_datastore_read.h | 2 +- lib/src/clixon_datastore_write.c | 1570 +++++------ lib/src/clixon_dispatcher.c | 84 +- lib/src/clixon_err.c | 154 +- lib/src/clixon_event.c | 254 +- lib/src/clixon_file.c | 140 +- lib/src/clixon_handle.c | 26 +- lib/src/clixon_hash.c | 178 +- lib/src/clixon_instance_id_parse.h | 2 +- lib/src/clixon_instance_id_parse.l | 2 +- lib/src/clixon_instance_id_parse.y | 92 +- lib/src/clixon_json.c | 1452 +++++----- lib/src/clixon_json_parse.h | 2 +- lib/src/clixon_json_parse.y | 50 +- lib/src/clixon_log.c | 126 +- lib/src/clixon_nacm.c | 900 +++---- lib/src/clixon_netconf_lib.c | 1372 +++++----- lib/src/clixon_netns.c | 190 +- lib/src/clixon_options.c | 438 +-- lib/src/clixon_path.c | 1590 +++++------ lib/src/clixon_plugin.c | 844 +++--- lib/src/clixon_proc.c | 776 +++--- lib/src/clixon_proto.c | 334 +-- lib/src/clixon_proto_client.c | 1054 ++++---- lib/src/clixon_regex.c | 246 +- lib/src/clixon_sig.c | 106 +- lib/src/clixon_stream.c | 580 ++-- lib/src/clixon_string.c | 558 ++-- lib/src/clixon_text_syntax.c | 452 ++-- lib/src/clixon_text_syntax_parse.h | 2 +- lib/src/clixon_text_syntax_parse.y | 124 +- lib/src/clixon_uid.c | 78 +- lib/src/clixon_validate.c | 1160 ++++---- lib/src/clixon_validate_minmax.c | 522 ++-- lib/src/clixon_xml.c | 1094 ++++---- lib/src/clixon_xml_bind.c | 676 ++--- lib/src/clixon_xml_changelog.c | 282 +- lib/src/clixon_xml_io.c | 644 ++--- lib/src/clixon_xml_map.c | 1896 ++++++------- lib/src/clixon_xml_nsctx.c | 364 +-- lib/src/clixon_xml_parse.h | 2 +- lib/src/clixon_xml_parse.l | 6 +- lib/src/clixon_xml_parse.y | 146 +- lib/src/clixon_xml_sort.c | 1310 ++++----- lib/src/clixon_xml_vec.c | 98 +- lib/src/clixon_xpath.c | 672 ++--- lib/src/clixon_xpath_ctx.c | 190 +- lib/src/clixon_xpath_eval.c | 1316 ++++----- lib/src/clixon_xpath_function.c | 364 +-- lib/src/clixon_xpath_optimize.c | 170 +- lib/src/clixon_xpath_parse.h | 2 +- lib/src/clixon_xpath_parse.y | 234 +- lib/src/clixon_xpath_yang.c | 350 +-- lib/src/clixon_yang.c | 2346 ++++++++--------- lib/src/clixon_yang_cardinality.c | 122 +- lib/src/clixon_yang_cardinality.h | 2 +- lib/src/clixon_yang_internal.h | 36 +- lib/src/clixon_yang_module.c | 476 ++-- lib/src/clixon_yang_parse.h | 10 +- lib/src/clixon_yang_parse.l | 4 +- lib/src/clixon_yang_parse.y | 576 ++-- lib/src/clixon_yang_parse_lib.c | 1676 ++++++------ lib/src/clixon_yang_schemanode_parse.h | 2 +- lib/src/clixon_yang_schemanode_parse.y | 104 +- lib/src/clixon_yang_sub_parse.c | 36 +- lib/src/clixon_yang_sub_parse.h | 2 +- lib/src/clixon_yang_sub_parse.y | 148 +- lib/src/clixon_yang_type.c | 1234 ++++----- test/all.sh | 8 +- test/example_social.sh | 6 +- test/fuzz/http1/runfuzz.sh | 6 +- test/lib.sh | 434 +-- test/long.sh | 4 +- test/mem.sh | 120 +- test/plot_perf.sh | 250 +- test/sum.sh | 24 +- test/test_api.sh | 32 +- test/test_api_path.sh | 2 +- test/test_augment.sh | 20 +- test/test_augment_default.sh | 10 +- test/test_augment_state.sh | 32 +- test/test_augment_trans.sh | 10 +- test/test_autocli_editmode.sh | 42 +- test/test_autocli_extension.sh | 36 +- test/test_autocli_listkey_compress.sh | 104 +- test/test_autocli_show.sh | 26 +- test/test_autocli_spec.sh | 70 +- test/test_autocli_strict_expand.sh | 4 +- test/test_autocli_sub.sh | 12 +- test/test_autocli_treeref.sh | 4 +- test/test_c++.sh | 64 +- test/test_choice.sh | 56 +- test/test_choice_recursive.sh | 74 +- test/test_cli.sh | 100 +- test/test_cli_apipath.sh | 4 +- test/test_cli_duplicates.sh | 48 +- test/test_cli_expand.sh | 10 +- test/test_cli_history.sh | 6 +- test/test_cli_leafref.sh | 86 +- test/test_cli_multikey.sh | 44 +- test/test_cli_rest.sh | 6 +- test/test_cli_submodes.sh | 6 +- test/test_cli_translate.sh | 20 +- test/test_cli_union.sh | 48 +- test/test_cli_varonly.sh | 22 +- test/test_client.sh | 24 +- test/test_confirmed_commit.sh | 14 +- test/test_copy_config.sh | 4 +- test/test_datastore_format.sh | 52 +- test/test_datastore_repair.sh | 32 +- test/test_db.sh | 88 +- test/test_debug.sh | 22 +- test/test_feature.sh | 24 +- test/test_feature_startup.sh | 4 +- test/test_helloworld.sh | 22 +- test/test_http_data.sh | 168 +- test/test_identity.sh | 6 +- test/test_insert.sh | 20 +- test/test_install.sh | 4 +- test/test_instance_id.sh | 2 +- test/test_json_list.sh | 12 +- test/test_leaf_default.sh | 62 +- test/test_leafref.sh | 20 +- test/test_leafref_augment.sh | 48 +- test/test_leafref_state.sh | 26 +- test/test_leafref_union.sh | 10 +- test/test_leafref_uses_augment.sh | 4 +- test/test_minmax.sh | 10 +- test/test_nacm.sh | 6 +- test/test_nacm_credentials.sh | 36 +- test/test_nacm_datanode.sh | 8 +- test/test_nacm_datanode_paths.sh | 4 +- test/test_nacm_datanode_read.sh | 8 +- test/test_nacm_datanode_write.sh | 38 +- test/test_nacm_default.sh | 124 +- test/test_nacm_ext.sh | 70 +- test/test_nacm_module_read.sh | 62 +- test/test_nacm_module_write.sh | 24 +- test/test_nacm_protocol.sh | 6 +- test/test_nacm_recovery.sh | 72 +- test/test_netconf.sh | 122 +- test/test_netconf_filter.sh | 4 +- test/test_netconf_framing.sh | 20 +- test/test_netconf_hello.sh | 6 +- test/test_netconf_notifications.sh | 4 +- test/test_netconf_ssh_callhome.sh | 6 +- test/test_netconf_whitespace.sh | 6 +- test/test_openconfig.sh | 28 +- test/test_openconfig_interfaces.sh | 14 +- test/test_openconfig_network_instance.sh | 6 +- test/test_order.sh | 14 +- test/test_pagination_config.sh | 4 +- test/test_pagination_draft.sh | 90 +- test/test_pagination_state.sh | 36 +- test/test_pattern.sh | 20 +- test/test_perf_cli.sh | 4 +- test/test_perf_mem.sh | 86 +- test/test_perf_netconf.sh | 6 +- test/test_perf_restconf.sh | 6 +- test/test_perf_restconf_ssl.sh | 6 +- test/test_perf_startup.sh | 24 +- test/test_perf_state.sh | 4 +- test/test_perf_state_only.sh | 4 +- test/test_privileges.sh | 22 +- test/test_refine.sh | 24 +- test/test_restconf.sh | 486 ++-- test/test_restconf_basic_auth.sh | 22 +- test/test_restconf_callhome.sh | 66 +- test/test_restconf_continue.sh | 12 +- test/test_restconf_err.sh | 50 +- test/test_restconf_http_upgrade.sh | 176 +- test/test_restconf_internal.sh | 40 +- test/test_restconf_internal_usecases.sh | 32 +- test/test_restconf_jukebox.sh | 4 +- test/test_restconf_listkey.sh | 4 +- test/test_restconf_netns.sh | 28 +- test/test_restconf_nmap.sh | 8 +- test/test_restconf_notifications.sh | 4 +- test/test_restconf_op.sh | 4 +- test/test_restconf_plain_patch.sh | 10 +- test/test_restconf_ssl_certs.sh | 94 +- test/test_restconf_startup.sh | 42 +- test/test_restconf_yang_patch_json.sh | 10 +- test/test_restconf_yang_patch_xml.sh | 6 +- test/test_rpc.sh | 168 +- test/test_search_index.sh | 2 +- test/test_snmp_entity.sh | 26 +- test/test_snmp_get.sh | 16 +- test/test_snmp_ifmib.sh | 42 +- test/test_snmp_rowstatus.sh | 16 +- test/test_snmp_set.sh | 82 +- test/test_snmp_system.sh | 30 +- test/test_sock.sh | 32 +- test/test_startup.sh | 36 +- test/test_submodule.sh | 4 +- test/test_transaction.sh | 32 +- test/test_transaction_restart.sh | 44 +- test/test_type.sh | 38 +- test/test_type_range.sh | 10 +- test/test_union.sh | 4 +- test/test_unique.sh | 4 +- test/test_unique_descendant.sh | 4 +- test/test_upgrade_auto.sh | 98 +- test/test_upgrade_checkold.sh | 74 +- test/test_upgrade_failsafe.sh | 38 +- test/test_upgrade_interfaces.sh | 240 +- test/test_upgrade_interfaces_rfc7895.sh | 240 +- test/test_upgrade_module.sh | 78 +- test/test_upgrade_quit.sh | 218 +- test/test_upgrade_repair.sh | 4 +- test/test_upgrade_simple.sh | 30 +- test/test_when_mandatory.sh | 38 +- test/test_when_must.sh | 4 +- test/test_with_default.sh | 4 +- test/test_xml.sh | 2 +- test/test_xml_trees.sh | 8 +- test/test_xml_validate.sh | 4 +- test/test_xpath_functions.sh | 42 +- test/test_yang.sh | 8 +- test/test_yang_action.sh | 4 +- test/test_yang_anydata.sh | 106 +- test/test_yang_bind.sh | 4 +- test/test_yang_default.sh | 14 +- test/test_yang_deviation.sh | 70 +- test/test_yang_extension.sh | 4 +- test/test_yang_load.sh | 18 +- test/test_yang_models_ietf.sh | 12 +- test/test_yang_namespace.sh | 4 +- test/test_yang_wdcc.sh | 26 +- test/test_yang_when.sh | 34 +- test/test_yang_with_defaults.sh | 50 +- test/vagrant/clixon.sh | 4 +- test/vagrant/mem.sh | 2 +- test/vagrant/nginx.sh | 32 +- test/vagrant/vagrant.sh | 282 +- test/valgrind-clixon.supp | 9 + util/clixon_netconf_ssh_callhome.c | 188 +- util/clixon_netconf_ssh_callhome_client.c | 166 +- util/clixon_restconf_callhome_client.c | 488 ++-- util/clixon_util_datastore.c | 390 +-- util/clixon_util_dispatcher.c | 124 +- util/clixon_util_grpc.c | 280 +- util/clixon_util_json.c | 94 +- util/clixon_util_path.c | 314 +-- util/clixon_util_regexp.c | 134 +- util/clixon_util_socket.c | 126 +- util/clixon_util_ssl.c | 280 +- util/clixon_util_stream.c | 174 +- util/clixon_util_validate.c | 168 +- util/clixon_util_xml.c | 356 +-- util/clixon_util_xml_mod.c | 270 +- util/clixon_util_xpath.c | 446 ++-- util/clixon_util_yang.c | 48 +- yang/clixon/clixon-autocli@2022-02-11.yang | 256 +- yang/clixon/clixon-config@2022-02-11.yang | 1254 ++++----- yang/clixon/clixon-config@2022-03-21.yang | 1312 ++++----- yang/clixon/clixon-lib@2021-11-11.yang | 280 +- yang/clixon/clixon-lib@2021-12-05.yang | 284 +- yang/clixon/clixon-restconf@2022-03-21.yang | 294 +-- yang/clixon/clixon-restconf@2022-08-01.yang | 408 +-- yang/clixon/clixon-rfc5277@2008-07-01.yang | 158 +- .../clixon-xml-changelog@2019-03-21.yang | 178 +- .../ietf-list-pagination-nc@2022-07-24.yang | 72 +- .../ietf-list-pagination@2022-07-24.yang | 288 +- .../ietf-netconf-acm@2018-02-14.yang | 478 ++-- .../ietf-restconf-monitoring@2017-01-26.yang | 160 +- 376 files changed, 38147 insertions(+), 38133 deletions(-) diff --git a/.editorconfig b/.editorconfig index 74abe869..3dfe8d83 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,2 +1,2 @@ -indent_style = tab +indent_style = space indent_size = 4 diff --git a/CHANGELOG.md b/CHANGELOG.md index a899c7e2..772a93f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,8 @@ Developers may need to change their code ### 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 diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 1e4aba09..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; } @@ -165,7 +165,7 @@ release_all_dbs(clicon_handle h, */ int backend_client_rm(clicon_handle h, - struct client_entry *ce) + struct client_entry *ce) { struct client_entry *c; struct client_entry *c0; @@ -204,17 +204,17 @@ 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; } retval = backend_client_delete(h, ce); /* actually purge it */ @@ -231,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; @@ -243,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; } @@ -275,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; @@ -284,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; } @@ -317,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; @@ -344,187 +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 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; - } - } + * 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; - } + 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 */ @@ -544,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; @@ -559,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); @@ -608,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; } @@ -625,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; @@ -640,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); @@ -689,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; } @@ -707,10 +707,10 @@ 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; @@ -722,69 +722,69 @@ from_client_lock(clicon_handle h, yang_stmt *yspec; 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 ((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_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; } @@ -800,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; @@ -813,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); /* @@ -834,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; } @@ -875,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; @@ -902,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 */ @@ -917,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; } @@ -966,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; @@ -986,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 @@ -1034,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; } @@ -1057,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); @@ -1094,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; @@ -1114,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; @@ -1134,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; @@ -1171,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; @@ -1185,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; } @@ -1218,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; @@ -1230,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: @@ -1257,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; @@ -1287,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; @@ -1317,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 } @@ -1515,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; @@ -1525,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 */ } @@ -1556,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_commit.c b/apps/backend/backend_commit.c index 02edc316..7af3470e 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -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,154 +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" + 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; @@ -339,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; @@ -349,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 */ @@ -390,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 */ @@ -470,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; @@ -481,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; @@ -572,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; @@ -582,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: @@ -651,9 +651,9 @@ candidate_validate(clicon_handle h, */ int candidate_commit(clicon_handle h, - cxobj *xe, - char *db, - cbuf *cbret) + cxobj *xe, + char *db, + cbuf *cbret) { int retval = -1; transaction_data_t *td = NULL; @@ -663,13 +663,13 @@ candidate_commit(clicon_handle h, /* 1. Start transaction */ if ((td = transaction_new()) == NULL) - goto done; + goto done; /* Common steps (with validate). Load candidate and running and compute diffs * Note this is only call that uses 3-values */ if ((ret = validate_common(h, db, td, &xret)) < 0) - goto done; + goto done; /* If the confirmed-commit feature is enabled, execute phase 2: * - If a valid confirming-commit, cancel the rollback event @@ -687,44 +687,44 @@ candidate_commit(clicon_handle h, } if (if_feature(yspec, "ietf-netconf", "confirmed-commit") - && confirmed_commit_state_get(h) != ROLLBACK - && xe != NULL){ - if (handle_confirmed_commit(h, xe) < 0) - goto done; + && confirmed_commit_state_get(h) != ROLLBACK + && xe != NULL){ + if (handle_confirmed_commit(h, xe) < 0) + 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; } /* 7. 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; 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 */ @@ -734,14 +734,14 @@ 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; @@ -777,10 +777,10 @@ candidate_commit(clicon_handle h, */ 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; @@ -796,37 +796,37 @@ from_client_commit(clicon_handle h, } 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; + 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; - } - if (netconf_in_use(cbret, "protocol", "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, 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; + 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 */ @@ -844,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; @@ -858,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); @@ -878,7 +878,7 @@ 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 */ } @@ -894,10 +894,10 @@ from_client_discard_changes(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; @@ -905,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: @@ -925,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"; @@ -941,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: @@ -1056,7 +1056,7 @@ tmp |----------------------> */ int load_failsafe(clicon_handle h, - char *phase) + char *phase) { int retval = -1; int ret; diff --git a/apps/backend/backend_confirm.c b/apps/backend/backend_confirm.c index 7c774ab8..5a94268d 100644 --- a/apps/backend/backend_confirm.c +++ b/apps/backend/backend_confirm.c @@ -92,12 +92,12 @@ confirmed_commit_init(clicon_handle h) struct confirmed_commit *cc = NULL; if ((cc = calloc(1, sizeof(*cc))) == NULL){ - clicon_err(OE_UNIX, errno, "calloc"); - goto done; + clicon_err(OE_UNIX, errno, "calloc"); + goto done; } cc->cc_state = INACTIVE; if (clicon_ptr_set(h, "confirmed-commit-struct", cc) < 0) - goto done; + goto done; retval = 0; done: return retval; @@ -114,9 +114,9 @@ confirmed_commit_free(clicon_handle h) 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); + if (cc->cc_persist_id != NULL) + free (cc->cc_persist_id); + free(cc); } clicon_ptr_del(h, "confirmed-commit-struct"); return 0; @@ -136,7 +136,7 @@ confirmed_commit_state_get(clicon_handle h) static int confirmed_commit_state_set(clicon_handle h, - enum confirmed_commit_state state) + enum confirmed_commit_state state) { struct confirmed_commit *cc = NULL; @@ -156,21 +156,21 @@ confirmed_commit_persist_id_get(clicon_handle h) static int confirmed_commit_persist_id_set(clicon_handle h, - char *persist_id) + 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); + 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; - } + if ((cc->cc_persist_id = strdup4(persist_id)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup4"); + return -1; + } } else - cc->cc_persist_id = NULL; + cc->cc_persist_id = NULL; return 0; } @@ -185,7 +185,7 @@ confirmed_commit_session_id_get(clicon_handle h) static int confirmed_commit_session_id_set(clicon_handle h, - uint32_t session_id) + uint32_t session_id) { struct confirmed_commit *cc = NULL; @@ -196,8 +196,8 @@ confirmed_commit_session_id_set(clicon_handle h, static int confirmed_commit_fn_arg_get(clicon_handle h, - int (**fn)(int, void*), - void **arg) + int (**fn)(int, void*), + void **arg) { struct confirmed_commit *cc = NULL; @@ -209,8 +209,8 @@ confirmed_commit_fn_arg_get(clicon_handle h, static int confirmed_commit_fn_arg_set(clicon_handle h, - int (*fn)(int, void*), - void *arg) + int (*fn)(int, void*), + void *arg) { struct confirmed_commit *cc = NULL; @@ -239,13 +239,13 @@ xe_confirmed(cxobj *xe) */ static int xe_persist(cxobj *xe, - char **str) + char **str) { cxobj *xml; if ((xml = xml_find_type(xe, NULL, "persist", CX_ELMNT)) != NULL){ - *str = xml_body(xml); - return 1; + *str = xml_body(xml); + return 1; } *str = NULL; return 0; @@ -260,13 +260,13 @@ xe_persist(cxobj *xe, */ static int xe_persist_id(cxobj *xe, - char **str) + char **str) { cxobj *xml; if ((xml = xml_find_type(xe, NULL, "persist-id", CX_ELMNT)) != NULL){ - *str = xml_body(xml); - return 1; + *str = xml_body(xml); + return 1; } *str = NULL; return 0; @@ -283,8 +283,8 @@ xe_timeout(cxobj *xe) char *str; if ((xml = xml_find_type(xe, NULL, "confirm-timeout", CX_ELMNT)) != NULL && - (str = xml_body(xml)) != NULL) - return strtoul(str, NULL, 10); + (str = xml_body(xml)) != NULL) + return strtoul(str, NULL, 10); return 0; } @@ -382,14 +382,14 @@ 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_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"); + clicon_err(OE_DB, 0, "Error deleting the rollback configuration"); return 0; } @@ -409,34 +409,34 @@ cancel_confirmed_commit(clicon_handle h) */ static int check_valid_confirming_commit(clicon_handle h, - cxobj *xe, + 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; + 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) { + 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 { + 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; + 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; + goto invalid; } case EPHEMERAL: if (myid == confirmed_commit_session_id_get(h)) { @@ -447,10 +447,10 @@ check_valid_confirming_commit(clicon_handle h, } 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; + goto invalid; default: clicon_debug(1, "commit-confirmed state !? %d", confirmed_commit_state_get(h)); - goto invalid; + goto invalid; } retval = 1; // valid done: @@ -475,7 +475,7 @@ check_valid_confirming_commit(clicon_handle h, */ int handle_confirmed_commit(clicon_handle h, - cxobj *xe) + cxobj *xe) { int retval = -1; uint32_t session_id; @@ -485,13 +485,13 @@ handle_confirmed_commit(clicon_handle h, int db_exists; if (xe == NULL){ - clicon_err(OE_CFG, EINVAL, "xe is 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; + 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 @@ -505,45 +505,45 @@ handle_confirmed_commit(clicon_handle h, } if (confirmed_commit_state_get(h) == PERSISTENT && - confirmed_commit_persist_id_get(h) != NULL) { - confirmed_commit_persist_id_set(h, NULL); + confirmed_commit_persist_id_get(h) != NULL) { + confirmed_commit_persist_id_set(h, NULL); } - confirmed_commit_state_set(h, INACTIVE); + 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)){ + confirm_timeout = xe_timeout(xe); + if (xe_persist(xe, &persist)){ if (persist == NULL) { - confirmed_commit_persist_id_set(h, NULL); + confirmed_commit_persist_id_set(h, NULL); } - else if (confirmed_commit_persist_id_set(h, persist) < 0){ + 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); + 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 { + 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); + 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), + confirmed_commit_session_id_get(h), confirm_timeout); } @@ -627,7 +627,7 @@ handle_confirmed_commit(clicon_handle h, */ int do_rollback(clicon_handle h, - uint8_t *errs) + uint8_t *errs) { int retval = -1; uint8_t errstate = 0; @@ -646,8 +646,8 @@ do_rollback(clicon_handle h, } if (confirmed_commit_state_get(h) == PERSISTENT && - confirmed_commit_persist_id_get(h) != NULL) { - confirmed_commit_persist_id_set(h, NULL); + 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 */ @@ -708,10 +708,10 @@ do_rollback(clicon_handle h, */ int from_client_cancel_commit(clicon_handle h, - cxobj *xe, - cbuf *cbret, - void *arg, - void *regarg) + cxobj *xe, + cbuf *cbret, + void *arg, + void *regarg) { cxobj *persist_id_xml; char *persist_id = NULL; @@ -737,37 +737,37 @@ from_client_cancel_commit(clicon_handle h, if (netconf_invalid_value(cbret, "protocol", "confirming-commit must be given within session that gave the confirmed-commit") < 0) goto done; } - else - rollback++; - break; + else + rollback++; + break; case PERSISTENT: if (persist_id_xml == NULL) { if (netconf_invalid_value(cbret, "protocol", "persist-id is required") < 0) goto done; } - else if (clicon_strcmp(persist_id, confirmed_commit_persist_id_get(h)) != 0){ - if (netconf_invalid_value(cbret, "application", "a confirmed-commit with the given persist-id was not found") < 0) - goto done; - } - else - rollback++; - break; + else if (clicon_strcmp(persist_id, confirmed_commit_persist_id_get(h)) != 0){ + if (netconf_invalid_value(cbret, "application", "a confirmed-commit with the given persist-id was not found") < 0) + goto done; + } + else + rollback++; + break; case INACTIVE: if (netconf_invalid_value(cbret, "application", "no confirmed-commit is in progress") < 0) goto done; - break; + break; default: if (netconf_invalid_value(cbret, "application", "server error") < 0) goto done; - break; + break; } /* all invalid conditions jump to done: and valid code paths jump to or fall through to here. */ if (rollback){ cancel_rollback_event(h); if (do_rollback(h, NULL) < 0) - goto done; - cprintf(cbret, "", NETCONF_BASE_NAMESPACE); - clicon_log(LOG_INFO, "a confirmed-commit has been cancelled by client request"); + goto done; + cprintf(cbret, "", NETCONF_BASE_NAMESPACE); + clicon_log(LOG_INFO, "a confirmed-commit has been cancelled by client request"); } retval = 0; done: @@ -785,20 +785,20 @@ from_client_cancel_commit(clicon_handle h, */ int from_client_confirmed_commit(clicon_handle h, - cxobj *xe, - uint32_t myid, - cbuf *cbret) + 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; + 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; + cancel_confirmed_commit(h); + cprintf(cbret, "", NETCONF_BASE_NAMESPACE); + goto dontcommit; } retval = 1; 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 aed5a80d..7de8ac41 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -101,29 +101,29 @@ 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 */ @@ -134,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__); @@ -153,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() */ } @@ -181,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; } @@ -205,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; } @@ -280,8 +280,8 @@ xmldb_drop_priv(clicon_handle h, */ static int check_drop_priv(clicon_handle h, - gid_t gid, - yang_stmt *yspec) + gid_t gid, + yang_stmt *yspec) { int retval = -1; uid_t uid; @@ -291,74 +291,74 @@ check_drop_priv(clicon_handle h, /* 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; + 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 (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 (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; @@ -383,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: @@ -412,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; @@ -430,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; @@ -452,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 \"