#include "libfrr.h"
#include "frrstr.h"
#include "lib_errors.h"
+#include "northbound_cli.h"
#include <arpa/telnet.h>
#include <termios.h>
/* Current directory. */
char *vty_cwd = NULL;
-/* Configure lock. */
-static int vty_config;
-static int vty_config_is_lockless = 0;
+/* Exclusive configuration lock. */
+struct vty *vty_exclusive_lock;
/* Login password check. */
static int no_password_check = 0;
/* Fatal I/O error. */
vty->monitor =
0; /* disable monitoring to avoid infinite recursion */
- zlog_warn("%s: write failed to vty client fd %d, closing: %s",
- __func__, vty->fd, safe_strerror(errno));
+ flog_err(EC_LIB_SOCKET,
+ "%s: write failed to vty client fd %d, closing: %s",
+ __func__, vty->fd, safe_strerror(errno));
buffer_reset(vty->obuf);
buffer_reset(vty->lbuf);
/* cannot call vty_close, because a parent routine may still try
if ((realtime = thread_consumed_time(&after, &before, &cputime))
> CONSUMED_TIME_CHECK)
/* Warn about CPU hog that must be fixed. */
- zlog_warn(
+ flog_warn(
+ EC_LIB_SLOW_THREAD,
"SLOW COMMAND: command took %lums (cpu time %lums): %s",
realtime / 1000, cputime / 1000, buf);
}
case LDP_L2VPN_NODE:
case LDP_PSEUDOWIRE_NODE:
case ISIS_NODE:
+ case OPENFABRIC_NODE:
case KEYCHAIN_NODE:
case KEYCHAIN_KEY_NODE:
case VTY_NODE:
case BGP_EVPN_VNI_NODE:
case BFD_NODE:
case BFD_PEER_NODE:
- vty_config_unlock(vty);
+ vty_config_exit(vty);
vty->node = ENABLE_NODE;
break;
default:
break;
}
+ vty->xpath_index = 0;
+
vty_prompt(vty);
vty->cp = 0;
}
case LDP_L2VPN_NODE:
case LDP_PSEUDOWIRE_NODE:
case ISIS_NODE:
+ case OPENFABRIC_NODE:
case KEYCHAIN_NODE:
case KEYCHAIN_KEY_NODE:
case VTY_NODE:
case BFD_NODE:
case BFD_PEER_NODE:
- vty_config_unlock(vty);
+ vty_config_exit(vty);
vty->node = ENABLE_NODE;
break;
default:
switch (vty->sb_buf[0]) {
case TELOPT_NAWS:
if (vty->sb_len != TELNET_NAWS_SB_LEN)
- zlog_warn(
+ flog_err(
+ EC_LIB_SYSTEM_CALL,
"RFC 1073 violation detected: telnet NAWS option "
"should send %d characters, but we received %lu",
TELNET_NAWS_SB_LEN,
(unsigned long)vty->sb_len);
else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN)
flog_err(
- LIB_ERR_DEVELOPMENT,
+ EC_LIB_DEVELOPMENT,
"Bug detected: sizeof(vty->sb_buf) %lu < %d, too small to handle the telnet NAWS option",
(unsigned long)sizeof(vty->sb_buf),
TELNET_NAWS_SB_LEN);
}
vty->monitor = 0; /* disable monitoring to avoid
infinite recursion */
- zlog_warn(
+ flog_err(
+ EC_LIB_SOCKET,
"%s: read error on vty client fd %d, closing: %s",
__func__, vty->fd, safe_strerror(errno));
buffer_reset(vty->obuf);
case BUFFER_ERROR:
vty->monitor =
0; /* disable monitoring to avoid infinite recursion */
- zlog_warn("buffer_flush failed on vty client fd %d, closing",
+ zlog_info("buffer_flush failed on vty client fd %d, closing",
vty->fd);
buffer_reset(vty->lbuf);
buffer_reset(vty->obuf);
new->lbuf = buffer_new(0);
new->obuf = buffer_new(0); /* Use default buffer size. */
new->buf = XCALLOC(MTYPE_VTY, VTY_BUFSIZ);
- new->error_buf = XCALLOC(MTYPE_VTY, VTY_BUFSIZ);
new->max = VTY_BUFSIZ;
return new;
memset(vty->hist, 0, sizeof(vty->hist));
vty->hp = 0;
vty->hindex = 0;
+ vty->xpath_index = 0;
+ memset(vty->xpath, 0, sizeof(vty->xpath));
+ vty->private_config = false;
+ vty->candidate_config = vty_shared_candidate_config;
vector_set_index(vtyvec, vty_sock, vty);
vty->status = VTY_NORMAL;
vty->lines = -1;
/* We can handle IPv4 or IPv6 socket. */
vty_sock = sockunion_accept(accept_sock, &su);
if (vty_sock < 0) {
- zlog_warn("can't accept vty socket : %s", safe_strerror(errno));
+ flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s",
+ safe_strerror(errno));
return -1;
}
set_nonblocking(vty_sock);
ret = getaddrinfo(hostname, port_str, &req, &ainfo);
if (ret != 0) {
- flog_err_sys(LIB_ERR_SYSTEM_CALL, "getaddrinfo failed: %s",
+ flog_err_sys(EC_LIB_SYSTEM_CALL, "getaddrinfo failed: %s",
gai_strerror(ret));
exit(1);
}
/* Make UNIX domain socket. */
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
- flog_err_sys(LIB_ERR_SOCKET,
+ flog_err_sys(EC_LIB_SOCKET,
"Cannot create unix stream socket: %s",
safe_strerror(errno));
return;
ret = bind(sock, (struct sockaddr *)&serv, len);
if (ret < 0) {
- flog_err_sys(LIB_ERR_SOCKET, "Cannot bind path %s: %s", path,
+ flog_err_sys(EC_LIB_SOCKET, "Cannot bind path %s: %s", path,
safe_strerror(errno));
close(sock); /* Avoid sd leak. */
return;
ret = listen(sock, 5);
if (ret < 0) {
- flog_err_sys(LIB_ERR_SOCKET, "listen(fd %d) failed: %s", sock,
+ flog_err_sys(EC_LIB_SOCKET, "listen(fd %d) failed: %s", sock,
safe_strerror(errno));
close(sock); /* Avoid sd leak. */
return;
if ((int)ids.gid_vty > 0) {
/* set group of socket */
if (chown(path, -1, ids.gid_vty)) {
- flog_err_sys(LIB_ERR_SYSTEM_CALL,
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
"vty_serv_un: could chown socket, %s",
safe_strerror(errno));
}
(socklen_t *)&client_len);
if (sock < 0) {
- zlog_warn("can't accept vty socket : %s", safe_strerror(errno));
+ flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s",
+ safe_strerror(errno));
return -1;
}
if (set_nonblocking(sock) < 0) {
- zlog_warn(
- "vtysh_accept: could not set vty socket %d to non-blocking,"
- " %s, closing",
+ flog_err(
+ EC_LIB_SOCKET,
+ "vtysh_accept: could not set vty socket %d to non-blocking, %s, closing",
sock, safe_strerror(errno));
close(sock);
return -1;
case BUFFER_ERROR:
vty->monitor =
0; /* disable monitoring to avoid infinite recursion */
- zlog_warn("%s: write error to fd %d, closing", __func__,
- vty->fd);
+ flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing",
+ __func__, vty->fd);
buffer_reset(vty->lbuf);
buffer_reset(vty->obuf);
vty_close(vty);
}
vty->monitor = 0; /* disable monitoring to avoid
infinite recursion */
- zlog_warn(
+ flog_err(
+ EC_LIB_SOCKET,
"%s: read failed on vtysh client fd %d, closing: %s",
__func__, sock, safe_strerror(errno));
}
#endif /* VTYSH */
}
+static void vty_error_delete(void *arg)
+{
+ struct vty_error *ve = arg;
+
+ XFREE(MTYPE_TMP, ve);
+}
+
/* Close vty interface. Warning: call this only from functions that
will be careful not to access the vty afterwards (since it has
now been freed). This is safest from top-level functions (called
if (vty->buf)
XFREE(MTYPE_VTY, vty->buf);
- if (vty->error_buf)
- XFREE(MTYPE_VTY, vty->error_buf);
+ if (vty->error) {
+ vty->error->del = vty_error_delete;
+ list_delete(&vty->error);
+ }
/* Check configure. */
- vty_config_unlock(vty);
+ vty_config_exit(vty);
/* OK free vty. */
XFREE(MTYPE_VTY, vty);
}
/* Read up configuration file from file_name. */
-static void vty_read_file(FILE *confp)
+static void vty_read_file(struct nb_config *config, FILE *confp)
{
int ret;
struct vty *vty;
+ struct vty_error *ve;
+ struct listnode *node;
unsigned int line_num = 0;
vty = vty_new();
vty->wfd = STDERR_FILENO;
vty->type = VTY_FILE;
vty->node = CONFIG_NODE;
+ if (config)
+ vty->candidate_config = config;
+ else {
+ vty->private_config = true;
+ vty->candidate_config = nb_config_new(NULL);
+ }
/* Execute configuration file */
ret = config_from_file(vty, confp, &line_num);
break;
}
- nl = strchr(vty->error_buf, '\n');
- if (nl)
- *nl = '\0';
- flog_err(LIB_ERR_VTY,
- "ERROR: %s on config line %u: %s", message, line_num,
- vty->error_buf);
+ for (ALL_LIST_ELEMENTS_RO(vty->error, node, ve)) {
+ nl = strchr(ve->error_buf, '\n');
+ if (nl)
+ *nl = '\0';
+ flog_err(EC_LIB_VTY, "ERROR: %s on config line %u: %s",
+ message, ve->line_num, ve->error_buf);
+ }
+ }
+
+ /*
+ * Automatically commit the candidate configuration after
+ * reading the configuration file.
+ */
+ if (config == NULL && vty->candidate_config
+ && frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
+ ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI,
+ true, "Read configuration file",
+ NULL);
+ if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
+ zlog_err("%s: failed to read configuration file.",
+ __func__);
}
vty_close(vty);
}
/* Read up configuration file from file_name. */
-bool vty_read_config(const char *config_file, char *config_default_dir)
+bool vty_read_config(struct nb_config *config, const char *config_file,
+ char *config_default_dir)
{
char cwd[MAXPATHLEN];
FILE *confp = NULL;
if (!IS_DIRECTORY_SEP(config_file[0])) {
if (getcwd(cwd, MAXPATHLEN) == NULL) {
flog_err_sys(
- LIB_ERR_SYSTEM_CALL,
- "Failure to determine Current Working Directory %d!",
- errno);
- exit(1);
+ EC_LIB_SYSTEM_CALL,
+ "%s: failure to determine Current Working Directory %d!",
+ __func__, errno);
+ goto tmp_free_and_out;
}
tmp = XMALLOC(MTYPE_TMP,
strlen(cwd) + strlen(config_file) + 2);
confp = fopen(fullpath, "r");
if (confp == NULL) {
- zlog_warn("%s: failed to open configuration file %s: %s, checking backup",
- __func__, fullpath, safe_strerror(errno));
+ flog_warn(
+ EC_LIB_BACKUP_CONFIG,
+ "%s: failed to open configuration file %s: %s, checking backup",
+ __func__, fullpath, safe_strerror(errno));
confp = vty_use_backup_config(fullpath);
if (confp)
- zlog_warn(
+ flog_warn(
+ EC_LIB_BACKUP_CONFIG,
"WARNING: using backup configuration file!");
else {
- flog_err(LIB_ERR_VTY,
- "can't open configuration file [%s]",
- config_file);
- exit(1);
+ flog_err(
+ EC_LIB_VTY,
+ "%s: can't open configuration file [%s]",
+ __func__, config_file);
+ goto tmp_free_and_out;
}
}
} else {
#endif /* VTYSH */
confp = fopen(config_default_dir, "r");
if (confp == NULL) {
- zlog_warn("%s: failed to open configuration file %s: %s, checking backup",
- __func__, config_default_dir,
- safe_strerror(errno));
+ flog_err(
+ EC_LIB_SYSTEM_CALL,
+ "%s: failed to open configuration file %s: %s, checking backup",
+ __func__, config_default_dir,
+ safe_strerror(errno));
confp = vty_use_backup_config(config_default_dir);
if (confp) {
- zlog_warn(
+ flog_warn(
+ EC_LIB_BACKUP_CONFIG,
"WARNING: using backup configuration file!");
fullpath = config_default_dir;
} else {
- flog_err(LIB_ERR_VTY,
- "can't open configuration file [%s]",
- config_default_dir);
+ flog_err(EC_LIB_VTY,
+ "can't open configuration file [%s]",
+ config_default_dir);
goto tmp_free_and_out;
}
} else
fullpath = config_default_dir;
}
- vty_read_file(confp);
+ vty_read_file(config, confp);
read_success = true;
fclose(confp);
}
}
-int vty_config_lock(struct vty *vty)
+int vty_config_enter(struct vty *vty, bool private_config, bool exclusive)
{
- if (vty_config_is_lockless)
- return 1;
- if (vty_config == 0) {
- vty->config = 1;
- vty_config = 1;
+ if (exclusive && !vty_config_exclusive_lock(vty)) {
+ vty_out(vty, "VTY configuration is locked by other VTY\n");
+ return CMD_WARNING;
}
- return vty->config;
+
+ vty->node = CONFIG_NODE;
+ vty->config = true;
+ vty->private_config = private_config;
+
+ if (private_config) {
+ vty->candidate_config = nb_config_dup(running_config);
+ vty->candidate_config_base = nb_config_dup(running_config);
+ vty_out(vty,
+ "Warning: uncommitted changes will be discarded on exit.\n\n");
+ } else {
+ vty->candidate_config = vty_shared_candidate_config;
+ if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
+ vty->candidate_config_base =
+ nb_config_dup(running_config);
+ }
+
+ return CMD_SUCCESS;
}
-int vty_config_unlock(struct vty *vty)
+void vty_config_exit(struct vty *vty)
{
- if (vty_config_is_lockless)
- return 0;
- if (vty_config == 1 && vty->config == 1) {
- vty->config = 0;
- vty_config = 0;
+ /* Check if there's a pending confirmed commit. */
+ if (vty->t_confirmed_commit_timeout) {
+ vty_out(vty,
+ "WARNING: exiting with a pending confirmed commit. Rolling back to previous configuration.\n\n");
+ nb_cli_confirmed_commit_rollback(vty);
+ nb_cli_confirmed_commit_clean(vty);
}
- return vty->config;
+
+ vty_config_exclusive_unlock(vty);
+
+ if (vty->candidate_config) {
+ if (vty->private_config)
+ nb_config_free(vty->candidate_config);
+ vty->candidate_config = NULL;
+ }
+ if (vty->candidate_config_base) {
+ nb_config_free(vty->candidate_config_base);
+ vty->candidate_config_base = NULL;
+ }
+}
+
+int vty_config_exclusive_lock(struct vty *vty)
+{
+ if (vty_exclusive_lock == NULL) {
+ vty_exclusive_lock = vty;
+ return 1;
+ }
+ return 0;
}
-void vty_config_lockless(void)
+void vty_config_exclusive_unlock(struct vty *vty)
{
- vty_config_is_lockless = 1;
+ if (vty_exclusive_lock == vty)
+ vty_exclusive_lock = NULL;
}
/* Master of the threads. */
* Hence not worrying about it too much.
*/
if (!chdir(SYSCONFDIR)) {
- flog_err_sys(LIB_ERR_SYSTEM_CALL,
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
"Failure to chdir to %s, errno: %d",
SYSCONFDIR, errno);
exit(-1);
}
if (getcwd(cwd, MAXPATHLEN) == NULL) {
- flog_err_sys(LIB_ERR_SYSTEM_CALL,
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
"Failure to getcwd, errno: %d", errno);
exit(-1);
}