404 lines
12 KiB
C
404 lines
12 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
|
|
|
|
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 <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <stdarg.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <pwd.h>
|
|
#include <syslog.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <assert.h>
|
|
#include <netinet/in.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clicon */
|
|
#include <clixon/clixon.h>
|
|
|
|
#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;
|
|
yang_stmt *ys;
|
|
int i;
|
|
|
|
/* All entries */
|
|
if (xml_apply(td->td_target, CX_ELMNT,
|
|
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
|
|
goto done;
|
|
|
|
/* changed entries */
|
|
for (i=0; i<td->td_clen; i++){
|
|
x1 = td->td_scvec[i]; /* source changed */
|
|
x2 = td->td_tcvec[i]; /* target changed */
|
|
if (xml_yang_validate_add(x2, NULL) < 0)
|
|
goto done;
|
|
}
|
|
/* deleted entries */
|
|
for (i=0; i<td->td_dlen; i++){
|
|
x1 = td->td_dvec[i];
|
|
ys = xml_spec(x1);
|
|
if (ys && yang_mandatory(ys)){
|
|
clicon_err(OE_CFG, 0,"Removed mandatory variable: %s",
|
|
xml_name(x1));
|
|
goto done;
|
|
}
|
|
}
|
|
/* added entries */
|
|
for (i=0; i<td->td_alen; i++){
|
|
x2 = td->td_avec[i];
|
|
if (xml_apply0(x2, CX_ELMNT,
|
|
(xml_applyfn_t*)xml_yang_validate_add, NULL) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Common code of candidate_validate and candidate_commit
|
|
* @param[in] h Clicon handle
|
|
* @param[in] candidate The candidate database. The wanted backend state
|
|
* @retval 0 OK
|
|
* @retval -1 Fatal error or validation fail
|
|
* @note Need to differentiate between error and validation fail
|
|
*/
|
|
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", "/", 1, &td->td_src) < 0)
|
|
goto done;
|
|
if (xmldb_get(h, candidate, "/", 1, &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>1)
|
|
transaction_print(stderr, td);
|
|
/* Mark as changed in tree */
|
|
for (i=0; i<td->td_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; i<td->td_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; i<td->td_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
|
|
* @param[in] candidate A candidate database, not necessarily "candidate"
|
|
* @retval 0 OK
|
|
* @retval -1 Fatal error or validation fail
|
|
* @note Need to differentiate between error and validation fail
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*! Commit changes from candidate to running
|
|
* @param[in] h Clicon handle
|
|
* @param[out] cbret Return xml value cligen buffer
|
|
* @retval 0 OK. This may indicate both ok and err msg back to client
|
|
* @retval -1 (Local) Error
|
|
*/
|
|
int
|
|
from_client_commit(clicon_handle h,
|
|
int mypid,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
int piddb;
|
|
|
|
/* Check if target locked by other client */
|
|
piddb = xmldb_islocked(h, "running");
|
|
if (piddb && mypid != piddb){
|
|
cprintf(cbret, "<rpc-reply><rpc-error>"
|
|
"<error-tag>lock-denied</error-tag>"
|
|
"<error-type>protocol</error-type>"
|
|
"<error-severity>error</error-severity>"
|
|
"<error-message>Operation failed, lock is already held</error-message>"
|
|
"<error-info><session-id>%d</session-id></error-info>"
|
|
"</rpc-error></rpc-reply>",
|
|
piddb);
|
|
goto ok;
|
|
}
|
|
if (candidate_commit(h, "candidate") < 0){ /* Assume validation fail, nofatal */
|
|
clicon_debug(1, "Commit candidate failed");
|
|
cprintf(cbret, "<rpc-reply><rpc-error>"
|
|
"<error-tag>invalid-value</error-tag>"
|
|
"<error-type>protocol</error-type>"
|
|
"<error-severity>error</error-severity>"
|
|
"<error-message>%s</error-message>"
|
|
"</rpc-error></rpc-reply>",
|
|
clicon_err_reason);
|
|
goto ok;
|
|
}
|
|
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
|
ok:
|
|
retval = 0;
|
|
// done:
|
|
return retval; /* may be zero if we ignoring errors from commit */
|
|
} /* from_client_commit */
|
|
|
|
/*! Discard all changes in candidate / revert to running
|
|
* @param[in] h Clicon handle
|
|
* @param[in] mypid Process/session id of calling client
|
|
* @param[out] cbret Return xml value cligen buffer
|
|
* @retval 0 OK. This may indicate both ok and err msg back to client
|
|
* @retval -1 (Local) Error
|
|
*/
|
|
int
|
|
from_client_discard_changes(clicon_handle h,
|
|
int mypid,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
int piddb;
|
|
|
|
/* Check if target locked by other client */
|
|
piddb = xmldb_islocked(h, "candidate");
|
|
if (piddb && mypid != piddb){
|
|
cprintf(cbret, "<rpc-reply><rpc-error>"
|
|
"<error-tag>lock-denied</error-tag>"
|
|
"<error-type>protocol</error-type>"
|
|
"<error-severity>error</error-severity>"
|
|
"<error-message>Operation failed, lock is already held</error-message>"
|
|
"<error-info><session-id>%d</session-id></error-info>"
|
|
"</rpc-error></rpc-reply>",
|
|
piddb);
|
|
goto ok;
|
|
}
|
|
if (xmldb_copy(h, "running", "candidate") < 0){
|
|
cprintf(cbret, "<rpc-reply><rpc-error>"
|
|
"<error-tag>operation-failed</error-tag>"
|
|
"<error-type>application</error-type>"
|
|
"<error-severity>error</error-severity>"
|
|
"<error-info>read-registry</error-info>"
|
|
"</rpc-error></rpc-reply>");
|
|
goto ok;
|
|
}
|
|
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
|
ok:
|
|
retval = 0;
|
|
// done:
|
|
return retval; /* may be zero if we ignoring errors from commit */
|
|
}
|
|
|
|
/*! Handle an incoming validate message from a client.
|
|
* @param[in] h Clicon handle
|
|
* @param[in] db Database name
|
|
* @param[out] cbret Return xml value cligen buffer
|
|
* @retval 0 OK. This may indicate both ok and err msg back to client (eg invalid)
|
|
* @retval -1 (Local) Error
|
|
*/
|
|
int
|
|
from_client_validate(clicon_handle h,
|
|
char *db,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
transaction_data_t *td = NULL;
|
|
|
|
if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){
|
|
cprintf(cbret, "<rpc-reply><rpc-error>"
|
|
"<error-tag>invalid-value</error-tag>"
|
|
"<error-type>protocol</error-type>"
|
|
"<error-severity>error</error-severity>"
|
|
"</rpc-error></rpc-reply>");
|
|
goto ok;
|
|
}
|
|
clicon_debug(1, "Validate %s", db);
|
|
|
|
/* 1. Start transaction */
|
|
if ((td = transaction_new()) == NULL)
|
|
goto done;
|
|
/* Common steps (with commit) */
|
|
if (validate_common(h, db, td) < 0){
|
|
clicon_debug(1, "Validate %s failed", db);
|
|
/* XXX: candidate_validate should have proper error handling */
|
|
cprintf(cbret, "<rpc-reply><rpc-error>"
|
|
"<error-tag>missing-attribute</error-tag>"
|
|
"<error-type>protocol</error-type>"
|
|
"<error-severity>error</error-severity>"
|
|
"<error-message>%s</error-message>"
|
|
"</rpc-error></rpc-reply>",
|
|
clicon_err_reason);
|
|
goto ok;
|
|
}
|
|
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (retval < 0 && td)
|
|
plugin_transaction_abort(h, td);
|
|
if (td)
|
|
transaction_free(td);
|
|
return retval;
|
|
} /* from_client_validate */
|