Skip to content

Commit

Permalink
yang: embed models into binaries
Browse files Browse the repository at this point in the history
This bakes our YANG models straight into the library/daemons, so they
don't need to be loaded from /usr/share/yang.  This makes the
installation quite a bit more robust, as well as gets us halfway to
running uninstalled.  (The other half is baking in the extension type
module.)

The /usr/share/yang directory is still searched as a fallback, as well
as for the experimental YANG model translator.  This is likely to stay
as is for the time being.

Signed-off-by: David Lamparter <equinox@diac24.net>
  • Loading branch information
eqvinox committed Nov 19, 2018
1 parent fef6daf commit 272c64f
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 0 deletions.
6 changes: 6 additions & 0 deletions lib/subdir.am
Expand Up @@ -92,6 +92,12 @@ lib_libfrr_la_SOURCES = \
lib/lua.c \
# end

nodist_lib_libfrr_la_SOURCES = \
yang/frr-interface.yang.c \
yang/frr-route-types.yang.c \
yang/frr-module-translator.yang.c \
# end

vtysh_scan += \
$(top_srcdir)/lib/distribute.c \
$(top_srcdir)/lib/filter.c \
Expand Down
40 changes: 40 additions & 0 deletions lib/yang.c
Expand Up @@ -32,6 +32,45 @@ DEFINE_MTYPE(LIB, YANG_DATA, "YANG data structure")
/* libyang container. */
struct ly_ctx *ly_native_ctx;

static struct yang_module_embed *embeds, **embedupd = &embeds;

void yang_module_embed(struct yang_module_embed *embed)
{
embed->next = NULL;
*embedupd = embed;
embedupd = &embed->next;
}

static const char *yang_module_imp_clb(const char *mod_name,
const char *mod_rev,
const char *submod_name,
const char *submod_rev,
void *user_data,
LYS_INFORMAT *format,
void (**free_module_data)
(void *, void*))
{
struct yang_module_embed *e;

if (submod_name || submod_rev)
return NULL;

for (e = embeds; e; e = e->next) {
if (strcmp(e->mod_name, mod_name))
continue;
if (mod_rev && strcmp(e->mod_rev, mod_rev))
continue;

*format = e->format;
return e->data;
}

flog_warn(EC_LIB_YANG_MODULE_LOAD,
"YANG model \"%s@%s\" not embedded, trying external file",
mod_name, mod_rev ? mod_rev : "*");
return NULL;
}

/* Generate the yang_modules tree. */
static inline int yang_module_compare(const struct yang_module *a,
const struct yang_module *b)
Expand Down Expand Up @@ -575,6 +614,7 @@ void yang_init(void)
flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
exit(1);
}
ly_ctx_set_module_imp_clb(ly_native_ctx, yang_module_imp_clb, NULL);
ly_ctx_set_searchdir(ly_native_ctx, YANG_MODELS_PATH);
ly_ctx_set_priv_dup_clb(ly_native_ctx, ly_dup_cb);

Expand Down
17 changes: 17 additions & 0 deletions lib/yang.h
Expand Up @@ -44,6 +44,13 @@ DECLARE_MTYPE(YANG_DATA)
/* Maximum string length of an YANG value. */
#define YANG_VALUE_MAXLEN 1024

struct yang_module_embed {
struct yang_module_embed *next;
const char *mod_name, *mod_rev;
const char *data;
LYS_INFORMAT format;
};

struct yang_module {
RB_ENTRY(yang_module) entry;
const char *name;
Expand Down Expand Up @@ -132,6 +139,16 @@ extern struct yang_module *yang_module_load(const char *module_name);
*/
extern struct yang_module *yang_module_find(const char *module_name);

/*
* Register a YANG module embedded in the binary file. Should be called
* from a constructor function.
*
* embed
* YANG module embedding structure to register. (static global provided
* by caller.)
*/
extern void yang_module_embed(struct yang_module_embed *embed);

/*
* Iterate over all libyang schema nodes from the given YANG module.
*
Expand Down
3 changes: 3 additions & 0 deletions ripd/subdir.am
Expand Up @@ -51,6 +51,9 @@ ripd_ripd_LDADD = ripd/librip.a lib/libfrr.la @LIBCAP@
ripd_ripd_SOURCES = \
ripd/rip_main.c \
# end
nodist_ripd_ripd_SOURCES = \
yang/frr-ripd.yang.c \
# end

ripd_ripd_snmp_la_SOURCES = ripd/rip_snmp.c
ripd_ripd_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99
Expand Down
77 changes: 77 additions & 0 deletions yang/embedmodel.py
@@ -0,0 +1,77 @@
#!/usr/bin/python3
#
# YANG module to C wrapper
# written 2018 by David Lamparter, placed in Public Domain.

import sys, string, re

inname = sys.argv[1]
outname = sys.argv[2]

# these are regexes to avoid a compile-time/host dependency on yang-tools
# or python-yang. Cross-compiling FRR is already somewhat involved, no need
# to make it even harder.

re_name = re.compile(r'\bmodule\s+([^\s]+)\s+\{')
re_rev = re.compile(r'\brevision\s+([\d-]+)\s+\{')


template = '''/* autogenerated by embedmodel.py. DO NOT EDIT */
#include "yang.h"
static const char model[] =
\t"%s";
static struct yang_module_embed embed = {
\t.mod_name = "%s",
\t.mod_rev = "%s",
\t.data = model,
\t.format = %s,
};
static void embed_register(void) __attribute__((_CONSTRUCTOR(2000)));
static void embed_register(void)
{
\tyang_module_embed(&embed);
}
'''

passchars = set(string.printable) - set('\\\'"%\r\n\t\x0b\x0c')
def escapech(char):
if char in passchars:
return char
if char == '\n':
return '\\n'
if char == '\t':
return '\\t'
if char in '"\\\'':
return '\\' + char
return '\\x%02x' % (ord(char))
def escape(line):
return ''.join([escapech(i) for i in line])

with open(inname, 'r') as fd:
data = fd.read()

# XML support isn't actively used currently, but it's here in case the need
# arises. It does avoid the regex'ing.
if '<?xml' in data:
from xml.etree import ElementTree
xml = ElementTree.fromstring(data)
name = xml.get('name')
rev = xml.find('{urn:ietf:params:xml:ns:yang:yin:1}revision').get('date')
fmt = 'LYS_YIN'
else:
name = re_name.search(data).group(1)
rev = re_rev.search(data).group(1)
fmt = 'LYS_YANG'

if name is None or rev is None:
raise ValueError('cannot determine YANG module name and revision')

lines = [escape(row) for row in data.split('\n')]
text = '\\n"\n\t"'.join(lines)

with open(outname, 'w') as fd:
fd.write(template % (text, escape(name), escape(rev), fmt))
21 changes: 21 additions & 0 deletions yang/subdir.am
@@ -1,3 +1,24 @@
SUFFIXES += .yang .yang.c .yin .yin.c
EXTRA_DIST += yang/embedmodel.py

.yang.yang.c:
$(AM_V_GEN)$(PYTHON) $(top_srcdir)/yang/embedmodel.py $^ $@
.yin.yin.c:
$(AM_V_GEN)$(PYTHON) $(top_srcdir)/yang/embedmodel.py $^ $@

# use .yang.c files like this:
#
# ripd_ripd_SOURCES = \
# ...
# nodist_ripd_ripd_SOURCES = \
# yang/frr-ripd.yang.c \
# # end
#
# Note that putting the .yang.c file into a static library.a will NOT work
# because the entire file is "optimized out" since it does not contain any
# global symbols :(. Just put it in the daemon. Dynamic libraries.so work
# without problems, as seen in libfrr.

dist_yangmodels_DATA += yang/frr-module-translator.yang
dist_yangmodels_DATA += yang/frr-interface.yang
dist_yangmodels_DATA += yang/frr-route-types.yang
Expand Down

0 comments on commit 272c64f

Please sign in to comment.