]> git.proxmox.com Git - mirror_frr.git/commitdiff
lib: introduce new northbound API
authorRenato Westphal <renato@opensourcerouting.org>
Thu, 7 Dec 2017 19:31:48 +0000 (17:31 -0200)
committerRenato Westphal <renato@opensourcerouting.org>
Sat, 27 Oct 2018 18:16:12 +0000 (16:16 -0200)
Signed-off-by: Renato Westphal <renato@opensourcerouting.org>
46 files changed:
.clang-format
Makefile.am
bgpd/bgp_main.c
configure.ac
doc/user/basic.rst
doc/user/installation.rst
ldpd/ldpd.c
lib/command.c
lib/command.h
lib/db.c [new file with mode: 0644]
lib/db.h [new file with mode: 0644]
lib/lib_errors.c
lib/lib_errors.h
lib/libfrr.c
lib/libfrr.h
lib/northbound.c [new file with mode: 0644]
lib/northbound.h [new file with mode: 0644]
lib/northbound_cli.c [new file with mode: 0644]
lib/northbound_cli.h [new file with mode: 0644]
lib/northbound_db.c [new file with mode: 0644]
lib/northbound_db.h [new file with mode: 0644]
lib/subdir.am
lib/vty.c
lib/vty.h
lib/yang.c [new file with mode: 0644]
lib/yang.h [new file with mode: 0644]
lib/yang_translator.c [new file with mode: 0644]
lib/yang_translator.h [new file with mode: 0644]
lib/yang_wrappers.c [new file with mode: 0644]
lib/yang_wrappers.h [new file with mode: 0644]
ripd/rip_main.c
ripngd/ripng_main.c
tests/bgpd/test_peer_attr.c
tests/helpers/c/main.c
tests/lib/cli/common_cli.c
tests/lib/cli/test_cli.refout.in
tests/lib/cli/test_commands.c
tools/.gitignore
tools/gen_northbound_callbacks.c [new file with mode: 0644]
tools/gen_yang_deviations.c [new file with mode: 0644]
tools/subdir.am
vtysh/vtysh_config.c
yang/frr-module-translator.yang [new file with mode: 0644]
yang/libyang_plugins/frr_user_types.c [new file with mode: 0644]
yang/libyang_plugins/subdir.am [new file with mode: 0644]
yang/subdir.am [new file with mode: 0644]

index 3c6a2784ceffb8abc58870d080f6cc3252cce669..a65a29f8c995c7aea022a6f998e11fb309b693d6 100644 (file)
@@ -44,6 +44,9 @@ ForEachMacros:
   - FOR_ALL_INTERFACES
   - FOR_ALL_INTERFACES_ADDRESSES
   - JSON_FOREACH
+  # libyang
+  - LY_TREE_FOR
+  - LY_TREE_DFS_BEGIN
   # zebra
   - RE_DEST_FOREACH_ROUTE
   - RE_DEST_FOREACH_ROUTE_SAFE
index 025afa5652433adac4db50b0467c9232a4ccfb92..d12d45264500d691fb11396be5c9c81bfccaf786 100644 (file)
@@ -95,9 +95,11 @@ noinst_LIBRARIES =
 nodist_noinst_DATA =
 lib_LTLIBRARIES =
 module_LTLIBRARIES =
+libyang_plugins_LTLIBRARIES =
 pkginclude_HEADERS =
 nodist_pkginclude_HEADERS =
 dist_examples_DATA =
+dist_yangmodels_DATA =
 man_MANS =
 vtysh_scan =
 
@@ -108,6 +110,7 @@ vtysh_scan =
 $(AUTOMAKE_DUMMY)install-moduleLTLIBRARIES: install-libLTLIBRARIES
 $(AUTOMAKE_DUMMY)install-binPROGRAMS: install-libLTLIBRARIES
 $(AUTOMAKE_DUMMY)install-sbinPROGRAMS: install-libLTLIBRARIES
+$(AUTOMAKE_DUMMY)install-libyang_pluginsLTLIBRARIES: install-libLTLIBRARIES
 
 include doc/subdir.am
 include doc/user/subdir.am
@@ -141,6 +144,8 @@ include pimd/subdir.am
 include pbrd/subdir.am
 include staticd/subdir.am
 include bfdd/subdir.am
+include yang/subdir.am
+include yang/libyang_plugins/subdir.am
 
 include vtysh/subdir.am
 include tests/subdir.am
index 503c3f9f88ba75917b212d5bcb3ee06ea504a9bc..bf0c6a42e64b82c45a7416e103ff23eea1a09b00 100644 (file)
@@ -137,7 +137,7 @@ void sighup(void)
        zlog_info("bgpd restarting!");
 
        /* Reload config file. */
-       vty_read_config(bgpd_di.config_file, config_default);
+       vty_read_config(NULL, bgpd_di.config_file, config_default);
 
        /* Try to return to normal operation. */
 }
index 5ebe188c19b94c458ca3673b6345322061374018..3053532a9feba227c719375cb60cd3c1f9bffc36 100755 (executable)
@@ -91,6 +91,12 @@ AC_ARG_WITH([moduledir], [AS_HELP_STRING([--with-moduledir=DIR], [module directo
 ])
 AC_SUBST([moduledir], [$moduledir])
 
+yangmodelsdir="\${datarootdir}/yang"
+AC_SUBST([yangmodelsdir], [$yangmodelsdir])
+
+libyang_pluginsdir="\${libdir}/frr/libyang_plugins"
+AC_SUBST(libyang_pluginsdir)
+
 AC_ARG_ENABLE(tcmalloc,
        AS_HELP_STRING([--enable-tcmalloc], [Turn on tcmalloc]),
 [case "${enableval}" in
@@ -417,6 +423,8 @@ AC_ARG_ENABLE(bgp-vnc,
   AS_HELP_STRING([--disable-bgp-vnc],[turn off BGP VNC support]))
 AC_ARG_ENABLE(snmp,
   AS_HELP_STRING([--enable-snmp], [enable SNMP support for agentx]))
+AC_ARG_ENABLE(config_rollbacks,
+  AS_HELP_STRING([--enable-config-rollbacks], [enable configuration rollbacks (requires sqlite3)]))
 AC_ARG_ENABLE(zeromq,
   AS_HELP_STRING([--enable-zeromq], [enable ZeroMQ handler (libfrrzmq)]))
 AC_ARG_WITH(libpam,
@@ -1563,6 +1571,28 @@ AM_CONDITIONAL([SNMP], [test "x${SNMP_METHOD}" != "x"])
 AC_SUBST(SNMP_LIBS)
 AC_SUBST(SNMP_CFLAGS)
 
+dnl ---------------
+dnl libyang
+dnl ---------------
+PKG_CHECK_MODULES(libyang, [libyang >= 0.16.7], , [
+  AC_MSG_ERROR([libyang (>= 0.16.7) was not found on your system.])
+])
+
+dnl ---------------
+dnl configuration rollbacks
+dnl ---------------
+SQLITE3=false
+if test "$enable_config_rollbacks" = "yes"; then
+  PKG_CHECK_MODULES(sqlite3,[sqlite3], [
+    AC_DEFINE(HAVE_CONFIG_ROLLBACKS,1,Enable configuration rollbacks)
+    AC_DEFINE(HAVE_SQLITE3,1,Enable sqlite3 database)
+    SQLITE3=true
+  ], [
+    AC_MSG_ERROR([--enable-config-rollbacks given but sqlite3 was not found on your system.])
+  ])
+fi
+AM_CONDITIONAL(SQLITE3, $SQLITE3)
+
 dnl ---------------
 dnl math
 dnl ---------------
@@ -2004,6 +2034,7 @@ AC_DEFINE_UNQUOTED(LDPD_SOCKET, "$frr_statedir/ldpd.sock",ldpd control socket)
 AC_DEFINE_UNQUOTED(ZEBRA_SERV_PATH, "$frr_statedir/zserv.api",zebra api socket)
 AC_DEFINE_UNQUOTED(BFDD_CONTROL_SOCKET, "$frr_statedir/bfdd.sock", bfdd control socket)
 AC_DEFINE_UNQUOTED(DAEMON_VTY_DIR, "$frr_statedir",daemon vty directory)
+AC_DEFINE_UNQUOTED(DAEMON_DB_DIR, "$frr_statedir",daemon database directory)
 
 dnl autoconf does this, but it does it too late...
 test "x$prefix" = xNONE && prefix=$ac_default_prefix
@@ -2021,17 +2052,25 @@ CFG_SYSCONF="$sysconfdir"
 CFG_SBIN="$sbindir"
 CFG_STATE="$frr_statedir"
 CFG_MODULE="$moduledir"
+CFG_YANGMODELS="$yangmodelsdir"
+CFG_LIBYANG_PLUGINS="$libyang_pluginsdir"
 for I in 1 2 3 4 5 6 7 8 9 10; do
        eval CFG_SYSCONF="\"$CFG_SYSCONF\""
        eval CFG_SBIN="\"$CFG_SBIN\""
        eval CFG_STATE="\"$CFG_STATE\""
        eval CFG_MODULE="\"$CFG_MODULE\""
+       eval CFG_YANGMODELS="\"$CFG_YANGMODELS\""
+       eval CFG_LIBYANG_PLUGINS="\"$CFG_LIBYANG_PLUGINS\""
 done
 AC_SUBST(CFG_SYSCONF)
 AC_SUBST(CFG_SBIN)
 AC_SUBST(CFG_STATE)
 AC_SUBST(CFG_MODULE)
+AC_SUBST(CFG_YANGMODELS)
+AC_SUBST(CFG_LIBYANG_PLUGINS)
 AC_DEFINE_UNQUOTED(MODULE_PATH, "$CFG_MODULE", path to modules)
+AC_DEFINE_UNQUOTED(YANG_MODELS_PATH, "$CFG_YANGMODELS", path to YANG data models)
+AC_DEFINE_UNQUOTED(LIBYANG_PLUGINS_PATH, "$CFG_LIBYANG_PLUGINS", path to libyang plugins)
 
 dnl ------------------------------------
 dnl Enable RPKI and add librtr to libs
index da22bb2f861f24be6247e8a27b283eb00d30b27e..8201fdf1b91b076d71e88a4afe7c95652dd69b62 100644 (file)
@@ -478,6 +478,10 @@ These options apply to all |PACKAGE_NAME| daemons.
    When initializing the daemon, allow the specification of a default
    log level at startup from one of the specified levels.
 
+.. option:: --tcli
+
+   Enable the transactional CLI mode.
+
 .. _loadable-module-support:
 
 Loadable Module Support
index da431916aec69a513ec14ef731899a44b3a0971f..59ae5830e1381032a5855b669fa86ec8d73b2d83 100644 (file)
@@ -222,6 +222,10 @@ options from the list below.
    the COMMIT (git hash) and TOKEN (codecov upload token) environment variables
    be set.
 
+.. option:: --enable-config-rollbacks
+
+   Build with configuration rollback support. Requires SQLite3.
+
 You may specify any combination of the above options to the configure
 script. By default, the executables are placed in :file:`/usr/local/sbin`
 and the configuration files in :file:`/usr/local/etc`. The :file:`/usr/local/`
index 137d9622d545d4a612940b494f06d68779237f1a..c611ec12adf18670d6d727d32693f2a1faca9c31 100644 (file)
@@ -138,7 +138,7 @@ sighup(void)
         * and build a new configuartion from scratch.
         */
        ldp_config_reset(vty_conf);
-       vty_read_config(ldpd_di.config_file, config_default);
+       vty_read_config(NULL, ldpd_di.config_file, config_default);
        ldp_config_apply(NULL, vty_conf);
 }
 
index 127e1a0a0ad2a82e2016f4eba20b919b4871921c..bd000c37465d1f7859edf80449f41703a0875ba2 100644 (file)
@@ -46,6 +46,7 @@
 #include "jhash.h"
 #include "hook.h"
 #include "lib_errors.h"
+#include "northbound_cli.h"
 
 DEFINE_MTYPE(LIB, HOST, "Host config")
 DEFINE_MTYPE(LIB, COMPLETION, "Completion item")
@@ -81,6 +82,7 @@ const char *node_names[] = {
        "config",                   // CONFIG_NODE,
        "debug",                    // DEBUG_NODE,
        "vrf debug",                // VRF_DEBUG_NODE,
+       "northbound debug",         // NORTHBOUND_DEBUG_NODE,
        "vnc debug",                // DEBUG_VNC_NODE,
        "aaa",                      // AAA_NODE,
        "keychain",                 // KEYCHAIN_NODE,
@@ -718,11 +720,14 @@ vector cmd_describe_command(vector vline, struct vty *vty, int *status)
 
        if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
                enum node_type onode;
+               int orig_xpath_index;
                vector shifted_vline;
                unsigned int index;
 
                onode = vty->node;
+               orig_xpath_index = vty->xpath_index;
                vty->node = ENABLE_NODE;
+               vty->xpath_index = 0;
                /* We can try it on enable node, cos' the vty is authenticated
                 */
 
@@ -737,6 +742,7 @@ vector cmd_describe_command(vector vline, struct vty *vty, int *status)
 
                vector_free(shifted_vline);
                vty->node = onode;
+               vty->xpath_index = orig_xpath_index;
                return ret;
        }
 
@@ -1075,14 +1081,17 @@ int cmd_execute_command(vector vline, struct vty *vty,
 {
        int ret, saved_ret = 0;
        enum node_type onode, try_node;
+       int orig_xpath_index;
 
        onode = try_node = vty->node;
+       orig_xpath_index = vty->xpath_index;
 
        if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
                vector shifted_vline;
                unsigned int index;
 
                vty->node = ENABLE_NODE;
+               vty->xpath_index = 0;
                /* We can try it on enable node, cos' the vty is authenticated
                 */
 
@@ -1097,6 +1106,7 @@ int cmd_execute_command(vector vline, struct vty *vty,
 
                vector_free(shifted_vline);
                vty->node = onode;
+               vty->xpath_index = orig_xpath_index;
                return ret;
        }
 
@@ -1113,6 +1123,8 @@ int cmd_execute_command(vector vline, struct vty *vty,
                while (vty->node > CONFIG_NODE) {
                        try_node = node_parent(try_node);
                        vty->node = try_node;
+                       if (vty->xpath_index > 0)
+                               vty->xpath_index--;
                        ret = cmd_execute_command_real(vline, FILTER_RELAXED,
                                                       vty, cmd);
                        if (ret == CMD_SUCCESS || ret == CMD_WARNING
@@ -1122,6 +1134,7 @@ int cmd_execute_command(vector vline, struct vty *vty,
                }
                /* no command succeeded, reset the vty to the original node */
                vty->node = onode;
+               vty->xpath_index = orig_xpath_index;
        }
 
        /* return command status for original node */
@@ -1285,7 +1298,6 @@ int command_config_read_one_line(struct vty *vty,
                                 uint32_t line_num, int use_daemon)
 {
        vector vline;
-       int saved_node;
        int ret;
 
        vline = cmd_make_strvec(vty->buf);
@@ -1303,14 +1315,16 @@ int command_config_read_one_line(struct vty *vty,
            && ret != CMD_SUCCESS && ret != CMD_WARNING
            && ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED
            && vty->node != CONFIG_NODE) {
-
-               saved_node = vty->node;
+               int saved_node = vty->node;
+               int saved_xpath_index = vty->xpath_index;
 
                while (!(use_daemon && ret == CMD_SUCCESS_DAEMON)
                       && !(!use_daemon && ret == CMD_ERR_NOTHING_TODO)
                       && ret != CMD_SUCCESS && ret != CMD_WARNING
                       && vty->node > CONFIG_NODE) {
                        vty->node = node_parent(vty->node);
+                       if (vty->xpath_index > 0)
+                               vty->xpath_index--;
                        ret = cmd_execute_command_strict(vline, vty, cmd);
                }
 
@@ -1320,6 +1334,7 @@ int command_config_read_one_line(struct vty *vty,
                    && !(!use_daemon && ret == CMD_ERR_NOTHING_TODO)
                    && ret != CMD_SUCCESS && ret != CMD_WARNING) {
                        vty->node = saved_node;
+                       vty->xpath_index = saved_xpath_index;
                }
        }
 
@@ -1377,6 +1392,12 @@ DEFUN (config_terminal,
                vty_out(vty, "VTY configuration is locked by other VTY\n");
                return CMD_WARNING_CONFIG_FAILED;
        }
+
+       vty->private_config = false;
+       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;
 }
 
@@ -1500,6 +1521,9 @@ void cmd_exit(struct vty *vty)
        default:
                break;
        }
+
+       if (vty->xpath_index > 0)
+               vty->xpath_index--;
 }
 
 /* ALIAS_FIXME */
@@ -1576,6 +1600,9 @@ DEFUN (config_end,
        default:
                break;
        }
+
+       vty->xpath_index = 0;
+
        return CMD_SUCCESS;
 }
 
@@ -2785,6 +2812,8 @@ void install_default(enum node_type node)
        install_element(node, &show_running_config_cmd);
 
        install_element(node, &autocomplete_cmd);
+
+       nb_cli_install_default(node);
 }
 
 /* Initialize command interface. Install basic nodes and commands.
index de65c8bd985cfd7ac3895a4d8af8f755dae258b9..873ecdda98a0084a5ee9dc3e944debed9aff7c9c 100644 (file)
@@ -77,6 +77,7 @@ enum node_type {
        CONFIG_NODE,             /* Config node. Default mode of config file. */
        DEBUG_NODE,              /* Debug node. */
        VRF_DEBUG_NODE,          /* Vrf Debug node. */
+       NORTHBOUND_DEBUG_NODE,   /* Northbound Debug node. */
        DEBUG_VNC_NODE,          /* Debug VNC node. */
        AAA_NODE,                /* AAA node. */
        KEYCHAIN_NODE,           /* Key-chain node. */
diff --git a/lib/db.c b/lib/db.c
new file mode 100644 (file)
index 0000000..32ba83b
--- /dev/null
+++ b/lib/db.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2018 Rafael Zalamena <rzalamena@gmail.com>
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+/*
+ * Copyright (c) 2016 Rafael Zalamena <rzalamena@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <zebra.h>
+
+#include "db.h"
+#include "log.h"
+
+static struct sqlite3 *dbp;
+
+/*
+ * Initialize the database in path.
+ *
+ * It's possible to use in memory database with ':memory:' path.
+ */
+int db_init(const char *path_fmt, ...)
+{
+       char path[BUFSIZ];
+       va_list ap;
+
+       if (dbp)
+               return -1;
+
+       va_start(ap, path_fmt);
+       vsnprintf(path, sizeof(path), path_fmt, ap);
+       va_end(ap);
+
+       if (sqlite3_open_v2(path, &dbp,
+                           (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE), NULL)
+           != SQLITE_OK) {
+               if (dbp == NULL) {
+                       zlog_warn("%s: failed to open dabatase '%s'", __func__,
+                                 path);
+                       return -1;
+               }
+
+               zlog_warn("%s: failed to open database '%s': %s", __func__,
+                         path, sqlite3_errmsg(dbp));
+               if (sqlite3_close_v2(dbp) != SQLITE_OK)
+                       zlog_warn("%s: failed to terminate database", __func__);
+               dbp = NULL;
+               return -1;
+       }
+
+       return 0;
+}
+
+/* Closes the database if open. */
+int db_close(void)
+{
+       if (dbp == NULL)
+               return 0;
+
+       if (sqlite3_close_v2(dbp) != SQLITE_OK) {
+               zlog_warn("%s: failed to terminate database", __func__);
+               return -1;
+       }
+       return 0;
+}
+
+/* Helper function to handle formating. */
+static int db_vbindf(struct sqlite3_stmt *ss, const char *fmt, va_list vl)
+{
+       const char *sptr = fmt;
+       int column = 1;
+       const char *str;
+       void *blob;
+       uint64_t uinteger64;
+       uint32_t uinteger;
+       int vlen;
+
+       while (*sptr) {
+               if (*sptr != '%') {
+                       sptr++;
+                       continue;
+               }
+               if (sptr++ && *sptr == 0)
+                       break;
+
+               switch (*sptr) {
+               case 'i':
+                       uinteger = va_arg(vl, uint32_t);
+                       if (sqlite3_bind_int(ss, column++, uinteger)
+                           != SQLITE_OK)
+                               return -1;
+                       break;
+               case 'd':
+                       uinteger64 = va_arg(vl, uint64_t);
+                       if (sqlite3_bind_int64(ss, column++, uinteger64)
+                           != SQLITE_OK)
+                               return -1;
+                       break;
+               case 's':
+                       str = va_arg(vl, const char *);
+                       vlen = va_arg(vl, int);
+                       if (sqlite3_bind_text(ss, column++, str, vlen,
+                                             SQLITE_STATIC)
+                           != SQLITE_OK)
+                               return -1;
+                       break;
+               case 'b':
+                       blob = va_arg(vl, void *);
+                       vlen = va_arg(vl, int);
+                       if (sqlite3_bind_blob(ss, column++, blob, vlen,
+                                             SQLITE_STATIC)
+                           != SQLITE_OK)
+                               return -1;
+                       break;
+               case 'n':
+                       if (sqlite3_bind_null(ss, column++) != SQLITE_OK)
+                               return -1;
+                       break;
+               default:
+                       zlog_warn("%s: invalid format '%c'", __func__, *sptr);
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * Binds values using format to the database query.
+ *
+ * Might be used to bind variables to a query, insert or update.
+ */
+int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...)
+{
+       va_list vl;
+       int result;
+
+       va_start(vl, fmt);
+       result = db_vbindf(ss, fmt, vl);
+       va_end(vl);
+
+       return result;
+}
+
+/* Prepares an statement to the database with the statement length. */
+struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen)
+{
+       struct sqlite3_stmt *ss;
+       int c;
+
+       if (dbp == NULL)
+               return NULL;
+
+       c = sqlite3_prepare_v2(dbp, stmt, stmtlen, &ss, NULL);
+       if (ss == NULL) {
+               zlog_warn("%s: failed to prepare (%d:%s)", __func__, c,
+                         sqlite3_errmsg(dbp));
+               return NULL;
+       }
+
+       return ss;
+}
+
+/* Prepares an statement to the database. */
+struct sqlite3_stmt *db_prepare(const char *stmt)
+{
+       return db_prepare_len(stmt, strlen(stmt));
+}
+
+/* Run a prepared statement. */
+int db_run(struct sqlite3_stmt *ss)
+{
+       int result;
+
+       result = sqlite3_step(ss);
+       switch (result) {
+       case SQLITE_BUSY:
+               /* TODO handle busy database. */
+               break;
+
+       case SQLITE_OK:
+       /*
+        * SQLITE_DONE just causes confusion since it means the query went OK,
+        * but it has a different value.
+        */
+       case SQLITE_DONE:
+               result = SQLITE_OK;
+               break;
+
+       case SQLITE_ROW:
+               /* NOTHING */
+               /* It is expected to receive SQLITE_ROW on search queries. */
+               break;
+
+       default:
+               zlog_warn("%s: step failed (%d:%s)", __func__, result,
+                         sqlite3_errstr(result));
+       }
+
+       return result;
+}
+
+/* Helper function to load format to variables. */
+static int db_vloadf(struct sqlite3_stmt *ss, const char *fmt, va_list vl)
+{
+       const char *sptr = fmt;
+       int column = 0;
+       const char **str;
+       void *blob;
+       const void *blobsrc;
+       uint64_t *uinteger64;
+       uint32_t *uinteger;
+       int vlen;
+       int dlen;
+       int columncount;
+
+       columncount = sqlite3_column_count(ss);
+       if (columncount == 0)
+               return -1;
+
+       while (*sptr) {
+               if (*sptr != '%') {
+                       sptr++;
+                       continue;
+               }
+               if (sptr++ && *sptr == 0)
+                       break;
+
+               switch (*sptr) {
+               case 'i':
+                       uinteger = va_arg(vl, uint32_t *);
+                       *uinteger = sqlite3_column_int(ss, column);
+                       break;
+               case 'd':
+                       uinteger64 = va_arg(vl, uint64_t *);
+                       *uinteger64 = sqlite3_column_int64(ss, column);
+                       break;
+               case 's':
+                       str = va_arg(vl, const char **);
+                       *str = (const char *)sqlite3_column_text(ss, column);
+                       break;
+               case 'b':
+                       blob = va_arg(vl, void *);
+                       vlen = va_arg(vl, int);
+                       dlen = sqlite3_column_bytes(ss, column);
+                       blobsrc = sqlite3_column_blob(ss, column);
+                       memcpy(blob, blobsrc, MIN(vlen, dlen));
+                       break;
+               default:
+                       zlog_warn("%s: invalid format '%c'", __func__, *sptr);
+                       return -1;
+               }
+
+               column++;
+       }
+
+       return 0;
+}
+
+/* Function to load format from database row. */
+int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...)
+{
+       va_list vl;
+       int result;
+
+       va_start(vl, fmt);
+       result = db_vloadf(ss, fmt, vl);
+       va_end(vl);
+
+       return result;
+}
+
+/* Finalize query and return memory. */
+void db_finalize(struct sqlite3_stmt **ss)
+{
+       sqlite3_finalize(*ss);
+       *ss = NULL;
+}
+
+/* Execute one or more statements. */
+int db_execute(const char *stmt_fmt, ...)
+{
+       char stmt[BUFSIZ];
+       va_list ap;
+
+       if (dbp == NULL)
+               return -1;
+
+       va_start(ap, stmt_fmt);
+       vsnprintf(stmt, sizeof(stmt), stmt_fmt, ap);
+       va_end(ap);
+
+       if (sqlite3_exec(dbp, stmt, NULL, 0, NULL) != SQLITE_OK) {
+               zlog_warn("%s: failed to execute statement(s): %s", __func__,
+                         sqlite3_errmsg(dbp));
+               return -1;
+       }
+
+       return 0;
+}
diff --git a/lib/db.h b/lib/db.h
new file mode 100644 (file)
index 0000000..8c936ec
--- /dev/null
+++ b/lib/db.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018 Rafael Zalamena <rzalamena@gmail.com>
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+/*
+ * Copyright (c) 2016 Rafael Zalamena <rzalamena@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _FRR_DB_H_
+#define _FRR_DB_H_
+#ifdef HAVE_SQLITE3
+
+#include <sqlite3.h>
+
+extern int db_init(const char *path_fmt, ...);
+extern int db_close(void);
+extern int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...);
+extern struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen);
+extern struct sqlite3_stmt *db_prepare(const char *stmt);
+extern int db_run(struct sqlite3_stmt *ss);
+extern int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...);
+extern void db_finalize(struct sqlite3_stmt **ss);
+extern int db_execute(const char *stmt_fmt, ...);
+
+#endif /* HAVE_SQLITE3 */
+#endif /* _FRR_DB_H_ */
index ad83ef1e5182109657c6c04eb82d6d81019cf621..71d1ec6e58fb87284f3a494789f5527790e65ecc 100644 (file)
@@ -68,6 +68,84 @@ static struct log_ref ferr_lib_warn[] = {
                .description = "The VRF subsystem, during initialization, has found a parsing error with input it has received",
                .suggestion = "Check the length of the vrf name and adjust accordingly",
        },
+       {
+               .code = EC_LIB_YANG_DATA_TRUNCATED,
+               .title = "YANG data truncation",
+               .description = "The northbound subsystem has detected that YANG data has been truncated as the given buffer wasn't big enough",
+               .suggestion = "Gather log data and open an Issue",
+       },
+       {
+               .code = EC_LIB_YANG_UNKNOWN_DATA_PATH,
+               .title = "Unknown YANG data path",
+               .description = "The northbound subsystem has detected an unknown YANG data path",
+               .suggestion = "Gather log data and open an Issue",
+       },
+       {
+               .code = EC_LIB_YANG_TRANSLATOR_LOAD,
+               .title = "Unable to load YANG module translator",
+               .description = "The northbound subsystem has detected an error while loading a YANG module translator",
+               .suggestion = "Ensure the YANG module translator file is valid. See documentation for further information.",
+       },
+       {
+               .code = EC_LIB_YANG_TRANSLATION_ERROR,
+               .title = "YANG translation error",
+               .description = "The northbound subsystem has detected an error while performing a YANG XPath translation",
+               .suggestion = "Gather log data and open an Issue",
+       },
+       {
+               .code = EC_LIB_NB_DATABASE,
+               .title = "The northbound database wasn't initialized correctly",
+               .description = "An error occurred while initializing the northbound database. As a result, the configuration rollback feature won't work as expected.",
+               .suggestion = "Ensure permissions are correct for FRR files, users and groups are correct."
+       },
+       {
+               .code = EC_LIB_NB_CB_UNNEEDED,
+               .title = "Unneeded northbound callback",
+               .description = "The northbound subsystem, during initialization, has detected a callback that doesn't need to be implemented",
+               .suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
+       },
+       {
+               .code = EC_LIB_NB_CB_CONFIG,
+               .title = "A northbound configuration callback has failed",
+               .description = "The northbound subsystem has detected that a callback used to process a configuration change has returned an error",
+               .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.",
+       },
+       {
+               .code = EC_LIB_NB_CB_STATE,
+               .title = "A northbound callback for operational data has failed",
+               .description = "The northbound subsystem has detected that a callback used to fetch operational data has returned an error",
+               .suggestion = "Gather log data and open an Issue",
+       },
+       {
+               .code = EC_LIB_NB_CB_RPC,
+               .title = "A northbound RPC callback has failed",
+               .description = "The northbound subsystem has detected that a callback used to process YANG RPCs/actions has returned an error",
+               .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.",
+       },
+       {
+               .code = EC_LIB_NB_CANDIDATE_INVALID,
+               .title = "Invalid candidate configuration",
+               .description = "The northbound subsystem failed to validate a candidate configuration",
+               .suggestion = "Check the log messages to see the validation errors and edit the candidate configuration to fix them",
+       },
+       {
+               .code = EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+               .title = "Failure to edit a candidate configuration",
+               .description = "The northbound subsystem failed to edit a candidate configuration",
+               .suggestion = "This is a bug; please report it"
+       },
+       {
+               .code = EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+               .title = "Failure to create a configuration transaction",
+               .description = "The northbound subsystem failed to create a configuration transaction",
+               .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.",
+       },
+       {
+               .code = EC_LIB_NB_TRANSACTION_RECORD_FAILED,
+               .title = "Failure to record a configuration transaction",
+               .description = "The northbound subsystem failed to record a configuration transaction in the northbound database",
+               .suggestion = "Gather log data and open an Issue",
+       },
        {
                .code = END_FERR,
        },
@@ -152,6 +230,96 @@ static struct log_ref ferr_lib_err[] = {
                .description = "FRR was not compiled with support for a particular feature, or it is not available on the current platform",
                .suggestion = "Recompile FRR with the feature enabled, or find out what platforms support the feature"
        },
+       {
+               .code = EC_LIB_YANG_MODULE_LOAD,
+               .title = "Unable to load YANG module from the file system",
+               .description = "The northbound subsystem has detected an error while loading a YANG module from the file system",
+               .suggestion = "Ensure all FRR YANG modules were installed correctly in the system.",
+       },
+       {
+               .code = EC_LIB_YANG_MODULE_LOADED_ALREADY,
+               .title = "Attempt to load a YANG module that is already loaded",
+               .description = "The northbound subsystem has detected an attempt to load a YANG module that is already loaded",
+               .suggestion = "This is a bug; please report it"
+       },
+       {
+               .code = EC_LIB_YANG_DATA_CONVERT,
+               .title = "YANG data conversion error",
+               .description = "An error has occurred while converting a YANG data value from string to binary representation or vice-versa",
+               .suggestion = "Open an Issue with all relevant log files and restart FRR"
+       },
+       {
+               .code = EC_LIB_YANG_DNODE_NOT_FOUND,
+               .title = "YANG data node not found",
+               .description = "The northbound subsystem failed to find a YANG data node that was supposed to exist",
+               .suggestion = "This is a bug; please report it"
+       },
+       {
+               .code = EC_LIB_NB_CB_MISSING,
+               .title = "Missing northbound callback",
+               .description = "The northbound subsystem, during initialization, has detected a missing callback for one node of the loaded YANG modules",
+               .suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
+       },
+       {
+               .code = EC_LIB_NB_CB_INVALID_PRIO,
+               .title = "Norhtbound callback has an invalid priority",
+               .description = "The northbound subsystem, during initialization, has detected a callback whose priority is invalid",
+               .suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
+       },
+       {
+               .code = EC_LIB_NB_CBS_VALIDATION,
+               .title = "Failure to validate the northbound callbacks",
+               .description = "The northbound subsystem, during initialization, has detected one or more errors while loading the northbound callbacks",
+               .suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
+       },
+       {
+               .code = EC_LIB_LIBYANG,
+               .title = "The libyang library returned an error",
+               .description = "The northbound subsystem has detected that the libyang library returned an error",
+               .suggestion = "Open an Issue with all relevant log files and restart FRR"
+       },
+       {
+               .code = EC_LIB_LIBYANG_PLUGIN_LOAD,
+               .title = "Failure to load a libyang plugin",
+               .description = "The northbound subsystem, during initialization, has detected that a libyang plugin failed to be loaded",
+               .suggestion = "Check if the FRR libyang plugins were installed correctly in the system",
+       },
+       {
+               .code = EC_LIB_CONFD_INIT,
+               .title = "ConfD initialization error",
+               .description = "Upon startup FRR failed to properly initialize and startup the ConfD northbound plugin",
+               .suggestion = "Check if ConfD is installed correctly in the system. Also, check if the confd daemon is running.",
+       },
+       {
+               .code = EC_LIB_CONFD_DATA_CONVERT,
+               .title = "ConfD data conversion error",
+               .description = "An error has occurred while converting a ConfD data value (binary) to a string",
+               .suggestion = "Open an Issue with all relevant log files and restart FRR"
+       },
+       {
+               .code = EC_LIB_LIBCONFD,
+               .title = "libconfd error",
+               .description = "The northbound subsystem has detected that the libconfd library returned an error",
+               .suggestion = "Open an Issue with all relevant log files and restart FRR"
+       },
+       {
+               .code = EC_LIB_SYSREPO_INIT,
+               .title = "Sysrepo initialization error",
+               .description = "Upon startup FRR failed to properly initialize and startup the Sysrepo northbound plugin",
+               .suggestion = "Check if Sysrepo is installed correctly in the system",
+       },
+       {
+               .code = EC_LIB_SYSREPO_DATA_CONVERT,
+               .title = "Sysrepo data conversion error",
+               .description = "An error has occurred while converting a YANG data value to the Sysrepo format",
+               .suggestion = "Open an Issue with all relevant log files and restart FRR"
+       },
+       {
+               .code = EC_LIB_LIBSYSREPO,
+               .title = "libsysrepo error",
+               .description = "The northbound subsystem has detected that the libsysrepo library returned an error",
+               .suggestion = "Open an Issue with all relevant log files and restart FRR"
+       },
        {
                .code = END_FERR,
        }
index e0f698a07d81eece1ed0876624c40b2899f2bc1b..3831116dd38dc8ee275a3bfa7a9460a98a67b2df 100644 (file)
@@ -44,6 +44,34 @@ enum lib_log_refs {
        EC_LIB_RMAP_RECURSION_LIMIT,
        EC_LIB_BACKUP_CONFIG,
        EC_LIB_VRF_LENGTH,
+       EC_LIB_YANG_MODULE_LOAD,
+       EC_LIB_YANG_MODULE_LOADED_ALREADY,
+       EC_LIB_YANG_DATA_CONVERT,
+       EC_LIB_YANG_DATA_TRUNCATED,
+       EC_LIB_YANG_UNKNOWN_DATA_PATH,
+       EC_LIB_YANG_DNODE_NOT_FOUND,
+       EC_LIB_YANG_TRANSLATOR_LOAD,
+       EC_LIB_YANG_TRANSLATION_ERROR,
+       EC_LIB_NB_DATABASE,
+       EC_LIB_NB_CB_UNNEEDED,
+       EC_LIB_NB_CB_MISSING,
+       EC_LIB_NB_CB_INVALID_PRIO,
+       EC_LIB_NB_CBS_VALIDATION,
+       EC_LIB_NB_CB_CONFIG,
+       EC_LIB_NB_CB_STATE,
+       EC_LIB_NB_CB_RPC,
+       EC_LIB_NB_CANDIDATE_INVALID,
+       EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+       EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+       EC_LIB_NB_TRANSACTION_RECORD_FAILED,
+       EC_LIB_LIBYANG,
+       EC_LIB_LIBYANG_PLUGIN_LOAD,
+       EC_LIB_CONFD_INIT,
+       EC_LIB_CONFD_DATA_CONVERT,
+       EC_LIB_LIBCONFD,
+       EC_LIB_SYSREPO_INIT,
+       EC_LIB_SYSREPO_DATA_CONVERT,
+       EC_LIB_LIBSYSREPO,
 };
 
 extern void lib_error_init(void);
index 35a6da577f692ac2f4533ac6e4de248bc307c3fd..6ebe24eef7940b550f653141f6f66da27a629e47 100644 (file)
@@ -36,6 +36,8 @@
 #include "module.h"
 #include "network.h"
 #include "lib_errors.h"
+#include "db.h"
+#include "northbound_cli.h"
 
 DEFINE_HOOK(frr_late_init, (struct thread_master * tm), (tm))
 DEFINE_KOOH(frr_early_fini, (), ())
@@ -43,6 +45,9 @@ DEFINE_KOOH(frr_fini, (), ())
 
 const char frr_sysconfdir[] = SYSCONFDIR;
 const char frr_vtydir[] = DAEMON_VTY_DIR;
+#ifdef HAVE_SQLITE3
+const char frr_dbdir[] = DAEMON_DB_DIR;
+#endif
 const char frr_moduledir[] = MODULE_PATH;
 
 char frr_protoname[256] = "NONE";
@@ -51,6 +56,9 @@ char frr_protonameinst[256] = "NONE";
 char config_default[512];
 char frr_zclientpath[256];
 static char pidfile_default[512];
+#ifdef HAVE_SQLITE3
+static char dbfile_default[512];
+#endif
 static char vtypath_default[256];
 
 bool debug_memstats_at_exit = 0;
@@ -82,6 +90,8 @@ static void opt_extend(const struct optspec *os)
 #define OPTION_MODULEDIR 1002
 #define OPTION_LOG       1003
 #define OPTION_LOGLEVEL  1004
+#define OPTION_TCLI      1005
+#define OPTION_DB_FILE   1006
 
 static const struct option lo_always[] = {
        {"help", no_argument, NULL, 'h'},
@@ -92,6 +102,7 @@ static const struct option lo_always[] = {
        {"moduledir", required_argument, NULL, OPTION_MODULEDIR},
        {"log", required_argument, NULL, OPTION_LOG},
        {"log-level", required_argument, NULL, OPTION_LOGLEVEL},
+       {"tcli", no_argument, NULL, OPTION_TCLI},
        {NULL}};
 static const struct optspec os_always = {
        "hvdM:",
@@ -102,13 +113,17 @@ static const struct optspec os_always = {
        "      --vty_socket   Override vty socket path\n"
        "      --moduledir    Override modules directory\n"
        "      --log          Set Logging to stdout, syslog, or file:<name>\n"
-       "      --log-level    Set Logging Level to use, debug, info, warn, etc\n",
+       "      --log-level    Set Logging Level to use, debug, info, warn, etc\n"
+       "      --tcli         Use transaction-based CLI\n",
        lo_always};
 
 
 static const struct option lo_cfg_pid_dry[] = {
        {"pid_file", required_argument, NULL, 'i'},
        {"config_file", required_argument, NULL, 'f'},
+#ifdef HAVE_SQLITE3
+       {"db_file", required_argument, NULL, OPTION_DB_FILE},
+#endif
        {"pathspace", required_argument, NULL, 'N'},
        {"dryrun", no_argument, NULL, 'C'},
        {"terminal", no_argument, NULL, 't'},
@@ -117,6 +132,9 @@ static const struct optspec os_cfg_pid_dry = {
        "f:i:CtN:",
        "  -f, --config_file  Set configuration file name\n"
        "  -i, --pid_file     Set process identifier file name\n"
+#ifdef HAVE_SQLITE3
+       "      --db_file      Set database file name\n"
+#endif
        "  -N, --pathspace    Insert prefix into config & socket paths\n"
        "  -C, --dryrun       Check configuration for validity and exit\n"
        "  -t, --terminal     Open terminal session on stdio\n"
@@ -289,11 +307,17 @@ void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv)
                 frr_sysconfdir, di->name);
        snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid",
                 frr_vtydir, di->name);
+#ifdef HAVE_SQLITE3
+       snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s.db",
+                frr_dbdir, di->name);
+#endif
 
        strlcpy(frr_protoname, di->logname, sizeof(frr_protoname));
        strlcpy(frr_protonameinst, di->logname, sizeof(frr_protonameinst));
 
        strlcpy(frr_zclientpath, ZEBRA_SERV_PATH, sizeof(frr_zclientpath));
+
+       di->cli_mode = FRR_CLI_CLASSIC;
 }
 
 void frr_opt_add(const char *optstr, const struct option *longopts,
@@ -380,6 +404,13 @@ static int frr_opt(int opt)
                }
                di->pathspace = optarg;
                break;
+#ifdef HAVE_SQLITE3
+       case OPTION_DB_FILE:
+               if (di->flags & FRR_NO_CFG_PID_DRY)
+                       return 1;
+               di->db_file = optarg;
+               break;
+#endif
        case 'C':
                if (di->flags & FRR_NO_CFG_PID_DRY)
                        return 1;
@@ -444,6 +475,9 @@ static int frr_opt(int opt)
                }
                di->module_path = optarg;
                break;
+       case OPTION_TCLI:
+               di->cli_mode = FRR_CLI_TRANSACTIONAL;
+               break;
        case 'u':
                if (di->flags & FRR_NO_PRIVSEP)
                        return 1;
@@ -556,6 +590,10 @@ struct thread_master *frr_init(void)
                 frr_sysconfdir, p_pathspace, di->name, p_instance);
        snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s%s%s.pid",
                 frr_vtydir, p_pathspace, di->name, p_instance);
+#ifdef HAVE_SQLITE3
+       snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s%s%s.db",
+                frr_dbdir, p_pathspace, di->name, p_instance);
+#endif
 
        zprivs_preinit(di->privs);
 
@@ -597,19 +635,39 @@ struct thread_master *frr_init(void)
        master = thread_master_create(NULL);
        signal_init(master, di->n_signals, di->signals);
 
+#ifdef HAVE_SQLITE3
+       if (!di->db_file)
+               di->db_file = dbfile_default;
+       db_init(di->db_file);
+#endif
+
        if (di->flags & FRR_LIMITED_CLI)
                cmd_init(-1);
        else
                cmd_init(1);
+
        vty_init(master);
        memory_init();
 
        log_ref_init();
        lib_error_init();
 
+       yang_init();
+       nb_init(di->yang_modules, di->n_yang_modules);
+
        return master;
 }
 
+const char *frr_get_progname(void)
+{
+       return di ? di->progname : NULL;
+}
+
+enum frr_cli_mode frr_get_cli_mode(void)
+{
+       return di ? di->cli_mode : FRR_CLI_CLASSIC;
+}
+
 static int rcvd_signal = 0;
 
 static void rcv_signal(int signum)
@@ -752,17 +810,23 @@ static void frr_daemonize(void)
  */
 static int frr_config_read_in(struct thread *t)
 {
-       if (!vty_read_config(di->config_file, config_default) &&
+       if (!vty_read_config(NULL, di->config_file, config_default) &&
            di->backup_config_file) {
                char *orig = XSTRDUP(MTYPE_TMP, host_config_get());
 
                zlog_info("Attempting to read backup config file: %s specified",
                          di->backup_config_file);
-               vty_read_config(di->backup_config_file, config_default);
+               vty_read_config(NULL, di->backup_config_file, config_default);
 
                host_config_set(orig);
                XFREE(MTYPE_TMP, orig);
        }
+
+       /*
+        * Update the shared candidate after reading the startup configuration.
+        */
+       nb_config_replace(vty_shared_candidate_config, running_config, true);
+
        return 0;
 }
 
@@ -963,6 +1027,11 @@ void frr_fini(void)
        /* memory_init -> nothing needed */
        vty_terminate();
        cmd_terminate();
+       nb_terminate();
+       yang_terminate();
+#ifdef HAVE_SQLITE3
+       db_close();
+#endif
        log_ref_fini();
        zprivs_terminate(di->privs);
        /* signal_init -> nothing needed */
index db58ff92be9a8f9b15bf6181996802106a79908f..2705397b8571bac27b8f096ea0ca9f2c6127ebcc 100644 (file)
@@ -28,6 +28,7 @@
 #include "getopt.h"
 #include "module.h"
 #include "hook.h"
+#include "northbound.h"
 
 /* The following options disable specific command line options that
  * are not applicable for a particular daemon.
  */
 #define FRR_DETACH_LATER       (1 << 5)
 
+enum frr_cli_mode {
+       FRR_CLI_CLASSIC = 0,
+       FRR_CLI_TRANSACTIONAL,
+};
+
 struct frr_daemon_info {
        unsigned flags;
 
@@ -60,11 +66,15 @@ struct frr_daemon_info {
        bool dryrun;
        bool daemon_mode;
        bool terminal;
+       enum frr_cli_mode cli_mode;
 
        struct thread *read_in;
        const char *config_file;
        const char *backup_config_file;
        const char *pid_file;
+#ifdef HAVE_SQLITE3
+       const char *db_file;
+#endif
        const char *vty_path;
        const char *module_path;
        const char *pathspace;
@@ -80,6 +90,9 @@ struct frr_daemon_info {
        size_t n_signals;
 
        struct zebra_privs_t *privs;
+
+       const struct frr_yang_module_info **yang_modules;
+       size_t n_yang_modules;
 };
 
 /* execname is the daemon's executable (and pidfile and configfile) name,
@@ -108,6 +121,8 @@ extern int frr_getopt(int argc, char *const argv[], int *longindex);
 extern void frr_help_exit(int status);
 
 extern struct thread_master *frr_init(void);
+extern const char *frr_get_progname(void);
+extern enum frr_cli_mode frr_get_cli_mode(void);
 
 DECLARE_HOOK(frr_late_init, (struct thread_master * tm), (tm))
 extern void frr_config_fork(void);
diff --git a/lib/northbound.c b/lib/northbound.c
new file mode 100644 (file)
index 0000000..8932b94
--- /dev/null
@@ -0,0 +1,1200 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 "libfrr.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "command.h"
+#include "db.h"
+#include "northbound.h"
+#include "northbound_cli.h"
+#include "northbound_db.h"
+
+DEFINE_MTYPE_STATIC(LIB, NB_NODE, "Northbound Node")
+DEFINE_MTYPE_STATIC(LIB, NB_CONFIG, "Northbound Configuration")
+
+/* Running configuration - shouldn't be modified directly. */
+struct nb_config *running_config;
+
+/*
+ * Global lock used to prevent multiple configuration transactions from
+ * happening concurrently.
+ */
+static bool transaction_in_progress;
+
+static int nb_configuration_callback(const enum nb_event event,
+                                    struct nb_config_change *change);
+static struct nb_transaction *nb_transaction_new(struct nb_config *config,
+                                                struct nb_config_cbs *changes,
+                                                enum nb_client client,
+                                                const char *comment);
+static void nb_transaction_free(struct nb_transaction *transaction);
+static int nb_transaction_process(enum nb_event event,
+                                 struct nb_transaction *transaction);
+static void nb_transaction_apply_finish(struct nb_transaction *transaction);
+
+static void nb_node_new_cb(const struct lys_node *snode, void *arg1, void *arg2)
+{
+       struct nb_node *nb_node;
+       struct lys_node *sparent, *sparent_list;
+
+       nb_node = XCALLOC(MTYPE_NB_NODE, sizeof(*nb_node));
+       yang_snode_get_path(snode, YANG_PATH_DATA, nb_node->xpath,
+                           sizeof(nb_node->xpath));
+       nb_node->priority = NB_DFLT_PRIORITY;
+       sparent = yang_snode_real_parent(snode);
+       if (sparent)
+               nb_node->parent = sparent->priv;
+       sparent_list = yang_snode_parent_list(snode);
+       if (sparent_list)
+               nb_node->parent_list = sparent_list->priv;
+
+       /*
+        * Link the northbound node and the libyang schema node with one
+        * another.
+        */
+       nb_node->snode = snode;
+       lys_set_private(snode, nb_node);
+}
+
+static void nb_node_del_cb(const struct lys_node *snode, void *arg1, void *arg2)
+{
+       struct nb_node *nb_node;
+
+       nb_node = snode->priv;
+       lys_set_private(snode, NULL);
+       XFREE(MTYPE_NB_NODE, nb_node);
+}
+
+struct nb_node *nb_node_find(const char *xpath)
+{
+       const struct lys_node *snode;
+
+       /*
+        * Use libyang to find the schema node associated to the xpath and get
+        * the northbound node from there (snode private pointer).
+        */
+       snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+       if (!snode)
+               return NULL;
+
+       return snode->priv;
+}
+
+static int nb_node_validate_cb(const struct nb_node *nb_node,
+                              enum nb_operation operation,
+                              int callback_implemented, bool optional)
+{
+       bool valid;
+
+       valid = nb_operation_is_valid(operation, nb_node->snode);
+
+       if (!valid && callback_implemented)
+               flog_warn(EC_LIB_NB_CB_UNNEEDED,
+                         "unneeded '%s' callback for '%s'",
+                         nb_operation_name(operation), nb_node->xpath);
+
+       if (!optional && valid && !callback_implemented) {
+               flog_err(EC_LIB_NB_CB_MISSING, "missing '%s' callback for '%s'",
+                        nb_operation_name(operation), nb_node->xpath);
+               return 1;
+       }
+
+       return 0;
+}
+
+/*
+ * Check if the required callbacks were implemented for the given northbound
+ * node.
+ */
+static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node)
+
+{
+       unsigned int error = 0;
+
+       error += nb_node_validate_cb(nb_node, NB_OP_CREATE,
+                                    !!nb_node->cbs.create, false);
+       error += nb_node_validate_cb(nb_node, NB_OP_MODIFY,
+                                    !!nb_node->cbs.modify, false);
+       error += nb_node_validate_cb(nb_node, NB_OP_DELETE,
+                                    !!nb_node->cbs.delete, false);
+       error += nb_node_validate_cb(nb_node, NB_OP_MOVE, !!nb_node->cbs.move,
+                                    false);
+       error += nb_node_validate_cb(nb_node, NB_OP_APPLY_FINISH,
+                                    !!nb_node->cbs.apply_finish, true);
+       error += nb_node_validate_cb(nb_node, NB_OP_GET_ELEM,
+                                    !!nb_node->cbs.get_elem, false);
+       error += nb_node_validate_cb(nb_node, NB_OP_GET_NEXT,
+                                    !!nb_node->cbs.get_next, false);
+       error += nb_node_validate_cb(nb_node, NB_OP_GET_KEYS,
+                                    !!nb_node->cbs.get_keys, false);
+       error += nb_node_validate_cb(nb_node, NB_OP_LOOKUP_ENTRY,
+                                    !!nb_node->cbs.lookup_entry, false);
+       error += nb_node_validate_cb(nb_node, NB_OP_RPC, !!nb_node->cbs.rpc,
+                                    false);
+
+       return error;
+}
+
+static unsigned int nb_node_validate_priority(const struct nb_node *nb_node)
+{
+       /* Top-level nodes can have any priority. */
+       if (!nb_node->parent)
+               return 0;
+
+       if (nb_node->priority < nb_node->parent->priority) {
+               flog_err(EC_LIB_NB_CB_INVALID_PRIO,
+                        "node has higher priority than its parent [xpath %s]",
+                        nb_node->xpath);
+               return 1;
+       }
+
+       return 0;
+}
+
+static void nb_node_validate(const struct lys_node *snode, void *arg1,
+                            void *arg2)
+{
+       struct nb_node *nb_node = snode->priv;
+       unsigned int *errors = arg1;
+
+       /* Validate callbacks and priority. */
+       *errors += nb_node_validate_cbs(nb_node);
+       *errors += nb_node_validate_priority(nb_node);
+}
+
+struct nb_config *nb_config_new(struct lyd_node *dnode)
+{
+       struct nb_config *config;
+
+       config = XCALLOC(MTYPE_NB_CONFIG, sizeof(*config));
+       if (dnode)
+               config->dnode = dnode;
+       else
+               config->dnode = yang_dnode_new(ly_native_ctx);
+       config->version = 0;
+
+       return config;
+}
+
+void nb_config_free(struct nb_config *config)
+{
+       if (config->dnode)
+               yang_dnode_free(config->dnode);
+       XFREE(MTYPE_NB_CONFIG, config);
+}
+
+struct nb_config *nb_config_dup(const struct nb_config *config)
+{
+       struct nb_config *dup;
+
+       dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup));
+       dup->dnode = yang_dnode_dup(config->dnode);
+       dup->version = config->version;
+
+       return dup;
+}
+
+int nb_config_merge(struct nb_config *config_dst, struct nb_config *config_src,
+                   bool preserve_source)
+{
+       int ret;
+
+       ret = lyd_merge(config_dst->dnode, config_src->dnode, LYD_OPT_EXPLICIT);
+       if (ret != 0)
+               flog_warn(EC_LIB_LIBYANG, "%s: lyd_merge() failed", __func__);
+
+       if (!preserve_source)
+               nb_config_free(config_src);
+
+       return (ret == 0) ? NB_OK : NB_ERR;
+}
+
+void nb_config_replace(struct nb_config *config_dst,
+                      struct nb_config *config_src, bool preserve_source)
+{
+       /* Update version. */
+       if (config_src->version != 0)
+               config_dst->version = config_src->version;
+
+       /* Update dnode. */
+       yang_dnode_free(config_dst->dnode);
+       if (preserve_source) {
+               config_dst->dnode = yang_dnode_dup(config_src->dnode);
+       } else {
+               config_dst->dnode = config_src->dnode;
+               config_src->dnode = NULL;
+               nb_config_free(config_src);
+       }
+}
+
+/* Generate the nb_config_cbs tree. */
+static inline int nb_config_cb_compare(const struct nb_config_cb *a,
+                                      const struct nb_config_cb *b)
+{
+       /* Sort by priority first. */
+       if (a->nb_node->priority < b->nb_node->priority)
+               return -1;
+       if (a->nb_node->priority > b->nb_node->priority)
+               return 1;
+
+       /*
+        * Use XPath as a tie-breaker. This will naturally sort parent nodes
+        * before their children.
+        */
+       return strcmp(a->xpath, b->xpath);
+}
+RB_GENERATE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare);
+
+static void nb_config_diff_add_change(struct nb_config_cbs *changes,
+                                     enum nb_operation operation,
+                                     const struct lyd_node *dnode)
+{
+       struct nb_config_change *change;
+
+       change = XCALLOC(MTYPE_TMP, sizeof(*change));
+       change->cb.operation = operation;
+       change->cb.nb_node = dnode->schema->priv;
+       yang_dnode_get_path(dnode, change->cb.xpath, sizeof(change->cb.xpath));
+       change->cb.dnode = dnode;
+
+       RB_INSERT(nb_config_cbs, changes, &change->cb);
+}
+
+static void nb_config_diff_del_changes(struct nb_config_cbs *changes)
+{
+       while (!RB_EMPTY(nb_config_cbs, changes)) {
+               struct nb_config_change *change;
+
+               change = (struct nb_config_change *)RB_ROOT(nb_config_cbs,
+                                                           changes);
+               RB_REMOVE(nb_config_cbs, changes, &change->cb);
+               XFREE(MTYPE_TMP, change);
+       }
+}
+
+/*
+ * Helper function used when calculating the delta between two different
+ * configurations. Given a new subtree, calculate all new YANG data nodes,
+ * excluding default leafs and leaf-lists. This is a recursive function.
+ */
+static void nb_config_diff_new_subtree(const struct lyd_node *dnode,
+                                      struct nb_config_cbs *changes)
+{
+       struct lyd_node *child;
+
+       LY_TREE_FOR (dnode->child, child) {
+               enum nb_operation operation;
+
+               switch (child->schema->nodetype) {
+               case LYS_LEAF:
+               case LYS_LEAFLIST:
+                       if (lyd_wd_default((struct lyd_node_leaf_list *)child))
+                               break;
+
+                       if (nb_operation_is_valid(NB_OP_CREATE, child->schema))
+                               operation = NB_OP_CREATE;
+                       else if (nb_operation_is_valid(NB_OP_MODIFY,
+                                                      child->schema))
+                               operation = NB_OP_MODIFY;
+                       else
+                               continue;
+
+                       nb_config_diff_add_change(changes, operation, child);
+                       break;
+               case LYS_CONTAINER:
+               case LYS_LIST:
+                       if (nb_operation_is_valid(NB_OP_CREATE, child->schema))
+                               nb_config_diff_add_change(changes, NB_OP_CREATE,
+                                                         child);
+                       nb_config_diff_new_subtree(child, changes);
+                       break;
+               default:
+                       break;
+               }
+       }
+}
+
+/* Calculate the delta between two different configurations. */
+static void nb_config_diff(const struct nb_config *config1,
+                          const struct nb_config *config2,
+                          struct nb_config_cbs *changes)
+{
+       struct lyd_difflist *diff;
+
+       diff = lyd_diff(config1->dnode, config2->dnode,
+                       LYD_DIFFOPT_WITHDEFAULTS);
+       assert(diff);
+
+       for (int i = 0; diff->type[i] != LYD_DIFF_END; i++) {
+               LYD_DIFFTYPE type;
+               struct lyd_node *dnode;
+               enum nb_operation operation;
+
+               type = diff->type[i];
+
+               switch (type) {
+               case LYD_DIFF_CREATED:
+                       dnode = diff->second[i];
+
+                       if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema))
+                               operation = NB_OP_CREATE;
+                       else if (nb_operation_is_valid(NB_OP_MODIFY,
+                                                      dnode->schema))
+                               operation = NB_OP_MODIFY;
+                       else
+                               continue;
+                       break;
+               case LYD_DIFF_DELETED:
+                       dnode = diff->first[i];
+                       operation = NB_OP_DELETE;
+                       break;
+               case LYD_DIFF_CHANGED:
+                       dnode = diff->second[i];
+                       operation = NB_OP_MODIFY;
+                       break;
+               case LYD_DIFF_MOVEDAFTER1:
+               case LYD_DIFF_MOVEDAFTER2:
+               default:
+                       continue;
+               }
+
+               nb_config_diff_add_change(changes, operation, dnode);
+
+               if (type == LYD_DIFF_CREATED
+                   && (dnode->schema->nodetype & (LYS_CONTAINER | LYS_LIST)))
+                       nb_config_diff_new_subtree(dnode, changes);
+       }
+
+       lyd_free_diff(diff);
+}
+
+int nb_candidate_edit(struct nb_config *candidate,
+                     const struct nb_node *nb_node,
+                     enum nb_operation operation, const char *xpath,
+                     const struct yang_data *previous,
+                     const struct yang_data *data)
+{
+       struct lyd_node *dnode;
+       char xpath_edit[XPATH_MAXLEN];
+
+       if (!nb_operation_is_valid(operation, nb_node->snode)) {
+               flog_warn(EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+                         "%s: %s operation not valid for %s", __func__,
+                         nb_operation_name(operation), xpath);
+               return NB_ERR;
+       }
+
+       /* Use special notation for leaf-lists (RFC 6020, section 9.13.5). */
+       if (nb_node->snode->nodetype == LYS_LEAFLIST)
+               snprintf(xpath_edit, sizeof(xpath_edit), "%s[.='%s']", xpath,
+                        data->value);
+       else
+               strlcpy(xpath_edit, xpath, sizeof(xpath_edit));
+
+       switch (operation) {
+       case NB_OP_CREATE:
+       case NB_OP_MODIFY:
+               ly_errno = 0;
+               dnode = lyd_new_path(candidate->dnode, ly_native_ctx,
+                                    xpath_edit, (void *)data->value, 0,
+                                    LYD_PATH_OPT_UPDATE);
+               if (!dnode && ly_errno) {
+                       flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+                                 __func__);
+                       return NB_ERR;
+               }
+
+               /*
+                * If a new node was created, call lyd_validate() only to create
+                * default child nodes.
+                */
+               if (dnode) {
+                       lyd_schema_sort(dnode, 0);
+                       lyd_validate(&dnode, LYD_OPT_CONFIG, ly_native_ctx);
+               }
+               break;
+       case NB_OP_DELETE:
+               dnode = yang_dnode_get(candidate->dnode, xpath_edit);
+               if (!dnode)
+                       /*
+                        * Return a special error code so the caller can choose
+                        * whether to ignore it or not.
+                        */
+                       return NB_ERR_NOT_FOUND;
+               lyd_free(dnode);
+               break;
+       case NB_OP_MOVE:
+               /* TODO: update configuration. */
+               break;
+       default:
+               flog_warn(EC_LIB_DEVELOPMENT,
+                         "%s: unknown operation (%u) [xpath %s]", __func__,
+                         operation, xpath_edit);
+               return NB_ERR;
+       }
+
+       return NB_OK;
+}
+
+bool nb_candidate_needs_update(const struct nb_config *candidate)
+{
+       if (candidate->version < running_config->version)
+               return true;
+
+       return false;
+}
+
+int nb_candidate_update(struct nb_config *candidate)
+{
+       struct nb_config *updated_config;
+
+       updated_config = nb_config_dup(running_config);
+       if (nb_config_merge(updated_config, candidate, true) != NB_OK)
+               return NB_ERR;
+
+       nb_config_replace(candidate, updated_config, false);
+
+       return NB_OK;
+}
+
+/*
+ * The northbound configuration callbacks use the 'priv' pointer present in the
+ * libyang lyd_node structure to store pointers to FRR internal variables
+ * associated to YANG lists and presence containers. Before commiting a
+ * candidate configuration, we must restore the 'priv' pointers stored in the
+ * running configuration since they might be lost while editing the candidate.
+ */
+static void nb_candidate_restore_priv_pointers(struct nb_config *candidate)
+{
+       struct lyd_node *root, *next, *dnode_iter;
+
+       LY_TREE_FOR (running_config->dnode, root) {
+               LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
+                       struct lyd_node *dnode_candidate;
+                       char xpath[XPATH_MAXLEN];
+
+                       if (!dnode_iter->priv)
+                               goto next;
+
+                       yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath));
+                       dnode_candidate =
+                               yang_dnode_get(candidate->dnode, xpath);
+                       if (dnode_candidate)
+                               yang_dnode_set_entry(dnode_candidate,
+                                                    dnode_iter->priv);
+
+               next:
+                       LY_TREE_DFS_END(root, next, dnode_iter);
+               }
+       }
+}
+
+/*
+ * Perform YANG syntactic and semantic validation.
+ *
+ * WARNING: lyd_validate() can change the configuration as part of the
+ * validation process.
+ */
+static int nb_candidate_validate_yang(struct nb_config *candidate)
+{
+       if (lyd_validate(&candidate->dnode, LYD_OPT_STRICT | LYD_OPT_CONFIG,
+                        ly_native_ctx)
+           != 0)
+               return NB_ERR_VALIDATION;
+
+       return NB_OK;
+}
+
+/* Perform code-level validation using the northbound callbacks. */
+static int nb_candidate_validate_changes(struct nb_config *candidate,
+                                        struct nb_config_cbs *changes)
+{
+       struct nb_config_cb *cb;
+
+       nb_candidate_restore_priv_pointers(candidate);
+       RB_FOREACH (cb, nb_config_cbs, changes) {
+               struct nb_config_change *change = (struct nb_config_change *)cb;
+               int ret;
+
+               ret = nb_configuration_callback(NB_EV_VALIDATE, change);
+               if (ret != NB_OK)
+                       return NB_ERR_VALIDATION;
+       }
+
+       return NB_OK;
+}
+
+int nb_candidate_validate(struct nb_config *candidate)
+{
+       struct nb_config_cbs changes;
+       int ret;
+
+       if (nb_candidate_validate_yang(candidate) != NB_OK)
+               return NB_ERR_VALIDATION;
+
+       RB_INIT(nb_config_cbs, &changes);
+       nb_config_diff(running_config, candidate, &changes);
+       ret = nb_candidate_validate_changes(candidate, &changes);
+       nb_config_diff_del_changes(&changes);
+
+       return ret;
+}
+
+int nb_candidate_commit_prepare(struct nb_config *candidate,
+                               enum nb_client client, const char *comment,
+                               struct nb_transaction **transaction)
+{
+       struct nb_config_cbs changes;
+
+       if (nb_candidate_validate_yang(candidate) != NB_OK) {
+               flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
+                         "%s: failed to validate candidate configuration",
+                         __func__);
+               return NB_ERR_VALIDATION;
+       }
+
+       RB_INIT(nb_config_cbs, &changes);
+       nb_config_diff(running_config, candidate, &changes);
+       if (RB_EMPTY(nb_config_cbs, &changes))
+               return NB_ERR_NO_CHANGES;
+
+       if (nb_candidate_validate_changes(candidate, &changes) != NB_OK) {
+               flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
+                         "%s: failed to validate candidate configuration",
+                         __func__);
+               nb_config_diff_del_changes(&changes);
+               return NB_ERR_VALIDATION;
+       }
+
+       *transaction = nb_transaction_new(candidate, &changes, client, comment);
+       if (*transaction == NULL) {
+               flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+                         "%s: failed to create transaction", __func__);
+               nb_config_diff_del_changes(&changes);
+               return NB_ERR_LOCKED;
+       }
+
+       return nb_transaction_process(NB_EV_PREPARE, *transaction);
+}
+
+void nb_candidate_commit_abort(struct nb_transaction *transaction)
+{
+       (void)nb_transaction_process(NB_EV_ABORT, transaction);
+       nb_transaction_free(transaction);
+}
+
+void nb_candidate_commit_apply(struct nb_transaction *transaction,
+                              bool save_transaction, uint32_t *transaction_id)
+{
+       (void)nb_transaction_process(NB_EV_APPLY, transaction);
+       nb_transaction_apply_finish(transaction);
+
+       /* Replace running by candidate. */
+       transaction->config->version++;
+       nb_config_replace(running_config, transaction->config, true);
+
+       /* Record transaction. */
+       if (save_transaction
+           && nb_db_transaction_save(transaction, transaction_id) != NB_OK)
+               flog_warn(EC_LIB_NB_TRANSACTION_RECORD_FAILED,
+                         "%s: failed to record transaction", __func__);
+
+       nb_transaction_free(transaction);
+}
+
+int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
+                       bool save_transaction, const char *comment,
+                       uint32_t *transaction_id)
+{
+       struct nb_transaction *transaction = NULL;
+       int ret;
+
+       ret = nb_candidate_commit_prepare(candidate, client, comment,
+                                         &transaction);
+       /*
+        * Apply the changes if the preparation phase succeeded. Otherwise abort
+        * the transaction.
+        */
+       if (ret == NB_OK)
+               nb_candidate_commit_apply(transaction, save_transaction,
+                                         transaction_id);
+       else if (transaction != NULL)
+               nb_candidate_commit_abort(transaction);
+
+       return ret;
+}
+
+static void nb_log_callback(const enum nb_event event,
+                           enum nb_operation operation, const char *xpath,
+                           const char *value)
+{
+       zlog_debug(
+               "northbound callback: event [%s] op [%s] xpath [%s] value [%s]",
+               nb_event_name(event), nb_operation_name(operation), xpath,
+               value);
+}
+
+/*
+ * Call the northbound configuration callback associated to a given
+ * configuration change.
+ */
+static int nb_configuration_callback(const enum nb_event event,
+                                    struct nb_config_change *change)
+{
+       enum nb_operation operation = change->cb.operation;
+       const char *xpath = change->cb.xpath;
+       const struct nb_node *nb_node = change->cb.nb_node;
+       const struct lyd_node *dnode = change->cb.dnode;
+       union nb_resource *resource;
+       int ret = NB_ERR;
+
+       if (debug_northbound) {
+               const char *value = "(none)";
+
+               if (dnode && !yang_snode_is_typeless_data(dnode->schema))
+                       value = yang_dnode_get_string(dnode, NULL);
+
+               nb_log_callback(event, operation, xpath, value);
+       }
+
+       if (event == NB_EV_VALIDATE)
+               resource = NULL;
+       else
+               resource = &change->resource;
+
+       switch (operation) {
+       case NB_OP_CREATE:
+               ret = (*nb_node->cbs.create)(event, dnode, resource);
+               break;
+       case NB_OP_MODIFY:
+               ret = (*nb_node->cbs.modify)(event, dnode, resource);
+               break;
+       case NB_OP_DELETE:
+               ret = (*nb_node->cbs.delete)(event, dnode);
+               break;
+       case NB_OP_MOVE:
+               ret = (*nb_node->cbs.move)(event, dnode);
+               break;
+       default:
+               break;
+       }
+
+       if (ret != NB_OK)
+               flog_warn(
+                       EC_LIB_NB_CB_CONFIG,
+                       "%s: error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]",
+                       __func__, nb_err_name(ret), nb_event_name(event),
+                       nb_operation_name(operation), xpath);
+
+       return ret;
+}
+
+static struct nb_transaction *nb_transaction_new(struct nb_config *config,
+                                                struct nb_config_cbs *changes,
+                                                enum nb_client client,
+                                                const char *comment)
+{
+       struct nb_transaction *transaction;
+
+       if (transaction_in_progress) {
+               flog_warn(
+                       EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+                       "%s: error - there's already another transaction in progress",
+                       __func__);
+               return NULL;
+       }
+       transaction_in_progress = true;
+
+       transaction = XCALLOC(MTYPE_TMP, sizeof(*transaction));
+       transaction->client = client;
+       if (comment)
+               strlcpy(transaction->comment, comment,
+                       sizeof(transaction->comment));
+       transaction->config = config;
+       transaction->changes = *changes;
+
+       return transaction;
+}
+
+static void nb_transaction_free(struct nb_transaction *transaction)
+{
+       nb_config_diff_del_changes(&transaction->changes);
+       XFREE(MTYPE_TMP, transaction);
+       transaction_in_progress = false;
+}
+
+/* Process all configuration changes associated to a transaction. */
+static int nb_transaction_process(enum nb_event event,
+                                 struct nb_transaction *transaction)
+{
+       struct nb_config_cb *cb;
+
+       RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
+               struct nb_config_change *change = (struct nb_config_change *)cb;
+               int ret;
+
+               /*
+                * Only try to release resources that were allocated
+                * successfully.
+                */
+               if (event == NB_EV_ABORT && change->prepare_ok == false)
+                       break;
+
+               /* Call the appropriate callback. */
+               ret = nb_configuration_callback(event, change);
+               switch (event) {
+               case NB_EV_PREPARE:
+                       if (ret != NB_OK)
+                               return ret;
+                       change->prepare_ok = true;
+                       break;
+               case NB_EV_ABORT:
+               case NB_EV_APPLY:
+                       /*
+                        * At this point it's not possible to reject the
+                        * transaction anymore, so any failure here can lead to
+                        * inconsistencies and should be treated as a bug.
+                        * Operations prone to errors, like validations and
+                        * resource allocations, should be performed during the
+                        * 'prepare' phase.
+                        */
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return NB_OK;
+}
+
+static struct nb_config_cb *
+nb_apply_finish_cb_new(struct nb_config_cbs *cbs, const char *xpath,
+                      const struct nb_node *nb_node,
+                      const struct lyd_node *dnode)
+{
+       struct nb_config_cb *cb;
+
+       cb = XCALLOC(MTYPE_TMP, sizeof(*cb));
+       strlcpy(cb->xpath, xpath, sizeof(cb->xpath));
+       cb->nb_node = nb_node;
+       cb->dnode = dnode;
+       RB_INSERT(nb_config_cbs, cbs, cb);
+
+       return cb;
+}
+
+static struct nb_config_cb *
+nb_apply_finish_cb_find(struct nb_config_cbs *cbs, const char *xpath,
+                       const struct nb_node *nb_node)
+{
+       struct nb_config_cb s;
+
+       strlcpy(s.xpath, xpath, sizeof(s.xpath));
+       s.nb_node = nb_node;
+       return RB_FIND(nb_config_cbs, cbs, &s);
+}
+
+/* Call the 'apply_finish' callbacks. */
+static void nb_transaction_apply_finish(struct nb_transaction *transaction)
+{
+       struct nb_config_cbs cbs;
+       struct nb_config_cb *cb;
+
+       /* Initialize tree of 'apply_finish' callbacks. */
+       RB_INIT(nb_config_cbs, &cbs);
+
+       /* Identify the 'apply_finish' callbacks that need to be called. */
+       RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
+               struct nb_config_change *change = (struct nb_config_change *)cb;
+               const struct lyd_node *dnode = change->cb.dnode;
+
+               /*
+                * Iterate up to the root of the data tree. When a node is being
+                * deleted, skip its 'apply_finish' callback if one is defined
+                * (the 'apply_finish' callbacks from the node ancestors should
+                * be called though).
+                */
+               if (change->cb.operation == NB_OP_DELETE) {
+                       char xpath[XPATH_MAXLEN];
+
+                       dnode = dnode->parent;
+                       if (!dnode)
+                               break;
+
+                       /*
+                        * The dnode from 'delete' callbacks point to elements
+                        * from the running configuration. Use yang_dnode_get()
+                        * to get the corresponding dnode from the candidate
+                        * configuration that is being committed.
+                        */
+                       yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+                       dnode = yang_dnode_get(transaction->config->dnode,
+                                              xpath);
+               }
+               while (dnode) {
+                       char xpath[XPATH_MAXLEN];
+                       struct nb_node *nb_node;
+
+                       nb_node = dnode->schema->priv;
+                       if (!nb_node->cbs.apply_finish)
+                               goto next;
+
+                       /*
+                        * Don't call the callback more than once for the same
+                        * data node.
+                        */
+                       yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+                       if (nb_apply_finish_cb_find(&cbs, xpath, nb_node))
+                               goto next;
+
+                       nb_apply_finish_cb_new(&cbs, xpath, nb_node, dnode);
+
+               next:
+                       dnode = dnode->parent;
+               }
+       }
+
+       /* Call the 'apply_finish' callbacks, sorted by their priorities. */
+       RB_FOREACH (cb, nb_config_cbs, &cbs) {
+               if (debug_northbound)
+                       nb_log_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH,
+                                       cb->xpath, NULL);
+
+               (*cb->nb_node->cbs.apply_finish)(cb->dnode);
+       }
+
+       /* Release memory. */
+       while (!RB_EMPTY(nb_config_cbs, &cbs)) {
+               cb = RB_ROOT(nb_config_cbs, &cbs);
+               RB_REMOVE(nb_config_cbs, &cbs, cb);
+               XFREE(MTYPE_TMP, cb);
+       }
+}
+
+bool nb_operation_is_valid(enum nb_operation operation,
+                          const struct lys_node *snode)
+{
+       struct lys_node_container *scontainer;
+       struct lys_node_leaf *sleaf;
+
+       switch (operation) {
+       case NB_OP_CREATE:
+               if (!(snode->flags & LYS_CONFIG_W))
+                       return false;
+
+               switch (snode->nodetype) {
+               case LYS_LEAF:
+                       sleaf = (struct lys_node_leaf *)snode;
+                       if (sleaf->type.base != LY_TYPE_EMPTY)
+                               return false;
+                       break;
+               case LYS_CONTAINER:
+                       scontainer = (struct lys_node_container *)snode;
+                       if (!scontainer->presence)
+                               return false;
+                       break;
+               case LYS_LIST:
+               case LYS_LEAFLIST:
+                       break;
+               default:
+                       return false;
+               }
+               return true;
+       case NB_OP_MODIFY:
+               if (!(snode->flags & LYS_CONFIG_W))
+                       return false;
+
+               switch (snode->nodetype) {
+               case LYS_LEAF:
+                       sleaf = (struct lys_node_leaf *)snode;
+                       if (sleaf->type.base == LY_TYPE_EMPTY)
+                               return false;
+
+                       /* List keys can't be modified. */
+                       if (lys_is_key(sleaf, NULL))
+                               return false;
+                       break;
+               default:
+                       return false;
+               }
+               return true;
+       case NB_OP_DELETE:
+               if (!(snode->flags & LYS_CONFIG_W))
+                       return false;
+
+               switch (snode->nodetype) {
+               case LYS_LEAF:
+                       sleaf = (struct lys_node_leaf *)snode;
+
+                       /* List keys can't be deleted. */
+                       if (lys_is_key(sleaf, NULL))
+                               return false;
+
+                       /*
+                        * Only optional leafs can be deleted, or leafs whose
+                        * parent is a case statement.
+                        */
+                       if (snode->parent->nodetype == LYS_CASE)
+                               return true;
+                       if (sleaf->when)
+                               return true;
+                       if ((sleaf->flags & LYS_MAND_TRUE) || sleaf->dflt)
+                               return false;
+                       break;
+               case LYS_CONTAINER:
+                       scontainer = (struct lys_node_container *)snode;
+                       if (!scontainer->presence)
+                               return false;
+                       break;
+               case LYS_LIST:
+               case LYS_LEAFLIST:
+                       break;
+               default:
+                       return false;
+               }
+               return true;
+       case NB_OP_MOVE:
+               if (!(snode->flags & LYS_CONFIG_W))
+                       return false;
+
+               switch (snode->nodetype) {
+               case LYS_LIST:
+               case LYS_LEAFLIST:
+                       if (!(snode->flags & LYS_USERORDERED))
+                               return false;
+                       break;
+               default:
+                       return false;
+               }
+               return true;
+       case NB_OP_APPLY_FINISH:
+               if (!(snode->flags & LYS_CONFIG_W))
+                       return false;
+               return true;
+       case NB_OP_GET_ELEM:
+               if (!(snode->flags & LYS_CONFIG_R))
+                       return false;
+
+               switch (snode->nodetype) {
+               case LYS_LEAF:
+                       break;
+               case LYS_CONTAINER:
+                       scontainer = (struct lys_node_container *)snode;
+                       if (!scontainer->presence)
+                               return false;
+                       break;
+               default:
+                       return false;
+               }
+               return true;
+       case NB_OP_GET_NEXT:
+       case NB_OP_GET_KEYS:
+       case NB_OP_LOOKUP_ENTRY:
+               if (!(snode->flags & LYS_CONFIG_R))
+                       return false;
+
+               switch (snode->nodetype) {
+               case LYS_LIST:
+                       break;
+               default:
+                       return false;
+               }
+               return true;
+       case NB_OP_RPC:
+               if (snode->flags & (LYS_CONFIG_W | LYS_CONFIG_R))
+                       return false;
+
+               switch (snode->nodetype) {
+               case LYS_RPC:
+               case LYS_ACTION:
+                       break;
+               default:
+                       return false;
+               }
+               return true;
+       default:
+               return false;
+       }
+}
+
+DEFINE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments),
+           (xpath, arguments));
+
+int nb_notification_send(const char *xpath, struct list *arguments)
+{
+       int ret;
+
+       ret = hook_call(nb_notification_send, xpath, arguments);
+       if (arguments)
+               list_delete(&arguments);
+
+       return ret;
+}
+
+const char *nb_event_name(enum nb_event event)
+{
+       switch (event) {
+       case NB_EV_VALIDATE:
+               return "validate";
+       case NB_EV_PREPARE:
+               return "prepare";
+       case NB_EV_ABORT:
+               return "abort";
+       case NB_EV_APPLY:
+               return "apply";
+       default:
+               return "unknown";
+       }
+}
+
+const char *nb_operation_name(enum nb_operation operation)
+{
+       switch (operation) {
+       case NB_OP_CREATE:
+               return "create";
+       case NB_OP_MODIFY:
+               return "modify";
+       case NB_OP_DELETE:
+               return "delete";
+       case NB_OP_MOVE:
+               return "move";
+       case NB_OP_APPLY_FINISH:
+               return "apply_finish";
+       case NB_OP_GET_ELEM:
+               return "get_elem";
+       case NB_OP_GET_NEXT:
+               return "get_next";
+       case NB_OP_GET_KEYS:
+               return "get_keys";
+       case NB_OP_LOOKUP_ENTRY:
+               return "lookup_entry";
+       case NB_OP_RPC:
+               return "rpc";
+       default:
+               return "unknown";
+       }
+}
+
+const char *nb_err_name(enum nb_error error)
+{
+       switch (error) {
+       case NB_OK:
+               return "ok";
+       case NB_ERR:
+               return "generic error";
+       case NB_ERR_NO_CHANGES:
+               return "no changes";
+       case NB_ERR_NOT_FOUND:
+               return "element not found";
+       case NB_ERR_LOCKED:
+               return "resource is locked";
+       case NB_ERR_VALIDATION:
+               return "validation error";
+       case NB_ERR_RESOURCE:
+               return "failed to allocate resource";
+       case NB_ERR_INCONSISTENCY:
+               return "internal inconsistency";
+       default:
+               return "unknown";
+       }
+}
+
+const char *nb_client_name(enum nb_client client)
+{
+       switch (client) {
+       case NB_CLIENT_CLI:
+               return "CLI";
+       default:
+               return "unknown";
+       }
+}
+
+static void nb_load_callbacks(const struct frr_yang_module_info *module)
+{
+       for (size_t i = 0; module->nodes[i].xpath; i++) {
+               struct nb_node *nb_node;
+               uint32_t priority;
+
+               nb_node = nb_node_find(module->nodes[i].xpath);
+               if (!nb_node) {
+                       flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                                 "%s: unknown data path: %s", __func__,
+                                 module->nodes[i].xpath);
+                       continue;
+               }
+
+               nb_node->cbs = module->nodes[i].cbs;
+               priority = module->nodes[i].priority;
+               if (priority != 0)
+                       nb_node->priority = priority;
+       }
+}
+
+void nb_init(const struct frr_yang_module_info *modules[], size_t nmodules)
+{
+       unsigned int errors = 0;
+
+       /* Load YANG modules. */
+       for (size_t i = 0; i < nmodules; i++)
+               yang_module_load(modules[i]->name);
+
+       /* Create a nb_node for all YANG schema nodes. */
+       yang_all_snodes_iterate(nb_node_new_cb, 0, NULL, NULL);
+
+       /* Load northbound callbacks. */
+       for (size_t i = 0; i < nmodules; i++)
+               nb_load_callbacks(modules[i]);
+
+       /* Validate northbound callbacks. */
+       yang_all_snodes_iterate(nb_node_validate, 0, &errors, NULL);
+       if (errors > 0) {
+               flog_err(
+                       EC_LIB_NB_CBS_VALIDATION,
+                       "%s: failed to validate northbound callbacks: %u error(s)",
+                       __func__, errors);
+               exit(1);
+       }
+
+       /* Initialize the northbound database (used for the rollback log). */
+       if (nb_db_init() != NB_OK)
+               flog_warn(EC_LIB_NB_DATABASE,
+                         "%s: failed to initialize northbound database",
+                         __func__);
+
+       /* Create an empty running configuration. */
+       running_config = nb_config_new(NULL);
+
+       /* Initialize the northbound CLI. */
+       nb_cli_init();
+}
+
+void nb_terminate(void)
+{
+       /* Terminate the northbound CLI. */
+       nb_cli_terminate();
+
+       /* Delete all nb_node's from all YANG modules. */
+       yang_all_snodes_iterate(nb_node_del_cb, 0, NULL, NULL);
+
+       /* Delete the running configuration. */
+       nb_config_free(running_config);
+}
diff --git a/lib/northbound.h b/lib/northbound.h
new file mode 100644 (file)
index 0000000..9963e14
--- /dev/null
@@ -0,0 +1,780 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+#ifndef _FRR_NORTHBOUND_H_
+#define _FRR_NORTHBOUND_H_
+
+#include "hook.h"
+#include "yang.h"
+#include "linklist.h"
+#include "openbsd-tree.h"
+
+/* Forward declaration(s). */
+struct vty;
+
+/* Northbound events. */
+enum nb_event {
+       /*
+        * The configuration callback is supposed to verify that the changes are
+        * valid and can be applied.
+        */
+       NB_EV_VALIDATE,
+
+       /*
+        * The configuration callback is supposed to prepare all resources
+        * required to apply the changes.
+        */
+       NB_EV_PREPARE,
+
+       /*
+        * Transaction has failed, the configuration callback needs to release
+        * all resources previously allocated.
+        */
+       NB_EV_ABORT,
+
+       /*
+        * The configuration changes need to be applied. The changes can't be
+        * rejected at this point (errors are logged and ignored).
+        */
+       NB_EV_APPLY,
+};
+
+/*
+ * Northbound operations.
+ *
+ * Refer to the documentation comments of nb_callbacks for more details.
+ */
+enum nb_operation {
+       NB_OP_CREATE,
+       NB_OP_MODIFY,
+       NB_OP_DELETE,
+       NB_OP_MOVE,
+       NB_OP_APPLY_FINISH,
+       NB_OP_GET_ELEM,
+       NB_OP_GET_NEXT,
+       NB_OP_GET_KEYS,
+       NB_OP_LOOKUP_ENTRY,
+       NB_OP_RPC,
+};
+
+union nb_resource {
+       int fd;
+       void *ptr;
+};
+
+struct nb_callbacks {
+       /*
+        * Configuration callback.
+        *
+        * A presence container, list entry, leaf-list entry or leaf of type
+        * empty has been created.
+        *
+        * For presence-containers and list entries, the callback is supposed to
+        * initialize the default values of its children (if any) from the YANG
+        * models.
+        *
+        * event
+        *    The transaction phase. Refer to the documentation comments of
+        *    nb_event for more details.
+        *
+        * dnode
+        *    libyang data node that is being created.
+        *
+        * resource
+        *    Pointer to store resource(s) allocated during the NB_EV_PREPARE
+        *    phase. The same pointer can be used during the NB_EV_ABORT and
+        *    NB_EV_APPLY phases to either release or make use of the allocated
+        *    resource(s). It's set to NULL when the event is NB_EV_VALIDATE.
+        *
+        * Returns:
+        *    - NB_OK on success.
+        *    - NB_ERR_VALIDATION when a validation error occurred.
+        *    - NB_ERR_RESOURCE when the callback failed to allocate a resource.
+        *    - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+        *    - NB_ERR for other errors.
+        */
+       int (*create)(enum nb_event event, const struct lyd_node *dnode,
+                     union nb_resource *resource);
+
+       /*
+        * Configuration callback.
+        *
+        * The value of a leaf has been modified.
+        *
+        * List keys don't need to implement this callback. When a list key is
+        * modified, the northbound treats this as if the list was deleted and a
+        * new one created with the updated key value.
+        *
+        * event
+        *    The transaction phase. Refer to the documentation comments of
+        *    nb_event for more details.
+        *
+        * dnode
+        *    libyang data node that is being modified
+        *
+        * resource
+        *    Pointer to store resource(s) allocated during the NB_EV_PREPARE
+        *    phase. The same pointer can be used during the NB_EV_ABORT and
+        *    NB_EV_APPLY phases to either release or make use of the allocated
+        *    resource(s). It's set to NULL when the event is NB_EV_VALIDATE.
+        *
+        * Returns:
+        *    - NB_OK on success.
+        *    - NB_ERR_VALIDATION when a validation error occurred.
+        *    - NB_ERR_RESOURCE when the callback failed to allocate a resource.
+        *    - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+        *    - NB_ERR for other errors.
+        */
+       int (*modify)(enum nb_event event, const struct lyd_node *dnode,
+                     union nb_resource *resource);
+
+       /*
+        * Configuration callback.
+        *
+        * A presence container, list entry, leaf-list entry or optional leaf
+        * has been deleted.
+        *
+        * The callback is supposed to delete the entire configuration object,
+        * including its children when they exist.
+        *
+        * event
+        *    The transaction phase. Refer to the documentation comments of
+        *    nb_event for more details.
+        *
+        * dnode
+        *    libyang data node that is being deleted.
+        *
+        * Returns:
+        *    - NB_OK on success.
+        *    - NB_ERR_VALIDATION when a validation error occurred.
+        *    - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+        *    - NB_ERR for other errors.
+        */
+       int (*delete)(enum nb_event event, const struct lyd_node *dnode);
+
+       /*
+        * Configuration callback.
+        *
+        * A list entry or leaf-list entry has been moved. Only applicable when
+        * the "ordered-by user" statement is present.
+        *
+        * event
+        *    The transaction phase. Refer to the documentation comments of
+        *    nb_event for more details.
+        *
+        * dnode
+        *    libyang data node that is being moved.
+        *
+        * Returns:
+        *    - NB_OK on success.
+        *    - NB_ERR_VALIDATION when a validation error occurred.
+        *    - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+        *    - NB_ERR for other errors.
+        */
+       int (*move)(enum nb_event event, const struct lyd_node *dnode);
+
+       /*
+        * Optional configuration callback.
+        *
+        * The 'apply_finish' callbacks are called after all other callbacks
+        * during the apply phase (NB_EV_APPLY). These callbacks are called only
+        * under one of the following two cases:
+        * - The data node has been created or modified (but not deleted);
+        * - Any change was made within the descendants of the data node (e.g. a
+        *   child leaf was modified, created or deleted).
+        *
+        * In the second case above, the 'apply_finish' callback is called only
+        * once even if multiple changes occurred within the descendants of the
+        * data node.
+        *
+        * dnode
+        *    libyang data node associated with the 'apply_finish' callback.
+        */
+       void (*apply_finish)(const struct lyd_node *dnode);
+
+       /*
+        * Operational data callback.
+        *
+        * The callback function should return the value of a specific leaf or
+        * inform if a typeless value (presence containers or leafs of type
+        * empty) exists or not.
+        *
+        * xpath
+        *    YANG data path of the data we want to get.
+        *
+        * list_entry
+        *    Pointer to list entry.
+        *
+        * Returns:
+        *    Pointer to newly created yang_data structure, or NULL to indicate
+        *    the absence of data.
+        */
+       struct yang_data *(*get_elem)(const char *xpath,
+                                     const void *list_entry);
+
+       /*
+        * Operational data callback for YANG lists.
+        *
+        * The callback function should return the next entry in the list. The
+        * 'list_entry' parameter will be NULL on the first invocation.
+        *
+        * xpath
+        *    Data path of the YANG list.
+        *
+        * list_entry
+        *    Pointer to list entry.
+        *
+        * Returns:
+        *    Pointer to the next entry in the list, or NULL to signal that the
+        *    end of the list was reached.
+        */
+       const void *(*get_next)(const char *xpath, const void *list_entry);
+
+       /*
+        * Operational data callback for YANG lists.
+        *
+        * The callback function should fill the 'keys' parameter based on the
+        * given list_entry.
+        *
+        * list_entry
+        *    Pointer to list entry.
+        *
+        * keys
+        *    Structure to be filled based on the attributes of the provided
+        *    list entry.
+        *
+        * Returns:
+        *    NB_OK on success, NB_ERR otherwise.
+        */
+       int (*get_keys)(const void *list_entry, struct yang_list_keys *keys);
+
+       /*
+        * Operational data callback for YANG lists.
+        *
+        * The callback function should return a list entry based on the list
+        * keys given as a parameter.
+        *
+        * keys
+        *    Structure containing the keys of the list entry.
+        *
+        * Returns:
+        *    Pointer to the list entry if found, or NULL if not found.
+        */
+       const void *(*lookup_entry)(const struct yang_list_keys *keys);
+
+       /*
+        * RPC and action callback.
+        *
+        * Both 'input' and 'output' are lists of 'yang_data' structures. The
+        * callback should fetch all the input parameters from the 'input' list,
+        * and add output parameters to the 'output' list if necessary.
+        *
+        * xpath
+        *    XPath of the YANG RPC or action.
+        *
+        * input
+        *    Read-only list of input parameters.
+        *
+        * output
+        *    List of output parameters to be populated by the callback.
+        *
+        * Returns:
+        *    NB_OK on success, NB_ERR otherwise.
+        */
+       int (*rpc)(const char *xpath, const struct list *input,
+                  struct list *output);
+
+       /*
+        * Optional callback to show the CLI command associated to the given
+        * YANG data node.
+        *
+        * vty
+        *    The vty terminal to dump the configuration to.
+        *
+        * dnode
+        *    libyang data node that should be shown in the form of a CLI
+        *    command.
+        *
+        * show_defaults
+        *    Specify whether to display default configuration values or not.
+        *    This parameter can be ignored most of the time since the
+        *    northbound doesn't call this callback for default leaves or
+        *    non-presence containers that contain only default child nodes.
+        *    The exception are commands associated to multiple configuration
+        *    nodes, in which case it might be desirable to hide one or more
+        *    parts of the command when this parameter is set to false.
+        */
+       void (*cli_show)(struct vty *vty, struct lyd_node *dnode,
+                        bool show_defaults);
+};
+
+/*
+ * Northbound-specific data that is allocated for each schema node of the native
+ * YANG modules.
+ */
+struct nb_node {
+       /* Back pointer to the libyang schema node. */
+       const struct lys_node *snode;
+
+       /* Data path of this YANG node. */
+       char xpath[XPATH_MAXLEN];
+
+       /* Priority - lower priorities are processed first. */
+       uint32_t priority;
+
+       /* Callbacks implemented for this node. */
+       struct nb_callbacks cbs;
+
+       /*
+        * Pointer to the parent node (disconsidering non-presence containers).
+        */
+       struct nb_node *parent;
+
+       /* Pointer to the nearest parent list, if any. */
+       struct nb_node *parent_list;
+
+#ifdef HAVE_CONFD
+       /* ConfD hash value corresponding to this YANG path. */
+       int confd_hash;
+#endif
+};
+
+struct frr_yang_module_info {
+       /* YANG module name. */
+       const char *name;
+
+       /* Northbound callbacks. */
+       const struct {
+               /* Data path of this YANG node. */
+               const char *xpath;
+
+               /* Callbacks implemented for this node. */
+               struct nb_callbacks cbs;
+
+               /* Priority - lower priorities are processed first. */
+               uint32_t priority;
+       } nodes[];
+};
+
+/* Northbound error codes. */
+enum nb_error {
+       NB_OK = 0,
+       NB_ERR,
+       NB_ERR_NO_CHANGES,
+       NB_ERR_NOT_FOUND,
+       NB_ERR_LOCKED,
+       NB_ERR_VALIDATION,
+       NB_ERR_RESOURCE,
+       NB_ERR_INCONSISTENCY,
+};
+
+/* Default priority. */
+#define NB_DFLT_PRIORITY (UINT32_MAX / 2)
+
+/* Default maximum of configuration rollbacks to store. */
+#define NB_DLFT_MAX_CONFIG_ROLLBACKS 20
+
+/* Northbound clients. */
+enum nb_client {
+       NB_CLIENT_CLI = 0,
+};
+
+/* Northbound configuration. */
+struct nb_config {
+       struct lyd_node *dnode;
+       uint32_t version;
+};
+
+/* Northbound configuration callback. */
+struct nb_config_cb {
+       RB_ENTRY(nb_config_cb) entry;
+       enum nb_operation operation;
+       char xpath[XPATH_MAXLEN];
+       const struct nb_node *nb_node;
+       const struct lyd_node *dnode;
+};
+RB_HEAD(nb_config_cbs, nb_config_cb);
+RB_PROTOTYPE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare);
+
+/* Northbound configuration change. */
+struct nb_config_change {
+       struct nb_config_cb cb;
+       union nb_resource resource;
+       bool prepare_ok;
+};
+
+/* Northbound configuration transaction. */
+struct nb_transaction {
+       enum nb_client client;
+       char comment[80];
+       struct nb_config *config;
+       struct nb_config_cbs changes;
+};
+
+DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments),
+            (xpath, arguments))
+
+extern int debug_northbound;
+extern struct nb_config *running_config;
+
+/*
+ * Find the northbound node corresponding to a YANG data path.
+ *
+ * xpath
+ *    XPath to search for (with or without predicates).
+ *
+ * Returns:
+ *    Pointer to northbound node if found, NULL otherwise.
+ */
+extern struct nb_node *nb_node_find(const char *xpath);
+
+/*
+ * Create a new northbound configuration.
+ *
+ * dnode
+ *    Pointer to a libyang data node containing the configuration data. If NULL
+ *    is given, an empty configuration will be created.
+ *
+ * Returns:
+ *    Pointer to newly created northbound configuration.
+ */
+extern struct nb_config *nb_config_new(struct lyd_node *dnode);
+
+/*
+ * Delete a northbound configuration.
+ *
+ * config
+ *    Pointer to the config that is going to be deleted.
+ */
+extern void nb_config_free(struct nb_config *config);
+
+/*
+ * Duplicate a northbound configuration.
+ *
+ * config
+ *    Northbound configuration to duplicate.
+ *
+ * Returns:
+ *    Pointer to duplicated configuration.
+ */
+extern struct nb_config *nb_config_dup(const struct nb_config *config);
+
+/*
+ * Merge one configuration into another.
+ *
+ * config_dst
+ *    Configuration to merge to.
+ *
+ * config_src
+ *    Configuration to merge config_dst with.
+ *
+ * preserve_source
+ *    Specify whether config_src should be deleted or not after the merge
+ *    operation.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_config_merge(struct nb_config *config_dst,
+                          struct nb_config *config_src, bool preserve_source);
+
+/*
+ * Replace one configuration by another.
+ *
+ * config_dst
+ *    Configuration to be replaced.
+ *
+ * config_src
+ *    Configuration to replace config_dst.
+ *
+ * preserve_source
+ *    Specify whether config_src should be deleted or not after the replace
+ *    operation.
+ */
+extern void nb_config_replace(struct nb_config *config_dst,
+                             struct nb_config *config_src,
+                             bool preserve_source);
+
+/*
+ * Edit a candidate configuration.
+ *
+ * candidate
+ *    Candidate configuration to edit.
+ *
+ * nb_node
+ *    Northbound node associated to the configuration being edited.
+ *
+ * operation
+ *    Operation to apply.
+ *
+ * xpath
+ *    XPath of the configuration node being edited.
+ *
+ * previous
+ *    Previous value of the configuration node. Should be used only when the
+ *    operation is NB_OP_MOVE, otherwise this parameter is ignored.
+ *
+ * data
+ *    New value of the configuration node.
+ *
+ * Returns:
+ *    - NB_OK on success.
+ *    - NB_ERR_NOT_FOUND when the element to be deleted was not found.
+ *    - NB_ERR for other errors.
+ */
+extern int nb_candidate_edit(struct nb_config *candidate,
+                            const struct nb_node *nb_node,
+                            enum nb_operation operation, const char *xpath,
+                            const struct yang_data *previous,
+                            const struct yang_data *data);
+
+/*
+ * Check if a candidate configuration is outdated and needs to be updated.
+ *
+ * candidate
+ *    Candidate configuration to check.
+ *
+ * Returns:
+ *    true if the candidate is outdated, false otherwise.
+ */
+extern bool nb_candidate_needs_update(const struct nb_config *candidate);
+
+/*
+ * Update a candidate configuration by rebasing the changes on top of the latest
+ * running configuration. Resolve conflicts automatically by giving preference
+ * to the changes done in the candidate configuration.
+ *
+ * candidate
+ *    Candidate configuration to update.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_candidate_update(struct nb_config *candidate);
+
+/*
+ * Validate a candidate configuration. Perform both YANG syntactic/semantic
+ * validation and code-level validation using the northbound callbacks.
+ *
+ * WARNING: the candidate can be modified as part of the validation process
+ * (e.g. add default nodes).
+ *
+ * candidate
+ *    Candidate configuration to validate.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR_VALIDATION otherwise.
+ */
+extern int nb_candidate_validate(struct nb_config *candidate);
+
+/*
+ * Create a new configuration transaction but do not commit it yet. Only
+ * validate the candidate and prepare all resources required to apply the
+ * configuration changes.
+ *
+ * candidate
+ *    Candidate configuration to commit.
+ *
+ * client
+ *    Northbound client performing the commit.
+ *
+ * comment
+ *    Optional comment describing the commit.
+ *
+ * transaction
+ *    Output parameter providing the created transaction when one is created
+ *    successfully. In this case, it must be either aborted using
+ *    nb_candidate_commit_abort() or committed using
+ *    nb_candidate_commit_apply().
+ *
+ * Returns:
+ *    - NB_OK on success.
+ *    - NB_ERR_NO_CHANGES when the candidate is identical to the running
+ *      configuration.
+ *    - NB_ERR_LOCKED when there's already another transaction in progress.
+ *    - NB_ERR_VALIDATION when the candidate fails the validation checks.
+ *    - NB_ERR_RESOURCE when the system fails to allocate resources to apply
+ *      the candidate configuration.
+ *    - NB_ERR for other errors.
+ */
+extern int nb_candidate_commit_prepare(struct nb_config *candidate,
+                                      enum nb_client client,
+                                      const char *comment,
+                                      struct nb_transaction **transaction);
+
+/*
+ * Abort a previously created configuration transaction, releasing all resources
+ * allocated during the preparation phase.
+ *
+ * transaction
+ *    Candidate configuration to abort. It's consumed by this function.
+ */
+extern void nb_candidate_commit_abort(struct nb_transaction *transaction);
+
+/*
+ * Commit a previously created configuration transaction.
+ *
+ * transaction
+ *    Configuration transaction to commit. It's consumed by this function.
+ *
+ * save_transaction
+ *    Specify whether the transaction should be recorded in the transactions log
+ *    or not.
+ *
+ * transaction_id
+ *    Optional output parameter providing the ID of the committed transaction.
+ */
+extern void nb_candidate_commit_apply(struct nb_transaction *transaction,
+                                     bool save_transaction,
+                                     uint32_t *transaction_id);
+
+/*
+ * Create a new transaction to commit a candidate configuration. This is a
+ * convenience function that performs the two-phase commit protocol
+ * transparently to the user. The cost is reduced flexibility, since
+ * network-wide and multi-daemon transactions require the network manager to
+ * take into account the results of the preparation phase of multiple managed
+ * entities.
+ *
+ * candidate
+ *    Candidate configuration to commit. It's preserved regardless if the commit
+ *    operation fails or not.
+ *
+ * client
+ *    Northbound client performing the commit.
+ *
+ * save_transaction
+ *    Specify whether the transaction should be recorded in the transactions log
+ *    or not.
+ *
+ * comment
+ *    Optional comment describing the commit.
+ *
+ * transaction_id
+ *    Optional output parameter providing the ID of the committed transaction.
+ *
+ * Returns:
+ *    - NB_OK on success.
+ *    - NB_ERR_NO_CHANGES when the candidate is identical to the running
+ *      configuration.
+ *    - NB_ERR_LOCKED when there's already another transaction in progress.
+ *    - NB_ERR_VALIDATION when the candidate fails the validation checks.
+ *    - NB_ERR_RESOURCE when the system fails to allocate resources to apply
+ *      the candidate configuration.
+ *    - NB_ERR for other errors.
+ */
+extern int nb_candidate_commit(struct nb_config *candidate,
+                              enum nb_client client, bool save_transaction,
+                              const char *comment, uint32_t *transaction_id);
+
+/*
+ * Validate if the northbound operation is valid for the given node.
+ *
+ * operation
+ *    Operation we want to check.
+ *
+ * snode
+ *    libyang schema node we want to check.
+ *
+ * Returns:
+ *    true if the operation is valid, false otherwise.
+ */
+extern bool nb_operation_is_valid(enum nb_operation operation,
+                                 const struct lys_node *snode);
+
+/*
+ * Send a YANG notification. This is a no-op unless the 'nb_notification_send'
+ * hook was registered by a northbound plugin.
+ *
+ * xpath
+ *    XPath of the YANG notification.
+ *
+ * arguments
+ *    Linked list containing the arguments that should be sent. This list is
+ *    deleted after being used.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_notification_send(const char *xpath, struct list *arguments);
+
+/*
+ * Return a human-readable string representing a northbound event.
+ *
+ * event
+ *    Northbound event.
+ *
+ * Returns:
+ *    String representation of the given northbound event.
+ */
+extern const char *nb_event_name(enum nb_event event);
+
+/*
+ * Return a human-readable string representing a northbound operation.
+ *
+ * operation
+ *    Northbound operation.
+ *
+ * Returns:
+ *    String representation of the given northbound operation.
+ */
+extern const char *nb_operation_name(enum nb_operation operation);
+
+/*
+ * Return a human-readable string representing a northbound error.
+ *
+ * error
+ *    Northbound error.
+ *
+ * Returns:
+ *    String representation of the given northbound error.
+ */
+extern const char *nb_err_name(enum nb_error error);
+
+/*
+ * Return a human-readable string representing a northbound client.
+ *
+ * client
+ *    Northbound client.
+ *
+ * Returns:
+ *    String representation of the given northbound client.
+ */
+extern const char *nb_client_name(enum nb_client client);
+
+/*
+ * Initialize the northbound layer. Should be called only once during the
+ * daemon initialization process.
+ *
+ * modules
+ *    Array of YANG modules to parse and initialize.
+ *
+ * nmodules
+ *    Size of the modules array.
+ */
+extern void nb_init(const struct frr_yang_module_info *modules[],
+                   size_t nmodules);
+
+/*
+ * Finish the northbound layer gracefully. Should be called only when the daemon
+ * is exiting.
+ */
+extern void nb_terminate(void);
+
+#endif /* _FRR_NORTHBOUND_H_ */
diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c
new file mode 100644 (file)
index 0000000..8ae44e7
--- /dev/null
@@ -0,0 +1,1448 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 "libfrr.h"
+#include "version.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "command.h"
+#include "termtable.h"
+#include "db.h"
+#include "yang_translator.h"
+#include "northbound.h"
+#include "northbound_cli.h"
+#include "northbound_db.h"
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/northbound_cli_clippy.c"
+#endif
+
+int debug_northbound;
+struct nb_config *vty_shared_candidate_config;
+
+static void vty_show_libyang_errors(struct vty *vty, struct ly_ctx *ly_ctx)
+{
+       struct ly_err_item *ei;
+       const char *path;
+
+       ei = ly_err_first(ly_ctx);
+       if (!ei)
+               return;
+
+       for (; ei; ei = ei->next)
+               vty_out(vty, "%s\n", ei->msg);
+
+       path = ly_errpath(ly_ctx);
+       if (path)
+               vty_out(vty, "YANG path: %s\n", path);
+
+       ly_err_clean(ly_ctx, NULL);
+}
+
+int nb_cli_cfg_change(struct vty *vty, char *xpath_base,
+                     struct cli_config_change changes[], size_t size)
+{
+       struct nb_config *candidate_transitory;
+       bool error = false;
+       int ret;
+
+       VTY_CHECK_XPATH;
+
+       /*
+        * Create a copy of the candidate configuration. For consistency, we
+        * need to ensure that either all changes made by the command are
+        * accepted or none are.
+        */
+       candidate_transitory = nb_config_dup(vty->candidate_config);
+
+       /* Edit candidate configuration. */
+       for (size_t i = 0; i < size; i++) {
+               struct cli_config_change *change = &changes[i];
+               struct nb_node *nb_node;
+               char xpath[XPATH_MAXLEN];
+               struct yang_data *data;
+
+               /* Handle relative XPaths. */
+               memset(xpath, 0, sizeof(xpath));
+               if (vty->xpath_index > 0
+                   && ((xpath_base && xpath_base[0] == '.')
+                       || change->xpath[0] == '.'))
+                       strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath));
+               if (xpath_base) {
+                       if (xpath_base[0] == '.')
+                               xpath_base++;
+                       strlcat(xpath, xpath_base, sizeof(xpath));
+               }
+               if (change->xpath[0] == '.')
+                       strlcat(xpath, change->xpath + 1, sizeof(xpath));
+               else
+                       strlcpy(xpath, change->xpath, sizeof(xpath));
+
+               nb_node = nb_node_find(xpath);
+               if (!nb_node) {
+                       flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                                 "%s: unknown data path: %s", __func__, xpath);
+                       error = true;
+                       break;
+               }
+
+               /* If the value is not set, get the default if it exists. */
+               if (change->value == NULL)
+                       change->value = yang_snode_get_default(nb_node->snode);
+               data = yang_data_new(xpath, change->value);
+
+               /*
+                * Ignore "not found" errors when editing the candidate
+                * configuration.
+                */
+               ret = nb_candidate_edit(candidate_transitory, nb_node,
+                                       change->operation, xpath, NULL, data);
+               yang_data_free(data);
+               if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) {
+                       flog_warn(
+                               EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+                               "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
+                               __func__, nb_operation_name(change->operation),
+                               xpath);
+                       error = true;
+                       break;
+               }
+       }
+
+       if (error) {
+               nb_config_free(candidate_transitory);
+
+               switch (frr_get_cli_mode()) {
+               case FRR_CLI_CLASSIC:
+                       vty_out(vty, "%% Configuration failed.\n\n");
+                       break;
+               case FRR_CLI_TRANSACTIONAL:
+                       vty_out(vty,
+                               "%% Failed to edit candidate configuration.\n\n");
+                       break;
+               }
+               vty_show_libyang_errors(vty, ly_native_ctx);
+
+               return CMD_WARNING_CONFIG_FAILED;
+       }
+
+       nb_config_replace(vty->candidate_config, candidate_transitory, false);
+
+       /* Do an implicit "commit" when using the classic CLI mode. */
+       if (frr_get_cli_mode() == FRR_CLI_CLASSIC) {
+               ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI,
+                                         false, NULL, NULL);
+               if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
+                       vty_out(vty, "%% Configuration failed: %s.\n\n",
+                               nb_err_name(ret));
+                       vty_out(vty,
+                               "Please check the logs for more details.\n");
+
+                       /* Regenerate candidate for consistency. */
+                       nb_config_replace(vty->candidate_config, running_config,
+                                         true);
+                       return CMD_WARNING_CONFIG_FAILED;
+               }
+       }
+
+       return CMD_SUCCESS;
+}
+
+int nb_cli_rpc(const char *xpath, struct list *input, struct list *output)
+{
+       struct nb_node *nb_node;
+       int ret;
+
+       nb_node = nb_node_find(xpath);
+       if (!nb_node) {
+               flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                         "%s: unknown data path: %s", __func__, xpath);
+               return CMD_WARNING;
+       }
+
+       ret = nb_node->cbs.rpc(xpath, input, output);
+       switch (ret) {
+       case NB_OK:
+               return CMD_SUCCESS;
+       default:
+               return CMD_WARNING;
+       }
+}
+
+static int nb_cli_commit(struct vty *vty, bool force, char *comment)
+{
+       uint32_t transaction_id;
+       int ret;
+
+       if (vty_exclusive_lock != NULL && vty_exclusive_lock != vty) {
+               vty_out(vty, "%% Configuration is locked by another VTY.\n\n");
+               return CMD_WARNING;
+       }
+
+       if (!force && nb_candidate_needs_update(vty->candidate_config)) {
+               vty_out(vty,
+                       "%% Candidate configuration needs to be updated before commit.\n\n");
+               vty_out(vty,
+                       "Use the \"update\" command or \"commit force\".\n");
+               return CMD_WARNING;
+       }
+
+       ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, true,
+                                 comment, &transaction_id);
+
+       /* Map northbound return code to CLI return code. */
+       switch (ret) {
+       case NB_OK:
+               nb_config_replace(vty->candidate_config_base, running_config,
+                                 true);
+               vty_out(vty,
+                       "%% Configuration committed successfully (Transaction ID #%u).\n\n",
+                       transaction_id);
+               return CMD_SUCCESS;
+       case NB_ERR_NO_CHANGES:
+               vty_out(vty, "%% No configuration changes to commit.\n\n");
+               return CMD_SUCCESS;
+       default:
+               vty_out(vty,
+                       "%% Failed to commit candidate configuration: %s.\n\n",
+                       nb_err_name(ret));
+               vty_out(vty, "Please check the logs for more details.\n");
+               return CMD_WARNING;
+       }
+}
+
+static int nb_cli_candidate_load_file(struct vty *vty,
+                                     enum nb_cfg_format format,
+                                     struct yang_translator *translator,
+                                     const char *path, bool replace)
+{
+       struct nb_config *loaded_config = NULL;
+       struct lyd_node *dnode;
+       struct ly_ctx *ly_ctx;
+       int ly_format;
+
+       switch (format) {
+       case NB_CFG_FMT_CMDS:
+               loaded_config = nb_config_new(NULL);
+               if (!vty_read_config(loaded_config, path, config_default)) {
+                       vty_out(vty, "%% Failed to load configuration.\n\n");
+                       vty_out(vty,
+                               "Please check the logs for more details.\n");
+                       nb_config_free(loaded_config);
+                       return CMD_WARNING;
+               }
+               break;
+       case NB_CFG_FMT_JSON:
+       case NB_CFG_FMT_XML:
+               ly_format = (format == NB_CFG_FMT_JSON) ? LYD_JSON : LYD_XML;
+
+               ly_ctx = translator ? translator->ly_ctx : ly_native_ctx;
+               dnode = lyd_parse_path(ly_ctx, path, ly_format, LYD_OPT_EDIT);
+               if (!dnode) {
+                       flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed",
+                                 __func__);
+                       vty_out(vty, "%% Failed to load configuration:\n\n");
+                       vty_show_libyang_errors(vty, ly_ctx);
+                       return CMD_WARNING;
+               }
+               if (translator
+                   && yang_translate_dnode(translator,
+                                           YANG_TRANSLATE_TO_NATIVE, &dnode)
+                              != YANG_TRANSLATE_SUCCESS) {
+                       vty_out(vty, "%% Failed to translate configuration\n");
+                       yang_dnode_free(dnode);
+                       return CMD_WARNING;
+               }
+               loaded_config = nb_config_new(dnode);
+               break;
+       }
+
+       if (replace)
+               nb_config_replace(vty->candidate_config, loaded_config, false);
+       else if (nb_config_merge(vty->candidate_config, loaded_config, false)
+                != NB_OK) {
+               vty_out(vty,
+                       "%% Failed to merge the loaded configuration:\n\n");
+               vty_show_libyang_errors(vty, ly_native_ctx);
+               return CMD_WARNING;
+       }
+
+       return CMD_SUCCESS;
+}
+
+static int nb_cli_candidate_load_transaction(struct vty *vty,
+                                            uint32_t transaction_id,
+                                            bool replace)
+{
+       struct nb_config *loaded_config;
+
+       loaded_config = nb_db_transaction_load(transaction_id);
+       if (!loaded_config) {
+               vty_out(vty, "%% Transaction %u does not exist.\n\n",
+                       transaction_id);
+               return CMD_WARNING;
+       }
+
+       if (replace)
+               nb_config_replace(vty->candidate_config, loaded_config, false);
+       else if (nb_config_merge(vty->candidate_config, loaded_config, false)
+                != NB_OK) {
+               vty_out(vty,
+                       "%% Failed to merge the loaded configuration:\n\n");
+               vty_show_libyang_errors(vty, ly_native_ctx);
+               return CMD_WARNING;
+       }
+
+       return CMD_SUCCESS;
+}
+
+void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *root,
+                           bool with_defaults)
+{
+       struct lyd_node *next, *child;
+
+       LY_TREE_DFS_BEGIN (root, next, child) {
+               struct nb_node *nb_node;
+
+               nb_node = child->schema->priv;
+               if (!nb_node->cbs.cli_show)
+                       goto next;
+
+               /* Skip default values. */
+               if (!with_defaults && yang_dnode_is_default_recursive(child))
+                       goto next;
+
+               (*nb_node->cbs.cli_show)(vty, child, with_defaults);
+       next:
+               LY_TREE_DFS_END(root, next, child);
+       }
+}
+
+static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config,
+                                   bool with_defaults)
+{
+       struct lyd_node *root;
+
+       vty_out(vty, "Configuration:\n");
+       vty_out(vty, "!\n");
+       vty_out(vty, "frr version %s\n", FRR_VER_SHORT);
+       vty_out(vty, "frr defaults %s\n", DFLT_NAME);
+
+       LY_TREE_FOR (config->dnode, root)
+               nb_cli_show_dnode_cmds(vty, root, with_defaults);
+
+       vty_out(vty, "!\n");
+       vty_out(vty, "end\n");
+}
+
+static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
+                                     struct nb_config *config,
+                                     struct yang_translator *translator,
+                                     bool with_defaults)
+{
+       struct lyd_node *dnode;
+       char *strp;
+       int options;
+
+       dnode = yang_dnode_dup(config->dnode);
+       if (translator
+           && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE,
+                                   &dnode)
+                      != YANG_TRANSLATE_SUCCESS) {
+               vty_out(vty, "%% Failed to translate configuration\n");
+               yang_dnode_free(dnode);
+               return CMD_WARNING;
+       }
+
+       options = LYP_FORMAT | LYP_WITHSIBLINGS;
+       if (with_defaults)
+               options |= LYP_WD_ALL;
+       else
+               options |= LYP_WD_TRIM;
+
+       if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) {
+               vty_out(vty, "%s", strp);
+               free(strp);
+       }
+
+       yang_dnode_free(dnode);
+
+       return CMD_SUCCESS;
+}
+
+static int nb_cli_show_config(struct vty *vty, struct nb_config *config,
+                             enum nb_cfg_format format,
+                             struct yang_translator *translator,
+                             bool with_defaults)
+{
+       switch (format) {
+       case NB_CFG_FMT_CMDS:
+               nb_cli_show_config_cmds(vty, config, with_defaults);
+               break;
+       case NB_CFG_FMT_JSON:
+               return nb_cli_show_config_libyang(vty, LYD_JSON, config,
+                                                 translator, with_defaults);
+       case NB_CFG_FMT_XML:
+               return nb_cli_show_config_libyang(vty, LYD_XML, config,
+                                                 translator, with_defaults);
+       }
+
+       return CMD_SUCCESS;
+}
+
+static int nb_write_config(struct nb_config *config, enum nb_cfg_format format,
+                          struct yang_translator *translator, char *path,
+                          size_t pathlen)
+{
+       int fd;
+       struct vty *file_vty;
+       int ret = 0;
+
+       snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX");
+       fd = mkstemp(path);
+       if (fd < 0) {
+               flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s",
+                         __func__, safe_strerror(errno));
+               return -1;
+       }
+
+       /* Make vty for configuration file. */
+       file_vty = vty_new();
+       file_vty->wfd = fd;
+       file_vty->type = VTY_FILE;
+       if (config)
+               ret = nb_cli_show_config(file_vty, config, format, translator,
+                                        false);
+       vty_close(file_vty);
+
+       return ret;
+}
+
+static int 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)
+{
+       char config1_path[256];
+       char config2_path[256];
+       char command[BUFSIZ];
+       FILE *fp;
+       char line[1024];
+       int lineno = 0;
+
+       if (nb_write_config(config1, format, translator, config1_path,
+                           sizeof(config1_path))
+           != 0) {
+               vty_out(vty, "%% Failed to process configurations.\n\n");
+               return CMD_WARNING;
+       }
+       if (nb_write_config(config2, format, translator, config2_path,
+                           sizeof(config2_path))
+           != 0) {
+               vty_out(vty, "%% Failed to process configurations.\n\n");
+               unlink(config1_path);
+               return CMD_WARNING;
+       }
+
+       snprintf(command, sizeof(command), "diff -u %s %s", config1_path,
+                config2_path);
+       fp = popen(command, "r");
+       if (!fp) {
+               vty_out(vty, "%% Failed to generate configuration diff.\n\n");
+               unlink(config1_path);
+               unlink(config2_path);
+               return CMD_WARNING;
+       }
+       /* Print diff line by line. */
+       while (fgets(line, sizeof(line), fp) != NULL) {
+               if (lineno++ < 2)
+                       continue;
+               vty_out(vty, "%s", line);
+       }
+       pclose(fp);
+
+       unlink(config1_path);
+       unlink(config2_path);
+
+       return CMD_SUCCESS;
+}
+
+/* Configure exclusively from this terminal. */
+DEFUN (config_exclusive,
+       config_exclusive_cmd,
+       "configure exclusive",
+       "Configuration from vty interface\n"
+       "Configure exclusively from this terminal\n")
+{
+       if (vty_config_exclusive_lock(vty))
+               vty->node = CONFIG_NODE;
+       else {
+               vty_out(vty, "VTY configuration is locked by other VTY\n");
+               return CMD_WARNING_CONFIG_FAILED;
+       }
+
+       vty->private_config = true;
+       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");
+
+       return CMD_SUCCESS;
+}
+
+/* Configure using a private candidate configuration. */
+DEFUN (config_private,
+       config_private_cmd,
+       "configure private",
+       "Configuration from vty interface\n"
+       "Configure using a private candidate configuration\n")
+{
+       if (vty_config_lock(vty))
+               vty->node = CONFIG_NODE;
+       else {
+               vty_out(vty, "VTY configuration is locked by other VTY\n");
+               return CMD_WARNING_CONFIG_FAILED;
+       }
+
+       vty->private_config = true;
+       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");
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (config_commit,
+       config_commit_cmd,
+       "commit [force$force]",
+       "Commit changes into the running configuration\n"
+       "Force commit even if the candidate is outdated\n")
+{
+       return nb_cli_commit(vty, !!force, NULL);
+}
+
+DEFPY (config_commit_comment,
+       config_commit_comment_cmd,
+       "commit [force$force] comment LINE...",
+       "Commit changes into the running configuration\n"
+       "Force commit even if the candidate is outdated\n"
+       "Assign a comment to this commit\n"
+       "Comment for this commit (Max 80 characters)\n")
+{
+       char *comment;
+       int idx = 0;
+       int ret;
+
+       argv_find(argv, argc, "LINE", &idx);
+       comment = argv_concat(argv, argc, idx);
+       ret = nb_cli_commit(vty, !!force, comment);
+       XFREE(MTYPE_TMP, comment);
+
+       return ret;
+}
+
+DEFPY (config_commit_check,
+       config_commit_check_cmd,
+       "commit check",
+       "Commit changes into the running configuration\n"
+       "Check if the configuration changes are valid\n")
+{
+       int ret;
+
+       ret = nb_candidate_validate(vty->candidate_config);
+       if (ret != NB_OK) {
+               vty_out(vty,
+                       "%% Failed to validate candidate configuration.\n\n");
+               vty_show_libyang_errors(vty, ly_native_ctx);
+               return CMD_WARNING;
+       }
+
+       vty_out(vty, "%% Candidate configuration validated successfully.\n\n");
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (config_update,
+       config_update_cmd,
+       "update",
+       "Update candidate configuration\n")
+{
+       if (!nb_candidate_needs_update(vty->candidate_config)) {
+               vty_out(vty, "%% Update is not necessary.\n\n");
+               return CMD_SUCCESS;
+       }
+
+       if (nb_candidate_update(vty->candidate_config) != NB_OK) {
+               vty_out(vty,
+                       "%% Failed to update the candidate configuration.\n\n");
+               vty_out(vty, "Please check the logs for more details.\n");
+               return CMD_WARNING;
+       }
+
+       nb_config_replace(vty->candidate_config_base, running_config, true);
+
+       vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (config_discard,
+       config_discard_cmd,
+       "discard",
+       "Discard changes in the candidate configuration\n")
+{
+       nb_config_replace(vty->candidate_config, vty->candidate_config_base,
+                         true);
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (config_load,
+       config_load_cmd,
+       "configuration load\
+          <\
+           file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
+           |transaction (1-4294967296)$tid\
+         >\
+         [replace$replace]",
+       "Configuration related settings\n"
+       "Load configuration into candidate\n"
+       "Load configuration file into candidate\n"
+       "Load configuration file in JSON format\n"
+       "Load configuration file in XML format\n"
+       "Translate configuration file\n"
+       "YANG module translator\n"
+       "Configuration file name (full path)\n"
+       "Load configuration from transaction into candidate\n"
+       "Transaction ID\n"
+       "Replace instead of merge\n")
+{
+       if (filename) {
+               enum nb_cfg_format format;
+               struct yang_translator *translator = NULL;
+
+               if (json)
+                       format = NB_CFG_FMT_JSON;
+               else if (xml)
+                       format = NB_CFG_FMT_XML;
+               else
+                       format = NB_CFG_FMT_CMDS;
+
+               if (translator_family) {
+                       translator = yang_translator_find(translator_family);
+                       if (!translator) {
+                               vty_out(vty,
+                                       "%% Module translator \"%s\" not found\n",
+                                       translator_family);
+                               return CMD_WARNING;
+                       }
+               }
+
+               return nb_cli_candidate_load_file(vty, format, translator,
+                                                 filename, !!replace);
+       }
+
+       return nb_cli_candidate_load_transaction(vty, tid, !!replace);
+}
+
+DEFPY (show_config_running,
+       show_config_running_cmd,
+       "show configuration running\
+          [<json$json|xml$xml> [translate WORD$translator_family]]\
+         [with-defaults$with_defaults]",
+       SHOW_STR
+       "Configuration information\n"
+       "Running configuration\n"
+       "Change output format to JSON\n"
+       "Change output format to XML\n"
+       "Translate output\n"
+       "YANG module translator\n"
+       "Show default values\n")
+
+{
+       enum nb_cfg_format format;
+       struct yang_translator *translator = NULL;
+
+       if (json)
+               format = NB_CFG_FMT_JSON;
+       else if (xml)
+               format = NB_CFG_FMT_XML;
+       else
+               format = NB_CFG_FMT_CMDS;
+
+       if (translator_family) {
+               translator = yang_translator_find(translator_family);
+               if (!translator) {
+                       vty_out(vty, "%% Module translator \"%s\" not found\n",
+                               translator_family);
+                       return CMD_WARNING;
+               }
+       }
+
+       nb_cli_show_config(vty, running_config, format, translator,
+                          !!with_defaults);
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (show_config_candidate,
+       show_config_candidate_cmd,
+       "show configuration candidate\
+          [<json$json|xml$xml> [translate WORD$translator_family]]\
+          [<\
+           with-defaults$with_defaults\
+           |changes$changes\
+          >]",
+       SHOW_STR
+       "Configuration information\n"
+       "Candidate configuration\n"
+       "Change output format to JSON\n"
+       "Change output format to XML\n"
+       "Translate output\n"
+       "YANG module translator\n"
+       "Show default values\n"
+       "Show changes applied in the candidate configuration\n")
+
+{
+       enum nb_cfg_format format;
+       struct yang_translator *translator = NULL;
+
+       if (json)
+               format = NB_CFG_FMT_JSON;
+       else if (xml)
+               format = NB_CFG_FMT_XML;
+       else
+               format = NB_CFG_FMT_CMDS;
+
+       if (translator_family) {
+               translator = yang_translator_find(translator_family);
+               if (!translator) {
+                       vty_out(vty, "%% Module translator \"%s\" not found\n",
+                               translator_family);
+                       return CMD_WARNING;
+               }
+       }
+
+       if (changes)
+               return nb_cli_show_config_compare(
+                       vty, vty->candidate_config_base, vty->candidate_config,
+                       format, translator);
+
+       nb_cli_show_config(vty, vty->candidate_config, format, translator,
+                          !!with_defaults);
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (show_config_compare,
+       show_config_compare_cmd,
+       "show configuration compare\
+          <\
+           candidate$c1_candidate\
+           |running$c1_running\
+           |transaction (1-4294967296)$c1_tid\
+         >\
+          <\
+           candidate$c2_candidate\
+           |running$c2_running\
+           |transaction (1-4294967296)$c2_tid\
+         >\
+         [<json$json|xml$xml> [translate WORD$translator_family]]",
+       SHOW_STR
+       "Configuration information\n"
+       "Compare two different configurations\n"
+       "Candidate configuration\n"
+       "Running configuration\n"
+       "Configuration transaction\n"
+       "Transaction ID\n"
+       "Candidate configuration\n"
+       "Running configuration\n"
+       "Configuration transaction\n"
+       "Transaction ID\n"
+       "Change output format to JSON\n"
+       "Change output format to XML\n"
+       "Translate output\n"
+       "YANG module translator\n")
+{
+       enum nb_cfg_format format;
+       struct yang_translator *translator = NULL;
+       struct nb_config *config1, *config_transaction1 = NULL;
+       struct nb_config *config2, *config_transaction2 = NULL;
+       int ret = CMD_WARNING;
+
+       if (c1_candidate)
+               config1 = vty->candidate_config;
+       else if (c1_running)
+               config1 = running_config;
+       else {
+               config_transaction1 = nb_db_transaction_load(c1_tid);
+               if (!config_transaction1) {
+                       vty_out(vty, "%% Transaction %u does not exist\n\n",
+                               (unsigned int)c1_tid);
+                       goto exit;
+               }
+               config1 = config_transaction1;
+       }
+
+       if (c2_candidate)
+               config2 = vty->candidate_config;
+       else if (c2_running)
+               config2 = running_config;
+       else {
+               config_transaction2 = nb_db_transaction_load(c2_tid);
+               if (!config_transaction2) {
+                       vty_out(vty, "%% Transaction %u does not exist\n\n",
+                               (unsigned int)c2_tid);
+                       goto exit;
+               }
+               config2 = config_transaction2;
+       }
+
+       if (json)
+               format = NB_CFG_FMT_JSON;
+       else if (xml)
+               format = NB_CFG_FMT_XML;
+       else
+               format = NB_CFG_FMT_CMDS;
+
+       if (translator_family) {
+               translator = yang_translator_find(translator_family);
+               if (!translator) {
+                       vty_out(vty, "%% Module translator \"%s\" not found\n",
+                               translator_family);
+                       goto exit;
+               }
+       }
+
+       ret = nb_cli_show_config_compare(vty, config1, config2, format,
+                                        translator);
+exit:
+       if (config_transaction1)
+               nb_config_free(config_transaction1);
+       if (config_transaction2)
+               nb_config_free(config_transaction2);
+
+       return ret;
+}
+
+/*
+ * Stripped down version of the "show configuration compare" command.
+ * The "candidate" option is not present so the command can be installed in
+ * the enable node.
+ */
+ALIAS (show_config_compare,
+       show_config_compare_without_candidate_cmd,
+       "show configuration compare\
+          <\
+           running$c1_running\
+           |transaction (1-4294967296)$c1_tid\
+         >\
+          <\
+           running$c2_running\
+           |transaction (1-4294967296)$c2_tid\
+         >\
+        [<json$json|xml$xml> [translate WORD$translator_family]]",
+       SHOW_STR
+       "Configuration information\n"
+       "Compare two different configurations\n"
+       "Running configuration\n"
+       "Configuration transaction\n"
+       "Transaction ID\n"
+       "Running configuration\n"
+       "Configuration transaction\n"
+       "Transaction ID\n"
+       "Change output format to JSON\n"
+       "Change output format to XML\n"
+       "Translate output\n"
+       "YANG module translator\n")
+
+DEFPY (clear_config_transactions,
+       clear_config_transactions_cmd,
+       "clear configuration transactions oldest (1-100)$n",
+       CLEAR_STR
+       "Configuration activity\n"
+       "Delete transactions from the transactions log\n"
+       "Delete oldest <n> transactions\n"
+       "Number of transactions to delete\n")
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+       if (nb_db_clear_transactions(n) != NB_OK) {
+               vty_out(vty, "%% Failed to delete transactions.\n\n");
+               return CMD_WARNING;
+       }
+#else
+       vty_out(vty,
+               "%% FRR was compiled without --enable-config-rollbacks.\n\n");
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (config_database_max_transactions,
+       config_database_max_transactions_cmd,
+       "configuration database max-transactions (1-100)$max",
+       "Configuration related settings\n"
+       "Configuration database\n"
+       "Set the maximum number of transactions to store\n"
+       "Number of transactions\n")
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+       if (nb_db_set_max_transactions(max) != NB_OK) {
+               vty_out(vty,
+                       "%% Failed to update the maximum number of transactions.\n\n");
+               return CMD_WARNING;
+       }
+       vty_out(vty,
+               "%% Maximum number of transactions updated successfully.\n\n");
+#else
+       vty_out(vty,
+               "%% FRR was compiled without --enable-config-rollbacks.\n\n");
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (yang_module_translator_load,
+       yang_module_translator_load_cmd,
+       "yang module-translator load FILENAME$filename",
+       "YANG related settings\n"
+       "YANG module translator\n"
+       "Load YANG module translator\n"
+       "File name (full path)\n")
+{
+       struct yang_translator *translator;
+
+       translator = yang_translator_load(filename);
+       if (!translator) {
+               vty_out(vty, "%% Failed to load \"%s\"\n\n", filename);
+               vty_out(vty, "Please check the logs for more details.\n");
+               return CMD_WARNING;
+       }
+
+       vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n",
+               translator->family);
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (yang_module_translator_unload_family,
+       yang_module_translator_unload_cmd,
+       "yang module-translator unload WORD$translator_family",
+       "YANG related settings\n"
+       "YANG module translator\n"
+       "Unload YANG module translator\n"
+       "Name of the module translator\n")
+{
+       struct yang_translator *translator;
+
+       translator = yang_translator_find(translator_family);
+       if (!translator) {
+               vty_out(vty, "%% Module translator \"%s\" not found\n",
+                       translator_family);
+               return CMD_WARNING;
+       }
+
+       yang_translator_unload(translator);
+
+       return CMD_SUCCESS;
+}
+
+#ifdef HAVE_CONFIG_ROLLBACKS
+static void nb_cli_show_transactions_cb(void *arg, int transaction_id,
+                                       const char *client_name,
+                                       const char *date, const char *comment)
+{
+       struct ttable *tt = arg;
+
+       ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date,
+                      comment);
+}
+
+static int nb_cli_show_transactions(struct vty *vty)
+{
+       struct ttable *tt;
+
+       /* Prepare table. */
+       tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+       ttable_add_row(tt, "Transaction ID|Client|Date|Comment");
+       tt->style.cell.rpad = 2;
+       tt->style.corner = '+';
+       ttable_restyle(tt);
+       ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+       /* Fetch transactions from the northbound database. */
+       if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt)
+           != NB_OK) {
+               vty_out(vty,
+                       "%% Failed to fetch configuration transactions.\n");
+               return CMD_WARNING;
+       }
+
+       /* Dump the generated table. */
+       if (tt->nrows > 1) {
+               char *table;
+
+               table = ttable_dump(tt, "\n");
+               vty_out(vty, "%s\n", table);
+               XFREE(MTYPE_TMP, table);
+       } else
+               vty_out(vty, "No configuration transactions to display.\n\n");
+
+       ttable_del(tt);
+
+       return CMD_SUCCESS;
+}
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+DEFPY (show_config_transaction,
+       show_config_transaction_cmd,
+       "show configuration transaction\
+          [\
+           (1-4294967296)$transaction_id\
+           [<json$json|xml$xml> [translate WORD$translator_family]]\
+            [<\
+             with-defaults$with_defaults\
+             |changes$changes\
+            >]\
+         ]",
+       SHOW_STR
+       "Configuration information\n"
+       "Configuration transaction\n"
+       "Transaction ID\n"
+       "Change output format to JSON\n"
+       "Change output format to XML\n"
+       "Translate output\n"
+       "YANG module translator\n"
+       "Show default values\n"
+       "Show changes compared to the previous transaction\n")
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+       if (transaction_id) {
+               struct nb_config *config;
+               enum nb_cfg_format format;
+               struct yang_translator *translator = NULL;
+
+               if (json)
+                       format = NB_CFG_FMT_JSON;
+               else if (xml)
+                       format = NB_CFG_FMT_XML;
+               else
+                       format = NB_CFG_FMT_CMDS;
+
+               if (translator_family) {
+                       translator = yang_translator_find(translator_family);
+                       if (!translator) {
+                               vty_out(vty,
+                                       "%% Module translator \"%s\" not found\n",
+                                       translator_family);
+                               return CMD_WARNING;
+                       }
+               }
+
+               config = nb_db_transaction_load(transaction_id);
+               if (!config) {
+                       vty_out(vty, "%% Transaction %u does not exist.\n\n",
+                               (unsigned int)transaction_id);
+                       return CMD_WARNING;
+               }
+
+               if (changes) {
+                       struct nb_config *prev_config;
+                       int ret;
+
+                       /* NOTE: this can be NULL. */
+                       prev_config =
+                               nb_db_transaction_load(transaction_id - 1);
+
+                       ret = nb_cli_show_config_compare(
+                               vty, prev_config, config, format, translator);
+                       if (prev_config)
+                               nb_config_free(prev_config);
+                       nb_config_free(config);
+
+                       return ret;
+               }
+
+               nb_cli_show_config(vty, config, format, translator,
+                                  !!with_defaults);
+               nb_config_free(config);
+
+               return CMD_SUCCESS;
+       }
+
+       return nb_cli_show_transactions(vty);
+#else
+       vty_out(vty,
+               "%% FRR was compiled without --enable-config-rollbacks.\n\n");
+       return CMD_WARNING;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+}
+
+DEFPY (show_yang_module,
+       show_yang_module_cmd,
+       "show yang module [module-translator WORD$translator_family]",
+       SHOW_STR
+       "YANG information\n"
+       "Show loaded modules\n"
+       "YANG module translator\n"
+       "YANG module translator\n")
+{
+       struct ly_ctx *ly_ctx;
+       struct yang_translator *translator = NULL;
+       const struct lys_module *module;
+       struct ttable *tt;
+       uint32_t idx = 0;
+
+       if (translator_family) {
+               translator = yang_translator_find(translator_family);
+               if (!translator) {
+                       vty_out(vty, "%% Module translator \"%s\" not found\n",
+                               translator_family);
+                       return CMD_WARNING;
+               }
+               ly_ctx = translator->ly_ctx;
+       } else
+               ly_ctx = ly_native_ctx;
+
+       /* Prepare table. */
+       tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+       ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace");
+       tt->style.cell.rpad = 2;
+       tt->style.corner = '+';
+       ttable_restyle(tt);
+       ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+       while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) {
+               char flags[8];
+
+               snprintf(flags, sizeof(flags), "%c%c",
+                        module->implemented ? 'I' : ' ',
+                        (module->deviated == 1) ? 'D' : ' ');
+
+               ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name,
+                              (module->version == 2) ? "1.1" : "1.0",
+                              (module->rev_size > 0) ? module->rev[0].date
+                                                     : "-",
+                              flags, module->ns);
+       }
+
+       /* Dump the generated table. */
+       if (tt->nrows > 1) {
+               char *table;
+
+               vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n");
+
+               table = ttable_dump(tt, "\n");
+               vty_out(vty, "%s\n", table);
+               XFREE(MTYPE_TMP, table);
+       } else
+               vty_out(vty, "No YANG modules to display.\n\n");
+
+       ttable_del(tt);
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (show_yang_module_detail,
+       show_yang_module_detail_cmd,
+       "show yang module\
+          [module-translator WORD$translator_family]\
+          WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
+       SHOW_STR
+       "YANG information\n"
+       "Show loaded modules\n"
+       "YANG module translator\n"
+       "YANG module translator\n"
+       "Module name\n"
+       "Display summary information about the module\n"
+       "Display module in the tree (RFC 8340) format\n"
+       "Display module in the YANG format\n"
+       "Display module in the YIN format\n")
+{
+       struct ly_ctx *ly_ctx;
+       struct yang_translator *translator = NULL;
+       const struct lys_module *module;
+       LYS_OUTFORMAT format;
+       char *strp;
+
+       if (translator_family) {
+               translator = yang_translator_find(translator_family);
+               if (!translator) {
+                       vty_out(vty, "%% Module translator \"%s\" not found\n",
+                               translator_family);
+                       return CMD_WARNING;
+               }
+               ly_ctx = translator->ly_ctx;
+       } else
+               ly_ctx = ly_native_ctx;
+
+       module = ly_ctx_get_module(ly_ctx, module_name, NULL, 0);
+       if (!module) {
+               vty_out(vty, "%% Module \"%s\" not found\n", module_name);
+               return CMD_WARNING;
+       }
+
+       if (yang)
+               format = LYS_OUT_YANG;
+       else if (yin)
+               format = LYS_OUT_YIN;
+       else if (tree)
+               format = LYS_OUT_TREE;
+       else
+               format = LYS_OUT_INFO;
+
+       if (lys_print_mem(&strp, module, format, NULL, 0, 0) == 0) {
+               vty_out(vty, "%s\n", strp);
+               free(strp);
+       } else {
+               /* Unexpected. */
+               vty_out(vty, "%% Error generating module information\n");
+               return CMD_WARNING;
+       }
+
+       return CMD_SUCCESS;
+}
+
+DEFPY (show_yang_module_translator,
+       show_yang_module_translator_cmd,
+       "show yang module-translator",
+       SHOW_STR
+       "YANG information\n"
+       "Show loaded YANG module translators\n")
+{
+       struct yang_translator *translator;
+       struct ttable *tt;
+
+       /* Prepare table. */
+       tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+       ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)");
+       tt->style.cell.rpad = 2;
+       tt->style.corner = '+';
+       ttable_restyle(tt);
+       ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+       RB_FOREACH (translator, yang_translators, &yang_translators) {
+               struct yang_tmodule *tmodule;
+               struct listnode *ln;
+
+               for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+                       ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family,
+                                      tmodule->module->name,
+                                      tmodule->deviations->name,
+                                      tmodule->coverage);
+               }
+       }
+
+       /* Dump the generated table. */
+       if (tt->nrows > 1) {
+               char *table;
+
+               table = ttable_dump(tt, "\n");
+               vty_out(vty, "%s\n", table);
+               XFREE(MTYPE_TMP, table);
+       } else
+               vty_out(vty, "No YANG module translators to display.\n\n");
+
+       ttable_del(tt);
+
+       return CMD_SUCCESS;
+}
+
+#ifdef HAVE_CONFIG_ROLLBACKS
+static int nb_cli_rollback_configuration(struct vty *vty,
+                                        uint32_t transaction_id)
+{
+       struct nb_config *candidate;
+       char comment[80];
+       int ret;
+
+       candidate = nb_db_transaction_load(transaction_id);
+       if (!candidate) {
+               vty_out(vty, "%% Transaction %u does not exist.\n\n",
+                       transaction_id);
+               return CMD_WARNING;
+       }
+
+       snprintf(comment, sizeof(comment), "Rollback to transaction %u",
+                transaction_id);
+
+       ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, true, comment,
+                                 NULL);
+       nb_config_free(candidate);
+       switch (ret) {
+       case NB_OK:
+               vty_out(vty,
+                       "%% Configuration was successfully rolled back.\n\n");
+               return CMD_SUCCESS;
+       case NB_ERR_NO_CHANGES:
+               vty_out(vty,
+                       "%% Aborting - no configuration changes detected.\n\n");
+               return CMD_WARNING;
+       default:
+               vty_out(vty, "%% Rollback failed.\n\n");
+               vty_out(vty, "Please check the logs for more details.\n");
+               return CMD_WARNING;
+       }
+}
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+DEFPY (rollback_config,
+       rollback_config_cmd,
+       "rollback configuration (1-4294967296)$transaction_id",
+       "Rollback to a previous state\n"
+       "Running configuration\n"
+       "Transaction ID\n")
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+       return nb_cli_rollback_configuration(vty, transaction_id);
+#else
+       vty_out(vty,
+               "%% FRR was compiled without --enable-config-rollbacks.\n\n");
+       return CMD_SUCCESS;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+}
+
+/* Debug CLI commands. */
+DEFUN (debug_nb,
+       debug_nb_cmd,
+       "debug northbound",
+       DEBUG_STR
+       "Northbound Debugging\n")
+{
+       debug_northbound = 1;
+
+       return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_nb,
+       no_debug_nb_cmd,
+       "no debug northbound",
+       NO_STR DEBUG_STR
+       "Northbound Debugging\n")
+{
+       debug_northbound = 0;
+
+       return CMD_SUCCESS;
+}
+
+static int nb_debug_config_write(struct vty *vty)
+{
+       if (debug_northbound)
+               vty_out(vty, "debug northbound\n");
+
+       return 1;
+}
+
+static struct cmd_node nb_debug_node = {NORTHBOUND_DEBUG_NODE, "", 1};
+
+void nb_cli_install_default(int node)
+{
+       if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL)
+               return;
+
+       install_element(node, &config_commit_cmd);
+       install_element(node, &config_commit_comment_cmd);
+       install_element(node, &config_commit_check_cmd);
+       install_element(node, &config_update_cmd);
+       install_element(node, &config_discard_cmd);
+       install_element(node, &show_config_running_cmd);
+       install_element(node, &show_config_candidate_cmd);
+       install_element(node, &show_config_compare_cmd);
+       install_element(node, &show_config_transaction_cmd);
+}
+
+/* YANG module autocomplete. */
+static void yang_module_autocomplete(vector comps, struct cmd_token *token)
+{
+       const struct lys_module *module;
+       struct yang_translator *module_tr;
+       uint32_t idx;
+
+       idx = 0;
+       while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx)))
+               vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name));
+
+       RB_FOREACH (module_tr, yang_translators, &yang_translators) {
+               idx = 0;
+               while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx,
+                                                       &idx)))
+                       vector_set(comps,
+                                  XSTRDUP(MTYPE_COMPLETION, module->name));
+       }
+}
+
+/* YANG module translator autocomplete. */
+static void yang_translator_autocomplete(vector comps, struct cmd_token *token)
+{
+       struct yang_translator *module_tr;
+
+       RB_FOREACH (module_tr, yang_translators, &yang_translators)
+               vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family));
+}
+
+static const struct cmd_variable_handler yang_var_handlers[] = {
+       {.varname = "module_name", .completions = yang_module_autocomplete},
+       {.varname = "translator_family",
+        .completions = yang_translator_autocomplete},
+       {.completions = NULL}};
+
+void nb_cli_init(void)
+{
+       /* Initialize the shared candidate configuration. */
+       vty_shared_candidate_config = nb_config_new(NULL);
+
+       /* Install debug commands */
+       install_node(&nb_debug_node, nb_debug_config_write);
+       install_element(ENABLE_NODE, &debug_nb_cmd);
+       install_element(ENABLE_NODE, &no_debug_nb_cmd);
+       install_element(CONFIG_NODE, &debug_nb_cmd);
+       install_element(CONFIG_NODE, &no_debug_nb_cmd);
+
+       /* Install commands specific to the transaction-base mode. */
+       if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
+               install_element(ENABLE_NODE, &config_exclusive_cmd);
+               install_element(ENABLE_NODE, &config_private_cmd);
+               install_element(ENABLE_NODE, &show_config_running_cmd);
+               install_element(ENABLE_NODE,
+                               &show_config_compare_without_candidate_cmd);
+               install_element(ENABLE_NODE, &show_config_transaction_cmd);
+               install_element(ENABLE_NODE, &rollback_config_cmd);
+               install_element(ENABLE_NODE, &clear_config_transactions_cmd);
+
+               install_element(CONFIG_NODE, &config_load_cmd);
+               install_element(CONFIG_NODE,
+                               &config_database_max_transactions_cmd);
+       }
+
+       /* Other commands. */
+       install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
+       install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
+       install_element(ENABLE_NODE, &show_yang_module_cmd);
+       install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
+       install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
+       cmd_variable_handler_register(yang_var_handlers);
+}
+
+void nb_cli_terminate(void)
+{
+       nb_config_free(vty_shared_candidate_config);
+}
diff --git a/lib/northbound_cli.h b/lib/northbound_cli.h
new file mode 100644 (file)
index 0000000..7f4a64c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+#ifndef _FRR_NORTHBOUND_CLI_H_
+#define _FRR_NORTHBOUND_CLI_H_
+
+#include "northbound.h"
+
+struct cli_config_change {
+       /*
+        * XPath (absolute or relative) of the configuration option being
+        * edited.
+        */
+       char xpath[XPATH_MAXLEN];
+
+       /*
+        * Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or
+        * NB_OP_DELETE).
+        */
+       enum nb_operation operation;
+
+       /*
+        * New value of the configuration option. Should be NULL for typeless
+        * YANG data (e.g. presence-containers). For convenience, NULL can also
+        * be used to restore a leaf to its default value.
+        */
+       const char *value;
+};
+
+/* Possible formats in which a configuration can be displayed. */
+enum nb_cfg_format {
+       NB_CFG_FMT_CMDS = 0,
+       NB_CFG_FMT_JSON,
+       NB_CFG_FMT_XML,
+};
+
+extern struct nb_config *vty_shared_candidate_config;
+
+/* Prototypes. */
+extern int nb_cli_cfg_change(struct vty *vty, char *xpath_list,
+                            struct cli_config_change changes[], size_t size);
+extern int nb_cli_rpc(const char *xpath, struct list *input,
+                     struct list *output);
+extern void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *dnode,
+                                  bool show_defaults);
+extern void nb_cli_install_default(int node);
+extern void nb_cli_init(void);
+extern void nb_cli_terminate(void);
+
+#endif /* _FRR_NORTHBOUND_CLI_H_ */
diff --git a/lib/northbound_db.c b/lib/northbound_db.c
new file mode 100644 (file)
index 0000000..598805b
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 "libfrr.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "command.h"
+#include "db.h"
+#include "northbound.h"
+#include "northbound_db.h"
+
+int nb_db_init(void)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+       /*
+        * NOTE: the delete_tail SQL trigger is used to implement a ring buffer
+        * where only the last N transactions are recorded in the configuration
+        * log.
+        */
+       if (db_execute(
+                   "BEGIN TRANSACTION;\n"
+                   "  CREATE TABLE IF NOT EXISTS transactions(\n"
+                   "    client         CHAR(32)             NOT NULL,\n"
+                   "    date           DATETIME             DEFAULT CURRENT_TIMESTAMP,\n"
+                   "    comment        CHAR(80)             ,\n"
+                   "    configuration  TEXT                 NOT NULL\n"
+                   "  );\n"
+                   "  CREATE TRIGGER IF NOT EXISTS delete_tail\n"
+                   "    AFTER INSERT ON transactions\n"
+                   "    FOR EACH ROW\n"
+                   "    BEGIN\n"
+                   "    DELETE\n"
+                   "    FROM\n"
+                   "      transactions\n"
+                   "    WHERE\n"
+                   "      rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
+                   "    END;\n"
+                   "COMMIT;",
+                   NB_DLFT_MAX_CONFIG_ROLLBACKS, NB_DLFT_MAX_CONFIG_ROLLBACKS)
+           != 0)
+               return NB_ERR;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+       return NB_OK;
+}
+
+int nb_db_transaction_save(const struct nb_transaction *transaction,
+                          uint32_t *transaction_id)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+       struct sqlite3_stmt *ss;
+       const char *client_name;
+       char *config_str = NULL;
+       int ret = NB_ERR;
+
+       /*
+        * Use a transaction to ensure consistency between the INSERT and SELECT
+        * queries.
+        */
+       if (db_execute("BEGIN TRANSACTION;") != 0)
+               return NB_ERR;
+
+       ss = db_prepare(
+               "INSERT INTO transactions\n"
+               "  (client, comment, configuration)\n"
+               "VALUES\n"
+               "  (?, ?, ?);");
+       if (!ss)
+               goto exit;
+
+       client_name = nb_client_name(transaction->client);
+       /* Always record configurations in the XML format. */
+       if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML,
+                         LYP_FORMAT | LYP_WITHSIBLINGS)
+           != 0)
+               goto exit;
+
+       if (db_bindf(ss, "%s%s%s", client_name, strlen(client_name),
+                    transaction->comment, strlen(transaction->comment),
+                    config_str ? config_str : "",
+                    config_str ? strlen(config_str) : 0)
+           != 0)
+               goto exit;
+
+       if (db_run(ss) != SQLITE_OK)
+               goto exit;
+
+       db_finalize(&ss);
+
+       /*
+        * transaction_id is an optional output parameter that provides the ID
+        * of the recorded transaction.
+        */
+       if (transaction_id) {
+               ss = db_prepare("SELECT last_insert_rowid();");
+               if (!ss)
+                       goto exit;
+
+               if (db_run(ss) != SQLITE_ROW)
+                       goto exit;
+
+               if (db_loadf(ss, "%i", transaction_id) != 0)
+                       goto exit;
+
+               db_finalize(&ss);
+       }
+
+       if (db_execute("COMMIT;") != 0)
+               goto exit;
+
+       ret = NB_OK;
+
+exit:
+       if (config_str)
+               free(config_str);
+       if (ss)
+               db_finalize(&ss);
+       if (ret != NB_OK)
+               (void)db_execute("ROLLBACK TRANSACTION;");
+
+       return ret;
+#else  /* HAVE_CONFIG_ROLLBACKS */
+       return NB_OK;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+}
+
+struct nb_config *nb_db_transaction_load(uint32_t transaction_id)
+{
+       struct nb_config *config = NULL;
+#ifdef HAVE_CONFIG_ROLLBACKS
+       struct lyd_node *dnode;
+       const char *config_str;
+       struct sqlite3_stmt *ss;
+
+       ss = db_prepare(
+               "SELECT\n"
+               "  configuration\n"
+               "FROM\n"
+               "  transactions\n"
+               "WHERE\n"
+               "  rowid=?;");
+       if (!ss)
+               return NULL;
+
+       if (db_bindf(ss, "%d", transaction_id) != 0)
+               goto exit;
+
+       if (db_run(ss) != SQLITE_ROW)
+               goto exit;
+
+       if (db_loadf(ss, "%s", &config_str) != 0)
+               goto exit;
+
+       dnode = lyd_parse_mem(ly_native_ctx, config_str, LYD_XML,
+                             LYD_OPT_CONFIG);
+       if (!dnode)
+               flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_mem() failed",
+                         __func__);
+       else
+               config = nb_config_new(dnode);
+
+exit:
+       db_finalize(&ss);
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+       return config;
+}
+
+int nb_db_clear_transactions(unsigned int n_oldest)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+       /* Delete oldest N entries. */
+       if (db_execute("DELETE\n"
+                      "FROM\n"
+                      "  transactions\n"
+                      "WHERE\n"
+                      "  ROWID IN (\n"
+                      "    SELECT\n"
+                      "      ROWID\n"
+                      "    FROM\n"
+                      "      transactions\n"
+                      "    ORDER BY ROWID ASC LIMIT %u\n"
+                      "  );",
+                      n_oldest)
+           != 0)
+               return NB_ERR;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+       return NB_OK;
+}
+
+int nb_db_set_max_transactions(unsigned int max)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+       /*
+        * Delete old entries if necessary and update the SQL trigger that
+        * auto-deletes old entries.
+        */
+       if (db_execute("BEGIN TRANSACTION;\n"
+                      "  DELETE\n"
+                      "  FROM\n"
+                      "    transactions\n"
+                      "  WHERE\n"
+                      "    ROWID IN (\n"
+                      "      SELECT\n"
+                      "        ROWID\n"
+                      "      FROM\n"
+                      "        transactions\n"
+                      "      ORDER BY ROWID DESC LIMIT -1 OFFSET %u\n"
+                      "    );\n"
+                      "  DROP TRIGGER delete_tail;\n"
+                      "  CREATE TRIGGER delete_tail\n"
+                      "  AFTER INSERT ON transactions\n"
+                      "    FOR EACH ROW\n"
+                      "    BEGIN\n"
+                      "    DELETE\n"
+                      "    FROM\n"
+                      "      transactions\n"
+                      "    WHERE\n"
+                      "      rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
+                      "    END;\n"
+                      "COMMIT;",
+                      max, max, max)
+           != 0)
+               return NB_ERR;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+       return NB_OK;
+}
+
+int nb_db_transactions_iterate(void (*func)(void *arg, int transaction_id,
+                                           const char *client_name,
+                                           const char *date,
+                                           const char *comment),
+                              void *arg)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+       struct sqlite3_stmt *ss;
+
+       /* Send SQL query and parse the result. */
+       ss = db_prepare(
+               "SELECT\n"
+               "  rowid, client, date, comment\n"
+               "FROM\n"
+               "  transactions\n"
+               "ORDER BY\n"
+               "  rowid DESC;");
+       if (!ss)
+               return NB_ERR;
+
+       while (db_run(ss) == SQLITE_ROW) {
+               int transaction_id;
+               const char *client_name;
+               const char *date;
+               const char *comment;
+               int ret;
+
+               ret = db_loadf(ss, "%i%s%s%s", &transaction_id, &client_name,
+                              &date, &comment);
+               if (ret != 0)
+                       continue;
+
+               (*func)(arg, transaction_id, client_name, date, comment);
+       }
+
+       db_finalize(&ss);
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+       return NB_OK;
+}
diff --git a/lib/northbound_db.h b/lib/northbound_db.h
new file mode 100644 (file)
index 0000000..ad60966
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+#ifndef _FRR_NORTHBOUND_DB_H_
+#define _FRR_NORTHBOUND_DB_H_
+
+#include "northbound.h"
+
+/*
+ * Initialize the northbound database.
+ *
+ * Currently the database is used only for storing and retrieving configuration
+ * transactions.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR otherwise.
+ */
+int nb_db_init(void);
+
+/*
+ * Save a configuration transaction in the northbound database.
+ *
+ * transaction
+ *    Configuration transaction to be saved.
+ *
+ * transaction_id
+ *    Output parameter providing the ID of the saved transaction.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR otherwise.
+ */
+int nb_db_transaction_save(const struct nb_transaction *transaction,
+                          uint32_t *transaction_id);
+
+/*
+ * Load a configuration transaction from the transactions log.
+ *
+ * transaction_id
+ *    ID of the transaction to be loaded.
+ *
+ * Returns:
+ *    Pointer to newly created configuration or NULL in the case of an error.
+ */
+extern struct nb_config *nb_db_transaction_load(uint32_t transaction_id);
+
+/*
+ * Delete the specified number of transactions from the transactions log.
+ *
+ * n_oldest
+ *    Number of transactions to delete.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_db_clear_transactions(unsigned int n_oldest);
+
+/*
+ * Specify the maximum number of transactions we want to record in the
+ * transactions log. Note that older transactions can be removed during this
+ * operation.
+ *
+ * max
+ *    New upper limit of maximum transactions to log.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_db_set_max_transactions(unsigned int max);
+
+/*
+ * Iterate over all configuration transactions stored in the northbound
+ * database, sorted in descending order.
+ *
+ * func
+ *    Function to call with each configuration transaction.
+ *
+ * arg
+ *    Arbitrary argument passed as the first parameter in each call to 'func'.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_db_transactions_iterate(
+       void (*func)(void *arg, int transaction_id, const char *client_name,
+                    const char *date, const char *comment),
+       void *arg);
+
+#endif /* _FRR_NORTHBOUND_DB_H_ */
index e2571f0c6791f4c402ec7e0642fced6572321749..5304f8fc8924877ca8526c980bfdc52bb64a58ee 100644 (file)
@@ -3,7 +3,7 @@
 #
 lib_LTLIBRARIES += lib/libfrr.la
 lib_libfrr_la_LDFLAGS = -version-info 0:0:0 -Xlinker -e_libfrr_version
-lib_libfrr_la_LIBADD = @LIBCAP@ $(UNWIND_LIBS)
+lib_libfrr_la_LIBADD = @LIBCAP@ $(UNWIND_LIBS) -lyang
 
 lib_libfrr_la_SOURCES = \
        lib/agg_table.c \
@@ -50,6 +50,9 @@ lib_libfrr_la_SOURCES = \
        lib/netns_linux.c \
        lib/netns_other.c \
        lib/nexthop_group.c \
+       lib/northbound.c \
+       lib/northbound_cli.c \
+       lib/northbound_db.c \
        lib/openbsd-tree.c \
        lib/pid_output.c \
        lib/plist.c \
@@ -80,6 +83,9 @@ lib_libfrr_la_SOURCES = \
        lib/vty.c \
        lib/wheel.c \
        lib/workqueue.c \
+       lib/yang.c \
+       lib/yang_translator.c \
+       lib/yang_wrappers.c \
        lib/zclient.c \
        lib/logicalrouter.c \
        lib/lua.c \
@@ -101,10 +107,17 @@ vtysh_scan += \
 # can be loaded as DSO - always include for vtysh
 vtysh_scan += $(top_srcdir)/lib/agentx.c
 
+if SQLITE3
+lib_libfrr_la_LIBADD += -lsqlite3
+lib_libfrr_la_SOURCES += lib/db.c
+endif
+
 lib/plist_clippy.c: $(CLIPPY_DEPS)
 lib/plist.lo: lib/plist_clippy.c
 lib/nexthop_group_clippy.c: $(CLIPPY_DEPS)
 lib/nexthop_group.lo: lib/nexthop_group_clippy.c
+lib/northbound_cli_clippy.c: $(CLIPPY_DEPS)
+lib/northbound_cli.lo: lib/northbound_cli_clippy.c
 
 pkginclude_HEADERS += \
        lib/agg_table.h \
@@ -117,6 +130,7 @@ pkginclude_HEADERS += \
        lib/command_match.h \
        lib/compiler.h \
        lib/csv.h \
+       lib/db.h \
        lib/debug.h \
        lib/distribute.h \
        lib/event_counter.h \
@@ -152,6 +166,9 @@ pkginclude_HEADERS += \
        lib/network.h \
        lib/nexthop.h \
        lib/nexthop_group.h \
+       lib/northbound.h \
+       lib/northbound_cli.h \
+       lib/northbound_db.h \
        lib/ns.h \
        lib/openbsd-queue.h \
        lib/openbsd-tree.h \
@@ -187,6 +204,9 @@ pkginclude_HEADERS += \
        lib/vxlan.h \
        lib/wheel.h \
        lib/workqueue.h \
+       lib/yang.h \
+       lib/yang_translator.h \
+       lib/yang_wrappers.h \
        lib/zassert.h \
        lib/zclient.h \
        lib/zebra.h \
index a73cc23b978c43e281ee23743ffb02edfa6806cf..90ca5f502a1c0c41f0bcf3aede28a8c028880557 100644 (file)
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -41,6 +41,7 @@
 #include "libfrr.h"
 #include "frrstr.h"
 #include "lib_errors.h"
+#include "northbound_cli.h"
 
 #include <arpa/telnet.h>
 #include <termios.h>
@@ -89,6 +90,9 @@ char *vty_cwd = NULL;
 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;
 
@@ -828,6 +832,8 @@ static void vty_end_config(struct vty *vty)
                break;
        }
 
+       vty->xpath_index = 0;
+
        vty_prompt(vty);
        vty->cp = 0;
 }
@@ -1718,6 +1724,10 @@ static struct vty *vty_new_init(int vty_sock)
        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;
@@ -2372,7 +2382,7 @@ static int vty_timeout(struct thread *thread)
 }
 
 /* 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;
@@ -2391,6 +2401,12 @@ static void vty_read_file(FILE *confp)
        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);
@@ -2436,6 +2452,22 @@ static void vty_read_file(FILE *confp)
                }
        }
 
+       /*
+        * Automatically commit the candidate configuration after
+        * reading the configuration file.
+        */
+       if (config == NULL && vty->candidate_config
+           && frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
+               int ret;
+
+               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);
 }
 
@@ -2494,7 +2526,8 @@ static FILE *vty_use_backup_config(const char *fullpath)
 }
 
 /* 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;
@@ -2508,9 +2541,9 @@ bool vty_read_config(const char *config_file, char *config_default_dir)
                        if (getcwd(cwd, MAXPATHLEN) == NULL) {
                                flog_err_sys(
                                        EC_LIB_SYSTEM_CALL,
-                                       "Failure to determine Current Working Directory %d!",
-                                       errno);
-                               exit(1);
+                                       "%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);
@@ -2533,10 +2566,11 @@ bool vty_read_config(const char *config_file, char *config_default_dir)
                                        EC_LIB_BACKUP_CONFIG,
                                        "WARNING: using backup configuration file!");
                        else {
-                               flog_err(EC_LIB_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 {
@@ -2593,7 +2627,7 @@ bool vty_read_config(const char *config_file, char *config_default_dir)
                        fullpath = config_default_dir;
        }
 
-       vty_read_file(confp);
+       vty_read_file(config, confp);
        read_success = true;
 
        fclose(confp);
@@ -2671,6 +2705,18 @@ int vty_config_lock(struct vty *vty)
 
 int vty_config_unlock(struct vty *vty)
 {
+       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;
+       }
+
        if (vty_config_is_lockless)
                return 0;
        if (vty_config == 1 && vty->config == 1) {
@@ -2685,6 +2731,21 @@ void vty_config_lockless(void)
        vty_config_is_lockless = 1;
 }
 
+int vty_config_exclusive_lock(struct vty *vty)
+{
+       if (vty_exclusive_lock == NULL) {
+               vty_exclusive_lock = vty;
+               return 1;
+       }
+       return 0;
+}
+
+void vty_config_exclusive_unlock(struct vty *vty)
+{
+       if (vty_exclusive_lock == vty)
+               vty_exclusive_lock = NULL;
+}
+
 /* Master of the threads. */
 static struct thread_master *vty_master;
 
index efe91a568b67cbc5102e6ae8924b167ae090dbb3..4c434fb2f206eaa43c6113d622528e53119ec47a 100644 (file)
--- a/lib/vty.h
+++ b/lib/vty.h
 #include "sockunion.h"
 #include "qobj.h"
 #include "compiler.h"
+#include "northbound.h"
 
 #define VTY_BUFSIZ 4096
 #define VTY_MAXHIST 20
+#define VTY_MAXDEPTH 8
 
 struct vty_error {
        char error_buf[VTY_BUFSIZ];
@@ -96,6 +98,19 @@ struct vty {
        /* History insert end point */
        int hindex;
 
+       /* XPath of the current node */
+       int xpath_index;
+       char xpath[VTY_MAXDEPTH][XPATH_MAXLEN];
+
+       /* Private candidate configuration mode. */
+       bool private_config;
+
+       /* Candidate configuration. */
+       struct nb_config *candidate_config;
+
+       /* Base candidate configuration. */
+       struct nb_config *candidate_config_base;
+
        /* qobj object ID (replacement for "index") */
        uint64_t qobj_index;
 
@@ -201,6 +216,34 @@ static inline void vty_push_context(struct vty *vty, int node, uint64_t id)
        struct structname *ptr = VTY_GET_CONTEXT(structname);                  \
        VTY_CHECK_CONTEXT(ptr);
 
+/* XPath macros. */
+#define VTY_PUSH_XPATH(nodeval, value)                                         \
+       do {                                                                   \
+               if (vty->xpath_index >= VTY_MAXDEPTH) {                        \
+                       vty_out(vty, "%% Reached maximum CLI depth (%u)\n",    \
+                               VTY_MAXDEPTH);                                 \
+                       return CMD_WARNING;                                    \
+               }                                                              \
+               vty->node = nodeval;                                           \
+               strlcpy(vty->xpath[vty->xpath_index], value,                   \
+                       sizeof(vty->xpath[0]));                                \
+               vty->xpath_index++;                                            \
+       } while (0)
+
+#define VTY_CURR_XPATH vty->xpath[vty->xpath_index - 1]
+
+#define VTY_CHECK_XPATH                                                        \
+       do {                                                                   \
+               if (vty->xpath_index > 0                                       \
+                   && !yang_dnode_exists(vty->candidate_config->dnode,        \
+                                         VTY_CURR_XPATH)) {                   \
+                       vty_out(vty,                                           \
+                               "Current configuration object was deleted "    \
+                               "by another process.\n\n");                    \
+                       return CMD_WARNING;                                    \
+               }                                                              \
+       } while (0)
+
 struct vty_arg {
        const char *name;
        const char *value;
@@ -228,6 +271,7 @@ struct vty_arg {
 
 /* Exported variables */
 extern char integrate_default[];
+extern struct vty *vty_exclusive_lock;
 
 /* Prototypes. */
 extern void vty_init(struct thread_master *);
@@ -247,7 +291,8 @@ extern void vty_frame(struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3);
 extern void vty_endframe(struct vty *, const char *);
 bool vty_set_include(struct vty *vty, const char *regexp);
 
-extern bool vty_read_config(const char *, char *);
+extern bool vty_read_config(struct nb_config *config, const char *config_file,
+                           char *config_default_dir);
 extern void vty_time_print(struct vty *, int);
 extern void vty_serv_sock(const char *, unsigned short, const char *);
 extern void vty_close(struct vty *);
@@ -257,6 +302,8 @@ extern void vty_log(const char *level, const char *proto, const char *fmt,
 extern int vty_config_lock(struct vty *);
 extern int vty_config_unlock(struct vty *);
 extern void vty_config_lockless(void);
+extern int vty_config_exclusive_lock(struct vty *vty);
+extern void vty_config_exclusive_unlock(struct vty *vty);
 extern int vty_shell(struct vty *);
 extern int vty_shell_serv(struct vty *);
 extern void vty_hello(struct vty *);
diff --git a/lib/yang.c b/lib/yang.c
new file mode 100644 (file)
index 0000000..bb5d38e
--- /dev/null
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 "log.h"
+#include "log_int.h"
+#include "lib_errors.h"
+#include "yang.h"
+#include "yang_translator.h"
+#include "northbound.h"
+
+DEFINE_MTYPE(LIB, YANG_MODULE, "YANG module")
+DEFINE_MTYPE(LIB, YANG_DATA, "YANG data structure")
+
+/* libyang container. */
+struct ly_ctx *ly_native_ctx;
+
+/* Generate the yang_modules tree. */
+static inline int yang_module_compare(const struct yang_module *a,
+                                     const struct yang_module *b)
+{
+       return strcmp(a->name, b->name);
+}
+RB_GENERATE(yang_modules, yang_module, entry, yang_module_compare)
+
+struct yang_modules yang_modules = RB_INITIALIZER(&yang_modules);
+
+struct yang_module *yang_module_load(const char *module_name)
+{
+       struct yang_module *module;
+       const struct lys_module *module_info;
+
+       module_info = ly_ctx_load_module(ly_native_ctx, module_name, NULL);
+       if (!module_info) {
+               flog_err(EC_LIB_YANG_MODULE_LOAD,
+                        "%s: failed to load data model: %s", __func__,
+                        module_name);
+               exit(1);
+       }
+
+       module = XCALLOC(MTYPE_YANG_MODULE, sizeof(*module));
+       module->name = module_name;
+       module->info = module_info;
+
+       if (RB_INSERT(yang_modules, &yang_modules, module) != NULL) {
+               flog_err(EC_LIB_YANG_MODULE_LOADED_ALREADY,
+                        "%s: YANG module is loaded already: %s", __func__,
+                        module_name);
+               exit(1);
+       }
+
+       return module;
+}
+
+struct yang_module *yang_module_find(const char *module_name)
+{
+       struct yang_module s;
+
+       s.name = module_name;
+       return RB_FIND(yang_modules, &yang_modules, &s);
+}
+
+/*
+ * Helper function for yang_module_snodes_iterate() and
+ * yang_all_snodes_iterate(). This is a recursive function.
+ */
+static void yang_snodes_iterate(const struct lys_node *snode,
+                               void (*func)(const struct lys_node *, void *,
+                                            void *),
+                               uint16_t flags, void *arg1, void *arg2)
+{
+       struct lys_node *child;
+
+       if (CHECK_FLAG(flags, YANG_ITER_FILTER_IMPLICIT)) {
+               switch (snode->nodetype) {
+               case LYS_CASE:
+               case LYS_INPUT:
+               case LYS_OUTPUT:
+                       if (snode->flags & LYS_IMPLICIT)
+                               goto next;
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       switch (snode->nodetype) {
+       case LYS_CONTAINER:
+               if (CHECK_FLAG(flags, YANG_ITER_FILTER_NPCONTAINERS)) {
+                       struct lys_node_container *scontainer;
+
+                       scontainer = (struct lys_node_container *)snode;
+                       if (!scontainer->presence)
+                               goto next;
+               }
+               break;
+       case LYS_LEAF:
+               if (CHECK_FLAG(flags, YANG_ITER_FILTER_LIST_KEYS)) {
+                       struct lys_node_leaf *sleaf;
+
+                       /* Ignore list keys. */
+                       sleaf = (struct lys_node_leaf *)snode;
+                       if (lys_is_key(sleaf, NULL))
+                               goto next;
+               }
+               break;
+       case LYS_GROUPING:
+               /* Return since we're not interested in the grouping subtree. */
+               return;
+       case LYS_USES:
+       case LYS_AUGMENT:
+               /* Always ignore nodes of these types. */
+               goto next;
+       case LYS_INPUT:
+       case LYS_OUTPUT:
+               if (CHECK_FLAG(flags, YANG_ITER_FILTER_INPUT_OUTPUT))
+                       goto next;
+               break;
+       default:
+               break;
+       }
+
+       (*func)(snode, arg1, arg2);
+
+next:
+       /*
+        * YANG leafs and leaf-lists can't have child nodes, and trying to
+        * access snode->child is undefined behavior.
+        */
+       if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST))
+               return;
+
+       LY_TREE_FOR (snode->child, child) {
+               if (child->parent != snode)
+                       continue;
+               yang_snodes_iterate(child, func, flags, arg1, arg2);
+       }
+}
+
+void yang_module_snodes_iterate(const struct lys_module *module,
+                               void (*func)(const struct lys_node *, void *,
+                                            void *),
+                               uint16_t flags, void *arg1, void *arg2)
+{
+       struct lys_node *snode;
+
+       LY_TREE_FOR (module->data, snode) {
+               yang_snodes_iterate(snode, func, flags, arg1, arg2);
+       }
+
+       for (uint8_t i = 0; i < module->augment_size; i++) {
+               yang_snodes_iterate(
+                       (const struct lys_node *)&module->augment[i], func,
+                       flags, arg1, arg2);
+       }
+}
+
+void yang_all_snodes_iterate(void (*func)(const struct lys_node *, void *,
+                                         void *),
+                            uint16_t flags, void *arg1, void *arg2)
+{
+       struct yang_module *module;
+
+       RB_FOREACH (module, yang_modules, &yang_modules)
+               yang_module_snodes_iterate(module->info, func, flags, arg1,
+                                          arg2);
+}
+
+void yang_snode_get_path(const struct lys_node *snode, enum yang_path_type type,
+                        char *xpath, size_t xpath_len)
+{
+       char *xpath_ptr;
+
+       switch (type) {
+       case YANG_PATH_SCHEMA:
+               xpath_ptr = lys_path(snode, 0);
+               break;
+       case YANG_PATH_DATA:
+               xpath_ptr = lys_data_path(snode);
+               break;
+       default:
+               flog_err(EC_LIB_DEVELOPMENT, "%s: unknown yang path type: %u",
+                        __func__, type);
+               exit(1);
+       }
+       strlcpy(xpath, xpath_ptr, xpath_len);
+       free(xpath_ptr);
+}
+
+struct lys_node *yang_snode_real_parent(const struct lys_node *snode)
+{
+       struct lys_node *parent = snode->parent;
+
+       while (parent) {
+               struct lys_node_container *scontainer;
+
+               switch (parent->nodetype) {
+               case LYS_CONTAINER:
+                       scontainer = (struct lys_node_container *)parent;
+                       if (scontainer->presence)
+                               return parent;
+                       break;
+               case LYS_LIST:
+                       return parent;
+               default:
+                       break;
+               }
+               parent = parent->parent;
+       }
+
+       return NULL;
+}
+
+struct lys_node *yang_snode_parent_list(const struct lys_node *snode)
+{
+       struct lys_node *parent = snode->parent;
+
+       while (parent) {
+               switch (parent->nodetype) {
+               case LYS_LIST:
+                       return parent;
+               default:
+                       break;
+               }
+               parent = parent->parent;
+       }
+
+       return NULL;
+}
+
+bool yang_snode_is_typeless_data(const struct lys_node *snode)
+{
+       struct lys_node_leaf *sleaf;
+
+       switch (snode->nodetype) {
+       case LYS_LEAF:
+               sleaf = (struct lys_node_leaf *)snode;
+               if (sleaf->type.base == LY_TYPE_EMPTY)
+                       return true;
+               return false;
+       case LYS_LEAFLIST:
+               return false;
+       default:
+               return true;
+       }
+}
+
+const char *yang_snode_get_default(const struct lys_node *snode)
+{
+       struct lys_node_leaf *sleaf;
+
+       switch (snode->nodetype) {
+       case LYS_LEAF:
+               sleaf = (struct lys_node_leaf *)snode;
+
+               /* NOTE: this might be null. */
+               return sleaf->dflt;
+       case LYS_LEAFLIST:
+               /* TODO: check leaf-list default values */
+               return NULL;
+       default:
+               return NULL;
+       }
+}
+
+const struct lys_type *yang_snode_get_type(const struct lys_node *snode)
+{
+       struct lys_node_leaf *sleaf = (struct lys_node_leaf *)snode;
+       struct lys_type *type;
+
+       if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)))
+               return NULL;
+
+       type = &sleaf->type;
+       while (type->base == LY_TYPE_LEAFREF)
+               type = &type->info.lref.target->type;
+
+       return type;
+}
+
+void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath,
+                        size_t xpath_len)
+{
+       char *xpath_ptr;
+
+       xpath_ptr = lyd_path(dnode);
+       strlcpy(xpath, xpath_ptr, xpath_len);
+       free(xpath_ptr);
+}
+
+struct lyd_node *yang_dnode_get(const struct lyd_node *dnode,
+                               const char *xpath_fmt, ...)
+{
+       va_list ap;
+       char xpath[XPATH_MAXLEN];
+       struct ly_set *set;
+       struct lyd_node *dnode_ret = NULL;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       set = lyd_find_path(dnode, xpath);
+       assert(set);
+       if (set->number == 0)
+               goto exit;
+
+       if (set->number > 1) {
+               flog_warn(EC_LIB_YANG_DNODE_NOT_FOUND,
+                         "%s: found %u elements (expected 0 or 1) [xpath %s]",
+                         __func__, set->number, xpath);
+               goto exit;
+       }
+
+       dnode_ret = set->set.d[0];
+
+exit:
+       ly_set_free(set);
+
+       return dnode_ret;
+}
+
+bool yang_dnode_exists(const struct lyd_node *dnode, const char *xpath_fmt, ...)
+{
+       va_list ap;
+       char xpath[XPATH_MAXLEN];
+       struct ly_set *set;
+       bool found;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       set = lyd_find_path(dnode, xpath);
+       assert(set);
+       found = (set->number > 0);
+       ly_set_free(set);
+
+       return found;
+}
+
+bool yang_dnode_is_default(const struct lyd_node *dnode, const char *xpath_fmt,
+                          ...)
+{
+       struct lys_node *snode;
+       struct lys_node_leaf *sleaf;
+       struct lys_node_container *scontainer;
+
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+
+               dnode = yang_dnode_get(dnode, xpath);
+       }
+
+       assert(dnode);
+       snode = dnode->schema;
+       switch (snode->nodetype) {
+       case LYS_LEAF:
+               sleaf = (struct lys_node_leaf *)snode;
+               if (sleaf->type.base == LY_TYPE_EMPTY)
+                       return false;
+               return lyd_wd_default((struct lyd_node_leaf_list *)dnode);
+       case LYS_LEAFLIST:
+               /* TODO: check leaf-list default values */
+               return false;
+       case LYS_CONTAINER:
+               scontainer = (struct lys_node_container *)snode;
+               if (scontainer->presence)
+                       return false;
+               return true;
+       default:
+               return false;
+       }
+}
+
+bool yang_dnode_is_default_recursive(const struct lyd_node *dnode)
+{
+       struct lys_node *snode;
+       struct lyd_node *root, *next, *dnode_iter;
+
+       snode = dnode->schema;
+       if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST))
+               return yang_dnode_is_default(dnode, NULL);
+
+       if (!yang_dnode_is_default(dnode, NULL))
+               return false;
+
+       LY_TREE_FOR (dnode->child, root) {
+               LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
+                       if (!yang_dnode_is_default(dnode_iter, NULL))
+                               return false;
+
+                       LY_TREE_DFS_END(root, next, dnode_iter);
+               }
+       }
+
+       return true;
+}
+
+void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value)
+{
+       assert(dnode->schema->nodetype == LYS_LEAF);
+       lyd_change_leaf((struct lyd_node_leaf_list *)dnode, value);
+}
+
+void yang_dnode_set_entry(const struct lyd_node *dnode, void *entry)
+{
+       assert(dnode->schema->nodetype & (LYS_LIST | LYS_CONTAINER));
+       lyd_set_private(dnode, entry);
+}
+
+void *yang_dnode_get_entry(const struct lyd_node *dnode)
+{
+       const struct lyd_node *orig_dnode = dnode;
+       char xpath[XPATH_MAXLEN];
+
+       while (dnode) {
+               switch (dnode->schema->nodetype) {
+               case LYS_CONTAINER:
+               case LYS_LIST:
+                       if (dnode->priv)
+                               return dnode->priv;
+                       break;
+               default:
+                       break;
+               }
+
+               dnode = dnode->parent;
+       }
+
+       yang_dnode_get_path(orig_dnode, xpath, sizeof(xpath));
+       flog_err(EC_LIB_YANG_DNODE_NOT_FOUND,
+                "%s: failed to find entry [xpath %s]", __func__, xpath);
+       zlog_backtrace(LOG_ERR);
+       abort();
+}
+
+struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx)
+{
+       struct lyd_node *dnode;
+
+       dnode = NULL;
+       if (lyd_validate(&dnode, LYD_OPT_CONFIG, ly_ctx) != 0) {
+               /* Should never happen. */
+               flog_err(EC_LIB_LIBYANG, "%s: lyd_validate() failed", __func__);
+               exit(1);
+       }
+
+       return dnode;
+}
+
+struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode)
+{
+       return lyd_dup_withsiblings(dnode, 1);
+}
+
+void yang_dnode_free(struct lyd_node *dnode)
+{
+       lyd_free_withsiblings(dnode);
+}
+
+struct yang_data *yang_data_new(const char *xpath, const char *value)
+{
+       const struct lys_node *snode;
+       struct yang_data *data;
+
+       snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+       if (!snode)
+               snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 1);
+       if (!snode) {
+               flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                        "%s: unknown data path: %s", __func__, xpath);
+               zlog_backtrace(LOG_ERR);
+               abort();
+       }
+
+       data = XCALLOC(MTYPE_YANG_DATA, sizeof(*data));
+       strlcpy(data->xpath, xpath, sizeof(data->xpath));
+       data->snode = snode;
+       if (value)
+               data->value = strdup(value);
+
+       return data;
+}
+
+void yang_data_free(struct yang_data *data)
+{
+       if (data->value)
+               free(data->value);
+       XFREE(MTYPE_YANG_DATA, data);
+}
+
+struct list *yang_data_list_new(void)
+{
+       struct list *list;
+
+       list = list_new();
+       list->del = (void (*)(void *))yang_data_free;
+
+       return list;
+}
+
+static void *ly_dup_cb(const void *priv)
+{
+       /* Make a shallow copy of the priv pointer. */
+       return (void *)priv;
+}
+
+/* Make libyang log its errors using FRR logging infrastructure. */
+static void ly_log_cb(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+       int priority;
+
+       switch (level) {
+       case LY_LLERR:
+               priority = LOG_ERR;
+               break;
+       case LY_LLWRN:
+               priority = LOG_WARNING;
+               break;
+       case LY_LLVRB:
+               priority = LOG_DEBUG;
+               break;
+       default:
+               return;
+       }
+
+       if (path)
+               zlog(priority, "libyang: %s (%s)", msg, path);
+       else
+               zlog(priority, "libyang: %s", msg);
+}
+
+void yang_init(void)
+{
+       static char ly_plugin_dir[PATH_MAX];
+       const char *const *ly_loaded_plugins;
+       const char *ly_plugin;
+       bool found_ly_frr_types = false;
+
+       /* Tell libyang where to find its plugins. */
+       snprintf(ly_plugin_dir, sizeof(ly_plugin_dir), "%s=%s",
+                "LIBYANG_USER_TYPES_PLUGINS_DIR", LIBYANG_PLUGINS_PATH);
+       putenv(ly_plugin_dir);
+
+       /* Initialize libyang global parameters that affect all containers. */
+       ly_set_log_clb(ly_log_cb, 1);
+       ly_log_options(LY_LOLOG | LY_LOSTORE);
+
+       /* Initialize libyang container for native models. */
+       ly_native_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
+       if (!ly_native_ctx) {
+               flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
+               exit(1);
+       }
+       ly_ctx_set_searchdir(ly_native_ctx, YANG_MODELS_PATH);
+       ly_ctx_set_priv_dup_clb(ly_native_ctx, ly_dup_cb);
+
+       /* Detect if the required libyang plugin(s) were loaded successfully. */
+       ly_loaded_plugins = ly_get_loaded_plugins();
+       for (size_t i = 0; (ly_plugin = ly_loaded_plugins[i]); i++) {
+               if (strmatch(ly_plugin, "frr_user_types")) {
+                       found_ly_frr_types = true;
+                       break;
+               }
+       }
+       if (!found_ly_frr_types) {
+               flog_err(EC_LIB_LIBYANG_PLUGIN_LOAD,
+                        "%s: failed to load frr_user_types.so", __func__);
+               exit(1);
+       }
+
+       yang_translator_init();
+}
+
+void yang_terminate(void)
+{
+       struct yang_module *module;
+
+       yang_translator_terminate();
+
+       while (!RB_EMPTY(yang_modules, &yang_modules)) {
+               module = RB_ROOT(yang_modules, &yang_modules);
+
+               /*
+                * We shouldn't call ly_ctx_remove_module() here because this
+                * function also removes other modules that depend on it.
+                *
+                * ly_ctx_destroy() will release all memory for us.
+                */
+               RB_REMOVE(yang_modules, &yang_modules, module);
+               XFREE(MTYPE_YANG_MODULE, module);
+       }
+
+       ly_ctx_destroy(ly_native_ctx, NULL);
+}
diff --git a/lib/yang.h b/lib/yang.h
new file mode 100644 (file)
index 0000000..cd5597f
--- /dev/null
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+#ifndef _FRR_YANG_H_
+#define _FRR_YANG_H_
+
+#include "memory.h"
+
+#include <libyang/libyang.h>
+#ifdef HAVE_SYSREPO
+#include <sysrepo.h>
+#endif
+
+#include "yang_wrappers.h"
+
+DECLARE_MTYPE(YANG_MODULE)
+DECLARE_MTYPE(YANG_DATA)
+
+/* Maximum XPath length. */
+#define XPATH_MAXLEN 256
+
+/* Maximum list key length. */
+#define LIST_MAXKEYS 8
+
+/* Maximum list key length. */
+#define LIST_MAXKEYLEN 128
+
+/* Maximum string length of an YANG value. */
+#define YANG_VALUE_MAXLEN 1024
+
+struct yang_module {
+       RB_ENTRY(yang_module) entry;
+       const char *name;
+       const struct lys_module *info;
+#ifdef HAVE_CONFD
+       int confd_hash;
+#endif
+#ifdef HAVE_SYSREPO
+       sr_subscription_ctx_t *sr_subscription;
+#endif
+};
+RB_HEAD(yang_modules, yang_module);
+RB_PROTOTYPE(yang_modules, yang_module, entry, yang_module_compare);
+
+struct yang_data {
+       /* XPath identifier of the data element. */
+       char xpath[XPATH_MAXLEN];
+
+       /*
+        * Schema information (necessary to interpret certain values like
+        * enums).
+        */
+       const struct lys_node *snode;
+
+       /* Value encoded as a raw string. */
+       char *value;
+};
+
+struct yang_list_keys {
+       /* Number os keys (max: LIST_MAXKEYS). */
+       uint8_t num;
+
+       struct {
+               /*
+                * Schema information (necessary to interpret certain values
+                * like enums).
+                */
+               struct lys_node *snode;
+
+               /* Value encoded as a raw string. */
+               char value[LIST_MAXKEYLEN];
+       } key[LIST_MAXKEYS];
+};
+
+enum yang_path_type {
+       YANG_PATH_SCHEMA = 0,
+       YANG_PATH_DATA,
+};
+
+/* Filter non-presence containers. */
+#define YANG_ITER_FILTER_NPCONTAINERS 0x0001
+/* Filter list keys (leafs). */
+#define YANG_ITER_FILTER_LIST_KEYS 0x0002
+/* Filter RPC input/output nodes. */
+#define YANG_ITER_FILTER_INPUT_OUTPUT 0x0004
+/* Filter implicitely created nodes. */
+#define YANG_ITER_FILTER_IMPLICIT 0x0008
+
+/* Global libyang context for native FRR models. */
+extern struct ly_ctx *ly_native_ctx;
+
+/* Tree of all loaded YANG modules. */
+extern struct yang_modules yang_modules;
+
+/*
+ * Create a new YANG module and load it using libyang. If the YANG module is not
+ * found in the YANG_MODELS_PATH directory, the program will exit with an error.
+ * Once loaded, a YANG module can't be unloaded anymore.
+ *
+ * module_name
+ *    Name of the YANG module.
+ *
+ * Returns:
+ *    Pointer to newly created YANG module.
+ */
+extern struct yang_module *yang_module_load(const char *module_name);
+
+/*
+ * Find a YANG module by its name.
+ *
+ * module_name
+ *    Name of the YANG module.
+ *
+ * Returns:
+ *    Pointer to YANG module if found, NULL otherwise.
+ */
+extern struct yang_module *yang_module_find(const char *module_name);
+
+/*
+ * Iterate over all libyang schema nodes from the given YANG module.
+ *
+ * module
+ *    YANG module to operate on.
+ *
+ * func
+ *    Function to call with each schema node.
+ *
+ * flags
+ *    YANG_ITER_FILTER_* flags to specify node types that should be filtered.
+ *
+ * arg1
+ *    Arbitrary argument passed as the second parameter in each call to 'func'.
+ *
+ * arg2
+ *    Arbitrary argument passed as the third parameter in each call to 'func'.
+ */
+extern void yang_module_snodes_iterate(const struct lys_module *module,
+                                      void (*func)(const struct lys_node *,
+                                                   void *, void *),
+                                      uint16_t flags, void *arg1, void *arg2);
+
+/*
+ * Iterate over all libyang schema nodes from all loaded YANG modules.
+ *
+ * func
+ *    Function to call with each schema node.
+ *
+ * flags
+ *    YANG_ITER_FILTER_* flags to specify node types that should be filtered.
+ *
+ * arg1
+ *    Arbitrary argument passed as the second parameter in each call to 'func'.
+ *
+ * arg2
+ *    Arbitrary argument passed as the third parameter in each call to 'func'.
+ */
+extern void yang_all_snodes_iterate(void (*func)(const struct lys_node *,
+                                                void *, void *),
+                                   uint16_t flags, void *arg1, void *arg2);
+
+/*
+ * Build schema path or data path of the schema node.
+ *
+ * snode
+ *    libyang schema node to be processed.
+ *
+ * type
+ *    Specify whether a schema path or a data path should be built.
+ *
+ * xpath
+ *    Pointer to previously allocated buffer.
+ *
+ * xpath_len
+ *    Size of the xpath buffer.
+ */
+extern void yang_snode_get_path(const struct lys_node *snode,
+                               enum yang_path_type type, char *xpath,
+                               size_t xpath_len);
+
+/*
+ * Find first parent schema node which is a presence-container or a list
+ * (non-presence containers are ignored).
+ *
+ * snode
+ *    libyang schema node to operate on.
+ *
+ * Returns:
+ *    The parent libyang schema node if found, or NULL if not found.
+ */
+extern struct lys_node *yang_snode_real_parent(const struct lys_node *snode);
+
+/*
+ * Find first parent schema node which is a list.
+ *
+ * snode
+ *    libyang schema node to operate on.
+ *
+ * Returns:
+ *    The parent libyang schema node (list) if found, or NULL if not found.
+ */
+extern struct lys_node *yang_snode_parent_list(const struct lys_node *snode);
+
+/*
+ * Check if the libyang schema node represents typeless data (e.g. containers,
+ * leafs of type empty, etc).
+ *
+ * snode
+ *    libyang schema node to operate on.
+ *
+ * Returns:
+ *    true if the schema node represents typeless data, false otherwise.
+ */
+extern bool yang_snode_is_typeless_data(const struct lys_node *snode);
+
+/*
+ * Get the default value associated to a YANG leaf or leaf-list.
+ *
+ * snode
+ *    libyang schema node to operate on.
+ *
+ * Returns:
+ *    The default value if it exists, NULL otherwise.
+ */
+extern const char *yang_snode_get_default(const struct lys_node *snode);
+
+/*
+ * Get the type structure of a leaf of leaf-list. If the type is a leafref, the
+ * final (if there is a chain of leafrefs) target's type is found.
+ *
+ * snode
+ *    libyang schema node to operate on.
+ *
+ * Returns:
+ *    The found type if the schema node represents a leaf or a leaf-list, NULL
+ *    otherwise.
+ */
+extern const struct lys_type *yang_snode_get_type(const struct lys_node *snode);
+
+/*
+ * Build data path of the data node.
+ *
+ * dnode
+ *    libyang data node to be processed.
+ *
+ * xpath
+ *    Pointer to previously allocated buffer.
+ *
+ * xpath_len
+ *    Size of the xpath buffer.
+ */
+extern void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath,
+                               size_t xpath_len);
+
+/*
+ * Find a libyang data node by its YANG data path.
+ *
+ * dnode
+ *    Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ *    XPath expression (absolute or relative).
+ *
+ * Returns:
+ *    The libyang data node if found, or NULL if not found.
+ */
+extern struct lyd_node *yang_dnode_get(const struct lyd_node *dnode,
+                                      const char *xpath_fmt, ...);
+
+/*
+ * Check if a libyang data node exists.
+ *
+ * dnode
+ *    Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ *    XPath expression (absolute or relative).
+ *
+ * Returns:
+ *    true if the libyang data node was found, false otherwise.
+ */
+extern bool yang_dnode_exists(const struct lyd_node *dnode,
+                             const char *xpath_fmt, ...);
+
+/*
+ * Check if the libyang data node contains a default value. Non-presence
+ * containers are assumed to always contain a default value.
+ *
+ * dnode
+ *    Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ *    Optional XPath expression (absolute or relative) to specify a different
+ *    data node to operate on in the same data tree.
+ *
+ * Returns:
+ *    true if the data node contains the default value, false otherwise.
+ */
+extern bool yang_dnode_is_default(const struct lyd_node *dnode,
+                                 const char *xpath_fmt, ...);
+
+/*
+ * Check if the libyang data node and all of its children contain default
+ * values. Non-presence containers are assumed to always contain a default
+ * value.
+ *
+ * dnode
+ *    libyang data node to operate on.
+ *
+ * Returns:
+ *    true if the data node and all of its children contain default values,
+ *    false otherwise.
+ */
+extern bool yang_dnode_is_default_recursive(const struct lyd_node *dnode);
+
+/*
+ * Change the value of a libyang leaf node.
+ *
+ * dnode
+ *    libyang data node to operate on.
+ *
+ * value
+ *    String representing the new value.
+ */
+extern void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value);
+
+/*
+ * Set the libyang private pointer to a user pointer. Can only be used on YANG
+ * lists and containers.
+ *
+ * dnode
+ *    libyang data node to operate on.
+ *
+ * entry
+ *    Arbitrary user-specified pointer.
+ */
+extern void yang_dnode_set_entry(const struct lyd_node *dnode, void *entry);
+
+/*
+ * Find the closest data node that contains an user pointer and return it.
+ *
+ * dnode
+ *    libyang data node to operate on.
+ *
+ * Returns:
+ *    User pointer if found, NULL otherwise.
+ */
+extern void *yang_dnode_get_entry(const struct lyd_node *dnode);
+
+/*
+ * Create a new libyang data node.
+ *
+ * ly_ctx
+ *    libyang context to operate on.
+ *
+ * Returns:
+ *    Pointer to newly created libyang data node.
+ */
+extern struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx);
+
+/*
+ * Duplicate a libyang data node.
+ *
+ * dnode
+ *    libyang data node to duplicate.
+ *
+ * Returns:
+ *    Pointer to duplicated libyang data node.
+ */
+extern struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode);
+
+/*
+ * Delete a libyang data node.
+ *
+ * dnode
+ *    Pointer to the libyang data node that is going to be deleted.
+ */
+extern void yang_dnode_free(struct lyd_node *dnode);
+
+/*
+ * Create a new yang_data structure.
+ *
+ * xpath
+ *    Data path of the YANG data.
+ *
+ * value
+ *    String representing the value of the YANG data.
+ *
+ * Returns:
+ *    Pointer to newly created yang_data structure.
+ */
+extern struct yang_data *yang_data_new(const char *xpath, const char *value);
+
+/*
+ * Delete a yang_data structure.
+ *
+ * data
+ *    yang_data to delete.
+ */
+extern void yang_data_free(struct yang_data *data);
+
+/*
+ * Create a new linked list of yang_data structures. The list 'del' callback is
+ * initialized appropriately so that the entire list can be deleted safely with
+ * list_delete_and_null().
+ *
+ * Returns:
+ *    Pointer to newly created linked list.
+ */
+extern struct list *yang_data_list_new(void);
+
+/*
+ * Initialize the YANG subsystem. Should be called only once during the
+ * daemon initialization process.
+ */
+extern void yang_init(void);
+
+/*
+ * Finish the YANG subsystem gracefully. Should be called only when the daemon
+ * is exiting.
+ */
+extern void yang_terminate(void);
+
+#endif /* _FRR_YANG_H_ */
diff --git a/lib/yang_translator.c b/lib/yang_translator.c
new file mode 100644 (file)
index 0000000..27b92a0
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 "log.h"
+#include "lib_errors.h"
+#include "hash.h"
+#include "yang.h"
+#include "yang_translator.h"
+
+DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR, "YANG Translator")
+DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MODULE, "YANG Translator Module")
+DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MAPPING, "YANG Translator Mapping")
+
+/* Generate the yang_translators tree. */
+static inline int yang_translator_compare(const struct yang_translator *a,
+                                         const struct yang_translator *b)
+{
+       return strcmp(a->family, b->family);
+}
+RB_GENERATE(yang_translators, yang_translator, entry, yang_translator_compare)
+
+struct yang_translators yang_translators = RB_INITIALIZER(&yang_translators);
+
+/* Separate libyang context for the translator module. */
+static struct ly_ctx *ly_translator_ctx;
+
+static unsigned int
+yang_translator_validate(struct yang_translator *translator);
+static unsigned int yang_module_nodes_count(const struct lys_module *module);
+static void str_replace(char *o_string, const char *s_string,
+                       const char *r_string);
+
+struct yang_mapping_node {
+       char xpath_from_canonical[XPATH_MAXLEN];
+       char xpath_from_fmt[XPATH_MAXLEN];
+       char xpath_to_fmt[XPATH_MAXLEN];
+};
+
+static bool yang_mapping_hash_cmp(const void *value1, const void *value2)
+{
+       const struct yang_mapping_node *c1 = value1;
+       const struct yang_mapping_node *c2 = value2;
+
+       return strmatch(c1->xpath_from_canonical, c2->xpath_from_canonical);
+}
+
+static unsigned int yang_mapping_hash_key(void *value)
+{
+       return string_hash_make(value);
+}
+
+static void *yang_mapping_hash_alloc(void *p)
+{
+       struct yang_mapping_node *new, *key = p;
+
+       new = XCALLOC(MTYPE_YANG_TRANSLATOR_MAPPING, sizeof(*new));
+       strlcpy(new->xpath_from_canonical, key->xpath_from_canonical,
+               sizeof(new->xpath_from_canonical));
+
+       return new;
+}
+
+static void yang_mapping_hash_free(void *arg)
+{
+       XFREE(MTYPE_YANG_TRANSLATOR_MAPPING, arg);
+}
+
+static struct yang_mapping_node *
+yang_mapping_lookup(const struct yang_translator *translator, int dir,
+                   const char *xpath)
+{
+       struct yang_mapping_node s;
+
+       strlcpy(s.xpath_from_canonical, xpath, sizeof(s.xpath_from_canonical));
+       return hash_lookup(translator->mappings[dir], &s);
+}
+
+static void yang_mapping_add(struct yang_translator *translator, int dir,
+                            const struct lys_node *snode,
+                            const char *xpath_from_fmt,
+                            const char *xpath_to_fmt)
+{
+       struct yang_mapping_node *mapping, s;
+
+       yang_snode_get_path(snode, YANG_PATH_DATA, s.xpath_from_canonical,
+                           sizeof(s.xpath_from_canonical));
+       mapping = hash_get(translator->mappings[dir], &s,
+                          yang_mapping_hash_alloc);
+       strlcpy(mapping->xpath_from_fmt, xpath_from_fmt,
+               sizeof(mapping->xpath_from_fmt));
+       strlcpy(mapping->xpath_to_fmt, xpath_to_fmt,
+               sizeof(mapping->xpath_to_fmt));
+       str_replace(mapping->xpath_from_fmt, "KEY1", "%[^']");
+       str_replace(mapping->xpath_from_fmt, "KEY2", "%[^']");
+       str_replace(mapping->xpath_from_fmt, "KEY3", "%[^']");
+       str_replace(mapping->xpath_from_fmt, "KEY4", "%[^']");
+       str_replace(mapping->xpath_to_fmt, "KEY1", "%s");
+       str_replace(mapping->xpath_to_fmt, "KEY2", "%s");
+       str_replace(mapping->xpath_to_fmt, "KEY3", "%s");
+       str_replace(mapping->xpath_to_fmt, "KEY4", "%s");
+}
+
+struct yang_translator *yang_translator_load(const char *path)
+{
+       struct yang_translator *translator;
+       struct yang_tmodule *tmodule;
+       const char *family;
+       struct lyd_node *dnode;
+       struct ly_set *set;
+       struct listnode *ln;
+
+       /* Load module translator (JSON file). */
+       dnode = lyd_parse_path(ly_translator_ctx, path, LYD_JSON,
+                              LYD_OPT_CONFIG);
+       if (!dnode) {
+               flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+                         "%s: lyd_parse_path() failed", __func__);
+               return NULL;
+       }
+       dnode = yang_dnode_get(dnode,
+                              "/frr-module-translator:frr-module-translator");
+       /*
+        * libyang guarantees the "frr-module-translator" top-level container is
+        * always present since it contains mandatory child nodes.
+        */
+       assert(dnode);
+
+       family = yang_dnode_get_string(dnode, "./family");
+       translator = yang_translator_find(family);
+       if (translator != NULL) {
+               flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+                         "%s: module translator \"%s\" is loaded already",
+                         __func__, family);
+               return NULL;
+       }
+
+       translator = XCALLOC(MTYPE_YANG_TRANSLATOR, sizeof(*translator));
+       strlcpy(translator->family, family, sizeof(translator->family));
+       translator->modules = list_new();
+       for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++)
+               translator->mappings[i] = hash_create(yang_mapping_hash_key,
+                                                     yang_mapping_hash_cmp,
+                                                     "YANG translation table");
+       RB_INSERT(yang_translators, &yang_translators, translator);
+
+       /* Initialize the translator libyang context. */
+       translator->ly_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
+       if (!translator->ly_ctx) {
+               flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
+               goto error;
+       }
+       ly_ctx_set_searchdir(translator->ly_ctx, YANG_MODELS_PATH);
+
+       /* Load modules and deviations. */
+       set = lyd_find_path(dnode, "./module");
+       assert(set);
+       for (size_t i = 0; i < set->number; i++) {
+               const char *module_name;
+
+               tmodule =
+                       XCALLOC(MTYPE_YANG_TRANSLATOR_MODULE, sizeof(*tmodule));
+
+               module_name = yang_dnode_get_string(set->set.d[i], "./name");
+               tmodule->module = ly_ctx_load_module(translator->ly_ctx,
+                                                    module_name, NULL);
+               if (!tmodule->module) {
+                       flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+                                 "%s: failed to load module: %s", __func__,
+                                 module_name);
+                       ly_set_free(set);
+                       goto error;
+               }
+
+               module_name =
+                       yang_dnode_get_string(set->set.d[i], "./deviations");
+               tmodule->deviations = ly_ctx_load_module(translator->ly_ctx,
+                                                        module_name, NULL);
+               if (!tmodule->deviations) {
+                       flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+                                 "%s: failed to load module: %s", __func__,
+                                 module_name);
+                       ly_set_free(set);
+                       goto error;
+               }
+               lys_set_disabled(tmodule->deviations);
+
+               listnode_add(translator->modules, tmodule);
+       }
+       ly_set_free(set);
+
+       /* Calculate the coverage. */
+       for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+               tmodule->nodes_before_deviations =
+                       yang_module_nodes_count(tmodule->module);
+
+               lys_set_enabled(tmodule->deviations);
+
+               tmodule->nodes_after_deviations =
+                       yang_module_nodes_count(tmodule->module);
+               tmodule->coverage = ((double)tmodule->nodes_after_deviations
+                                    / (double)tmodule->nodes_before_deviations)
+                                   * 100;
+       }
+
+       /* Load mappings. */
+       set = lyd_find_path(dnode, "./module/mappings");
+       assert(set);
+       for (size_t i = 0; i < set->number; i++) {
+               const char *xpath_custom, *xpath_native;
+               const struct lys_node *snode_custom, *snode_native;
+
+               xpath_custom = yang_dnode_get_string(set->set.d[i], "./custom");
+               snode_custom = ly_ctx_get_node(translator->ly_ctx, NULL,
+                                              xpath_custom, 0);
+               if (!snode_custom) {
+                       flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+                                 "%s: unknown data path: %s", __func__,
+                                 xpath_custom);
+                       ly_set_free(set);
+                       goto error;
+               }
+
+               xpath_native = yang_dnode_get_string(set->set.d[i], "./native");
+               snode_native =
+                       ly_ctx_get_node(ly_native_ctx, NULL, xpath_native, 0);
+               if (!snode_native) {
+                       flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+                                 "%s: unknown data path: %s", __func__,
+                                 xpath_native);
+                       ly_set_free(set);
+                       goto error;
+               }
+
+               yang_mapping_add(translator, YANG_TRANSLATE_TO_NATIVE,
+                                snode_custom, xpath_custom, xpath_native);
+               yang_mapping_add(translator, YANG_TRANSLATE_FROM_NATIVE,
+                                snode_native, xpath_native, xpath_custom);
+       }
+       ly_set_free(set);
+
+       /* Validate mappings. */
+       if (yang_translator_validate(translator) != 0)
+               goto error;
+
+       yang_dnode_free(dnode);
+
+       return translator;
+
+error:
+       yang_dnode_free(dnode);
+       yang_translator_unload(translator);
+
+       return NULL;
+}
+
+static void yang_tmodule_delete(struct yang_tmodule *tmodule)
+{
+       XFREE(MTYPE_YANG_TRANSLATOR_MODULE, tmodule);
+}
+
+void yang_translator_unload(struct yang_translator *translator)
+{
+       for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++)
+               hash_clean(translator->mappings[i], yang_mapping_hash_free);
+       translator->modules->del = (void (*)(void *))yang_tmodule_delete;
+       list_delete(&translator->modules);
+       ly_ctx_destroy(translator->ly_ctx, NULL);
+       RB_REMOVE(yang_translators, &yang_translators, translator);
+       XFREE(MTYPE_YANG_TRANSLATOR, translator);
+}
+
+struct yang_translator *yang_translator_find(const char *family)
+{
+       struct yang_translator s;
+
+       strlcpy(s.family, family, sizeof(s.family));
+       return RB_FIND(yang_translators, &yang_translators, &s);
+}
+
+enum yang_translate_result
+yang_translate_xpath(const struct yang_translator *translator, int dir,
+                    char *xpath, size_t xpath_len)
+{
+       struct ly_ctx *ly_ctx;
+       const struct lys_node *snode;
+       struct yang_mapping_node *mapping;
+       char xpath_canonical[XPATH_MAXLEN];
+       char keys[4][LIST_MAXKEYLEN];
+       int n;
+
+       if (dir == YANG_TRANSLATE_TO_NATIVE)
+               ly_ctx = translator->ly_ctx;
+       else
+               ly_ctx = ly_native_ctx;
+
+       snode = ly_ctx_get_node(ly_ctx, NULL, xpath, 0);
+       if (!snode) {
+               flog_warn(EC_LIB_YANG_TRANSLATION_ERROR,
+                         "%s: unknown data path: %s", __func__, xpath);
+               return YANG_TRANSLATE_FAILURE;
+       }
+
+       yang_snode_get_path(snode, YANG_PATH_DATA, xpath_canonical,
+                           sizeof(xpath_canonical));
+       mapping = yang_mapping_lookup(translator, dir, xpath_canonical);
+       if (!mapping)
+               return YANG_TRANSLATE_NOTFOUND;
+
+       n = sscanf(xpath, mapping->xpath_from_fmt, keys[0], keys[1], keys[2],
+                  keys[3]);
+       if (n < 0) {
+               flog_warn(EC_LIB_YANG_TRANSLATION_ERROR,
+                         "%s: sscanf() failed: %s", __func__,
+                         safe_strerror(errno));
+               return YANG_TRANSLATE_FAILURE;
+       }
+
+       snprintf(xpath, xpath_len, mapping->xpath_to_fmt, keys[0], keys[1],
+                keys[2], keys[3]);
+
+       return YANG_TRANSLATE_SUCCESS;
+}
+
+int yang_translate_dnode(const struct yang_translator *translator, int dir,
+                        struct lyd_node **dnode)
+{
+       struct ly_ctx *ly_ctx;
+       struct lyd_node *new;
+       struct lyd_node *root, *next, *dnode_iter;
+
+       /* Create new libyang data node to hold the translated data. */
+       if (dir == YANG_TRANSLATE_TO_NATIVE)
+               ly_ctx = ly_native_ctx;
+       else
+               ly_ctx = translator->ly_ctx;
+       new = yang_dnode_new(ly_ctx);
+
+       /* Iterate over all nodes from the data tree. */
+       LY_TREE_FOR (*dnode, root) {
+               LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
+                       char xpath[XPATH_MAXLEN];
+                       enum yang_translate_result ret;
+
+                       yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath));
+                       ret = yang_translate_xpath(translator, dir, xpath,
+                                                  sizeof(xpath));
+                       switch (ret) {
+                       case YANG_TRANSLATE_SUCCESS:
+                               break;
+                       case YANG_TRANSLATE_NOTFOUND:
+                               goto next;
+                       case YANG_TRANSLATE_FAILURE:
+                               goto error;
+                       }
+
+                       /* Create new node in the tree of translated data. */
+                       ly_errno = 0;
+                       if (!lyd_new_path(new, ly_ctx, xpath,
+                                         (void *)yang_dnode_get_string(
+                                                 dnode_iter, NULL),
+                                         0, LYD_PATH_OPT_UPDATE)
+                           && ly_errno) {
+                               flog_err(EC_LIB_LIBYANG,
+                                        "%s: lyd_new_path() failed", __func__);
+                               goto error;
+                       }
+
+               next:
+                       LY_TREE_DFS_END(root, next, dnode_iter);
+               }
+       }
+
+       /* Replace dnode by the new translated dnode. */
+       yang_dnode_free(*dnode);
+       *dnode = new;
+
+       return YANG_TRANSLATE_SUCCESS;
+
+error:
+       yang_dnode_free(new);
+
+       return YANG_TRANSLATE_FAILURE;
+}
+
+static void yang_translator_validate_cb(const struct lys_node *snode_custom,
+                                       void *arg1, void *arg2)
+{
+       struct yang_translator *translator = arg1;
+       unsigned int *errors = arg2;
+       struct yang_mapping_node *mapping;
+       const struct lys_node *snode_native;
+       const struct lys_type *stype_custom, *stype_native;
+       char xpath[XPATH_MAXLEN];
+
+       yang_snode_get_path(snode_custom, YANG_PATH_DATA, xpath, sizeof(xpath));
+       mapping = yang_mapping_lookup(translator, YANG_TRANSLATE_TO_NATIVE,
+                                     xpath);
+       if (!mapping) {
+               flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+                         "%s: missing mapping for \"%s\"", __func__, xpath);
+               *errors += 1;
+               return;
+       }
+
+       snode_native =
+               ly_ctx_get_node(ly_native_ctx, NULL, mapping->xpath_to_fmt, 0);
+       assert(snode_native);
+
+       /* Check if the YANG types are compatible. */
+       stype_custom = yang_snode_get_type(snode_custom);
+       stype_native = yang_snode_get_type(snode_native);
+       if (stype_custom && stype_native) {
+               if (stype_custom->base != stype_native->base) {
+                       flog_warn(
+                               EC_LIB_YANG_TRANSLATOR_LOAD,
+                               "%s: YANG types are incompatible (xpath: \"%s\")",
+                               __func__, xpath);
+                       *errors += 1;
+                       return;
+               }
+
+               /* TODO: check if the value spaces are identical. */
+       }
+}
+
+/*
+ * Check if the modules from the translator have a mapping for all of their
+ * schema nodes (after loading the deviations).
+ */
+static unsigned int yang_translator_validate(struct yang_translator *translator)
+{
+       struct yang_tmodule *tmodule;
+       struct listnode *ln;
+       unsigned int errors = 0;
+
+       for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+               yang_module_snodes_iterate(
+                       tmodule->module, yang_translator_validate_cb,
+                       YANG_ITER_FILTER_NPCONTAINERS
+                               | YANG_ITER_FILTER_LIST_KEYS
+                               | YANG_ITER_FILTER_INPUT_OUTPUT,
+                       translator, &errors);
+       }
+
+       if (errors)
+               flog_warn(
+                       EC_LIB_YANG_TRANSLATOR_LOAD,
+                       "%s: failed to validate \"%s\" module translator: %u error(s)",
+                       __func__, translator->family, errors);
+
+       return errors;
+}
+
+static void yang_module_nodes_count_cb(const struct lys_node *snode, void *arg1,
+                                      void *arg2)
+{
+       unsigned int *total = arg1;
+
+       *total += 1;
+}
+
+/* Calculate the number of nodes for the given module. */
+static unsigned int yang_module_nodes_count(const struct lys_module *module)
+{
+       unsigned int total = 0;
+
+       yang_module_snodes_iterate(module, yang_module_nodes_count_cb,
+                                  YANG_ITER_FILTER_NPCONTAINERS
+                                          | YANG_ITER_FILTER_LIST_KEYS
+                                          | YANG_ITER_FILTER_INPUT_OUTPUT,
+                                  &total, NULL);
+
+       return total;
+}
+
+/* TODO: rewrite this function. */
+static void str_replace(char *o_string, const char *s_string,
+                       const char *r_string)
+{
+       char buffer[BUFSIZ];
+       char *ch;
+
+       ch = strstr(o_string, s_string);
+       if (!ch)
+               return;
+
+       strncpy(buffer, o_string, ch - o_string);
+       buffer[ch - o_string] = 0;
+
+       sprintf(buffer + (ch - o_string), "%s%s", r_string,
+               ch + strlen(s_string));
+
+       o_string[0] = 0;
+       strcpy(o_string, buffer);
+       return str_replace(o_string, s_string, r_string);
+}
+
+void yang_translator_init(void)
+{
+       ly_translator_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
+       if (!ly_translator_ctx) {
+               flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
+               exit(1);
+       }
+       ly_ctx_set_searchdir(ly_translator_ctx, YANG_MODELS_PATH);
+
+       if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator",
+                               NULL)) {
+               flog_err(
+                       EC_LIB_YANG_MODULE_LOAD,
+                       "%s: failed to load the \"frr-module-translator\" module",
+                       __func__);
+               exit(1);
+       }
+}
+
+void yang_translator_terminate(void)
+{
+       while (!RB_EMPTY(yang_translators, &yang_translators)) {
+               struct yang_translator *translator;
+
+               translator = RB_ROOT(yang_translators, &yang_translators);
+               yang_translator_unload(translator);
+       }
+
+       ly_ctx_destroy(ly_translator_ctx, NULL);
+}
diff --git a/lib/yang_translator.h b/lib/yang_translator.h
new file mode 100644 (file)
index 0000000..6b49d1a
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+#ifndef _FRR_YANG_TRANSLATOR_H_
+#define _FRR_YANG_TRANSLATOR_H_
+
+#define YANG_TRANSLATE_TO_NATIVE 0
+#define YANG_TRANSLATE_FROM_NATIVE 1
+#define YANG_TRANSLATE_MAX 2
+
+struct yang_tmodule {
+       const struct lys_module *module;
+       const struct lys_module *deviations;
+       uint32_t nodes_before_deviations;
+       uint32_t nodes_after_deviations;
+       double coverage;
+};
+
+struct yang_translator {
+       RB_ENTRY(yang_translator) entry;
+       char family[32];
+       struct ly_ctx *ly_ctx;
+       struct list *modules;
+       struct hash *mappings[YANG_TRANSLATE_MAX];
+};
+RB_HEAD(yang_translators, yang_translator);
+RB_PROTOTYPE(yang_translators, yang_translator, entry, yang_translator_compare);
+
+enum yang_translate_result {
+       YANG_TRANSLATE_SUCCESS,
+       YANG_TRANSLATE_NOTFOUND,
+       YANG_TRANSLATE_FAILURE,
+};
+
+/* Tree of all loaded YANG module translators. */
+extern struct yang_translators yang_translators;
+
+/*
+ * Load a YANG module translator from a JSON file.
+ *
+ * path
+ *    Absolute path to the module translator file.
+ *
+ * Returns:
+ *    Pointer to newly created YANG module translator, or NULL in the case of an
+ *    error.
+ */
+extern struct yang_translator *yang_translator_load(const char *path);
+
+/*
+ * Unload a YANG module translator.
+ *
+ * translator
+ *    Pointer to the YANG module translator.
+ */
+extern void yang_translator_unload(struct yang_translator *translator);
+
+/*
+ * Find a YANG module translator by its family name.
+ *
+ * family
+ *    Family of the YANG module translator (e.g. ietf, openconfig).
+ *
+ * Returns:
+ *    Pointer to the YANG module translator if found, NULL otherwise.
+ */
+extern struct yang_translator *yang_translator_find(const char *family);
+
+/*
+ * Translate an XPath expression.
+ *
+ * translator
+ *    Pointer to YANG module translator.
+ *
+ * dir
+ *    Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or
+ *    YANG_TRANSLATE_FROM_NATIVE).
+ *
+ * xpath
+ *    Pointer to previously allocated buffer containing the xpath expression to
+ *    be translated.
+ *
+ * xpath_len
+ *    Size of the xpath buffer.
+ *
+ * Returns:
+ *    - YANG_TRANSLATE_SUCCESS on success.
+ *    - YANG_TRANSLATE_NOTFOUND when there's no available mapping to perform
+ *      the translation.
+ *    - YANG_TRANSLATE_FAILURE when an error occurred during the translation.
+ */
+extern enum yang_translate_result
+yang_translate_xpath(const struct yang_translator *translator, int dir,
+                    char *xpath, size_t xpath_len);
+
+/*
+ * Translate an entire libyang data node.
+ *
+ * translator
+ *    Pointer to YANG module translator.
+ *
+ * dir
+ *    Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or
+ *    YANG_TRANSLATE_FROM_NATIVE).
+ *
+ * dnode
+ *    libyang schema node we want to translate.
+ *
+ * Returns:
+ *    - YANG_TRANSLATE_SUCCESS on success.
+ *    - YANG_TRANSLATE_FAILURE when an error occurred during the translation.
+ */
+extern int yang_translate_dnode(const struct yang_translator *translator,
+                               int dir, struct lyd_node **dnode);
+
+/*
+ * Initialize the YANG module translator subsystem. Should be called only once
+ * during the daemon initialization process.
+ */
+extern void yang_translator_init(void);
+
+/*
+ * Finish the YANG module translator subsystem gracefully. Should be called only
+ * when the daemon is exiting.
+ */
+extern void yang_translator_terminate(void);
+
+#endif /* _FRR_YANG_TRANSLATOR_H_ */
diff --git a/lib/yang_wrappers.c b/lib/yang_wrappers.c
new file mode 100644 (file)
index 0000000..60a7456
--- /dev/null
@@ -0,0 +1,990 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 "log.h"
+#include "lib_errors.h"
+#include "northbound.h"
+
+static const char *yang_get_default_value(const char *xpath)
+{
+       const struct lys_node *snode;
+       const char *value;
+
+       snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+       if (snode == NULL) {
+               flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                        "%s: unknown data path: %s", __func__, xpath);
+               zlog_backtrace(LOG_ERR);
+               abort();
+       }
+
+       value = yang_snode_get_default(snode);
+       assert(value);
+
+       return value;
+}
+
+#define YANG_DNODE_GET_ASSERT(dnode, xpath)                                    \
+       do {                                                                   \
+               if ((dnode) == NULL) {                                         \
+                       flog_err(EC_LIB_YANG_DNODE_NOT_FOUND,                  \
+                                "%s: couldn't find %s", __func__, (xpath));   \
+                       zlog_backtrace(LOG_ERR);                               \
+                       abort();                                               \
+               }                                                              \
+       } while (0)
+
+/*
+ * Primitive type: bool.
+ */
+bool yang_str2bool(const char *value)
+{
+       return strmatch(value, "true");
+}
+
+struct yang_data *yang_data_new_bool(const char *xpath, bool value)
+{
+       return yang_data_new(xpath, (value == true) ? "true" : "false");
+}
+
+bool yang_dnode_get_bool(const struct lyd_node *dnode, const char *xpath_fmt,
+                        ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_BOOL);
+       return dleaf->value.bln;
+}
+
+bool yang_get_default_bool(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2bool(value);
+}
+
+/*
+ * Primitive type: dec64.
+ */
+double yang_str2dec64(const char *xpath, const char *value)
+{
+       double dbl = 0;
+
+       if (sscanf(value, "%lf", &dbl) != 1) {
+               flog_err(EC_LIB_YANG_DATA_CONVERT,
+                        "%s: couldn't convert string to decimal64 [xpath %s]",
+                        __func__, xpath);
+               zlog_backtrace(LOG_ERR);
+               abort();
+       }
+
+       return dbl;
+}
+
+struct yang_data *yang_data_new_dec64(const char *xpath, double value)
+{
+       char value_str[BUFSIZ];
+
+       snprintf(value_str, sizeof(value_str), "%lf", value);
+       return yang_data_new(xpath, value_str);
+}
+
+double yang_dnode_get_dec64(const struct lyd_node *dnode, const char *xpath_fmt,
+                           ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_DEC64);
+
+       return lyd_dec64_to_double(dnode);
+}
+
+double yang_get_default_dec64(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2dec64(xpath, value);
+}
+
+/*
+ * Primitive type: enum.
+ */
+int yang_str2enum(const char *xpath, const char *value)
+{
+       const struct lys_node *snode;
+       const struct lys_node_leaf *sleaf;
+       const struct lys_type_info_enums *enums;
+
+       snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+       if (snode == NULL) {
+               flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                        "%s: unknown data path: %s", __func__, xpath);
+               zlog_backtrace(LOG_ERR);
+               abort();
+       }
+
+       sleaf = (const struct lys_node_leaf *)snode;
+       enums = &sleaf->type.info.enums;
+       for (unsigned int i = 0; i < enums->count; i++) {
+               const struct lys_type_enum *enm = &enums->enm[i];
+
+               if (strmatch(value, enm->name))
+                       return enm->value;
+       }
+
+       flog_err(EC_LIB_YANG_DATA_CONVERT,
+                "%s: couldn't convert string to enum [xpath %s]", __func__,
+                xpath);
+       zlog_backtrace(LOG_ERR);
+       abort();
+}
+
+struct yang_data *yang_data_new_enum(const char *xpath, int value)
+{
+       const struct lys_node *snode;
+       const struct lys_node_leaf *sleaf;
+       const struct lys_type_info_enums *enums;
+
+       snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+       if (snode == NULL) {
+               flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                        "%s: unknown data path: %s", __func__, xpath);
+               zlog_backtrace(LOG_ERR);
+               abort();
+       }
+
+       sleaf = (const struct lys_node_leaf *)snode;
+       enums = &sleaf->type.info.enums;
+       for (unsigned int i = 0; i < enums->count; i++) {
+               const struct lys_type_enum *enm = &enums->enm[i];
+
+               if (value == enm->value)
+                       return yang_data_new(xpath, enm->name);
+       }
+
+       flog_err(EC_LIB_YANG_DATA_CONVERT,
+                "%s: couldn't convert enum to string [xpath %s]", __func__,
+                xpath);
+       zlog_backtrace(LOG_ERR);
+       abort();
+}
+
+int yang_dnode_get_enum(const struct lyd_node *dnode, const char *xpath_fmt,
+                       ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_ENUM);
+       return dleaf->value.enm->value;
+}
+
+int yang_get_default_enum(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2enum(xpath, value);
+}
+
+/*
+ * Primitive type: int8.
+ */
+int8_t yang_str2int8(const char *value)
+{
+       return strtol(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_int8(const char *xpath, int8_t value)
+{
+       char value_str[BUFSIZ];
+
+       snprintf(value_str, sizeof(value_str), "%d", value);
+       return yang_data_new(xpath, value_str);
+}
+
+int8_t yang_dnode_get_int8(const struct lyd_node *dnode, const char *xpath_fmt,
+                          ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_INT8);
+       return dleaf->value.int8;
+}
+
+int8_t yang_get_default_int8(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2int8(value);
+}
+
+/*
+ * Primitive type: int16.
+ */
+int16_t yang_str2int16(const char *value)
+{
+       return strtol(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_int16(const char *xpath, int16_t value)
+{
+       char value_str[BUFSIZ];
+
+       snprintf(value_str, sizeof(value_str), "%d", value);
+       return yang_data_new(xpath, value_str);
+}
+
+int16_t yang_dnode_get_int16(const struct lyd_node *dnode,
+                            const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_INT16);
+       return dleaf->value.int16;
+}
+
+int16_t yang_get_default_int16(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2int16(value);
+}
+
+/*
+ * Primitive type: int32.
+ */
+int32_t yang_str2int32(const char *value)
+{
+       return strtol(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_int32(const char *xpath, int32_t value)
+{
+       char value_str[BUFSIZ];
+
+       snprintf(value_str, sizeof(value_str), "%d", value);
+       return yang_data_new(xpath, value_str);
+}
+
+int32_t yang_dnode_get_int32(const struct lyd_node *dnode,
+                            const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_INT32);
+       return dleaf->value.int32;
+}
+
+int32_t yang_get_default_int32(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2int32(value);
+}
+
+/*
+ * Primitive type: int64.
+ */
+int64_t yang_str2int64(const char *value)
+{
+       return strtoll(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_int64(const char *xpath, int64_t value)
+{
+       char value_str[BUFSIZ];
+
+       snprintf(value_str, sizeof(value_str), "%" PRId64, value);
+       return yang_data_new(xpath, value_str);
+}
+
+int64_t yang_dnode_get_int64(const struct lyd_node *dnode,
+                            const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_INT64);
+       return dleaf->value.int64;
+}
+
+int64_t yang_get_default_int64(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2int64(value);
+}
+
+/*
+ * Primitive type: uint8.
+ */
+uint8_t yang_str2uint8(const char *value)
+{
+       return strtoul(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value)
+{
+       char value_str[BUFSIZ];
+
+       snprintf(value_str, sizeof(value_str), "%u", value);
+       return yang_data_new(xpath, value_str);
+}
+
+uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode,
+                            const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_UINT8);
+       return dleaf->value.uint8;
+}
+
+uint8_t yang_get_default_uint8(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2uint8(value);
+}
+
+/*
+ * Primitive type: uint16.
+ */
+uint16_t yang_str2uint16(const char *value)
+{
+       return strtoul(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_uint16(const char *xpath, uint16_t value)
+{
+       char value_str[BUFSIZ];
+
+       snprintf(value_str, sizeof(value_str), "%u", value);
+       return yang_data_new(xpath, value_str);
+}
+
+uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode,
+                              const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_UINT16);
+       return dleaf->value.uint16;
+}
+
+uint16_t yang_get_default_uint16(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2uint16(value);
+}
+
+/*
+ * Primitive type: uint32.
+ */
+uint32_t yang_str2uint32(const char *value)
+{
+       return strtoul(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_uint32(const char *xpath, uint32_t value)
+{
+       char value_str[BUFSIZ];
+
+       snprintf(value_str, sizeof(value_str), "%u", value);
+       return yang_data_new(xpath, value_str);
+}
+
+uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode,
+                              const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_UINT32);
+       return dleaf->value.uint32;
+}
+
+uint32_t yang_get_default_uint32(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2uint32(value);
+}
+
+/*
+ * Primitive type: uint64.
+ */
+uint64_t yang_str2uint64(const char *value)
+{
+       return strtoull(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_uint64(const char *xpath, uint64_t value)
+{
+       char value_str[BUFSIZ];
+
+       snprintf(value_str, sizeof(value_str), "%" PRIu64, value);
+       return yang_data_new(xpath, value_str);
+}
+
+uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode,
+                              const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_UINT64);
+       return dleaf->value.uint64;
+}
+
+uint64_t yang_get_default_uint64(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       return yang_str2uint64(value);
+}
+
+/*
+ * Primitive type: string.
+ *
+ * All string wrappers can be used with non-string types.
+ */
+struct yang_data *yang_data_new_string(const char *xpath, const char *value)
+{
+       return yang_data_new(xpath, value);
+}
+
+const char *yang_dnode_get_string(const struct lyd_node *dnode,
+                                 const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       return dleaf->value_str;
+}
+
+void yang_dnode_get_string_buf(char *buf, size_t size,
+                              const struct lyd_node *dnode,
+                              const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       if (strlcpy(buf, dleaf->value_str, size) >= size) {
+               char xpath[XPATH_MAXLEN];
+
+               yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+               flog_warn(EC_LIB_YANG_DATA_TRUNCATED,
+                         "%s: value was truncated [xpath %s]", __func__,
+                         xpath);
+       }
+}
+
+const char *yang_get_default_string(const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       return yang_get_default_value(xpath);
+}
+
+void yang_get_default_string_buf(char *buf, size_t size, const char *xpath_fmt,
+                                ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       if (strlcpy(buf, value, size) >= size)
+               flog_warn(EC_LIB_YANG_DATA_TRUNCATED,
+                         "%s: value was truncated [xpath %s]", __func__,
+                         xpath);
+}
+
+/*
+ * Derived type: ipv4.
+ */
+void yang_str2ipv4(const char *value, struct in_addr *addr)
+{
+       (void)inet_pton(AF_INET, value, addr);
+}
+
+struct yang_data *yang_data_new_ipv4(const char *xpath,
+                                    const struct in_addr *addr)
+{
+       char value_str[INET_ADDRSTRLEN];
+
+       (void)inet_ntop(AF_INET, addr, value_str, sizeof(value_str));
+       return yang_data_new(xpath, value_str);
+}
+
+void yang_dnode_get_ipv4(struct in_addr *addr, const struct lyd_node *dnode,
+                        const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_STRING);
+       memcpy(addr, dleaf->value.ptr, sizeof(*addr));
+}
+
+void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       yang_str2ipv4(value, var);
+}
+
+/*
+ * Derived type: ipv4p.
+ */
+void yang_str2ipv4p(const char *value, union prefixptr prefix)
+{
+       struct prefix_ipv4 *prefix4 = prefix.p4;
+
+       (void)str2prefix_ipv4(value, prefix4);
+       apply_mask_ipv4(prefix4);
+}
+
+struct yang_data *yang_data_new_ipv4p(const char *xpath,
+                                     const union prefixptr prefix)
+{
+       char value_str[PREFIX2STR_BUFFER];
+
+       (void)prefix2str(prefix.p, value_str, sizeof(value_str));
+       return yang_data_new(xpath, value_str);
+}
+
+void yang_dnode_get_ipv4p(union prefixptr prefix, const struct lyd_node *dnode,
+                         const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+       struct prefix_ipv4 *prefix4 = prefix.p4;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_STRING);
+       memcpy(prefix4, dleaf->value.ptr, sizeof(*prefix4));
+}
+
+void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       yang_str2ipv4p(value, var);
+}
+
+/*
+ * Derived type: ipv6.
+ */
+void yang_str2ipv6(const char *value, struct in6_addr *addr)
+{
+       (void)inet_pton(AF_INET6, value, addr);
+}
+
+struct yang_data *yang_data_new_ipv6(const char *xpath,
+                                    const struct in6_addr *addr)
+{
+       char value_str[INET6_ADDRSTRLEN];
+
+       (void)inet_ntop(AF_INET6, addr, value_str, sizeof(value_str));
+       return yang_data_new(xpath, value_str);
+}
+
+void yang_dnode_get_ipv6(struct in6_addr *addr, const struct lyd_node *dnode,
+                        const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_STRING);
+       memcpy(addr, dleaf->value.ptr, sizeof(*addr));
+}
+
+void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       yang_str2ipv6(value, var);
+}
+
+/*
+ * Derived type: ipv6p.
+ */
+void yang_str2ipv6p(const char *value, union prefixptr prefix)
+{
+       struct prefix_ipv6 *prefix6 = prefix.p6;
+
+       (void)str2prefix_ipv6(value, prefix6);
+       apply_mask_ipv6(prefix6);
+}
+
+struct yang_data *yang_data_new_ipv6p(const char *xpath,
+                                     const union prefixptr prefix)
+{
+       char value_str[PREFIX2STR_BUFFER];
+
+       (void)prefix2str(prefix.p, value_str, sizeof(value_str));
+       return yang_data_new(xpath, value_str);
+}
+
+void yang_dnode_get_ipv6p(union prefixptr prefix, const struct lyd_node *dnode,
+                         const char *xpath_fmt, ...)
+{
+       const struct lyd_node_leaf_list *dleaf;
+       struct prefix_ipv6 *prefix6 = prefix.p6;
+
+       assert(dnode);
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+               dnode = yang_dnode_get(dnode, xpath);
+               YANG_DNODE_GET_ASSERT(dnode, xpath);
+       }
+
+       dleaf = (const struct lyd_node_leaf_list *)dnode;
+       assert(dleaf->value_type == LY_TYPE_STRING);
+       memcpy(prefix6, dleaf->value.ptr, sizeof(*prefix6));
+}
+
+void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt, ...)
+{
+       char xpath[XPATH_MAXLEN];
+       const char *value;
+       va_list ap;
+
+       va_start(ap, xpath_fmt);
+       vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+       va_end(ap);
+
+       value = yang_get_default_value(xpath);
+       yang_str2ipv6p(value, var);
+}
diff --git a/lib/yang_wrappers.h b/lib/yang_wrappers.h
new file mode 100644 (file)
index 0000000..08a263b
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+#ifndef _FRR_NORTHBOUND_WRAPPERS_H_
+#define _FRR_NORTHBOUND_WRAPPERS_H_
+
+#include "prefix.h"
+
+/* bool */
+extern bool yang_str2bool(const char *value);
+extern struct yang_data *yang_data_new_bool(const char *xpath, bool value);
+extern bool yang_dnode_get_bool(const struct lyd_node *dnode,
+                               const char *xpath_fmt, ...);
+extern bool yang_get_default_bool(const char *xpath_fmt, ...);
+
+/* dec64 */
+extern double yang_str2dec64(const char *xpath, const char *value);
+extern struct yang_data *yang_data_new_dec64(const char *xpath, double value);
+extern double yang_dnode_get_dec64(const struct lyd_node *dnode,
+                                  const char *xpath_fmt, ...);
+extern double yang_get_default_dec64(const char *xpath_fmt, ...);
+
+/* enum */
+extern int yang_str2enum(const char *xpath, const char *value);
+extern struct yang_data *yang_data_new_enum(const char *xpath, int value);
+extern int yang_dnode_get_enum(const struct lyd_node *dnode,
+                              const char *xpath_fmt, ...);
+extern int yang_get_default_enum(const char *xpath_fmt, ...);
+
+/* int8 */
+extern int8_t yang_str2int8(const char *value);
+extern struct yang_data *yang_data_new_int8(const char *xpath, int8_t value);
+extern int8_t yang_dnode_get_int8(const struct lyd_node *dnode,
+                                 const char *xpath_fmt, ...);
+extern int8_t yang_get_default_int8(const char *xpath_fmt, ...);
+
+/* int16 */
+extern int16_t yang_str2int16(const char *value);
+extern struct yang_data *yang_data_new_int16(const char *xpath, int16_t value);
+extern int16_t yang_dnode_get_int16(const struct lyd_node *dnode,
+                                   const char *xpath_fmt, ...);
+extern int16_t yang_get_default_int16(const char *xpath_fmt, ...);
+
+/* int32 */
+extern int32_t yang_str2int32(const char *value);
+extern struct yang_data *yang_data_new_int32(const char *xpath, int32_t value);
+extern int32_t yang_dnode_get_int32(const struct lyd_node *dnode,
+                                   const char *xpath_fmt, ...);
+extern int32_t yang_get_default_int32(const char *xpath_fmt, ...);
+
+/* int64 */
+extern int64_t yang_str2int64(const char *value);
+extern struct yang_data *yang_data_new_int64(const char *xpath, int64_t value);
+extern int64_t yang_dnode_get_int64(const struct lyd_node *dnode,
+                                   const char *xpath_fmt, ...);
+extern int64_t yang_get_default_int64(const char *xpath_fmt, ...);
+
+/* uint8 */
+extern uint8_t yang_str2uint8(const char *value);
+extern struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value);
+extern uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode,
+                                   const char *xpath_fmt, ...);
+extern uint8_t yang_get_default_uint8(const char *xpath_fmt, ...);
+
+/* uint16 */
+extern uint16_t yang_str2uint16(const char *value);
+extern struct yang_data *yang_data_new_uint16(const char *xpath,
+                                             uint16_t value);
+extern uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode,
+                                     const char *xpath_fmt, ...);
+extern uint16_t yang_get_default_uint16(const char *xpath_fmt, ...);
+
+/* uint32 */
+extern uint32_t yang_str2uint32(const char *value);
+extern struct yang_data *yang_data_new_uint32(const char *xpath,
+                                             uint32_t value);
+extern uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode,
+                                     const char *xpath_fmt, ...);
+extern uint32_t yang_get_default_uint32(const char *xpath_fmt, ...);
+
+/* uint64 */
+extern uint64_t yang_str2uint64(const char *value);
+extern struct yang_data *yang_data_new_uint64(const char *xpath,
+                                             uint64_t value);
+extern uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode,
+                                     const char *xpath_fmt, ...);
+extern uint64_t yang_get_default_uint64(const char *xpath_fmt, ...);
+
+/* string */
+extern struct yang_data *yang_data_new_string(const char *xpath,
+                                             const char *value);
+extern const char *yang_dnode_get_string(const struct lyd_node *dnode,
+                                        const char *xpath_fmt, ...);
+extern void yang_dnode_get_string_buf(char *buf, size_t size,
+                                     const struct lyd_node *dnode,
+                                     const char *xpath_fmt, ...);
+extern const char *yang_get_default_string(const char *xpath_fmt, ...);
+extern void yang_get_default_string_buf(char *buf, size_t size,
+                                       const char *xpath_fmt, ...);
+
+/* ipv4 */
+extern void yang_str2ipv4(const char *value, struct in_addr *addr);
+extern struct yang_data *yang_data_new_ipv4(const char *xpath,
+                                           const struct in_addr *addr);
+extern void yang_dnode_get_ipv4(struct in_addr *addr,
+                               const struct lyd_node *dnode,
+                               const char *xpath_fmt, ...);
+extern void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt,
+                                 ...);
+
+/* ipv4p */
+extern void yang_str2ipv4p(const char *value, union prefixptr prefix);
+extern struct yang_data *yang_data_new_ipv4p(const char *xpath,
+                                            const union prefixptr prefix);
+extern void yang_dnode_get_ipv4p(union prefixptr prefix,
+                                const struct lyd_node *dnode,
+                                const char *xpath_fmt, ...);
+extern void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt,
+                                  ...);
+
+/* ipv6 */
+extern void yang_str2ipv6(const char *value, struct in6_addr *addr);
+extern struct yang_data *yang_data_new_ipv6(const char *xpath,
+                                           const struct in6_addr *addr);
+extern void yang_dnode_get_ipv6(struct in6_addr *addr,
+                               const struct lyd_node *dnode,
+                               const char *xpath_fmt, ...);
+extern void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt,
+                                 ...);
+
+/* ipv6p */
+extern void yang_str2ipv6p(const char *value, union prefixptr prefix);
+extern struct yang_data *yang_data_new_ipv6p(const char *xpath,
+                                            const union prefixptr prefix);
+extern void yang_dnode_get_ipv6p(union prefixptr prefix,
+                                const struct lyd_node *dnode,
+                                const char *xpath_fmt, ...);
+extern void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt,
+                                  ...);
+
+#endif /* _FRR_NORTHBOUND_WRAPPERS_H_ */
index 16087c2349ee5963a78d502c32cd0911052e281f..4602cfcd61e78b2ac11cde6f23c424972580d8d7 100644 (file)
@@ -76,7 +76,7 @@ static void sighup(void)
        zlog_info("ripd restarting!");
 
        /* Reload config file. */
-       vty_read_config(ripd_di.config_file, config_default);
+       vty_read_config(NULL, ripd_di.config_file, config_default);
 
        /* Try to return to normal operation. */
 }
index bc81a956d6cffd5957f4da544d23b3d8f2064826..b416b2fd07d658d87e05a2dfbad8bbdee6748765 100644 (file)
@@ -76,7 +76,7 @@ static void sighup(void)
        ripng_reset();
 
        /* Reload config file. */
-       vty_read_config(ripngd_di.config_file, config_default);
+       vty_read_config(NULL, ripngd_di.config_file, config_default);
 
        /* Try to return to normal operation. */
 }
index 8e81119f5dc3be6107d7319a9f4b13c0293a6653..2f464d7217334cb46386899fa115c210258e52cf 100644 (file)
@@ -1383,6 +1383,8 @@ static void bgp_startup(void)
                 LOG_DAEMON);
        zprivs_preinit(&bgpd_privs);
        zprivs_init(&bgpd_privs);
+       yang_init();
+       nb_init(NULL, 0);
 
        master = thread_master_create(NULL);
        bgp_master_init(master);
@@ -1428,6 +1430,8 @@ static void bgp_shutdown(void)
 
        vty_terminate();
        cmd_terminate();
+       nb_terminate();
+       yang_terminate();
        zprivs_terminate(&bgpd_privs);
        thread_master_free(master);
        master = NULL;
index fed1d5a53735f306cce0dcf0569459e4014184a7..9e34a7c255ba45f9345b3b2978190941bbe343ee 100644 (file)
@@ -155,6 +155,8 @@ int main(int argc, char **argv)
        cmd_init(1);
        vty_init(master);
        memory_init();
+       yang_init();
+       nb_init(NULL, 0);
 
        /* OSPF vty inits. */
        test_vty_init();
@@ -171,7 +173,7 @@ int main(int argc, char **argv)
        /* Configuration file read*/
        if (!config_file)
                usage(progname, 1);
-       vty_read_config(config_file, NULL);
+       vty_read_config(NULL, config_file, NULL);
 
        test_timer_init();
 
index 0fd2f80a39b5b12878588868a672e368270b0cb5..04f1e3253d0b625e5eec58647054561dcdb33da0 100644 (file)
@@ -50,6 +50,8 @@ static void vty_do_exit(int isexit)
        printf("\nend.\n");
        cmd_terminate();
        vty_terminate();
+       nb_terminate();
+       yang_terminate();
        thread_master_free(master);
        closezlog();
 
@@ -81,6 +83,8 @@ int main(int argc, char **argv)
 
        vty_init(master);
        memory_init();
+       yang_init();
+       nb_init(NULL, 0);
 
        test_init(argc, argv);
 
index ba789de81ca701eb4e80f2b2d6c4ea787f71ca5f..af8f9ce56afd5ab7274685e8494fe9dd6a859bdb 100644 (file)
@@ -313,6 +313,7 @@ frr defaults @DFLT_NAME@
 hostname test\r
 !\r
 !\r
+!\r
 line vty\r
 !\r
 end\r
@@ -328,6 +329,7 @@ frr defaults @DFLT_NAME@
 hostname foohost\r
 !\r
 !\r
+!\r
 line vty\r
 !\r
 end\r
index a8b42ba789d8ae2043f84dff23b12defca32cddb..74816ece8c54c32a62610cd6c5411de4c76cf6bf 100644 (file)
@@ -142,6 +142,8 @@ static void test_init(void)
        struct cmd_element *cmd;
 
        cmd_init(1);
+       yang_init();
+       nb_init(NULL, 0);
 
        install_node(&bgp_node, NULL);
        install_node(&rip_node, NULL);
@@ -184,6 +186,8 @@ static void test_terminate(void)
                XFREE(MTYPE_TMP, vector_slot(test_cmds, i));
        vector_free(test_cmds);
        cmd_terminate();
+       nb_terminate();
+       yang_terminate();
 }
 
 static void test_run(struct prng *prng, struct vty *vty, const char *cmd,
index a8d01d680e05767bad456e7ccc1f0309163b9716..c23322c4c67ab2ba314f650f5e4bfd0fa0d3b1d1 100644 (file)
@@ -1,3 +1,5 @@
 /frr
+/gen_northbound_callbacks
+/gen_yang_deviations
 /permutations
 /ssd
diff --git a/tools/gen_northbound_callbacks.c b/tools/gen_northbound_callbacks.c
new file mode 100644 (file)
index 0000000..8ef1054
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+#define REALLY_NEED_PLAIN_GETOPT 1
+
+#include <zebra.h>
+
+#include <unistd.h>
+
+#include "yang.h"
+#include "northbound.h"
+
+static void __attribute__((noreturn)) usage(int status)
+{
+       fprintf(stderr, "usage: gen_northbound_callbacks [-h] MODULE\n");
+       exit(status);
+}
+
+static struct nb_callback_info {
+       int operation;
+       bool optional;
+       char return_type[32];
+       char return_value[32];
+       char arguments[128];
+} nb_callbacks[] = {
+       {
+               .operation = NB_OP_CREATE,
+               .return_type = "int ",
+               .return_value = "NB_OK",
+               .arguments =
+                       "enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource",
+       },
+       {
+               .operation = NB_OP_MODIFY,
+               .return_type = "int ",
+               .return_value = "NB_OK",
+               .arguments =
+                       "enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource",
+       },
+       {
+               .operation = NB_OP_DELETE,
+               .return_type = "int ",
+               .return_value = "NB_OK",
+               .arguments =
+                       "enum nb_event event, const struct lyd_node *dnode",
+       },
+       {
+               .operation = NB_OP_MOVE,
+               .return_type = "int ",
+               .return_value = "NB_OK",
+               .arguments =
+                       "enum nb_event event, const struct lyd_node *dnode",
+       },
+       {
+               .operation = NB_OP_APPLY_FINISH,
+               .optional = true,
+               .return_type = "void ",
+               .return_value = "",
+               .arguments = "const struct lyd_node *dnode",
+       },
+       {
+               .operation = NB_OP_GET_ELEM,
+               .return_type = "struct yang_data *",
+               .return_value = "NULL",
+               .arguments = "const char *xpath, const void *list_entry",
+       },
+       {
+               .operation = NB_OP_GET_NEXT,
+               .return_type = "const void *",
+               .return_value = "NULL",
+               .arguments = "const char *xpath, const void *list_entry",
+       },
+       {
+               .operation = NB_OP_GET_KEYS,
+               .return_type = "int ",
+               .return_value = "NB_OK",
+               .arguments = "const void *list_entry, struct yang_list_keys *keys",
+       },
+       {
+               .operation = NB_OP_LOOKUP_ENTRY,
+               .return_type = "const void *",
+               .return_value = "NULL",
+               .arguments = "const struct yang_list_keys *keys",
+       },
+       {
+               .operation = NB_OP_RPC,
+               .return_type = "int ",
+               .return_value = "NB_OK",
+               .arguments =
+                       "const char *xpath, const struct list *input, struct list *output",
+       },
+       {
+               /* sentinel */
+               .operation = -1,
+       },
+};
+
+static void replace_hyphens_by_underscores(char *str)
+{
+       char *p;
+
+       p = str;
+       while ((p = strchr(p, '-')) != NULL)
+               *p++ = '_';
+}
+
+static void generate_callback_name(struct lys_node *snode,
+                                  enum nb_operation operation, char *buffer,
+                                  size_t size)
+{
+       struct list *snodes;
+       struct listnode *ln;
+
+       snodes = list_new();
+       for (; snode; snode = lys_parent(snode)) {
+               /* Skip schema-only snodes. */
+               if (snode->nodetype
+                   & (LYS_USES | LYS_CHOICE | LYS_CASE | LYS_INPUT
+                      | LYS_OUTPUT))
+                       continue;
+
+               listnode_add_head(snodes, snode);
+       }
+
+       memset(buffer, 0, size);
+       for (ALL_LIST_ELEMENTS_RO(snodes, ln, snode)) {
+               strlcat(buffer, snode->name, size);
+               strlcat(buffer, "_", size);
+       }
+       strlcat(buffer, nb_operation_name(operation), size);
+       list_delete(&snodes);
+
+       replace_hyphens_by_underscores(buffer);
+}
+
+static void generate_callbacks(const struct lys_node *snode, void *arg1,
+                              void *arg2)
+{
+       bool first = true;
+
+       switch (snode->nodetype) {
+       case LYS_CONTAINER:
+       case LYS_LEAF:
+       case LYS_LEAFLIST:
+       case LYS_LIST:
+       case LYS_NOTIF:
+       case LYS_RPC:
+               break;
+       default:
+               return;
+       }
+
+       for (struct nb_callback_info *cb = &nb_callbacks[0];
+            cb->operation != -1; cb++) {
+               char cb_name[BUFSIZ];
+
+               if (cb->optional
+                   || !nb_operation_is_valid(cb->operation, snode))
+                       continue;
+
+               if (first) {
+                       char xpath[XPATH_MAXLEN];
+
+                       yang_snode_get_path(snode, YANG_PATH_DATA, xpath,
+                                           sizeof(xpath));
+
+                       printf("/*\n"
+                              " * XPath: %s\n"
+                              " */\n",
+                              xpath);
+                       first = false;
+               }
+
+               generate_callback_name((struct lys_node *)snode, cb->operation,
+                                      cb_name, sizeof(cb_name));
+               printf("static %s%s(%s)\n"
+                      "{\n"
+                      "\t/* TODO: implement me. */\n"
+                      "\treturn %s;\n"
+                      "}\n\n",
+                      nb_callbacks[cb->operation].return_type, cb_name,
+                      nb_callbacks[cb->operation].arguments,
+                      nb_callbacks[cb->operation].return_value);
+       }
+}
+
+static void generate_nb_nodes(const struct lys_node *snode, void *arg1,
+                             void *arg2)
+{
+       bool first = true;
+
+       switch (snode->nodetype) {
+       case LYS_CONTAINER:
+       case LYS_LEAF:
+       case LYS_LEAFLIST:
+       case LYS_LIST:
+       case LYS_NOTIF:
+       case LYS_RPC:
+               break;
+       default:
+               return;
+       }
+
+       for (struct nb_callback_info *cb = &nb_callbacks[0];
+            cb->operation != -1; cb++) {
+               char cb_name[BUFSIZ];
+
+               if (cb->optional
+                   || !nb_operation_is_valid(cb->operation, snode))
+                       continue;
+
+               if (first) {
+                       char xpath[XPATH_MAXLEN];
+
+                       yang_snode_get_path(snode, YANG_PATH_DATA, xpath,
+                                           sizeof(xpath));
+
+                       printf("\t\t{\n"
+                              "\t\t\t.xpath = \"%s\",\n",
+                              xpath);
+                       first = false;
+               }
+
+               generate_callback_name((struct lys_node *)snode, cb->operation,
+                                      cb_name, sizeof(cb_name));
+               printf("\t\t\t.cbs.%s = %s,\n",
+                      nb_operation_name(cb->operation), cb_name);
+       }
+
+       if (!first)
+               printf("\t\t},\n");
+}
+
+int main(int argc, char *argv[])
+{
+       struct yang_module *module;
+       char module_name_underscores[64];
+       int opt;
+
+       while ((opt = getopt(argc, argv, "h")) != -1) {
+               switch (opt) {
+               case 'h':
+                       usage(EXIT_SUCCESS);
+                       /* NOTREACHED */
+               default:
+                       usage(EXIT_FAILURE);
+                       /* NOTREACHED */
+               }
+       }
+       argc -= optind;
+       argv += optind;
+       if (argc != 1)
+               usage(EXIT_FAILURE);
+
+       yang_init();
+
+       /* Load YANG module. */
+       module = yang_module_load(argv[0]);
+
+       /* Generate callback functions. */
+       yang_module_snodes_iterate(module->info, generate_callbacks, 0, NULL,
+                                  NULL);
+
+       strlcpy(module_name_underscores, module->name,
+               sizeof(module_name_underscores));
+       replace_hyphens_by_underscores(module_name_underscores);
+
+       /* Generate frr_yang_module_info array. */
+       printf("/* clang-format off */\n"
+              "const struct frr_yang_module_info %s_info = {\n"
+              "\t.name = \"%s\",\n"
+              "\t.nodes = {\n",
+              module_name_underscores, module->name);
+       yang_module_snodes_iterate(module->info, generate_nb_nodes, 0, NULL,
+                                  NULL);
+       printf("\t\t{\n"
+              "\t\t\t.xpath = NULL,\n"
+              "\t\t},\n");
+       printf("\t}\n"
+              "};\n");
+
+       /* Cleanup and exit. */
+       yang_terminate();
+
+       return 0;
+}
diff --git a/tools/gen_yang_deviations.c b/tools/gen_yang_deviations.c
new file mode 100644 (file)
index 0000000..121969c
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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
+ */
+
+#define REALLY_NEED_PLAIN_GETOPT 1
+
+#include <zebra.h>
+
+#include <unistd.h>
+
+#include "yang.h"
+#include "northbound.h"
+
+static void __attribute__((noreturn)) usage(int status)
+{
+       fprintf(stderr, "usage: gen_yang_deviations [-h] MODULE\n");
+       exit(status);
+}
+
+static void generate_yang_deviation(const struct lys_node *snode, void *arg1,
+                                   void *arg2)
+{
+       char xpath[XPATH_MAXLEN];
+
+       yang_snode_get_path(snode, YANG_PATH_SCHEMA, xpath, sizeof(xpath));
+
+       printf("  deviation \"%s\" {\n", xpath);
+       printf("    deviate not-supported;\n");
+       printf("  }\n\n");
+}
+
+int main(int argc, char *argv[])
+{
+       struct yang_module *module;
+       int opt;
+
+       while ((opt = getopt(argc, argv, "h")) != -1) {
+               switch (opt) {
+               case 'h':
+                       usage(EXIT_SUCCESS);
+                       /* NOTREACHED */
+               default:
+                       usage(EXIT_FAILURE);
+                       /* NOTREACHED */
+               }
+       }
+       argc -= optind;
+       argv += optind;
+       if (argc != 1)
+               usage(EXIT_FAILURE);
+
+       yang_init();
+
+       /* Load YANG module. */
+       module = yang_module_load(argv[0]);
+
+       /* Generate deviations. */
+       yang_module_snodes_iterate(module->info, generate_yang_deviation,
+                                  YANG_ITER_FILTER_IMPLICIT, NULL, NULL);
+
+       /* Cleanup and exit. */
+       yang_terminate();
+
+       return 0;
+}
index f8a14d10cc94d178cdd0e25213dfd13075de0ba8..2e68dfee09506f160d5fc988f72972b14e7020fd 100644 (file)
@@ -2,7 +2,12 @@
 # tools
 #
 
-noinst_PROGRAMS += tools/permutations
+noinst_PROGRAMS += \
+       tools/permutations \
+       tools/gen_northbound_callbacks \
+       tools/gen_yang_deviations \
+       # end
+
 sbin_PROGRAMS += tools/ssd
 sbin_SCRIPTS += \
        tools/frr-reload \
@@ -13,6 +18,12 @@ sbin_SCRIPTS += \
 tools_permutations_SOURCES = tools/permutations.c
 tools_permutations_LDADD = lib/libfrr.la
 
+tools_gen_northbound_callbacks_SOURCES = tools/gen_northbound_callbacks.c
+tools_gen_northbound_callbacks_LDADD = lib/libfrr.la -lyang
+
+tools_gen_yang_deviations_SOURCES = tools/gen_yang_deviations.c
+tools_gen_yang_deviations_LDADD = lib/libfrr.la -lyang
+
 tools_ssd_SOURCES = tools/start-stop-daemon.c
 
 EXTRA_DIST += \
index 541eafcf7fae11d88c1ad960358e87ab00b484a5..fe12f82ef28065a5c3406c790306175fb3b1aa0c 100644 (file)
@@ -366,6 +366,10 @@ void vtysh_config_parse_line(void *arg, const char *line)
                        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", strlen("debug")) == 0)
                        config = config_get(DEBUG_NODE, line);
                else if (strncmp(line, "password", strlen("password")) == 0
@@ -411,7 +415,7 @@ void vtysh_config_parse_line(void *arg, const char *line)
         || (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) == MPLS_NODE)
+        || (I) == NORTHBOUND_DEBUG_NODE || (I) == MPLS_NODE)
 
 /* Display configuration to file pointer. */
 void vtysh_config_dump(void)
diff --git a/yang/frr-module-translator.yang b/yang/frr-module-translator.yang
new file mode 100644 (file)
index 0000000..3d64ec5
--- /dev/null
@@ -0,0 +1,68 @@
+module frr-module-translator {
+  yang-version 1.1;
+  namespace "http://frrouting.org/yang/frr-module-translator";
+  prefix frr-module-translator;
+
+  organization
+    "Free Range Routing";
+  contact
+    "FRR Users List:       <mailto:frog@lists.frrouting.org>
+     FRR Development List: <mailto:dev@lists.frrouting.org>";
+  description
+    "A model for FRR YANG module translators.";
+
+  revision 2018-07-31 {
+    description
+      "Initial revision.";
+  }
+
+  container frr-module-translator {
+    leaf family {
+      type string {
+        length "0 .. 32";
+      }
+      mandatory true;
+      description
+        "Family of YANG models.";
+    }
+    list module {
+      key "name";
+      ordered-by user;
+      description
+        "YANG module.";
+
+      leaf name {
+        type string;
+        description
+          "Module name.";
+      }
+      leaf deviations {
+        type string;
+        mandatory true;
+        description
+          "Module containing the YANG deviations.";
+      }
+      list mappings {
+        key "custom";
+        description
+          "YANG mappings between the custom module and FRR native modules.";
+
+        leaf custom {
+          type string {
+            length "0 .. 256";
+          }
+          description
+            "YANG path of the custom module.";
+        }
+        leaf native {
+          type string {
+            length "0 .. 256";
+          }
+          mandatory true;
+          description
+            "Corresponding path of the native YANG modules";
+        }
+      }
+    }
+  }
+}
diff --git a/yang/libyang_plugins/frr_user_types.c b/yang/libyang_plugins/frr_user_types.c
new file mode 100644 (file)
index 0000000..4814f5b
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * This program 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 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 "prefix.h"
+
+#include <libyang/user_types.h>
+
+static int ipv4_address_store_clb(const char *type_name, const char *value_str,
+                                 lyd_val *value, char **err_msg)
+{
+       value->ptr = malloc(sizeof(struct in_addr));
+       if (!value->ptr)
+               return 1;
+
+       if (inet_pton(AF_INET, value_str, value->ptr) != 1) {
+               free(value->ptr);
+               return 1;
+       }
+
+       return 0;
+}
+
+static int ipv6_address_store_clb(const char *type_name, const char *value_str,
+                                 lyd_val *value, char **err_msg)
+{
+       value->ptr = malloc(INET6_ADDRSTRLEN);
+       if (!value->ptr)
+               return 1;
+
+       if (inet_pton(AF_INET6, value_str, value->ptr) != 1) {
+               free(value->ptr);
+               return 1;
+       }
+
+       return 0;
+}
+
+static int ipv4_prefix_store_clb(const char *type_name, const char *value_str,
+                                lyd_val *value, char **err_msg)
+{
+       value->ptr = malloc(sizeof(struct prefix_ipv4));
+       if (!value->ptr)
+               return 1;
+
+       if (str2prefix_ipv4(value_str, value->ptr) == 0) {
+               free(value->ptr);
+               return 1;
+       }
+
+       return 0;
+}
+
+static int ipv6_prefix_store_clb(const char *type_name, const char *value_str,
+                                lyd_val *value, char **err_msg)
+{
+       value->ptr = malloc(sizeof(struct prefix_ipv6));
+       if (!value->ptr)
+               return 1;
+
+       if (str2prefix_ipv6(value_str, value->ptr) == 0) {
+               free(value->ptr);
+               return 1;
+       }
+
+       return 0;
+}
+
+struct lytype_plugin_list frr_user_types[] = {
+       {"ietf-inet-types", "2013-07-15", "ipv4-address",
+        ipv4_address_store_clb, free},
+       {"ietf-inet-types", "2013-07-15", "ipv4-address-no-zone",
+        ipv4_address_store_clb, free},
+       {"ietf-inet-types", "2013-07-15", "ipv6-address",
+        ipv6_address_store_clb, free},
+       {"ietf-inet-types", "2013-07-15", "ipv6-address-no-zone",
+        ipv6_address_store_clb, free},
+       {"ietf-inet-types", "2013-07-15", "ipv4-prefix", ipv4_prefix_store_clb,
+        free},
+       {"ietf-inet-types", "2013-07-15", "ipv6-prefix", ipv6_prefix_store_clb,
+        free},
+       {NULL, NULL, NULL, NULL, NULL} /* terminating item */
+};
diff --git a/yang/libyang_plugins/subdir.am b/yang/libyang_plugins/subdir.am
new file mode 100644 (file)
index 0000000..956d225
--- /dev/null
@@ -0,0 +1,9 @@
+#
+# libyang user types
+#
+libyang_plugins_LTLIBRARIES += yang/libyang_plugins/frr_user_types.la
+
+yang_libyang_plugins_frr_user_types_la_CFLAGS = $(WERROR)
+yang_libyang_plugins_frr_user_types_la_LDFLAGS = -avoid-version -module -shared -export-dynamic
+yang_libyang_plugins_frr_user_types_la_LIBADD =
+yang_libyang_plugins_frr_user_types_la_SOURCES = yang/libyang_plugins/frr_user_types.c
diff --git a/yang/subdir.am b/yang/subdir.am
new file mode 100644 (file)
index 0000000..b290f9b
--- /dev/null
@@ -0,0 +1 @@
+dist_yangmodels_DATA += yang/frr-module-translator.yang