summaryrefslogtreecommitdiffstats
path: root/vtysh/vtysh_config.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
commit2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch)
treec05dc0f8e6aa3accc84e3e5cffc933ed94941383 /vtysh/vtysh_config.c
parentInitial commit. (diff)
downloadfrr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz
frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--vtysh/vtysh_config.c682
1 files changed, 682 insertions, 0 deletions
diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c
new file mode 100644
index 0000000..d98f83d
--- /dev/null
+++ b/vtysh/vtysh_config.c
@@ -0,0 +1,682 @@
+/* Configuration generator.
+ * Copyright (C) 2000 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "command.h"
+#include "linklist.h"
+#include "memory.h"
+#include "typesafe.h"
+
+#include "vtysh/vtysh.h"
+#include "vtysh/vtysh_user.h"
+
+DEFINE_MGROUP(MVTYSH, "vtysh");
+DEFINE_MTYPE_STATIC(MVTYSH, VTYSH_CONFIG, "Vtysh configuration");
+DEFINE_MTYPE_STATIC(MVTYSH, VTYSH_CONFIG_LINE, "Vtysh configuration line");
+
+vector configvec;
+
+PREDECL_LIST(config_master);
+PREDECL_HASH(config_master_hash);
+
+struct config {
+ /* Configuration node name. */
+ char *name;
+
+ /* Configuration string line. */
+ struct list *line;
+
+ /* Configuration can be nested. */
+ struct config *parent;
+ vector nested;
+
+ /* Exit command. */
+ char *exit;
+
+ /* Index of this config. */
+ uint32_t index;
+
+ /* Node entry for the typed Red-black tree */
+ struct config_master_item rbt_item;
+ struct config_master_hash_item hash_item;
+};
+
+struct list *config_top;
+
+static int line_cmp(char *c1, char *c2)
+{
+ return strcmp(c1, c2);
+}
+
+static void line_del(char *line)
+{
+ XFREE(MTYPE_VTYSH_CONFIG_LINE, line);
+}
+
+static struct config *config_new(void)
+{
+ struct config *config;
+ config = XCALLOC(MTYPE_VTYSH_CONFIG, sizeof(struct config));
+ return config;
+}
+
+static void config_del(struct config *config)
+{
+ vector_free(config->nested);
+ list_delete(&config->line);
+ if (config->exit)
+ XFREE(MTYPE_VTYSH_CONFIG_LINE, config->exit);
+ XFREE(MTYPE_VTYSH_CONFIG_LINE, config->name);
+ XFREE(MTYPE_VTYSH_CONFIG, config);
+}
+
+static int config_cmp(const struct config *c1, const struct config *c2)
+{
+ return strcmp(c1->name, c2->name);
+}
+
+static uint32_t config_hash(const struct config *c)
+{
+ return string_hash_make(c->name);
+}
+
+DECLARE_LIST(config_master, struct config, rbt_item);
+DECLARE_HASH(config_master_hash, struct config, hash_item, config_cmp,
+ config_hash);
+
+/*
+ * The config_master_head is a list for order of receipt
+ * The hash is for quick lookup under this NODE
+ */
+struct configuration {
+ struct config_master_head master;
+ struct config_master_hash_head hash_master;
+};
+
+static struct config *config_get_vec(vector vec, int index, const char *line)
+{
+ struct config *config, *config_loop;
+ struct configuration *configuration;
+ struct config lookup;
+
+ config = config_loop = NULL;
+
+ configuration = vector_lookup_ensure(vec, index);
+
+ if (!configuration) {
+ configuration = XMALLOC(MTYPE_VTYSH_CONFIG,
+ sizeof(struct configuration));
+ config_master_init(&configuration->master);
+ config_master_hash_init(&configuration->hash_master);
+ vector_set_index(vec, index, configuration);
+ }
+
+ lookup.name = (char *)line;
+ config = config_master_hash_find(&configuration->hash_master, &lookup);
+
+ if (!config) {
+ config = config_new();
+ config->line = list_new();
+ config->line->del = (void (*)(void *))line_del;
+ config->line->cmp = (int (*)(void *, void *))line_cmp;
+ config->name = XSTRDUP(MTYPE_VTYSH_CONFIG_LINE, line);
+ config->exit = NULL;
+ config->index = index;
+ config->nested = vector_init(1);
+ config_master_add_tail(&configuration->master, config);
+ config_master_hash_add(&configuration->hash_master, config);
+ }
+ return config;
+}
+
+static struct config *config_get(int index, const char *line)
+{
+ return config_get_vec(configvec, index, line);
+}
+
+static struct config *config_get_nested(struct config *parent, int index,
+ const char *line)
+{
+ struct config *config;
+
+ config = config_get_vec(parent->nested, index, line);
+ config->parent = parent;
+
+ return config;
+}
+
+void config_add_line(struct list *config, const char *line)
+{
+ listnode_add(config, XSTRDUP(MTYPE_VTYSH_CONFIG_LINE, line));
+}
+
+static void config_add_line_uniq(struct list *config, const char *line)
+{
+ struct listnode *node, *nnode;
+ char *pnt;
+
+ for (ALL_LIST_ELEMENTS(config, node, nnode, pnt)) {
+ if (strcmp(pnt, line) == 0)
+ return;
+ }
+ listnode_add_sort(config, XSTRDUP(MTYPE_VTYSH_CONFIG_LINE, line));
+}
+
+/*
+ * Add a line that should only be shown once, and always show at the end of the
+ * config block.
+ *
+ * If the line already exists, it will be moved to the end of the block. If it
+ * does not exist, it will be added at the end of the block.
+ *
+ * Note that this only makes sense when there is just one such line that should
+ * show up at the very end of a config block. Furthermore, if the same block
+ * can show up from multiple daemons, all of them must make sure to print the
+ * line at the end of their config, otherwise the line will show at the end of
+ * the config for the last daemon that printed it.
+ *
+ * Here is a motivating example with the 'exit-vrf' command. Suppose we receive
+ * a config from Zebra like so:
+ *
+ * vrf BLUE
+ * ip route A
+ * ip route B
+ * exit-vrf
+ *
+ * Then suppose we later receive this config from PIM:
+ *
+ * vrf BLUE
+ * ip msdp mesh-group MyGroup member 1.2.3.4
+ * exit-vrf
+ *
+ * Then we will combine them into one config block like so:
+ *
+ * vrf BLUE
+ * ip route A
+ * ip route B
+ * ip msdp mesh-group MyGroup member 1.2.3.4
+ * exit-vrf
+ *
+ * Because PIM also sent us an 'exit-vrf', we noticed that we already had one
+ * under the 'vrf BLUE' config block and so we moved it to the end of the
+ * config block again. If PIM had neglected to send us 'exit-vrf', the result
+ * would be this:
+ *
+ * vrf BLUE
+ * ip route A
+ * ip route B
+ * exit-vrf
+ * ip msdp mesh-group MyGroup member 1.2.3.4
+ *
+ * Therefore, daemons that share config blocks must take care to consistently
+ * print the same block terminators.
+ *
+ * Ideally this would be solved by adding a string to struct config that is
+ * always printed at the end when dumping a config. However, this would only
+ * work when the user is using integrated config. In the non-integrated config
+ * case, daemons are responsible for writing their own config files, and so the
+ * must be able to print these blocks correctly independently of vtysh, which
+ * means they are the ones that need to handle printing the block terminators
+ * and VTYSH needs to be smart enough to combine them properly.
+ *
+ * ---
+ *
+ * config
+ * The config to add the line to
+ *
+ * line
+ * The line to add to the end of the config
+ */
+static void config_add_line_uniq_end(struct list *config, const char *line)
+{
+ struct listnode *node;
+ char *pnt;
+
+ for (ALL_LIST_ELEMENTS_RO(config, node, pnt)) {
+ if (strcmp(pnt, line) == 0)
+ break;
+ }
+
+ if (!node)
+ config_add_line(config, line);
+ else
+ listnode_move_to_tail(config, node);
+}
+
+void vtysh_config_parse_line(void *arg, const char *line)
+{
+ char c;
+ static struct config *config = NULL;
+
+ if (!line)
+ return;
+
+ c = line[0];
+
+ if (c == '\0')
+ return;
+
+ switch (c) {
+ /* Suppress exclamation points ! and commented lines. The !s are
+ * generated
+ * dynamically in vtysh_config_dump() */
+ case '!':
+ case '#':
+ break;
+ case ' ':
+ /* Store line to current configuration. */
+ if (config) {
+ if (config->index == KEYCHAIN_NODE
+ && strncmp(line, " key", strlen(" key")) == 0) {
+ config = config_get_nested(
+ config, KEYCHAIN_KEY_NODE, line);
+ } else if (config->index == KEYCHAIN_KEY_NODE) {
+ if (strncmp(line, " exit", strlen(" exit"))
+ == 0) {
+ config_add_line_uniq_end(config->line,
+ line);
+ config = config->parent;
+ } else {
+ config_add_line_uniq(config->line,
+ line);
+ }
+ } else if (strncmp(line, " link-params",
+ strlen(" link-params"))
+ == 0) {
+ config_add_line(config->line, line);
+ config->index = LINK_PARAMS_NODE;
+ } else if (strncmp(line, " ip multicast boundary",
+ strlen(" ip multicast boundary"))
+ == 0) {
+ config_add_line_uniq_end(config->line, line);
+ } else if (strncmp(line, " ip igmp query-interval",
+ strlen(" ip igmp query-interval"))
+ == 0) {
+ config_add_line_uniq_end(config->line, line);
+ } else if (config->index == LINK_PARAMS_NODE
+ && strncmp(line, " exit-link-params",
+ strlen(" exit"))
+ == 0) {
+ config_add_line(config->line, line);
+ config->index = INTERFACE_NODE;
+ } else if (!strncmp(line, " vrrp", strlen(" vrrp"))
+ || !strncmp(line, " no vrrp",
+ strlen(" no vrrp"))) {
+ config_add_line(config->line, line);
+ } else if (!strncmp(line, " ip mroute",
+ strlen(" ip mroute"))) {
+ config_add_line_uniq_end(config->line, line);
+ } else if (config->index == RMAP_NODE
+ || config->index == INTERFACE_NODE
+ || config->index == VTY_NODE
+ || config->index == NH_GROUP_NODE)
+ config_add_line_uniq(config->line, line);
+ else
+ config_add_line(config->line, line);
+ } else
+ config_add_line(config_top, line);
+ break;
+ default:
+ if (strncmp(line, "exit", strlen("exit")) == 0) {
+ if (config)
+ config->exit =
+ XSTRDUP(MTYPE_VTYSH_CONFIG_LINE, line);
+ } else if (strncmp(line, "interface", strlen("interface")) == 0)
+ config = config_get(INTERFACE_NODE, line);
+ else if (strncmp(line, "pseudowire", strlen("pseudowire")) == 0)
+ config = config_get(PW_NODE, line);
+ else if (strncmp(line, "vrf", strlen("vrf")) == 0)
+ config = config_get(VRF_NODE, line);
+ else if (strncmp(line, "nexthop-group", strlen("nexthop-group"))
+ == 0)
+ config = config_get(NH_GROUP_NODE, line);
+ else if (strncmp(line, "router-id", strlen("router-id")) == 0)
+ config = config_get(ZEBRA_NODE, line);
+ else if (strncmp(line, "router rip", strlen("router rip")) == 0)
+ config = config_get(RIP_NODE, line);
+ else if (strncmp(line, "router ripng", strlen("router ripng"))
+ == 0)
+ config = config_get(RIPNG_NODE, line);
+ else if (strncmp(line, "router eigrp", strlen("router eigrp"))
+ == 0)
+ config = config_get(EIGRP_NODE, line);
+ else if (strncmp(line, "router babel", strlen("router babel"))
+ == 0)
+ config = config_get(BABEL_NODE, line);
+ else if (strncmp(line, "router ospf", strlen("router ospf"))
+ == 0)
+ config = config_get(OSPF_NODE, line);
+ else if (strncmp(line, "router ospf6", strlen("router ospf6"))
+ == 0)
+ config = config_get(OSPF6_NODE, line);
+ else if (strncmp(line, "mpls ldp", strlen("mpls ldp")) == 0)
+ config = config_get(LDP_NODE, line);
+ else if (strncmp(line, "l2vpn", strlen("l2vpn")) == 0)
+ config = config_get(LDP_L2VPN_NODE, line);
+ else if (strncmp(line, "router bgp", strlen("router bgp")) == 0)
+ config = config_get(BGP_NODE, line);
+ else if (strncmp(line, "router isis", strlen("router isis"))
+ == 0)
+ config = config_get(ISIS_NODE, line);
+ else if (strncmp(line, "router openfabric", strlen("router openfabric"))
+ == 0)
+ config = config_get(OPENFABRIC_NODE, line);
+ else if (strncmp(line, "route-map", strlen("route-map")) == 0)
+ config = config_get(RMAP_NODE, line);
+ else if (strncmp(line, "no route-map", strlen("no route-map"))
+ == 0)
+ config = config_get(RMAP_NODE, line);
+ else if (strncmp(line, "pbr-map", strlen("pbr-map")) == 0)
+ config = config_get(PBRMAP_NODE, line);
+ else if (strncmp(line, "access-list", strlen("access-list"))
+ == 0)
+ config = config_get(ACCESS_NODE, line);
+ else if (strncmp(line, "ipv6 access-list",
+ strlen("ipv6 access-list"))
+ == 0)
+ config = config_get(ACCESS_IPV6_NODE, line);
+ else if (strncmp(line, "mac access-list",
+ strlen("mac access-list"))
+ == 0)
+ config = config_get(ACCESS_MAC_NODE, line);
+ else if (strncmp(line, "ip prefix-list",
+ strlen("ip prefix-list"))
+ == 0)
+ config = config_get(PREFIX_NODE, line);
+ else if (strncmp(line, "ipv6 prefix-list",
+ strlen("ipv6 prefix-list"))
+ == 0)
+ config = config_get(PREFIX_IPV6_NODE, line);
+ else if (strncmp(line, "bgp as-path access-list",
+ strlen("bgp as-path access-list"))
+ == 0)
+ config = config_get(AS_LIST_NODE, line);
+ else if (strncmp(line, "bgp community-list",
+ strlen("bgp community-list"))
+ == 0
+ || strncmp(line, "bgp extcommunity-list",
+ strlen("bgp extcommunity-list"))
+ == 0
+ || strncmp(line, "bgp large-community-list",
+ strlen("bgp large-community-list"))
+ == 0)
+ config = config_get(COMMUNITY_LIST_NODE, line);
+ else if (strncmp(line, "bgp community alias",
+ strlen("bgp community alias")) == 0)
+ config = config_get(COMMUNITY_ALIAS_NODE, line);
+ else if (strncmp(line, "ip route", strlen("ip route")) == 0)
+ config = config_get(IP_NODE, line);
+ else if (strncmp(line, "ipv6 route", strlen("ipv6 route")) == 0)
+ config = config_get(IP_NODE, line);
+ else if (strncmp(line, "key", strlen("key")) == 0)
+ config = config_get(KEYCHAIN_NODE, line);
+ else if (strncmp(line, "line", strlen("line")) == 0)
+ config = config_get(VTY_NODE, line);
+ else if ((strncmp(line, "ipv6 forwarding",
+ strlen("ipv6 forwarding"))
+ == 0)
+ || (strncmp(line, "ip forwarding",
+ strlen("ip forwarding"))
+ == 0))
+ config = config_get(FORWARDING_NODE, line);
+ else if (strncmp(line, "debug vrf", strlen("debug vrf")) == 0)
+ config = config_get(VRF_DEBUG_NODE, line);
+ else if (strncmp(line, "debug northbound",
+ strlen("debug northbound"))
+ == 0)
+ config = config_get(NORTHBOUND_DEBUG_NODE, line);
+ else if (strncmp(line, "debug route-map",
+ strlen("debug route-map"))
+ == 0)
+ config = config_get(RMAP_DEBUG_NODE, line);
+ else if (strncmp(line, "debug resolver",
+ strlen("debug resolver")) == 0)
+ config = config_get(RESOLVER_DEBUG_NODE, line);
+ else if (strncmp(line, "debug", strlen("debug")) == 0)
+ config = config_get(DEBUG_NODE, line);
+ else if (strncmp(line, "password", strlen("password")) == 0
+ || strncmp(line, "enable password",
+ strlen("enable password"))
+ == 0)
+ config = config_get(AAA_NODE, line);
+ else if (strncmp(line, "ip protocol", strlen("ip protocol"))
+ == 0)
+ config = config_get(PROTOCOL_NODE, line);
+ else if (strncmp(line, "ipv6 protocol", strlen("ipv6 protocol"))
+ == 0)
+ config = config_get(PROTOCOL_NODE, line);
+ else if (strncmp(line, "ip nht", strlen("ip nht")) == 0)
+ config = config_get(PROTOCOL_NODE, line);
+ else if (strncmp(line, "ipv6 nht", strlen("ipv6 nht")) == 0)
+ config = config_get(PROTOCOL_NODE, line);
+ else if (strncmp(line, "mpls", strlen("mpls")) == 0)
+ config = config_get(MPLS_NODE, line);
+ else if (strncmp(line, "segment-routing",
+ strlen("segment-routing"))
+ == 0)
+ config = config_get(SEGMENT_ROUTING_NODE, line);
+ else if (strncmp(line, "bfd", strlen("bfd")) == 0)
+ config = config_get(BFD_NODE, line);
+ else if (strncmp(line, "rpki", strlen("rpki")) == 0)
+ config = config_get(RPKI_NODE, line);
+ else {
+ if (strncmp(line, "log", strlen("log")) == 0 ||
+ strncmp(line, "hostname", strlen("hostname")) ==
+ 0 ||
+ strncmp(line, "domainname", strlen("domainname")) ==
+ 0 ||
+ strncmp(line, "allow-reserved-ranges",
+ strlen("allow-reserved-ranges")) == 0 ||
+ strncmp(line, "frr", strlen("frr")) == 0 ||
+ strncmp(line, "agentx", strlen("agentx")) == 0 ||
+ strncmp(line, "no log", strlen("no log")) == 0 ||
+ strncmp(line, "no ip prefix-list",
+ strlen("no ip prefix-list")) == 0 ||
+ strncmp(line, "no ipv6 prefix-list",
+ strlen("no ipv6 prefix-list")) == 0 ||
+ strncmp(line, "service cputime-stats",
+ strlen("service cputime-stats")) == 0 ||
+ strncmp(line, "no service cputime-stats",
+ strlen("no service cputime-stats")) == 0 ||
+ strncmp(line, "service cputime-warning",
+ strlen("service cputime-warning")) == 0)
+ config_add_line_uniq(config_top, line);
+ else
+ config_add_line(config_top, line);
+ config = NULL;
+ }
+ break;
+ }
+}
+
+/* Macro to check delimiter is needed between each configuration line
+ * or not. */
+#define NO_DELIMITER(I) \
+ ((I) == ACCESS_NODE || (I) == PREFIX_NODE || (I) == IP_NODE \
+ || (I) == AS_LIST_NODE || (I) == COMMUNITY_LIST_NODE \
+ || (I) == COMMUNITY_ALIAS_NODE || (I) == ACCESS_IPV6_NODE \
+ || (I) == ACCESS_MAC_NODE || (I) == PREFIX_IPV6_NODE \
+ || (I) == FORWARDING_NODE || (I) == DEBUG_NODE || (I) == AAA_NODE \
+ || (I) == VRF_DEBUG_NODE || (I) == NORTHBOUND_DEBUG_NODE \
+ || (I) == RMAP_DEBUG_NODE || (I) == RESOLVER_DEBUG_NODE \
+ || (I) == MPLS_NODE || (I) == KEYCHAIN_KEY_NODE)
+
+static void configvec_dump(vector vec, bool nested)
+{
+ struct listnode *mnode, *mnnode;
+ struct config *config;
+ struct configuration *configuration;
+ char *line;
+ unsigned int i;
+
+ for (i = 0; i < vector_active(vec); i++)
+ if ((configuration = vector_slot(vec, i)) != NULL) {
+ while ((config = config_master_pop(
+ &configuration->master))) {
+ config_master_hash_del(
+ &configuration->hash_master, config);
+ /* Don't print empty sections for interface.
+ * Route maps on the
+ * other hand could have a legitimate empty
+ * section at the end.
+ * VRF is handled in the backend, we could have
+ * "configured" VRFs with static routes which
+ * are not under the VRF node.
+ */
+ if (config->index == INTERFACE_NODE
+ && (listcount(config->line) == 1)
+ && (line = listnode_head(config->line))
+ && strmatch(line, "exit")) {
+ config_del(config);
+ continue;
+ }
+
+ vty_out(vty, "%s\n", config->name);
+
+ for (ALL_LIST_ELEMENTS(config->line, mnode,
+ mnnode, line))
+ vty_out(vty, "%s\n", line);
+
+ configvec_dump(config->nested, true);
+
+ if (config->exit)
+ vty_out(vty, "%s\n", config->exit);
+
+ if (!NO_DELIMITER(i))
+ vty_out(vty, "!\n");
+
+ config_del(config);
+ }
+ config_master_fini(&configuration->master);
+ config_master_hash_fini(&configuration->hash_master);
+ XFREE(MTYPE_VTYSH_CONFIG, configuration);
+ vector_slot(vec, i) = NULL;
+ if (!nested && NO_DELIMITER(i))
+ vty_out(vty, "!\n");
+ }
+}
+
+void vtysh_config_dump(void)
+{
+ struct listnode *node, *nnode;
+ char *line;
+
+ for (ALL_LIST_ELEMENTS(config_top, node, nnode, line))
+ vty_out(vty, "%s\n", line);
+
+ list_delete_all_node(config_top);
+
+ vty_out(vty, "!\n");
+
+ configvec_dump(configvec, false);
+}
+
+/* Read up configuration file from file_name. */
+static int vtysh_read_file(FILE *confp, bool dry_run)
+{
+ struct vty *vty;
+ int ret;
+
+ vty = vty_new();
+ vty->wfd = STDERR_FILENO;
+ vty->type = VTY_TERM;
+ vty->node = CONFIG_NODE;
+
+ vtysh_execute_no_pager("enable");
+ vtysh_execute_no_pager("configure terminal");
+
+ if (!dry_run)
+ vtysh_execute_no_pager("XFRR_start_configuration");
+
+ /* Execute configuration file. */
+ ret = vtysh_config_from_file(vty, confp);
+
+ if (!dry_run)
+ vtysh_execute_no_pager("XFRR_end_configuration");
+
+ vtysh_execute_no_pager("end");
+ vtysh_execute_no_pager("disable");
+
+ vty_close(vty);
+
+ return (ret);
+}
+
+/* Read up configuration file from config_default_dir. */
+int vtysh_read_config(const char *config_default_dir, bool dry_run)
+{
+ FILE *confp = NULL;
+ bool save;
+ int ret;
+
+ confp = fopen(config_default_dir, "r");
+ if (confp == NULL) {
+ fprintf(stderr,
+ "%% Can't open configuration file %s due to '%s'.\n",
+ config_default_dir, safe_strerror(errno));
+ return CMD_ERR_NO_FILE;
+ }
+
+ save = vtysh_add_timestamp;
+ vtysh_add_timestamp = false;
+
+ ret = vtysh_read_file(confp, dry_run);
+ fclose(confp);
+
+ vtysh_add_timestamp = save;
+
+ return (ret);
+}
+
+/* We don't write vtysh specific into file from vtysh. vtysh.conf should
+ * be edited by hand. So, we handle only "write terminal" case here and
+ * integrate vtysh specific conf with conf from daemons.
+ */
+void vtysh_config_write(void)
+{
+ char line[512];
+
+ if (cmd_hostname_get()) {
+ snprintf(line, sizeof(line), "hostname %s", cmd_hostname_get());
+ vtysh_config_parse_line(NULL, line);
+ }
+
+ if (cmd_domainname_get()) {
+ snprintf(line, sizeof(line), "domainname %s",
+ cmd_domainname_get());
+ vtysh_config_parse_line(NULL, line);
+ }
+ if (vtysh_write_integrated == WRITE_INTEGRATED_NO)
+ vtysh_config_parse_line(NULL,
+ "no service integrated-vtysh-config");
+ if (vtysh_write_integrated == WRITE_INTEGRATED_YES)
+ vtysh_config_parse_line(NULL,
+ "service integrated-vtysh-config");
+
+ user_config_write();
+}
+
+void vtysh_config_init(void)
+{
+ config_top = list_new();
+ config_top->del = (void (*)(void *))line_del;
+ configvec = vector_init(1);
+}