/* * 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]; if (xml_yang_validate(x2, xml_spec(x2)) < 0) goto done; if (xml_apply(x2, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate, NULL) < 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 * @param[in] running The current database. The original backend state * @param[in] candidate: The candidate database. The wanted backend state */ int candidate_commit(clicon_handle h, char *candidate, char *running) { int retval = -1; int i; cxobj *xn; struct stat sb; void *firsterr = NULL; yang_spec *yspec; transaction_data_t *td = NULL; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } /* Sanity checks that databases exists. */ if (stat(running, &sb) < 0){ clicon_err(OE_DB, errno, "%s", running); goto done; } if (stat(candidate, &sb) < 0){ clicon_err(OE_DB, errno, "%s", candidate); goto done; } /* 1. Start transaction */ if ((td = transaction_new()) == NULL) goto done; /* 2. Parse xml trees */ if (xmldb_get(running, "/", yspec, &td->td_src) < 0) goto done; if (xmldb_get(candidate, "/", yspec, &td->td_target) < 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); } 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); } for (i=0; itd_clen; i++){ /* Also up */ xn = td->td_scvec[i]; xml_flag(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; /* 7. Call plugin transaction commit callbacks */ if (plugin_transaction_commit(h, td) < 0) goto done; /* 8. Success: Copy candidate to running */ if (file_cp(candidate, running) < 0){ clicon_err(OE_UNIX, errno, "file_cp(candidate; running)"); goto done; } /* 9. Call plugin transaction end callbacks */ plugin_transaction_end(h, td); /* 8. Copy running back to running in case end functions updated running */ if (file_cp(running, candidate) < 0){ /* ignore errors or signal major setback ? */ clicon_err(OE_UNIX, errno, "file_cp(running, candidate)"); 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); if (firsterr) clicon_err_restore(firsterr); return retval; } /*! Do a diff between candidate and running, then start a validate transaction * * @param[in] h Clicon handle * @param[in] running The current database. The original backend state * @param[in] candidate: The candidate database. The wanted backend state */ int candidate_validate(clicon_handle h, char *candidate, char *running) { int retval = -1; struct stat sb; yang_spec *yspec; transaction_data_t *td = NULL; int i; cxobj *xn; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } /* Sanity checks that databases exists. */ if (stat(running, &sb) < 0){ clicon_err(OE_DB, errno, "%s", running); goto done; } if (stat(candidate, &sb) < 0){ clicon_err(OE_DB, errno, "%s", candidate); goto done; } /* 1. Start transaction */ if ((td = transaction_new()) == NULL) goto done; /* 2. Parse xml trees */ if (xmldb_get(running, "/", yspec, &td->td_src) < 0) goto done; if (xmldb_get(candidate, "/", yspec, &td->td_target) < 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); } for (i=0; itd_alen; i++){ /* Also down */ xn = td->td_avec[i]; xml_flag_set(xn, XML_FLAG_ADD); } for (i=0; itd_clen; i++){ /* Also up */ xn = td->td_scvec[i]; xml_flag_set(xn, XML_FLAG_CHANGE); xn = td->td_tcvec[i]; xml_flag(xn, XML_FLAG_CHANGE); } /* 4. Call plugin start transaction 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 validate transaction callbacks */ if (plugin_transaction_validate(h, td) < 0) goto done; /* 7. Call plugin complete transaction callbacks */ if (plugin_transaction_complete(h, 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 *snapshot_0; char *archive_dir; char *startup_config; if (clicon_msg_commit_decode(msg, &candidate, &running, &snapshot, &startup, label) < 0) goto err; if (candidate_commit(h, candidate, running) < 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; } snapshot_0 = chunk_sprintf(__FUNCTION__, "%s/0", archive_dir); if (file_cp(snapshot_0, startup_config) < 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 */ /* * Call backend plugin */ int from_client_validate(clicon_handle h, int s, struct clicon_msg *msg, const char *label) { char *dbname; char *running_db; int retval = -1; if (clicon_msg_validate_decode(msg, &dbname, label) < 0){ send_msg_err(s, clicon_errno, clicon_suberrno, clicon_err_reason); goto err; } clicon_debug(1, "Validate %s", dbname); if ((running_db = clicon_running_db(h)) == NULL){ clicon_err(OE_FATAL, 0, "running db not set"); goto err; } if (candidate_validate(h, dbname, running_db) < 0){ clicon_debug(1, "Validate %s failed", dbname); 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 */