1 /*
2  * Copyright (C) 2018  NetDEF, Inc.
3  *                     Renato Westphal
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the Free
7  * Software Foundation; either version 2 of the License, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; see the file COPYING; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18  */
19 
20 #include <zebra.h>
21 
22 #include "libfrr.h"
23 #include "version.h"
24 #include "defaults.h"
25 #include "log.h"
26 #include "lib_errors.h"
27 #include "command.h"
28 #include "termtable.h"
29 #include "db.h"
30 #include "debug.h"
31 #include "yang_translator.h"
32 #include "northbound.h"
33 #include "northbound_cli.h"
34 #include "northbound_db.h"
35 #ifndef VTYSH_EXTRACT_PL
36 #include "lib/northbound_cli_clippy.c"
37 #endif
38 
39 struct debug nb_dbg_cbs_config = {0, "Northbound callbacks: configuration"};
40 struct debug nb_dbg_cbs_state = {0, "Northbound callbacks: state"};
41 struct debug nb_dbg_cbs_rpc = {0, "Northbound callbacks: RPCs"};
42 struct debug nb_dbg_notif = {0, "Northbound notifications"};
43 struct debug nb_dbg_events = {0, "Northbound events"};
44 struct debug nb_dbg_libyang = {0, "libyang debugging"};
45 
46 struct nb_config *vty_shared_candidate_config;
47 static struct thread_master *master;
48 
vty_show_nb_errors(struct vty * vty,int error,const char * errmsg)49 static void vty_show_nb_errors(struct vty *vty, int error, const char *errmsg)
50 {
51 	vty_out(vty, "Error type: %s\n", nb_err_name(error));
52 	if (strlen(errmsg) > 0)
53 		vty_out(vty, "Error description: %s\n", errmsg);
54 }
55 
nb_cli_classic_commit(struct vty * vty)56 static int nb_cli_classic_commit(struct vty *vty)
57 {
58 	struct nb_context context = {};
59 	char errmsg[BUFSIZ] = {0};
60 	int ret;
61 
62 	context.client = NB_CLIENT_CLI;
63 	context.user = vty;
64 	ret = nb_candidate_commit(&context, vty->candidate_config, true, NULL,
65 				  NULL, errmsg, sizeof(errmsg));
66 	switch (ret) {
67 	case NB_OK:
68 		/* Successful commit. Print warnings (if any). */
69 		if (strlen(errmsg) > 0)
70 			vty_out(vty, "%s\n", errmsg);
71 		break;
72 	case NB_ERR_NO_CHANGES:
73 		break;
74 	default:
75 		vty_out(vty, "%% Configuration failed.\n\n");
76 		vty_show_nb_errors(vty, ret, errmsg);
77 		if (vty->t_pending_commit)
78 			vty_out(vty,
79 				"The following commands were dynamically grouped into the same transaction and rejected:\n%s",
80 				vty->pending_cmds_buf);
81 
82 		/* Regenerate candidate for consistency. */
83 		nb_config_replace(vty->candidate_config, running_config, true);
84 		return CMD_WARNING_CONFIG_FAILED;
85 	}
86 
87 	return CMD_SUCCESS;
88 }
89 
nb_cli_pending_commit_clear(struct vty * vty)90 static void nb_cli_pending_commit_clear(struct vty *vty)
91 {
92 	THREAD_TIMER_OFF(vty->t_pending_commit);
93 	vty->backoff_cmd_count = 0;
94 	XFREE(MTYPE_TMP, vty->pending_cmds_buf);
95 	vty->pending_cmds_buflen = 0;
96 	vty->pending_cmds_bufpos = 0;
97 }
98 
nb_cli_pending_commit_cb(struct thread * thread)99 static int nb_cli_pending_commit_cb(struct thread *thread)
100 {
101 	struct vty *vty = THREAD_ARG(thread);
102 
103 	(void)nb_cli_classic_commit(vty);
104 	nb_cli_pending_commit_clear(vty);
105 
106 	return 0;
107 }
108 
nb_cli_pending_commit_check(struct vty * vty)109 void nb_cli_pending_commit_check(struct vty *vty)
110 {
111 	if (vty->t_pending_commit) {
112 		(void)nb_cli_classic_commit(vty);
113 		nb_cli_pending_commit_clear(vty);
114 	}
115 }
116 
nb_cli_backoff_start(struct vty * vty)117 static bool nb_cli_backoff_start(struct vty *vty)
118 {
119 	struct timeval now, delta;
120 
121 	/*
122 	 * Start the configuration backoff timer only if 100 YANG-modeled
123 	 * commands or more were entered within the last second.
124 	 */
125 	monotime(&now);
126 	if (monotime_since(&vty->backoff_start, &delta) >= 1000000) {
127 		vty->backoff_start = now;
128 		vty->backoff_cmd_count = 1;
129 		return false;
130 	}
131 	if (++vty->backoff_cmd_count < 100)
132 		return false;
133 
134 	return true;
135 }
136 
nb_cli_schedule_command(struct vty * vty)137 static int nb_cli_schedule_command(struct vty *vty)
138 {
139 	/* Append command to dynamically sized buffer of scheduled commands. */
140 	if (!vty->pending_cmds_buf) {
141 		vty->pending_cmds_buflen = 4096;
142 		vty->pending_cmds_buf =
143 			XCALLOC(MTYPE_TMP, vty->pending_cmds_buflen);
144 	}
145 	if ((strlen(vty->buf) + 3)
146 	    > (vty->pending_cmds_buflen - vty->pending_cmds_bufpos)) {
147 		vty->pending_cmds_buflen *= 2;
148 		vty->pending_cmds_buf =
149 			XREALLOC(MTYPE_TMP, vty->pending_cmds_buf,
150 				 vty->pending_cmds_buflen);
151 	}
152 	strlcat(vty->pending_cmds_buf, "- ", vty->pending_cmds_buflen);
153 	vty->pending_cmds_bufpos = strlcat(vty->pending_cmds_buf, vty->buf,
154 					   vty->pending_cmds_buflen);
155 
156 	/* Schedule the commit operation. */
157 	THREAD_TIMER_OFF(vty->t_pending_commit);
158 	thread_add_timer_msec(master, nb_cli_pending_commit_cb, vty, 100,
159 			      &vty->t_pending_commit);
160 
161 	return CMD_SUCCESS;
162 }
163 
nb_cli_enqueue_change(struct vty * vty,const char * xpath,enum nb_operation operation,const char * value)164 void nb_cli_enqueue_change(struct vty *vty, const char *xpath,
165 			   enum nb_operation operation, const char *value)
166 {
167 	struct vty_cfg_change *change;
168 
169 	if (vty->num_cfg_changes == VTY_MAXCFGCHANGES) {
170 		/* Not expected to happen. */
171 		vty_out(vty,
172 			"%% Exceeded the maximum number of changes (%u) for a single command\n\n",
173 			VTY_MAXCFGCHANGES);
174 		return;
175 	}
176 
177 	change = &vty->cfg_changes[vty->num_cfg_changes++];
178 	strlcpy(change->xpath, xpath, sizeof(change->xpath));
179 	change->operation = operation;
180 	change->value = value;
181 }
182 
nb_cli_apply_changes(struct vty * vty,const char * xpath_base_fmt,...)183 int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...)
184 {
185 	char xpath_base[XPATH_MAXLEN] = {};
186 	bool error = false;
187 
188 	VTY_CHECK_XPATH;
189 
190 	/* Parse the base XPath format string. */
191 	if (xpath_base_fmt) {
192 		va_list ap;
193 
194 		va_start(ap, xpath_base_fmt);
195 		vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
196 		va_end(ap);
197 	}
198 
199 	/* Edit candidate configuration. */
200 	for (size_t i = 0; i < vty->num_cfg_changes; i++) {
201 		struct vty_cfg_change *change = &vty->cfg_changes[i];
202 		struct nb_node *nb_node;
203 		char xpath[XPATH_MAXLEN];
204 		struct yang_data *data;
205 		int ret;
206 
207 		/* Handle relative XPaths. */
208 		memset(xpath, 0, sizeof(xpath));
209 		if (vty->xpath_index > 0
210 		    && ((xpath_base_fmt && xpath_base[0] == '.')
211 			|| change->xpath[0] == '.'))
212 			strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath));
213 		if (xpath_base_fmt) {
214 			if (xpath_base[0] == '.')
215 				strlcat(xpath, xpath_base + 1, sizeof(xpath));
216 			else
217 				strlcat(xpath, xpath_base, sizeof(xpath));
218 		}
219 		if (change->xpath[0] == '.')
220 			strlcat(xpath, change->xpath + 1, sizeof(xpath));
221 		else
222 			strlcpy(xpath, change->xpath, sizeof(xpath));
223 
224 		/* Find the northbound node associated to the data path. */
225 		nb_node = nb_node_find(xpath);
226 		if (!nb_node) {
227 			flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
228 				  "%s: unknown data path: %s", __func__, xpath);
229 			error = true;
230 			continue;
231 		}
232 
233 		/* If the value is not set, get the default if it exists. */
234 		if (change->value == NULL)
235 			change->value = yang_snode_get_default(nb_node->snode);
236 		data = yang_data_new(xpath, change->value);
237 
238 		/*
239 		 * Ignore "not found" errors when editing the candidate
240 		 * configuration.
241 		 */
242 		ret = nb_candidate_edit(vty->candidate_config, nb_node,
243 					change->operation, xpath, NULL, data);
244 		yang_data_free(data);
245 		if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) {
246 			flog_warn(
247 				EC_LIB_NB_CANDIDATE_EDIT_ERROR,
248 				"%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
249 				__func__, nb_operation_name(change->operation),
250 				xpath);
251 			error = true;
252 			continue;
253 		}
254 	}
255 
256 	if (error) {
257 		char buf[BUFSIZ];
258 
259 		/*
260 		 * Failure to edit the candidate configuration should never
261 		 * happen in practice, unless there's a bug in the code. When
262 		 * that happens, log the error but otherwise ignore it.
263 		 */
264 		vty_out(vty, "%% Failed to edit configuration.\n\n");
265 		vty_out(vty, "%s",
266 			yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
267 	}
268 
269 	/*
270 	 * Do an implicit commit when using the classic CLI mode.
271 	 *
272 	 * NOTE: the implicit commit might be scheduled to run later when
273 	 * too many commands are being sent at the same time. This is a
274 	 * protection mechanism where multiple commands are grouped into the
275 	 * same configuration transaction, allowing them to be processed much
276 	 * faster.
277 	 */
278 	if (frr_get_cli_mode() == FRR_CLI_CLASSIC) {
279 		if (vty->t_pending_commit || nb_cli_backoff_start(vty))
280 			return nb_cli_schedule_command(vty);
281 		return nb_cli_classic_commit(vty);
282 	}
283 
284 	return CMD_SUCCESS;
285 }
286 
nb_cli_rpc(const char * xpath,struct list * input,struct list * output)287 int nb_cli_rpc(const char *xpath, struct list *input, struct list *output)
288 {
289 	struct nb_node *nb_node;
290 	int ret;
291 
292 	nb_node = nb_node_find(xpath);
293 	if (!nb_node) {
294 		flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
295 			  "%s: unknown data path: %s", __func__, xpath);
296 		return CMD_WARNING;
297 	}
298 
299 	ret = nb_callback_rpc(nb_node, xpath, input, output);
300 	switch (ret) {
301 	case NB_OK:
302 		return CMD_SUCCESS;
303 	default:
304 		return CMD_WARNING;
305 	}
306 }
307 
nb_cli_confirmed_commit_clean(struct vty * vty)308 void nb_cli_confirmed_commit_clean(struct vty *vty)
309 {
310 	THREAD_TIMER_OFF(vty->t_confirmed_commit_timeout);
311 	nb_config_free(vty->confirmed_commit_rollback);
312 	vty->confirmed_commit_rollback = NULL;
313 }
314 
nb_cli_confirmed_commit_rollback(struct vty * vty)315 int nb_cli_confirmed_commit_rollback(struct vty *vty)
316 {
317 	struct nb_context context = {};
318 	uint32_t transaction_id;
319 	char errmsg[BUFSIZ] = {0};
320 	int ret;
321 
322 	/* Perform the rollback. */
323 	context.client = NB_CLIENT_CLI;
324 	context.user = vty;
325 	ret = nb_candidate_commit(
326 		&context, vty->confirmed_commit_rollback, true,
327 		"Rollback to previous configuration - confirmed commit has timed out",
328 		&transaction_id, errmsg, sizeof(errmsg));
329 	if (ret == NB_OK) {
330 		vty_out(vty,
331 			"Rollback performed successfully (Transaction ID #%u).\n",
332 			transaction_id);
333 		/* Print warnings (if any). */
334 		if (strlen(errmsg) > 0)
335 			vty_out(vty, "%s\n", errmsg);
336 	} else {
337 		vty_out(vty,
338 			"Failed to rollback to previous configuration.\n\n");
339 		vty_show_nb_errors(vty, ret, errmsg);
340 	}
341 
342 	return ret;
343 }
344 
nb_cli_confirmed_commit_timeout(struct thread * thread)345 static int nb_cli_confirmed_commit_timeout(struct thread *thread)
346 {
347 	struct vty *vty = THREAD_ARG(thread);
348 
349 	/* XXX: broadcast this message to all logged-in users? */
350 	vty_out(vty,
351 		"\nConfirmed commit has timed out, rolling back to previous configuration.\n\n");
352 
353 	nb_cli_confirmed_commit_rollback(vty);
354 	nb_cli_confirmed_commit_clean(vty);
355 
356 	return 0;
357 }
358 
nb_cli_commit(struct vty * vty,bool force,unsigned int confirmed_timeout,char * comment)359 static int nb_cli_commit(struct vty *vty, bool force,
360 			 unsigned int confirmed_timeout, char *comment)
361 {
362 	struct nb_context context = {};
363 	uint32_t transaction_id = 0;
364 	char errmsg[BUFSIZ] = {0};
365 	int ret;
366 
367 	/* Check if there's a pending confirmed commit. */
368 	if (vty->t_confirmed_commit_timeout) {
369 		if (confirmed_timeout) {
370 			/* Reset timeout if "commit confirmed" is used again. */
371 			vty_out(vty,
372 				"%% Resetting confirmed-commit timeout to %u minute(s)\n\n",
373 				confirmed_timeout);
374 
375 			THREAD_TIMER_OFF(vty->t_confirmed_commit_timeout);
376 			thread_add_timer(master,
377 					 nb_cli_confirmed_commit_timeout, vty,
378 					 confirmed_timeout * 60,
379 					 &vty->t_confirmed_commit_timeout);
380 		} else {
381 			/* Accept commit confirmation. */
382 			vty_out(vty, "%% Commit complete.\n\n");
383 			nb_cli_confirmed_commit_clean(vty);
384 		}
385 		return CMD_SUCCESS;
386 	}
387 
388 	/* "force" parameter. */
389 	if (!force && nb_candidate_needs_update(vty->candidate_config)) {
390 		vty_out(vty,
391 			"%% Candidate configuration needs to be updated before commit.\n\n");
392 		vty_out(vty,
393 			"Use the \"update\" command or \"commit force\".\n");
394 		return CMD_WARNING;
395 	}
396 
397 	/* "confirm" parameter. */
398 	if (confirmed_timeout) {
399 		vty->confirmed_commit_rollback = nb_config_dup(running_config);
400 
401 		vty->t_confirmed_commit_timeout = NULL;
402 		thread_add_timer(master, nb_cli_confirmed_commit_timeout, vty,
403 				 confirmed_timeout * 60,
404 				 &vty->t_confirmed_commit_timeout);
405 	}
406 
407 	context.client = NB_CLIENT_CLI;
408 	context.user = vty;
409 	ret = nb_candidate_commit(&context, vty->candidate_config, true,
410 				  comment, &transaction_id, errmsg,
411 				  sizeof(errmsg));
412 
413 	/* Map northbound return code to CLI return code. */
414 	switch (ret) {
415 	case NB_OK:
416 		nb_config_replace(vty->candidate_config_base, running_config,
417 				  true);
418 		vty_out(vty,
419 			"%% Configuration committed successfully (Transaction ID #%u).\n\n",
420 			transaction_id);
421 		/* Print warnings (if any). */
422 		if (strlen(errmsg) > 0)
423 			vty_out(vty, "%s\n", errmsg);
424 		return CMD_SUCCESS;
425 	case NB_ERR_NO_CHANGES:
426 		vty_out(vty, "%% No configuration changes to commit.\n\n");
427 		return CMD_SUCCESS;
428 	default:
429 		vty_out(vty,
430 			"%% Failed to commit candidate configuration.\n\n");
431 		vty_show_nb_errors(vty, ret, errmsg);
432 		return CMD_WARNING;
433 	}
434 }
435 
nb_cli_candidate_load_file(struct vty * vty,enum nb_cfg_format format,struct yang_translator * translator,const char * path,bool replace)436 static int nb_cli_candidate_load_file(struct vty *vty,
437 				      enum nb_cfg_format format,
438 				      struct yang_translator *translator,
439 				      const char *path, bool replace)
440 {
441 	struct nb_config *loaded_config = NULL;
442 	struct lyd_node *dnode;
443 	struct ly_ctx *ly_ctx;
444 	int ly_format;
445 	char buf[BUFSIZ];
446 
447 	switch (format) {
448 	case NB_CFG_FMT_CMDS:
449 		loaded_config = nb_config_new(NULL);
450 		if (!vty_read_config(loaded_config, path, config_default)) {
451 			vty_out(vty, "%% Failed to load configuration.\n\n");
452 			vty_out(vty,
453 				"Please check the logs for more details.\n");
454 			nb_config_free(loaded_config);
455 			return CMD_WARNING;
456 		}
457 		break;
458 	case NB_CFG_FMT_JSON:
459 	case NB_CFG_FMT_XML:
460 		ly_format = (format == NB_CFG_FMT_JSON) ? LYD_JSON : LYD_XML;
461 
462 		ly_ctx = translator ? translator->ly_ctx : ly_native_ctx;
463 		dnode = lyd_parse_path(ly_ctx, path, ly_format, LYD_OPT_EDIT);
464 		if (!dnode) {
465 			flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed",
466 				  __func__);
467 			vty_out(vty, "%% Failed to load configuration:\n\n");
468 			vty_out(vty, "%s",
469 				yang_print_errors(ly_native_ctx, buf,
470 						  sizeof(buf)));
471 			return CMD_WARNING;
472 		}
473 		if (translator
474 		    && yang_translate_dnode(translator,
475 					    YANG_TRANSLATE_TO_NATIVE, &dnode)
476 			       != YANG_TRANSLATE_SUCCESS) {
477 			vty_out(vty, "%% Failed to translate configuration\n");
478 			yang_dnode_free(dnode);
479 			return CMD_WARNING;
480 		}
481 		loaded_config = nb_config_new(dnode);
482 		break;
483 	}
484 
485 	if (replace)
486 		nb_config_replace(vty->candidate_config, loaded_config, false);
487 	else if (nb_config_merge(vty->candidate_config, loaded_config, false)
488 		 != NB_OK) {
489 		vty_out(vty,
490 			"%% Failed to merge the loaded configuration:\n\n");
491 		vty_out(vty, "%s",
492 			yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
493 		return CMD_WARNING;
494 	}
495 
496 	return CMD_SUCCESS;
497 }
498 
nb_cli_candidate_load_transaction(struct vty * vty,uint32_t transaction_id,bool replace)499 static int nb_cli_candidate_load_transaction(struct vty *vty,
500 					     uint32_t transaction_id,
501 					     bool replace)
502 {
503 	struct nb_config *loaded_config;
504 	char buf[BUFSIZ];
505 
506 	loaded_config = nb_db_transaction_load(transaction_id);
507 	if (!loaded_config) {
508 		vty_out(vty, "%% Transaction %u does not exist.\n\n",
509 			transaction_id);
510 		return CMD_WARNING;
511 	}
512 
513 	if (replace)
514 		nb_config_replace(vty->candidate_config, loaded_config, false);
515 	else if (nb_config_merge(vty->candidate_config, loaded_config, false)
516 		 != NB_OK) {
517 		vty_out(vty,
518 			"%% Failed to merge the loaded configuration:\n\n");
519 		vty_out(vty, "%s",
520 			yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
521 		return CMD_WARNING;
522 	}
523 
524 	return CMD_SUCCESS;
525 }
526 
527 /*
528  * ly_iter_next_is_up: detects when iterating up on the yang model.
529  *
530  * This function detects whether next node in the iteration is upwards,
531  * then return the node otherwise return NULL.
532  */
ly_iter_next_up(const struct lyd_node * elem)533 static struct lyd_node *ly_iter_next_up(const struct lyd_node *elem)
534 {
535 	/* Are we going downwards? Is this still not a leaf? */
536 	if (!(elem->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)))
537 		return NULL;
538 
539 	/* Are there still leaves in this branch? */
540 	if (elem->next != NULL)
541 		return NULL;
542 
543 	return elem->parent;
544 }
545 
546 /* Prepare the configuration for display. */
nb_cli_show_config_prepare(struct nb_config * config,bool with_defaults)547 void nb_cli_show_config_prepare(struct nb_config *config, bool with_defaults)
548 {
549 	/* Nothing to do for daemons that don't implement any YANG module. */
550 	if (config->dnode == NULL)
551 		return;
552 
553 	lyd_schema_sort(config->dnode, 1);
554 
555 	/*
556 	 * Call lyd_validate() only to create default child nodes, ignoring
557 	 * any possible validation error. This doesn't need to be done when
558 	 * displaying the running configuration since it's always fully
559 	 * validated.
560 	 */
561 	if (config != running_config)
562 		(void)lyd_validate(&config->dnode,
563 				   LYD_OPT_CONFIG | LYD_OPT_WHENAUTODEL,
564 				   ly_native_ctx);
565 }
566 
nb_cli_show_dnode_cmds(struct vty * vty,struct lyd_node * root,bool with_defaults)567 void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *root,
568 			    bool with_defaults)
569 {
570 	struct lyd_node *next, *child, *parent;
571 
572 	LY_TREE_DFS_BEGIN (root, next, child) {
573 		struct nb_node *nb_node;
574 
575 		nb_node = child->schema->priv;
576 		if (!nb_node || !nb_node->cbs.cli_show)
577 			goto next;
578 
579 		/* Skip default values. */
580 		if (!with_defaults && yang_dnode_is_default_recursive(child))
581 			goto next;
582 
583 		(*nb_node->cbs.cli_show)(vty, child, with_defaults);
584 	next:
585 		/*
586 		 * When transiting upwards in the yang model we should
587 		 * give the previous container/list node a chance to
588 		 * print its close vty output (e.g. "!" or "end-family"
589 		 * etc...).
590 		 */
591 		parent = ly_iter_next_up(child);
592 		if (parent != NULL) {
593 			nb_node = parent->schema->priv;
594 			if (nb_node && nb_node->cbs.cli_show_end)
595 				(*nb_node->cbs.cli_show_end)(vty, parent);
596 		}
597 
598 		/*
599 		 * There is a possible path in this macro that ends up
600 		 * dereferencing child->parent->parent. We just null checked
601 		 * child->parent by checking (ly_iter_next_up(child) != NULL)
602 		 * above.
603 		 *
604 		 * I am not sure whether it is possible for the other
605 		 * conditions within this macro guarding the problem
606 		 * dereference to be satisfied when child->parent == NULL.
607 		 */
608 #ifndef __clang_analyzer__
609 		LY_TREE_DFS_END(root, next, child);
610 #endif
611 	}
612 }
613 
nb_cli_show_config_cmds(struct vty * vty,struct nb_config * config,bool with_defaults)614 static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config,
615 				    bool with_defaults)
616 {
617 	struct lyd_node *root;
618 
619 	vty_out(vty, "Configuration:\n");
620 	vty_out(vty, "!\n");
621 	vty_out(vty, "frr version %s\n", FRR_VER_SHORT);
622 	vty_out(vty, "frr defaults %s\n", frr_defaults_profile());
623 
624 	LY_TREE_FOR (config->dnode, root)
625 		nb_cli_show_dnode_cmds(vty, root, with_defaults);
626 
627 	vty_out(vty, "!\n");
628 	vty_out(vty, "end\n");
629 }
630 
nb_cli_show_config_libyang(struct vty * vty,LYD_FORMAT format,struct nb_config * config,struct yang_translator * translator,bool with_defaults)631 static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
632 				      struct nb_config *config,
633 				      struct yang_translator *translator,
634 				      bool with_defaults)
635 {
636 	struct lyd_node *dnode;
637 	char *strp;
638 	int options = 0;
639 
640 	dnode = yang_dnode_dup(config->dnode);
641 	if (translator
642 	    && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE,
643 				    &dnode)
644 		       != YANG_TRANSLATE_SUCCESS) {
645 		vty_out(vty, "%% Failed to translate configuration\n");
646 		yang_dnode_free(dnode);
647 		return CMD_WARNING;
648 	}
649 
650 	SET_FLAG(options, LYP_FORMAT | LYP_WITHSIBLINGS);
651 	if (with_defaults)
652 		SET_FLAG(options, LYP_WD_ALL);
653 	else
654 		SET_FLAG(options, LYP_WD_TRIM);
655 
656 	if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) {
657 		vty_out(vty, "%s", strp);
658 		free(strp);
659 	}
660 
661 	yang_dnode_free(dnode);
662 
663 	return CMD_SUCCESS;
664 }
665 
nb_cli_show_config(struct vty * vty,struct nb_config * config,enum nb_cfg_format format,struct yang_translator * translator,bool with_defaults)666 static int nb_cli_show_config(struct vty *vty, struct nb_config *config,
667 			      enum nb_cfg_format format,
668 			      struct yang_translator *translator,
669 			      bool with_defaults)
670 {
671 	nb_cli_show_config_prepare(config, with_defaults);
672 
673 	switch (format) {
674 	case NB_CFG_FMT_CMDS:
675 		nb_cli_show_config_cmds(vty, config, with_defaults);
676 		break;
677 	case NB_CFG_FMT_JSON:
678 		return nb_cli_show_config_libyang(vty, LYD_JSON, config,
679 						  translator, with_defaults);
680 	case NB_CFG_FMT_XML:
681 		return nb_cli_show_config_libyang(vty, LYD_XML, config,
682 						  translator, with_defaults);
683 	}
684 
685 	return CMD_SUCCESS;
686 }
687 
nb_write_config(struct nb_config * config,enum nb_cfg_format format,struct yang_translator * translator,char * path,size_t pathlen)688 static int nb_write_config(struct nb_config *config, enum nb_cfg_format format,
689 			   struct yang_translator *translator, char *path,
690 			   size_t pathlen)
691 {
692 	int fd;
693 	struct vty *file_vty;
694 	int ret = 0;
695 
696 	snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX");
697 	fd = mkstemp(path);
698 	if (fd < 0) {
699 		flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s",
700 			  __func__, safe_strerror(errno));
701 		return -1;
702 	}
703 
704 	/* Make vty for configuration file. */
705 	file_vty = vty_new();
706 	file_vty->wfd = fd;
707 	file_vty->type = VTY_FILE;
708 	if (config)
709 		ret = nb_cli_show_config(file_vty, config, format, translator,
710 					 false);
711 	vty_close(file_vty);
712 
713 	return ret;
714 }
715 
nb_cli_show_config_compare(struct vty * vty,struct nb_config * config1,struct nb_config * config2,enum nb_cfg_format format,struct yang_translator * translator)716 static int nb_cli_show_config_compare(struct vty *vty,
717 				      struct nb_config *config1,
718 				      struct nb_config *config2,
719 				      enum nb_cfg_format format,
720 				      struct yang_translator *translator)
721 {
722 	char config1_path[256];
723 	char config2_path[256];
724 	char command[BUFSIZ];
725 	FILE *fp;
726 	char line[1024];
727 	int lineno = 0;
728 
729 	if (nb_write_config(config1, format, translator, config1_path,
730 			    sizeof(config1_path))
731 	    != 0) {
732 		vty_out(vty, "%% Failed to process configurations.\n\n");
733 		return CMD_WARNING;
734 	}
735 	if (nb_write_config(config2, format, translator, config2_path,
736 			    sizeof(config2_path))
737 	    != 0) {
738 		vty_out(vty, "%% Failed to process configurations.\n\n");
739 		unlink(config1_path);
740 		return CMD_WARNING;
741 	}
742 
743 	snprintf(command, sizeof(command), "diff -u %s %s", config1_path,
744 		 config2_path);
745 	fp = popen(command, "r");
746 	if (!fp) {
747 		vty_out(vty, "%% Failed to generate configuration diff.\n\n");
748 		unlink(config1_path);
749 		unlink(config2_path);
750 		return CMD_WARNING;
751 	}
752 	/* Print diff line by line. */
753 	while (fgets(line, sizeof(line), fp) != NULL) {
754 		if (lineno++ < 2)
755 			continue;
756 		vty_out(vty, "%s", line);
757 	}
758 	pclose(fp);
759 
760 	unlink(config1_path);
761 	unlink(config2_path);
762 
763 	return CMD_SUCCESS;
764 }
765 
766 /* Configure exclusively from this terminal. */
767 DEFUN (config_exclusive,
768        config_exclusive_cmd,
769        "configure exclusive",
770        "Configuration from vty interface\n"
771        "Configure exclusively from this terminal\n")
772 {
773 	return vty_config_enter(vty, true, true);
774 }
775 
776 /* Configure using a private candidate configuration. */
777 DEFUN (config_private,
778        config_private_cmd,
779        "configure private",
780        "Configuration from vty interface\n"
781        "Configure using a private candidate configuration\n")
782 {
783 	return vty_config_enter(vty, true, false);
784 }
785 
786 DEFPY (config_commit,
787        config_commit_cmd,
788        "commit [{force$force|confirmed (1-60)}]",
789        "Commit changes into the running configuration\n"
790        "Force commit even if the candidate is outdated\n"
791        "Rollback this commit unless there is a confirming commit\n"
792        "Timeout in minutes for the commit to be confirmed\n")
793 {
794 	return nb_cli_commit(vty, !!force, confirmed, NULL);
795 }
796 
797 DEFPY (config_commit_comment,
798        config_commit_comment_cmd,
799        "commit [{force$force|confirmed (1-60)}] comment LINE...",
800        "Commit changes into the running configuration\n"
801        "Force commit even if the candidate is outdated\n"
802        "Rollback this commit unless there is a confirming commit\n"
803        "Timeout in minutes for the commit to be confirmed\n"
804        "Assign a comment to this commit\n"
805        "Comment for this commit (Max 80 characters)\n")
806 {
807 	char *comment;
808 	int idx = 0;
809 	int ret;
810 
811 	argv_find(argv, argc, "LINE", &idx);
812 	comment = argv_concat(argv, argc, idx);
813 	ret = nb_cli_commit(vty, !!force, confirmed, comment);
814 	XFREE(MTYPE_TMP, comment);
815 
816 	return ret;
817 }
818 
819 DEFPY (config_commit_check,
820        config_commit_check_cmd,
821        "commit check",
822        "Commit changes into the running configuration\n"
823        "Check if the configuration changes are valid\n")
824 {
825 	struct nb_context context = {};
826 	char errmsg[BUFSIZ] = {0};
827 	int ret;
828 
829 	context.client = NB_CLIENT_CLI;
830 	context.user = vty;
831 	ret = nb_candidate_validate(&context, vty->candidate_config, errmsg,
832 				    sizeof(errmsg));
833 	if (ret != NB_OK) {
834 		vty_out(vty,
835 			"%% Failed to validate candidate configuration.\n\n");
836 		vty_show_nb_errors(vty, ret, errmsg);
837 		return CMD_WARNING;
838 	}
839 
840 	vty_out(vty, "%% Candidate configuration validated successfully.\n\n");
841 
842 	return CMD_SUCCESS;
843 }
844 
845 DEFPY (config_update,
846        config_update_cmd,
847        "update",
848        "Update candidate configuration\n")
849 {
850 	if (!nb_candidate_needs_update(vty->candidate_config)) {
851 		vty_out(vty, "%% Update is not necessary.\n\n");
852 		return CMD_SUCCESS;
853 	}
854 
855 	if (nb_candidate_update(vty->candidate_config) != NB_OK) {
856 		vty_out(vty,
857 			"%% Failed to update the candidate configuration.\n\n");
858 		vty_out(vty, "Please check the logs for more details.\n");
859 		return CMD_WARNING;
860 	}
861 
862 	nb_config_replace(vty->candidate_config_base, running_config, true);
863 
864 	vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
865 
866 	return CMD_SUCCESS;
867 }
868 
869 DEFPY (config_discard,
870        config_discard_cmd,
871        "discard",
872        "Discard changes in the candidate configuration\n")
873 {
874 	nb_config_replace(vty->candidate_config, vty->candidate_config_base,
875 			  true);
876 
877 	return CMD_SUCCESS;
878 }
879 
880 DEFPY (config_load,
881        config_load_cmd,
882        "configuration load\
883           <\
884 	    file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
885 	    |transaction (1-4294967295)$tid\
886 	  >\
887 	  [replace$replace]",
888        "Configuration related settings\n"
889        "Load configuration into candidate\n"
890        "Load configuration file into candidate\n"
891        "Load configuration file in JSON format\n"
892        "Load configuration file in XML format\n"
893        "Translate configuration file\n"
894        "YANG module translator\n"
895        "Configuration file name (full path)\n"
896        "Load configuration from transaction into candidate\n"
897        "Transaction ID\n"
898        "Replace instead of merge\n")
899 {
900 	if (filename) {
901 		enum nb_cfg_format format;
902 		struct yang_translator *translator = NULL;
903 
904 		if (json)
905 			format = NB_CFG_FMT_JSON;
906 		else if (xml)
907 			format = NB_CFG_FMT_XML;
908 		else
909 			format = NB_CFG_FMT_CMDS;
910 
911 		if (translator_family) {
912 			translator = yang_translator_find(translator_family);
913 			if (!translator) {
914 				vty_out(vty,
915 					"%% Module translator \"%s\" not found\n",
916 					translator_family);
917 				return CMD_WARNING;
918 			}
919 		}
920 
921 		return nb_cli_candidate_load_file(vty, format, translator,
922 						  filename, !!replace);
923 	}
924 
925 	return nb_cli_candidate_load_transaction(vty, tid, !!replace);
926 }
927 
928 DEFPY (show_config_running,
929        show_config_running_cmd,
930        "show configuration running\
931           [<json$json|xml$xml> [translate WORD$translator_family]]\
932 	  [with-defaults$with_defaults]",
933        SHOW_STR
934        "Configuration information\n"
935        "Running configuration\n"
936        "Change output format to JSON\n"
937        "Change output format to XML\n"
938        "Translate output\n"
939        "YANG module translator\n"
940        "Show default values\n")
941 
942 {
943 	enum nb_cfg_format format;
944 	struct yang_translator *translator = NULL;
945 
946 	if (json)
947 		format = NB_CFG_FMT_JSON;
948 	else if (xml)
949 		format = NB_CFG_FMT_XML;
950 	else
951 		format = NB_CFG_FMT_CMDS;
952 
953 	if (translator_family) {
954 		translator = yang_translator_find(translator_family);
955 		if (!translator) {
956 			vty_out(vty, "%% Module translator \"%s\" not found\n",
957 				translator_family);
958 			return CMD_WARNING;
959 		}
960 	}
961 
962 	nb_cli_show_config(vty, running_config, format, translator,
963 			   !!with_defaults);
964 
965 	return CMD_SUCCESS;
966 }
967 
968 DEFPY (show_config_candidate,
969        show_config_candidate_cmd,
970        "show configuration candidate\
971           [<json$json|xml$xml> [translate WORD$translator_family]]\
972           [<\
973 	    with-defaults$with_defaults\
974 	    |changes$changes\
975 	   >]",
976        SHOW_STR
977        "Configuration information\n"
978        "Candidate configuration\n"
979        "Change output format to JSON\n"
980        "Change output format to XML\n"
981        "Translate output\n"
982        "YANG module translator\n"
983        "Show default values\n"
984        "Show changes applied in the candidate configuration\n")
985 
986 {
987 	enum nb_cfg_format format;
988 	struct yang_translator *translator = NULL;
989 
990 	if (json)
991 		format = NB_CFG_FMT_JSON;
992 	else if (xml)
993 		format = NB_CFG_FMT_XML;
994 	else
995 		format = NB_CFG_FMT_CMDS;
996 
997 	if (translator_family) {
998 		translator = yang_translator_find(translator_family);
999 		if (!translator) {
1000 			vty_out(vty, "%% Module translator \"%s\" not found\n",
1001 				translator_family);
1002 			return CMD_WARNING;
1003 		}
1004 	}
1005 
1006 	if (changes)
1007 		return nb_cli_show_config_compare(
1008 			vty, vty->candidate_config_base, vty->candidate_config,
1009 			format, translator);
1010 
1011 	nb_cli_show_config(vty, vty->candidate_config, format, translator,
1012 			   !!with_defaults);
1013 
1014 	return CMD_SUCCESS;
1015 }
1016 
1017 DEFPY (show_config_candidate_section,
1018        show_config_candidate_section_cmd,
1019        "show",
1020        SHOW_STR)
1021 {
1022 	struct lyd_node *dnode;
1023 
1024 	/* Top-level configuration node, display everything. */
1025 	if (vty->xpath_index == 0)
1026 		return nb_cli_show_config(vty, vty->candidate_config,
1027 					  NB_CFG_FMT_CMDS, NULL, false);
1028 
1029 	/* Display only the current section of the candidate configuration. */
1030 	dnode = yang_dnode_get(vty->candidate_config->dnode, VTY_CURR_XPATH);
1031 	if (!dnode)
1032 		/* Shouldn't happen. */
1033 		return CMD_WARNING;
1034 
1035 	nb_cli_show_dnode_cmds(vty, dnode, 0);
1036 	vty_out(vty, "!\n");
1037 
1038 	return CMD_SUCCESS;
1039 }
1040 
1041 DEFPY (show_config_compare,
1042        show_config_compare_cmd,
1043        "show configuration compare\
1044           <\
1045 	    candidate$c1_candidate\
1046 	    |running$c1_running\
1047 	    |transaction (1-4294967295)$c1_tid\
1048 	  >\
1049           <\
1050 	    candidate$c2_candidate\
1051 	    |running$c2_running\
1052 	    |transaction (1-4294967295)$c2_tid\
1053 	  >\
1054 	  [<json$json|xml$xml> [translate WORD$translator_family]]",
1055        SHOW_STR
1056        "Configuration information\n"
1057        "Compare two different configurations\n"
1058        "Candidate configuration\n"
1059        "Running configuration\n"
1060        "Configuration transaction\n"
1061        "Transaction ID\n"
1062        "Candidate configuration\n"
1063        "Running configuration\n"
1064        "Configuration transaction\n"
1065        "Transaction ID\n"
1066        "Change output format to JSON\n"
1067        "Change output format to XML\n"
1068        "Translate output\n"
1069        "YANG module translator\n")
1070 {
1071 	enum nb_cfg_format format;
1072 	struct yang_translator *translator = NULL;
1073 	struct nb_config *config1, *config_transaction1 = NULL;
1074 	struct nb_config *config2, *config_transaction2 = NULL;
1075 	int ret = CMD_WARNING;
1076 
1077 	if (c1_candidate)
1078 		config1 = vty->candidate_config;
1079 	else if (c1_running)
1080 		config1 = running_config;
1081 	else {
1082 		config_transaction1 = nb_db_transaction_load(c1_tid);
1083 		if (!config_transaction1) {
1084 			vty_out(vty, "%% Transaction %u does not exist\n\n",
1085 				(unsigned int)c1_tid);
1086 			goto exit;
1087 		}
1088 		config1 = config_transaction1;
1089 	}
1090 
1091 	if (c2_candidate)
1092 		config2 = vty->candidate_config;
1093 	else if (c2_running)
1094 		config2 = running_config;
1095 	else {
1096 		config_transaction2 = nb_db_transaction_load(c2_tid);
1097 		if (!config_transaction2) {
1098 			vty_out(vty, "%% Transaction %u does not exist\n\n",
1099 				(unsigned int)c2_tid);
1100 			goto exit;
1101 		}
1102 		config2 = config_transaction2;
1103 	}
1104 
1105 	if (json)
1106 		format = NB_CFG_FMT_JSON;
1107 	else if (xml)
1108 		format = NB_CFG_FMT_XML;
1109 	else
1110 		format = NB_CFG_FMT_CMDS;
1111 
1112 	if (translator_family) {
1113 		translator = yang_translator_find(translator_family);
1114 		if (!translator) {
1115 			vty_out(vty, "%% Module translator \"%s\" not found\n",
1116 				translator_family);
1117 			goto exit;
1118 		}
1119 	}
1120 
1121 	ret = nb_cli_show_config_compare(vty, config1, config2, format,
1122 					 translator);
1123 exit:
1124 	if (config_transaction1)
1125 		nb_config_free(config_transaction1);
1126 	if (config_transaction2)
1127 		nb_config_free(config_transaction2);
1128 
1129 	return ret;
1130 }
1131 
1132 /*
1133  * Stripped down version of the "show configuration compare" command.
1134  * The "candidate" option is not present so the command can be installed in
1135  * the enable node.
1136  */
1137 ALIAS (show_config_compare,
1138        show_config_compare_without_candidate_cmd,
1139        "show configuration compare\
1140           <\
1141 	    running$c1_running\
1142 	    |transaction (1-4294967295)$c1_tid\
1143 	  >\
1144           <\
1145 	    running$c2_running\
1146 	    |transaction (1-4294967295)$c2_tid\
1147 	  >\
1148 	 [<json$json|xml$xml> [translate WORD$translator_family]]",
1149        SHOW_STR
1150        "Configuration information\n"
1151        "Compare two different configurations\n"
1152        "Running configuration\n"
1153        "Configuration transaction\n"
1154        "Transaction ID\n"
1155        "Running configuration\n"
1156        "Configuration transaction\n"
1157        "Transaction ID\n"
1158        "Change output format to JSON\n"
1159        "Change output format to XML\n"
1160        "Translate output\n"
1161        "YANG module translator\n")
1162 
1163 DEFPY (clear_config_transactions,
1164        clear_config_transactions_cmd,
1165        "clear configuration transactions oldest (1-100)$n",
1166        CLEAR_STR
1167        "Configuration activity\n"
1168        "Delete transactions from the transactions log\n"
1169        "Delete oldest <n> transactions\n"
1170        "Number of transactions to delete\n")
1171 {
1172 #ifdef HAVE_CONFIG_ROLLBACKS
1173 	if (nb_db_clear_transactions(n) != NB_OK) {
1174 		vty_out(vty, "%% Failed to delete transactions.\n\n");
1175 		return CMD_WARNING;
1176 	}
1177 #else
1178 	vty_out(vty,
1179 		"%% FRR was compiled without --enable-config-rollbacks.\n\n");
1180 #endif /* HAVE_CONFIG_ROLLBACKS */
1181 
1182 	return CMD_SUCCESS;
1183 }
1184 
1185 DEFPY (config_database_max_transactions,
1186        config_database_max_transactions_cmd,
1187        "configuration database max-transactions (1-100)$max",
1188        "Configuration related settings\n"
1189        "Configuration database\n"
1190        "Set the maximum number of transactions to store\n"
1191        "Number of transactions\n")
1192 {
1193 #ifdef HAVE_CONFIG_ROLLBACKS
1194 	if (nb_db_set_max_transactions(max) != NB_OK) {
1195 		vty_out(vty,
1196 			"%% Failed to update the maximum number of transactions.\n\n");
1197 		return CMD_WARNING;
1198 	}
1199 	vty_out(vty,
1200 		"%% Maximum number of transactions updated successfully.\n\n");
1201 #else
1202 	vty_out(vty,
1203 		"%% FRR was compiled without --enable-config-rollbacks.\n\n");
1204 #endif /* HAVE_CONFIG_ROLLBACKS */
1205 
1206 	return CMD_SUCCESS;
1207 }
1208 
1209 DEFPY (yang_module_translator_load,
1210        yang_module_translator_load_cmd,
1211        "yang module-translator load FILENAME$filename",
1212        "YANG related settings\n"
1213        "YANG module translator\n"
1214        "Load YANG module translator\n"
1215        "File name (full path)\n")
1216 {
1217 	struct yang_translator *translator;
1218 
1219 	translator = yang_translator_load(filename);
1220 	if (!translator) {
1221 		vty_out(vty, "%% Failed to load \"%s\"\n\n", filename);
1222 		vty_out(vty, "Please check the logs for more details.\n");
1223 		return CMD_WARNING;
1224 	}
1225 
1226 	vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n",
1227 		translator->family);
1228 
1229 	return CMD_SUCCESS;
1230 }
1231 
1232 DEFPY (yang_module_translator_unload_family,
1233        yang_module_translator_unload_cmd,
1234        "yang module-translator unload WORD$translator_family",
1235        "YANG related settings\n"
1236        "YANG module translator\n"
1237        "Unload YANG module translator\n"
1238        "Name of the module translator\n")
1239 {
1240 	struct yang_translator *translator;
1241 
1242 	translator = yang_translator_find(translator_family);
1243 	if (!translator) {
1244 		vty_out(vty, "%% Module translator \"%s\" not found\n",
1245 			translator_family);
1246 		return CMD_WARNING;
1247 	}
1248 
1249 	yang_translator_unload(translator);
1250 
1251 	return CMD_SUCCESS;
1252 }
1253 
1254 #ifdef HAVE_CONFIG_ROLLBACKS
nb_cli_show_transactions_cb(void * arg,int transaction_id,const char * client_name,const char * date,const char * comment)1255 static void nb_cli_show_transactions_cb(void *arg, int transaction_id,
1256 					const char *client_name,
1257 					const char *date, const char *comment)
1258 {
1259 	struct ttable *tt = arg;
1260 
1261 	ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date,
1262 		       comment);
1263 }
1264 
nb_cli_show_transactions(struct vty * vty)1265 static int nb_cli_show_transactions(struct vty *vty)
1266 {
1267 	struct ttable *tt;
1268 
1269 	/* Prepare table. */
1270 	tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1271 	ttable_add_row(tt, "Transaction ID|Client|Date|Comment");
1272 	tt->style.cell.rpad = 2;
1273 	tt->style.corner = '+';
1274 	ttable_restyle(tt);
1275 	ttable_rowseps(tt, 0, BOTTOM, true, '-');
1276 
1277 	/* Fetch transactions from the northbound database. */
1278 	if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt)
1279 	    != NB_OK) {
1280 		vty_out(vty,
1281 			"%% Failed to fetch configuration transactions.\n");
1282 		return CMD_WARNING;
1283 	}
1284 
1285 	/* Dump the generated table. */
1286 	if (tt->nrows > 1) {
1287 		char *table;
1288 
1289 		table = ttable_dump(tt, "\n");
1290 		vty_out(vty, "%s\n", table);
1291 		XFREE(MTYPE_TMP, table);
1292 	} else
1293 		vty_out(vty, "No configuration transactions to display.\n\n");
1294 
1295 	ttable_del(tt);
1296 
1297 	return CMD_SUCCESS;
1298 }
1299 #endif /* HAVE_CONFIG_ROLLBACKS */
1300 
1301 DEFPY (show_config_transaction,
1302        show_config_transaction_cmd,
1303        "show configuration transaction\
1304           [\
1305 	    (1-4294967295)$transaction_id\
1306 	    [<json$json|xml$xml> [translate WORD$translator_family]]\
1307             [<\
1308 	      with-defaults$with_defaults\
1309 	      |changes$changes\
1310 	     >]\
1311 	  ]",
1312        SHOW_STR
1313        "Configuration information\n"
1314        "Configuration transaction\n"
1315        "Transaction ID\n"
1316        "Change output format to JSON\n"
1317        "Change output format to XML\n"
1318        "Translate output\n"
1319        "YANG module translator\n"
1320        "Show default values\n"
1321        "Show changes compared to the previous transaction\n")
1322 {
1323 #ifdef HAVE_CONFIG_ROLLBACKS
1324 	if (transaction_id) {
1325 		struct nb_config *config;
1326 		enum nb_cfg_format format;
1327 		struct yang_translator *translator = NULL;
1328 
1329 		if (json)
1330 			format = NB_CFG_FMT_JSON;
1331 		else if (xml)
1332 			format = NB_CFG_FMT_XML;
1333 		else
1334 			format = NB_CFG_FMT_CMDS;
1335 
1336 		if (translator_family) {
1337 			translator = yang_translator_find(translator_family);
1338 			if (!translator) {
1339 				vty_out(vty,
1340 					"%% Module translator \"%s\" not found\n",
1341 					translator_family);
1342 				return CMD_WARNING;
1343 			}
1344 		}
1345 
1346 		config = nb_db_transaction_load(transaction_id);
1347 		if (!config) {
1348 			vty_out(vty, "%% Transaction %u does not exist.\n\n",
1349 				(unsigned int)transaction_id);
1350 			return CMD_WARNING;
1351 		}
1352 
1353 		if (changes) {
1354 			struct nb_config *prev_config;
1355 			int ret;
1356 
1357 			/* NOTE: this can be NULL. */
1358 			prev_config =
1359 				nb_db_transaction_load(transaction_id - 1);
1360 
1361 			ret = nb_cli_show_config_compare(
1362 				vty, prev_config, config, format, translator);
1363 			if (prev_config)
1364 				nb_config_free(prev_config);
1365 			nb_config_free(config);
1366 
1367 			return ret;
1368 		}
1369 
1370 		nb_cli_show_config(vty, config, format, translator,
1371 				   !!with_defaults);
1372 		nb_config_free(config);
1373 
1374 		return CMD_SUCCESS;
1375 	}
1376 
1377 	return nb_cli_show_transactions(vty);
1378 #else
1379 	vty_out(vty,
1380 		"%% FRR was compiled without --enable-config-rollbacks.\n\n");
1381 	return CMD_WARNING;
1382 #endif /* HAVE_CONFIG_ROLLBACKS */
1383 }
1384 
nb_cli_oper_data_cb(const struct lys_node * snode,struct yang_translator * translator,struct yang_data * data,void * arg)1385 static int nb_cli_oper_data_cb(const struct lys_node *snode,
1386 			       struct yang_translator *translator,
1387 			       struct yang_data *data, void *arg)
1388 {
1389 	struct lyd_node *dnode = arg;
1390 	struct ly_ctx *ly_ctx;
1391 
1392 	if (translator) {
1393 		int ret;
1394 
1395 		ret = yang_translate_xpath(translator,
1396 					   YANG_TRANSLATE_FROM_NATIVE,
1397 					   data->xpath, sizeof(data->xpath));
1398 		switch (ret) {
1399 		case YANG_TRANSLATE_SUCCESS:
1400 			break;
1401 		case YANG_TRANSLATE_NOTFOUND:
1402 			goto exit;
1403 		case YANG_TRANSLATE_FAILURE:
1404 			goto error;
1405 		}
1406 
1407 		ly_ctx = translator->ly_ctx;
1408 	} else
1409 		ly_ctx = ly_native_ctx;
1410 
1411 	ly_errno = 0;
1412 	dnode = lyd_new_path(dnode, ly_ctx, data->xpath, (void *)data->value, 0,
1413 			     LYD_PATH_OPT_UPDATE);
1414 	if (!dnode && ly_errno) {
1415 		flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
1416 			  __func__);
1417 		goto error;
1418 	}
1419 
1420 exit:
1421 	yang_data_free(data);
1422 	return NB_OK;
1423 
1424 error:
1425 	yang_data_free(data);
1426 	return NB_ERR;
1427 }
1428 
1429 DEFPY (show_yang_operational_data,
1430        show_yang_operational_data_cmd,
1431        "show yang operational-data XPATH$xpath\
1432          [{\
1433 	   format <json$json|xml$xml>\
1434 	   |translate WORD$translator_family\
1435 	 }]",
1436        SHOW_STR
1437        "YANG information\n"
1438        "Show YANG operational data\n"
1439        "XPath expression specifying the YANG data path\n"
1440        "Set the output format\n"
1441        "JavaScript Object Notation\n"
1442        "Extensible Markup Language\n"
1443        "Translate operational data\n"
1444        "YANG module translator\n")
1445 {
1446 	LYD_FORMAT format;
1447 	struct yang_translator *translator = NULL;
1448 	struct ly_ctx *ly_ctx;
1449 	struct lyd_node *dnode;
1450 	char *strp;
1451 
1452 	if (xml)
1453 		format = LYD_XML;
1454 	else
1455 		format = LYD_JSON;
1456 
1457 	if (translator_family) {
1458 		translator = yang_translator_find(translator_family);
1459 		if (!translator) {
1460 			vty_out(vty, "%% Module translator \"%s\" not found\n",
1461 				translator_family);
1462 			return CMD_WARNING;
1463 		}
1464 
1465 		ly_ctx = translator->ly_ctx;
1466 	} else
1467 		ly_ctx = ly_native_ctx;
1468 
1469 	/* Obtain data. */
1470 	dnode = yang_dnode_new(ly_ctx, false);
1471 	if (nb_oper_data_iterate(xpath, translator, 0, nb_cli_oper_data_cb,
1472 				 dnode)
1473 	    != NB_OK) {
1474 		vty_out(vty, "%% Failed to fetch operational data.\n");
1475 		yang_dnode_free(dnode);
1476 		return CMD_WARNING;
1477 	}
1478 	lyd_validate(&dnode, LYD_OPT_GET, ly_ctx);
1479 
1480 	/* Display the data. */
1481 	if (lyd_print_mem(&strp, dnode, format,
1482 			  LYP_FORMAT | LYP_WITHSIBLINGS | LYP_WD_ALL)
1483 		    != 0
1484 	    || !strp) {
1485 		vty_out(vty, "%% Failed to display operational data.\n");
1486 		yang_dnode_free(dnode);
1487 		return CMD_WARNING;
1488 	}
1489 	vty_out(vty, "%s", strp);
1490 	free(strp);
1491 	yang_dnode_free(dnode);
1492 
1493 	return CMD_SUCCESS;
1494 }
1495 
1496 DEFPY (show_yang_module,
1497        show_yang_module_cmd,
1498        "show yang module [module-translator WORD$translator_family]",
1499        SHOW_STR
1500        "YANG information\n"
1501        "Show loaded modules\n"
1502        "YANG module translator\n"
1503        "YANG module translator\n")
1504 {
1505 	struct ly_ctx *ly_ctx;
1506 	struct yang_translator *translator = NULL;
1507 	const struct lys_module *module;
1508 	struct ttable *tt;
1509 	uint32_t idx = 0;
1510 
1511 	if (translator_family) {
1512 		translator = yang_translator_find(translator_family);
1513 		if (!translator) {
1514 			vty_out(vty, "%% Module translator \"%s\" not found\n",
1515 				translator_family);
1516 			return CMD_WARNING;
1517 		}
1518 		ly_ctx = translator->ly_ctx;
1519 	} else
1520 		ly_ctx = ly_native_ctx;
1521 
1522 	/* Prepare table. */
1523 	tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1524 	ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace");
1525 	tt->style.cell.rpad = 2;
1526 	tt->style.corner = '+';
1527 	ttable_restyle(tt);
1528 	ttable_rowseps(tt, 0, BOTTOM, true, '-');
1529 
1530 	while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) {
1531 		char flags[8];
1532 
1533 		snprintf(flags, sizeof(flags), "%c%c",
1534 			 module->implemented ? 'I' : ' ',
1535 			 (module->deviated == 1) ? 'D' : ' ');
1536 
1537 		ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name,
1538 			       (module->version == 2) ? "1.1" : "1.0",
1539 			       (module->rev_size > 0) ? module->rev[0].date
1540 						      : "-",
1541 			       flags, module->ns);
1542 	}
1543 
1544 	/* Dump the generated table. */
1545 	if (tt->nrows > 1) {
1546 		char *table;
1547 
1548 		vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n");
1549 
1550 		table = ttable_dump(tt, "\n");
1551 		vty_out(vty, "%s\n", table);
1552 		XFREE(MTYPE_TMP, table);
1553 	} else
1554 		vty_out(vty, "No YANG modules to display.\n\n");
1555 
1556 	ttable_del(tt);
1557 
1558 	return CMD_SUCCESS;
1559 }
1560 
1561 DEFPY (show_yang_module_detail,
1562        show_yang_module_detail_cmd,
1563        "show yang module\
1564           [module-translator WORD$translator_family]\
1565           WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
1566        SHOW_STR
1567        "YANG information\n"
1568        "Show loaded modules\n"
1569        "YANG module translator\n"
1570        "YANG module translator\n"
1571        "Module name\n"
1572        "Display summary information about the module\n"
1573        "Display module in the tree (RFC 8340) format\n"
1574        "Display module in the YANG format\n"
1575        "Display module in the YIN format\n")
1576 {
1577 	struct ly_ctx *ly_ctx;
1578 	struct yang_translator *translator = NULL;
1579 	const struct lys_module *module;
1580 	LYS_OUTFORMAT format;
1581 	char *strp;
1582 
1583 	if (translator_family) {
1584 		translator = yang_translator_find(translator_family);
1585 		if (!translator) {
1586 			vty_out(vty, "%% Module translator \"%s\" not found\n",
1587 				translator_family);
1588 			return CMD_WARNING;
1589 		}
1590 		ly_ctx = translator->ly_ctx;
1591 	} else
1592 		ly_ctx = ly_native_ctx;
1593 
1594 	module = ly_ctx_get_module(ly_ctx, module_name, NULL, 0);
1595 	if (!module) {
1596 		vty_out(vty, "%% Module \"%s\" not found\n", module_name);
1597 		return CMD_WARNING;
1598 	}
1599 
1600 	if (yang)
1601 		format = LYS_OUT_YANG;
1602 	else if (yin)
1603 		format = LYS_OUT_YIN;
1604 	else if (tree)
1605 		format = LYS_OUT_TREE;
1606 	else
1607 		format = LYS_OUT_INFO;
1608 
1609 	if (lys_print_mem(&strp, module, format, NULL, 0, 0) == 0) {
1610 		vty_out(vty, "%s\n", strp);
1611 		free(strp);
1612 	} else {
1613 		/* Unexpected. */
1614 		vty_out(vty, "%% Error generating module information\n");
1615 		return CMD_WARNING;
1616 	}
1617 
1618 	return CMD_SUCCESS;
1619 }
1620 
1621 DEFPY (show_yang_module_translator,
1622        show_yang_module_translator_cmd,
1623        "show yang module-translator",
1624        SHOW_STR
1625        "YANG information\n"
1626        "Show loaded YANG module translators\n")
1627 {
1628 	struct yang_translator *translator;
1629 	struct ttable *tt;
1630 
1631 	/* Prepare table. */
1632 	tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1633 	ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)");
1634 	tt->style.cell.rpad = 2;
1635 	tt->style.corner = '+';
1636 	ttable_restyle(tt);
1637 	ttable_rowseps(tt, 0, BOTTOM, true, '-');
1638 
1639 	RB_FOREACH (translator, yang_translators, &yang_translators) {
1640 		struct yang_tmodule *tmodule;
1641 		struct listnode *ln;
1642 
1643 		for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
1644 			ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family,
1645 				       tmodule->module->name,
1646 				       tmodule->deviations->name,
1647 				       tmodule->coverage);
1648 		}
1649 	}
1650 
1651 	/* Dump the generated table. */
1652 	if (tt->nrows > 1) {
1653 		char *table;
1654 
1655 		table = ttable_dump(tt, "\n");
1656 		vty_out(vty, "%s\n", table);
1657 		XFREE(MTYPE_TMP, table);
1658 	} else
1659 		vty_out(vty, "No YANG module translators to display.\n\n");
1660 
1661 	ttable_del(tt);
1662 
1663 	return CMD_SUCCESS;
1664 }
1665 
1666 #ifdef HAVE_CONFIG_ROLLBACKS
nb_cli_rollback_configuration(struct vty * vty,uint32_t transaction_id)1667 static int nb_cli_rollback_configuration(struct vty *vty,
1668 					 uint32_t transaction_id)
1669 {
1670 	struct nb_context context = {};
1671 	struct nb_config *candidate;
1672 	char comment[80];
1673 	char errmsg[BUFSIZ] = {0};
1674 	int ret;
1675 
1676 	candidate = nb_db_transaction_load(transaction_id);
1677 	if (!candidate) {
1678 		vty_out(vty, "%% Transaction %u does not exist.\n\n",
1679 			transaction_id);
1680 		return CMD_WARNING;
1681 	}
1682 
1683 	snprintf(comment, sizeof(comment), "Rollback to transaction %u",
1684 		 transaction_id);
1685 
1686 	context.client = NB_CLIENT_CLI;
1687 	context.user = vty;
1688 	ret = nb_candidate_commit(&context, candidate, true, comment, NULL,
1689 				  errmsg, sizeof(errmsg));
1690 	nb_config_free(candidate);
1691 	switch (ret) {
1692 	case NB_OK:
1693 		vty_out(vty,
1694 			"%% Configuration was successfully rolled back.\n\n");
1695 		/* Print warnings (if any). */
1696 		if (strlen(errmsg) > 0)
1697 			vty_out(vty, "%s\n", errmsg);
1698 		return CMD_SUCCESS;
1699 	case NB_ERR_NO_CHANGES:
1700 		vty_out(vty,
1701 			"%% Aborting - no configuration changes detected.\n\n");
1702 		return CMD_WARNING;
1703 	default:
1704 		vty_out(vty, "%% Rollback failed.\n\n");
1705 		vty_show_nb_errors(vty, ret, errmsg);
1706 		return CMD_WARNING;
1707 	}
1708 }
1709 #endif /* HAVE_CONFIG_ROLLBACKS */
1710 
1711 DEFPY (rollback_config,
1712        rollback_config_cmd,
1713        "rollback configuration (1-4294967295)$transaction_id",
1714        "Rollback to a previous state\n"
1715        "Running configuration\n"
1716        "Transaction ID\n")
1717 {
1718 #ifdef HAVE_CONFIG_ROLLBACKS
1719 	return nb_cli_rollback_configuration(vty, transaction_id);
1720 #else
1721 	vty_out(vty,
1722 		"%% FRR was compiled without --enable-config-rollbacks.\n\n");
1723 	return CMD_SUCCESS;
1724 #endif /* HAVE_CONFIG_ROLLBACKS */
1725 }
1726 
1727 /* Debug CLI commands. */
1728 static struct debug *nb_debugs[] = {
1729 	&nb_dbg_cbs_config, &nb_dbg_cbs_state, &nb_dbg_cbs_rpc,
1730 	&nb_dbg_notif,      &nb_dbg_events,    &nb_dbg_libyang,
1731 };
1732 
1733 static const char *const nb_debugs_conflines[] = {
1734 	"debug northbound callbacks configuration",
1735 	"debug northbound callbacks state",
1736 	"debug northbound callbacks rpc",
1737 	"debug northbound notifications",
1738 	"debug northbound events",
1739 	"debug northbound libyang",
1740 };
1741 
1742 DEFINE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set));
1743 
nb_debug_set_all(uint32_t flags,bool set)1744 static void nb_debug_set_all(uint32_t flags, bool set)
1745 {
1746 	for (unsigned int i = 0; i < array_size(nb_debugs); i++) {
1747 		DEBUG_FLAGS_SET(nb_debugs[i], flags, set);
1748 
1749 		/* If all modes have been turned off, don't preserve options. */
1750 		if (!DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_ALL))
1751 			DEBUG_CLEAR(nb_debugs[i]);
1752 	}
1753 
1754 	hook_call(nb_client_debug_set_all, flags, set);
1755 }
1756 
1757 DEFPY (debug_nb,
1758        debug_nb_cmd,
1759        "[no] debug northbound\
1760           [<\
1761 	    callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\
1762 	    |notifications$notifications\
1763 	    |events$events\
1764 	    |libyang$libyang\
1765           >]",
1766        NO_STR
1767        DEBUG_STR
1768        "Northbound debugging\n"
1769        "Callbacks\n"
1770        "Configuration\n"
1771        "State\n"
1772        "RPC\n"
1773        "Notifications\n"
1774        "Events\n"
1775        "libyang debugging\n")
1776 {
1777 	uint32_t mode = DEBUG_NODE2MODE(vty->node);
1778 
1779 	if (cbs) {
1780 		bool none = (!cbs_cfg && !cbs_state && !cbs_rpc);
1781 
1782 		if (none || cbs_cfg)
1783 			DEBUG_MODE_SET(&nb_dbg_cbs_config, mode, !no);
1784 		if (none || cbs_state)
1785 			DEBUG_MODE_SET(&nb_dbg_cbs_state, mode, !no);
1786 		if (none || cbs_rpc)
1787 			DEBUG_MODE_SET(&nb_dbg_cbs_rpc, mode, !no);
1788 	}
1789 	if (notifications)
1790 		DEBUG_MODE_SET(&nb_dbg_notif, mode, !no);
1791 	if (events)
1792 		DEBUG_MODE_SET(&nb_dbg_events, mode, !no);
1793 	if (libyang) {
1794 		DEBUG_MODE_SET(&nb_dbg_libyang, mode, !no);
1795 		yang_debugging_set(!no);
1796 	}
1797 
1798 	/* no specific debug --> act on all of them */
1799 	if (strmatch(argv[argc - 1]->text, "northbound")) {
1800 		nb_debug_set_all(mode, !no);
1801 		yang_debugging_set(!no);
1802 	}
1803 
1804 	return CMD_SUCCESS;
1805 }
1806 
1807 DEFINE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty));
1808 
nb_debug_config_write(struct vty * vty)1809 static int nb_debug_config_write(struct vty *vty)
1810 {
1811 	for (unsigned int i = 0; i < array_size(nb_debugs); i++)
1812 		if (DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_CONF))
1813 			vty_out(vty, "%s\n", nb_debugs_conflines[i]);
1814 
1815 	hook_call(nb_client_debug_config_write, vty);
1816 
1817 	return 1;
1818 }
1819 
1820 static struct debug_callbacks nb_dbg_cbs = {.debug_set_all = nb_debug_set_all};
1821 static struct cmd_node nb_debug_node = {
1822 	.name = "northbound debug",
1823 	.node = NORTHBOUND_DEBUG_NODE,
1824 	.prompt = "",
1825 	.config_write = nb_debug_config_write,
1826 };
1827 
nb_cli_install_default(int node)1828 void nb_cli_install_default(int node)
1829 {
1830 	install_element(node, &show_config_candidate_section_cmd);
1831 
1832 	if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL)
1833 		return;
1834 
1835 	install_element(node, &config_commit_cmd);
1836 	install_element(node, &config_commit_comment_cmd);
1837 	install_element(node, &config_commit_check_cmd);
1838 	install_element(node, &config_update_cmd);
1839 	install_element(node, &config_discard_cmd);
1840 	install_element(node, &show_config_running_cmd);
1841 	install_element(node, &show_config_candidate_cmd);
1842 	install_element(node, &show_config_compare_cmd);
1843 	install_element(node, &show_config_transaction_cmd);
1844 }
1845 
1846 /* YANG module autocomplete. */
yang_module_autocomplete(vector comps,struct cmd_token * token)1847 static void yang_module_autocomplete(vector comps, struct cmd_token *token)
1848 {
1849 	const struct lys_module *module;
1850 	struct yang_translator *module_tr;
1851 	uint32_t idx;
1852 
1853 	idx = 0;
1854 	while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx)))
1855 		vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name));
1856 
1857 	RB_FOREACH (module_tr, yang_translators, &yang_translators) {
1858 		idx = 0;
1859 		while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx,
1860 							&idx)))
1861 			vector_set(comps,
1862 				   XSTRDUP(MTYPE_COMPLETION, module->name));
1863 	}
1864 }
1865 
1866 /* YANG module translator autocomplete. */
yang_translator_autocomplete(vector comps,struct cmd_token * token)1867 static void yang_translator_autocomplete(vector comps, struct cmd_token *token)
1868 {
1869 	struct yang_translator *module_tr;
1870 
1871 	RB_FOREACH (module_tr, yang_translators, &yang_translators)
1872 		vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family));
1873 }
1874 
1875 static const struct cmd_variable_handler yang_var_handlers[] = {
1876 	{.varname = "module_name", .completions = yang_module_autocomplete},
1877 	{.varname = "translator_family",
1878 	 .completions = yang_translator_autocomplete},
1879 	{.completions = NULL}};
1880 
nb_cli_init(struct thread_master * tm)1881 void nb_cli_init(struct thread_master *tm)
1882 {
1883 	master = tm;
1884 
1885 	/* Initialize the shared candidate configuration. */
1886 	vty_shared_candidate_config = nb_config_new(NULL);
1887 
1888 	debug_init(&nb_dbg_cbs);
1889 
1890 	install_node(&nb_debug_node);
1891 	install_element(ENABLE_NODE, &debug_nb_cmd);
1892 	install_element(CONFIG_NODE, &debug_nb_cmd);
1893 
1894 	/* Install commands specific to the transaction-base mode. */
1895 	if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
1896 		install_element(ENABLE_NODE, &config_exclusive_cmd);
1897 		install_element(ENABLE_NODE, &config_private_cmd);
1898 		install_element(ENABLE_NODE, &show_config_running_cmd);
1899 		install_element(ENABLE_NODE,
1900 				&show_config_compare_without_candidate_cmd);
1901 		install_element(ENABLE_NODE, &show_config_transaction_cmd);
1902 		install_element(ENABLE_NODE, &rollback_config_cmd);
1903 		install_element(ENABLE_NODE, &clear_config_transactions_cmd);
1904 
1905 		install_element(CONFIG_NODE, &config_load_cmd);
1906 		install_element(CONFIG_NODE,
1907 				&config_database_max_transactions_cmd);
1908 	}
1909 
1910 	/* Other commands. */
1911 	install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
1912 	install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
1913 	install_element(ENABLE_NODE, &show_yang_operational_data_cmd);
1914 	install_element(ENABLE_NODE, &show_yang_module_cmd);
1915 	install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
1916 	install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
1917 	cmd_variable_handler_register(yang_var_handlers);
1918 }
1919 
nb_cli_terminate(void)1920 void nb_cli_terminate(void)
1921 {
1922 	nb_config_free(vty_shared_candidate_config);
1923 }
1924