/* * Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren This file is part of CLIXON. CLIXON is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. CLIXON is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with CLIXON; see the file LICENSE. If not, see . */ #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 #include /* cligen */ #include /* clicon */ #include #include "clixon_backend_transaction.h" #include "backend_plugin.h" #include "backend_handle.h" #include "backend_commit.h" #include "backend_client.h" /*! Key values are checked for validity independent of user-defined callbacks * * Key values are checked as follows: * 1. If no value and default value defined, add it. * 2. If no value and mandatory flag set in spec, report error. * 3. Validate value versus spec, and report error if no match. Currently only int ranges and * string regexp checked. * See also db_lv_set() where defaults are also filled in. The case here for defaults * are if code comes via XML/NETCONF. * @param yspec Yang spec * @param td Transaction data */ static int generic_validate(yang_spec *yspec, transaction_data_t *td) { int retval = -1; cxobj *x1; cxobj *x2; int i; yang_stmt *ys; /* changed entries */ for (i=0; itd_clen; i++){ x1 = td->td_scvec[i]; /* source changed */ x2 = td->td_tcvec[i]; /* target changed */ ys = xml_spec(x1); if (xml_yang_validate(x2, ys) < 0) goto done; } /* deleted entries */ for (i=0; itd_dlen; i++){ x1 = td->td_dvec[i]; ys = xml_spec(x1); if (yang_mandatory(ys)){ clicon_err(OE_CFG, 0,"Removed mandatory variable: %s", xml_name(x1)); goto done; } } /* added entries */ for (i=0; itd_alen; i++){ x2 = td->td_avec[i]; ys = xml_spec(x2); if (xml_yang_validate(x2, ys) < 0) goto done; if (xml_apply(x2, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate, NULL) < 0) goto done; } retval = 0; done: return retval; } /*! Common code of candidate_validate and candidate_commit */ static int validate_common(clicon_handle h, char *candidate, transaction_data_t *td) { int retval = -1; yang_spec *yspec; int i; cxobj *xn; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } /* 2. Parse xml trees */ if (xmldb_get(h, "running", "/", 0, &td->td_src, NULL, NULL) < 0) goto done; if (xmldb_get(h, candidate, "/", 0, &td->td_target, NULL, NULL) < 0) 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; if (debug) 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); } 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); } 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); } /* 4. Call plugin transaction start callbacks */ if (plugin_transaction_begin(h, td) < 0) goto done; /* 5. Make generic validation on all new or changed data. */ if (generic_validate(yspec, td) < 0) goto done; /* 6. Call plugin transaction validate callbacks */ if (plugin_transaction_validate(h, td) < 0) goto done; /* 7. Call plugin transaction complete callbacks */ if (plugin_transaction_complete(h, td) < 0) 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 */ int candidate_commit(clicon_handle h, char *candidate) { int retval = -1; transaction_data_t *td = NULL; /* 1. Start transaction */ if ((td = transaction_new()) == NULL) goto done; /* Common steps (with validate) */ if (validate_common(h, candidate, td) < 0) goto done; /* 7. Call plugin transaction commit callbacks */ if (plugin_transaction_commit(h, td) < 0) goto done; /* 8. Success: Copy candidate to running */ if (xmldb_copy(h, candidate, "running") < 0) goto done; /* 9. Call plugin transaction end callbacks */ plugin_transaction_end(h, td); /* 8. Copy running back to candidate in case end functions updated running */ if (xmldb_copy(h, "running", candidate) < 0){ /* ignore errors or signal major setback ? */ clicon_log(LOG_NOTICE, "Error in rollback, trying to continue"); goto done; } retval = 0; done: /* In case of failure, call plugin transaction termination callbacks */ if (retval < 0 && td) plugin_transaction_abort(h, td); if (td) transaction_free(td); return retval; } /*! Do a diff between candidate and running, then start a validate transaction * * @param[in] h Clicon handle * @param[in] candidate: The candidate database. The wanted backend state */ int candidate_validate(clicon_handle h, char *candidate) { int retval = -1; transaction_data_t *td = NULL; /* 1. Start transaction */ if ((td = transaction_new()) == NULL) goto done; /* Common steps (with commit) */ if (validate_common(h, candidate, td) < 0) goto done; retval = 0; done: /* In case of failure, call plugin transaction termination callbacks */ if (retval < 0 && td) plugin_transaction_abort(h, td); if (td) transaction_free(td); return retval; } /*! Handle an incoming commit message from a client. * XXX: If commit succeeds and snapshot/startup fails, we have strange state: * the commit has succeeded but an error message is returned. */ int from_client_commit(clicon_handle h, int s, struct clicon_msg *msg, const char *label) { int retval = -1; char *candidate; char *running; uint32_t snapshot; uint32_t startup; char *archive_dir; char *startup_config; if (clicon_msg_commit_decode(msg, &candidate, &running, &snapshot, &startup, label) < 0) goto err; if (strcmp(candidate, "candidate") && strcmp(candidate, "tmp")){ clicon_err(OE_PLUGIN, 0, "candidate is not \"candidate\" or tmp"); goto err; } if (strcmp(running, "running")){ clicon_err(OE_PLUGIN, 0, "running db is not \"running\""); goto err; } if (candidate_commit(h, "candidate") < 0){ clicon_debug(1, "Commit %s failed", candidate); retval = 0; /* We ignore errors from commit, but maybe we should fail on fatal errors? */ goto err; } clicon_debug(1, "Commit %s", candidate); if (snapshot){ if ((archive_dir = clicon_archive_dir(h)) == NULL){ clicon_err(OE_PLUGIN, 0, "snapshot set and clicon_archive_dir not defined"); goto err; } if (config_snapshot(h, running, archive_dir) < 0) goto err; } if (startup){ if ((archive_dir = clicon_archive_dir(h)) == NULL){ clicon_err(OE_PLUGIN, 0, "startup set but clicon_archive_dir not defined"); goto err; } if ((startup_config = clicon_startup_config(h)) == NULL){ clicon_err(OE_PLUGIN, 0, "startup set but startup_config not defined"); goto err; } if (clicon_file_copy("snapshot", "startup") < 0){ clicon_err(OE_PROTO, errno, "%s: Error when creating startup", __FUNCTION__); goto err; } } retval = 0; if (send_msg_ok(s) < 0) goto done; goto done; err: /* XXX: more elaborate errstring? */ if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0) retval = -1; done: unchunk_group(__FUNCTION__); return retval; /* may be zero if we ignoring errors from commit */ } /* from_client_commit */ /*! Handle an incoming validate message from a client. */ int from_client_validate(clicon_handle h, int s, struct clicon_msg *msg, const char *label) { int retval = -1; char *candidate; if (clicon_msg_validate_decode(msg, &candidate, label) < 0){ send_msg_err(s, clicon_errno, clicon_suberrno, clicon_err_reason); goto err; } if (strcmp(candidate, "candidate") != 0 && strcmp(candidate, "tmp") != 0){ clicon_err(OE_PLUGIN, 0, "candidate is not \"candidate\" or tmp"); goto err; } clicon_debug(1, "Validate %s", candidate); if (candidate_validate(h, candidate) < 0){ clicon_debug(1, "Validate %s failed", candidate); retval = 0; /* We ignore errors from commit, but maybe we should fail on fatal errors? */ goto err; } retval = 0; if (send_msg_ok(s) < 0) goto done; goto done; err: /* XXX: more elaborate errstring? */ if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0) retval = -1; done: unchunk_group(__FUNCTION__); return retval; } /* from_client_validate */