]> git.puffer.fish Git - matthieu/frr.git/commitdiff
lib: add ability to decode from lua scripts
authorQuentin Young <qlyoung@nvidia.com>
Mon, 30 Nov 2020 03:09:58 +0000 (22:09 -0500)
committerQuentin Young <qlyoung@nvidia.com>
Tue, 1 Dec 2020 23:37:14 +0000 (18:37 -0500)
This implements the ability to get results out from lua scripts after
they've run.

For each C type we support passing to Lua, there is a corresponding
`struct frrscript_codec`. This struct contains a typename field - just a
string identifying the type - and two function pointers. The first
function pointer, encode, takes a lua_State and a pointer to the C value
and pushes some corresponding Lua representation onto the stack. The
second, decode, assumes there is some Lua value on the stack and decodes
it into the corresponding C value.

Each supported type's `struct frrscript_codec` is registered with the
scripting stuff in the library, which creates a mapping between the type
name (string) and the `struct frrscript_codec`. When calling a script,
you specify arguments by passing an array of `struct frrscript_env`.
Each of these structs has a void *, a type name, and a desired binding
name. The type names are used to look up the appropriate function to
encode the pointed-at value onto the Lua stack, then bind the pushed
value to the provided binding name, so that the converted value is
accessible by that name within the script.

Results work in a similar way. After a script runs, call
frrscript_get_result() with the script and a `struct frrscript_env`.
The typename and name fields are used to fetch the Lua value from the
script's environment and use the registered decoder for the typename to
convert the Lua value back into a C value, which is returned from the
function. The caller is responsible for freeing these.

frrscript_call()'s macro foo has been stripped, as the underlying
function now takes fixed arrays. varargs have awful performance
characteristics, they're hard to read, and structs are more defined than
an order sensitive list.

Signed-off-by: Quentin Young <qlyoung@nvidia.com>
lib/command.c
lib/frrlua.c
lib/frrlua.h
lib/frrscript.c
lib/frrscript.h

index 56af946997e51c8b91d284615723a5230b493795..3ca5c5882b1f7cf697d3d9b2d2d16c2367705c39 100644 (file)
@@ -2297,7 +2297,7 @@ DEFUN(script,
                vty_out(vty, "Script '/etc/frr/scripts/%s.lua' not found\n",
                        argv[1]->arg);
        } else {
-               int ret = frrscript_call(fs, FRRSCRIPT_ARGS("cool", "prefix", &p), FRRSCRIPT_RESULTS());
+               int ret = frrscript_call(fs, NULL);
                vty_out(vty, "Script result: %d\n", ret);
        }
 
index dd468a3def1f357eb6916c53a8555929d2c79f93..bd1e5b00e55d20529333c8d7ddfb94354488d352 100644 (file)
@@ -26,7 +26,6 @@
 #include "frrlua.h"
 #include "log.h"
 #include "buffer.h"
-#include "frrscript.h"
 
 /* Lua stuff */
 
@@ -72,6 +71,17 @@ void lua_pushprefix(lua_State *L, const struct prefix *prefix)
        lua_setfield(L, -2, "family");
 }
 
+void *lua_toprefix(lua_State *L, int idx)
+{
+       struct prefix *p = XCALLOC(MTYPE_TMP, sizeof(struct prefix));
+
+       lua_getfield(L, idx, "network");
+       str2prefix(lua_tostring(L, -1), p);
+       lua_pop(L, 1);
+
+       return p;
+}
+
 void lua_pushinterface(lua_State *L, const struct interface *ifp)
 {
        zlog_debug("frrlua: pushing interface table");
@@ -101,6 +111,47 @@ void lua_pushinterface(lua_State *L, const struct interface *ifp)
        lua_setfield(L, -2, "linklayer_type");
 }
 
+void *lua_tointerface(lua_State *L, int idx)
+{
+       struct interface *ifp = XCALLOC(MTYPE_TMP, sizeof(struct interface));
+
+       lua_getfield(L, idx, "name");
+       strlcpy(ifp->name, lua_tostring(L, -1), sizeof(ifp->name));
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "ifindex");
+       ifp->ifindex = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "status");
+       ifp->status = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "flags");
+       ifp->flags = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "metric");
+       ifp->metric = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "speed");
+       ifp->speed = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "mtu");
+       ifp->mtu = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "mtu6");
+       ifp->mtu6 = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "bandwidth");
+       ifp->bandwidth = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "link_ifindex");
+       ifp->link_ifindex = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+       lua_getfield(L, idx, "linklayer_type");
+       ifp->ll_type = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+
+       return ifp;
+}
+
 void lua_pushinaddr(lua_State *L, const struct in_addr *addr)
 {
        zlog_debug("frrlua: pushing inaddr table");
@@ -115,6 +166,17 @@ void lua_pushinaddr(lua_State *L, const struct in_addr *addr)
        lua_setfield(L, -2, "string");
 }
 
+void *lua_toinaddr(lua_State *L, int idx)
+{
+       struct in_addr *inaddr = XCALLOC(MTYPE_TMP, sizeof(struct in_addr));
+
+       lua_getfield(L, idx, "value");
+       inaddr->s_addr = lua_tointeger(L, -1);
+       lua_pop(L, 1);
+
+       return inaddr;
+}
+
 
 void lua_pushin6addr(lua_State *L, const struct in6_addr *addr)
 {
@@ -130,6 +192,17 @@ void lua_pushin6addr(lua_State *L, const struct in6_addr *addr)
        lua_setfield(L, -2, "string");
 }
 
+void *lua_toin6addr(lua_State *L, int idx)
+{
+       struct in6_addr *in6addr = XCALLOC(MTYPE_TMP, sizeof(struct in6_addr));
+
+       lua_getfield(L, idx, "string");
+       inet_pton(AF_INET6, lua_tostring(L, -1), in6addr);
+       lua_pop(L, 1);
+
+       return in6addr;
+}
+
 void lua_pushsockunion(lua_State *L, const union sockunion *su)
 {
        zlog_debug("frrlua: pushing sockunion table");
@@ -145,16 +218,53 @@ void lua_pushsockunion(lua_State *L, const union sockunion *su)
        lua_setfield(L, -2, "string");
 }
 
+void *lua_tosockunion(lua_State *L, int idx)
+{
+       union sockunion *su = XCALLOC(MTYPE_TMP, sizeof(union sockunion));
+
+       lua_getfield(L, idx, "string");
+       str2sockunion(lua_tostring(L, -1), su);
+
+       return su;
+}
+
 void lua_pushtimet(lua_State *L, const time_t *time)
 {
        lua_pushinteger(L, *time);
 }
 
-void lua_pushintegerp(lua_State *L, const int *num)
+void *lua_totimet(lua_State *L, int idx)
+{
+       time_t *t = XCALLOC(MTYPE_TMP, sizeof(time_t));
+
+       *t = lua_tointeger(L, idx);
+
+       return t;
+}
+
+void lua_pushintegerp(lua_State *L, const long long *num)
 {
        lua_pushinteger(L, *num);
 }
 
+void *lua_tointegerp(lua_State *L, int idx)
+{
+       int isnum;
+       long long *num = XCALLOC(MTYPE_TMP, sizeof(long long));
+
+       *num = lua_tonumberx(L, idx, &isnum);
+       assert(isnum);
+
+       return num;
+}
+
+void *lua_tostringp(lua_State *L, int idx)
+{
+       char *string = XSTRDUP(MTYPE_TMP, lua_tostring(L, idx));
+
+       return string;
+}
+
 /*
  * Logging.
  *
index f9ab5509a65da22c01c4026e91afe411323fee5c..a105bd069d9e9d2ab52b98254690eeefc97adf04 100644 (file)
@@ -33,42 +33,104 @@ extern "C" {
 #endif
 
 /*
- * Pushes a new table containing relevant fields from a prefix structure.
+ * Converts a prefix to a Lua value and pushes it on the stack.
  */
 void lua_pushprefix(lua_State *L, const struct prefix *prefix);
 
 /*
- * Pushes a new table containing relevant fields from an interface structure.
+ * Converts the Lua value at idx to a prefix.
+ *
+ * Returns:
+ *    struct prefix allocated with MTYPE_TMP
+ */
+void *lua_toprefix(lua_State *L, int idx);
+
+/*
+ * Converts an interface to a Lua value and pushes it on the stack.
  */
 void lua_pushinterface(lua_State *L, const struct interface *ifp);
 
 /*
- * Pushes a new table containing both numeric and string representations of an
- * in_addr to the stack.
+ * Converts the Lua value at idx to an interface.
+ *
+ * Returns:
+ *    struct interface allocated with MTYPE_TMP. This interface is not hooked
+ *    to anything, nor is it inserted in the global interface tree.
+ */
+void *lua_tointerface(lua_State *L, int idx);
+
+/*
+ * Converts an in_addr to a Lua value and pushes it on the stack.
  */
 void lua_pushinaddr(lua_State *L, const struct in_addr *addr);
 
 /*
- * Pushes a new table containing both numeric and string representations of an
- * in6_addr to the stack.
+ * Converts the Lua value at idx to an in_addr.
+ *
+ * Returns:
+ *    struct in_addr allocated with MTYPE_TMP.
+ */
+void *lua_toinaddr(lua_State *L, int idx);
+
+/*
+ * Converts an in6_addr to a Lua value and pushes it on the stack.
  */
 void lua_pushin6addr(lua_State *L, const struct in6_addr *addr);
 
 /*
- * Pushes a time_t to the stack.
+ * Converts the Lua value at idx to an in6_addr.
+ *
+ * Returns:
+ *    struct in6_addr allocated with MTYPE_TMP.
+ */
+void *lua_toin6addr(lua_State *L, int idx);
+
+/*
+ * Converts a time_t to a Lua value and pushes it on the stack.
  */
 void lua_pushtimet(lua_State *L, const time_t *time);
 
 /*
- * Pushes a table representing a sockunion to the stack.
+ * Converts the Lua value at idx to a time_t.
+ *
+ * Returns:
+ *    time_t allocated with MTYPE_TMP.
+ */
+void *lua_totimet(lua_State *L, int idx);
+
+/*
+ * Converts a sockunion to a Lua value and pushes it on the stack.
  */
 void lua_pushsockunion(lua_State *L, const union sockunion *su);
 
 /*
- * Push integer. This just wraps lua_pushinteger(), but it takes a pointer, so
- * as to be compatible with the encoder_func signature.
+ * Converts the Lua value at idx to a sockunion.
+ *
+ * Returns:
+ *    sockunion allocated with MTYPE_TMP.
+ */
+void *lua_tosockunion(lua_State *L, int idx);
+
+/*
+ * Converts an int to a Lua value and pushes it on the stack.
+ */
+void lua_pushintegerp(lua_State *L, const long long *num);
+
+/*
+ * Converts the Lua value at idx to an int.
+ *
+ * Returns:
+ *    int allocated with MTYPE_TMP.
+ */
+void *lua_tointegerp(lua_State *L, int idx);
+
+/*
+ * Pop string.
+ *
+ * Sets *string to a copy of the string at the top of the stack. The copy is
+ * allocated with MTYPE_TMP and the caller is responsible for freeing it.
  */
-void lua_pushintegerp(lua_State *L, const int *num);
+void *lua_tostringp(lua_State *L, int idx);
 
 /*
  * Retrieve an integer from table on the top of the stack.
index 081ecd026f224b2c641cf9ba825178e3e9bc97c5..c8ea946334cc3d96c6d6c45c82c5f14de840878e 100644 (file)
 
 #include "frrlua.h"
 
-/* Type encoders */
-
-struct encoder {
-       char *typename;
-       encoder_func encoder;
-};
-
-struct hash *encoder_hash;
-
-static unsigned int encoder_hash_key(const void *data)
+/* Codecs */
+
+struct frrscript_codec frrscript_codecs_lib[] = {
+       {.typename = "integer",
+        .encoder = (encoder_func)lua_pushintegerp,
+        .decoder = lua_tointegerp},
+       {.typename = "string",
+        .encoder = (encoder_func)lua_pushstring,
+        .decoder = lua_tostringp},
+       {.typename = "prefix",
+        .encoder = (encoder_func)lua_pushprefix,
+        .decoder = lua_toprefix},
+       {.typename = "interface",
+        .encoder = (encoder_func)lua_pushinterface,
+        .decoder = lua_tointerface},
+       {.typename = "in_addr",
+        .encoder = (encoder_func)lua_pushinaddr,
+        .decoder = lua_toinaddr},
+       {.typename = "in6_addr",
+        .encoder = (encoder_func)lua_pushin6addr,
+        .decoder = lua_toin6addr},
+       {.typename = "sockunion",
+        .encoder = (encoder_func)lua_pushsockunion,
+        .decoder = lua_tosockunion},
+       {.typename = "time_t",
+        .encoder = (encoder_func)lua_pushtimet,
+        .decoder = lua_totimet},
+       {}};
+
+/* Type codecs */
+
+struct hash *codec_hash;
+
+static unsigned int codec_hash_key(const void *data)
 {
-       const struct encoder *e = data;
+       const struct frrscript_codec *c = data;
 
-       return string_hash_make(e->typename);
+       return string_hash_make(c->typename);
 }
 
-static bool encoder_hash_cmp(const void *d1, const void *d2)
+static bool codec_hash_cmp(const void *d1, const void *d2)
 {
-       const struct encoder *e1 = d1;
-       const struct encoder *e2 = d2;
+       const struct frrscript_codec *e1 = d1;
+       const struct frrscript_codec *e2 = d2;
 
        return strmatch(e1->typename, e2->typename);
 }
 
-static void *encoder_alloc(void *arg)
+static void *codec_alloc(void *arg)
 {
-       struct encoder *tmp = arg;
+       struct frrscript_codec *tmp = arg;
 
-       struct encoder *e = XCALLOC(MTYPE_TMP, sizeof(struct encoder));
+       struct frrscript_codec *e =
+               XCALLOC(MTYPE_TMP, sizeof(struct frrscript_codec));
        e->typename = XSTRDUP(MTYPE_TMP, tmp->typename);
        e->encoder = tmp->encoder;
+       e->decoder = tmp->decoder;
 
        return e;
 }
 
 #if 0
-static void encoder_free(struct encoder *e)
+static void codec_free(struct codec *c)
 {
-       XFREE(MTYPE_TMP, e->typename);
-       XFREE(MTYPE_TMP, e);
+       XFREE(MTYPE_TMP, c->typename);
+       XFREE(MTYPE_TMP, c);
 }
 #endif
 
 /* Generic script APIs */
 
-int frrscript_lua_call(struct frrscript *fs, ...)
+int frrscript_call(struct frrscript *fs, struct frrscript_env *env)
 {
-       va_list vl;
-       va_start(vl, fs);
-
-       int nargs = va_arg(vl, int);
-       assert(nargs % 3 == 0);
-
-       zlog_debug("%s: Script '%s' called with # args: %d", __func__, fs->name,
-                  nargs);
-
-       struct encoder e = {};
-       void *arg;
+       struct frrscript_codec c = {};
+       const void *arg;
        const char *bindname;
 
        /* Encode script arguments */
-       for (int i = 0; i < nargs; i += 3) {
-               bindname = va_arg(vl, const char *);
-               e.typename = va_arg(vl, char *);
-               arg = va_arg(vl, void *);
+       for (int i = 0; env && env[i].val != NULL; i++) {
+               bindname = env[i].name;
+               c.typename = env[i].typename;
+               arg = env[i].val;
 
                zlog_debug("Script argument | Bind name: %s | Type: %s",
-                          bindname, e.typename);
+                          bindname, c.typename);
 
-               struct encoder *enc = hash_lookup(encoder_hash, &e);
-               assert(enc
+               struct frrscript_codec *codec = hash_lookup(codec_hash, &c);
+               assert(codec
                       && "No encoder for type; rerun with debug logs to see more");
-               enc->encoder(fs->L, arg);
+               codec->encoder(fs->L, arg);
 
                lua_setglobal(fs->L, bindname);
        }
 
-       int nresults = va_arg(vl, int);
-       zlog_debug("Expected script results: %d", nresults);
-
-       int ret = lua_pcall(fs->L, 0, nresults, 0);
+       int ret = lua_pcall(fs->L, 0, 0, 0);
 
        switch (ret) {
        case LUA_OK:
                break;
        case LUA_ERRRUN:
-               zlog_err("Script '%s' runtime error: %s", fs->name, lua_tostring(fs->L, -1));
+               zlog_err("Script '%s' runtime error: %s", fs->name,
+                        lua_tostring(fs->L, -1));
                break;
        case LUA_ERRMEM:
-               zlog_err("Script '%s' memory error: %s", fs->name, lua_tostring(fs->L, -1));
+               zlog_err("Script '%s' memory error: %s", fs->name,
+                        lua_tostring(fs->L, -1));
                break;
        case LUA_ERRERR:
-               zlog_err("Script '%s' error handler error: %s", fs->name, lua_tostring(fs->L, -1));
+               zlog_err("Script '%s' error handler error: %s", fs->name,
+                        lua_tostring(fs->L, -1));
                break;
        case LUA_ERRGCMM:
-               zlog_err("Script '%s' garbage collector error: %s", fs->name, lua_tostring(fs->L, -1));
+               zlog_err("Script '%s' garbage collector error: %s", fs->name,
+                        lua_tostring(fs->L, -1));
                break;
        default:
-               zlog_err("Script '%s' unknown error: %s", fs->name, lua_tostring(fs->L, -1));
+               zlog_err("Script '%s' unknown error: %s", fs->name,
+                        lua_tostring(fs->L, -1));
                break;
        }
 
-       if (ret != LUA_OK)
+       if (ret != LUA_OK) {
                lua_pop(fs->L, 1);
-
-       /* After script returns, decode results */
-       for (int i = 0; i < nresults; i++) {
-               const char *resultname = va_arg(vl, const char *);
-               fprintf(stderr, "result: %s\n", resultname);
+               goto done;
        }
 
+done:
        /* LUA_OK is 0, so we can just return lua_pcall's result directly */
        return ret;
 }
 
-void frrscript_register_type_encoder(const char *typename, encoder_func encoder)
+void *frrscript_get_result(struct frrscript *fs,
+                          const struct frrscript_env *result)
 {
-       struct encoder e = {.typename = (char *)typename, .encoder = encoder};
+       void *r;
+       struct frrscript_codec c = {.typename = result->typename};
+
+       struct frrscript_codec *codec = hash_lookup(codec_hash, &c);
+
+       lua_getglobal(fs->L, result->name);
+       r = codec->decoder(fs->L, -1);
+       lua_pop(fs->L, 1);
+
+       return r;
+}
 
-       if (hash_lookup(encoder_hash, &e)) {
+void frrscript_register_type_codec(struct frrscript_codec *codec)
+{
+       struct frrscript_codec c = *codec;
+
+       zlog_debug("Registering codec for '%s'", codec->typename);
+
+       if (hash_lookup(codec_hash, &c)) {
                zlog_backtrace(LOG_ERR);
-               assert(!"Type encoder double-registered.");
+               assert(!"Type codec double-registered.");
        }
 
-       assert(hash_get(encoder_hash, &e, encoder_alloc));
+       assert(hash_get(codec_hash, &c, codec_alloc));
 }
 
+void frrscript_register_type_codecs(struct frrscript_codec *codecs)
+{
+       for (int i = 0; codecs[i].typename != NULL; i++)
+               frrscript_register_type_codec(&codecs[i]);
+}
 
 struct frrscript *frrscript_load(const char *name,
                                 int (*load_cb)(struct frrscript *))
@@ -216,17 +254,9 @@ void frrscript_unload(struct frrscript *fs)
 
 void frrscript_init()
 {
-       encoder_hash = hash_create(encoder_hash_key, encoder_hash_cmp,
-                                  "Lua type encoders");
+       codec_hash = hash_create(codec_hash_key, codec_hash_cmp,
+                                "Lua type encoders");
 
        /* Register core library types */
-       frrscript_register_type_encoder("integer", (encoder_func) lua_pushintegerp);
-       frrscript_register_type_encoder("string", (encoder_func) lua_pushstring);
-       frrscript_register_type_encoder("prefix", (encoder_func)lua_pushprefix);
-       frrscript_register_type_encoder("interface",
-                                       (encoder_func)lua_pushinterface);
-       frrscript_register_type_encoder("sockunion", (encoder_func) lua_pushsockunion);
-       frrscript_register_type_encoder("in_addr", (encoder_func) lua_pushinaddr);
-       frrscript_register_type_encoder("in6_addr", (encoder_func) lua_pushin6addr);
-       frrscript_register_type_encoder("time_t", (encoder_func) lua_pushtimet);
+       frrscript_register_type_codecs(frrscript_codecs_lib);
 }
index 44067bf0b4efb4fcdfc4e6d828b3aa7a108bc4b0..cbc0ca6c51c5214b9f3aea508a0261eeb5b9c53a 100644 (file)
@@ -29,6 +29,13 @@ extern "C" {
 #define FRRSCRIPT_PATH "/etc/frr/scripts"
 
 typedef void (*encoder_func)(lua_State *, const void *);
+typedef void *(*decoder_func)(lua_State *, int);
+
+struct frrscript_codec {
+       const char *typename;
+       encoder_func encoder;
+       decoder_func decoder;
+};
 
 struct frrscript {
        /* Script name */
@@ -38,6 +45,16 @@ struct frrscript {
        struct lua_State *L;
 };
 
+struct frrscript_env {
+       /* Value type */
+       const char *typename;
+
+       /* Binding name */
+       const char *name;
+
+       /* Value */
+       const void *val;
+};
 
 /*
  * Create new FRR script.
@@ -51,64 +68,61 @@ struct frrscript *frrscript_load(const char *name,
 void frrscript_unload(struct frrscript *fs);
 
 /*
- * Register a Lua encoder for a type.
+ * Register a Lua codec for a type.
  *
  * tname
  *    Name of type; e.g., "peer", "ospf_interface", etc. Chosen at will.
  *
- * encoder
- *    Function pointer to encoder function. Encoder function should push a Lua
+ * codec(s)
+ *    Function pointer to codec struct. Encoder function should push a Lua
  *    table representing the passed argument - which will have the C type
- *    associated with the chosen 'tname' to the provided stack.
+ *    associated with the chosen 'tname' to the provided stack. The decoder
+ *    function should pop a value from the top of the stack and return a heap
+ *    chunk containing that value. Allocations should be made with MTYPE_TMP.
+ *
+ *    If using the plural function variant, pass a NULL-terminated array.
  *
  */
-void frrscript_register_type_encoder(const char *tname, encoder_func encoder);
+void frrscript_register_type_codec(struct frrscript_codec *codec);
+void frrscript_register_type_codecs(struct frrscript_codec *codecs);
 
 /*
  * Initialize scripting subsystem. Call this before anything else.
  */
 void frrscript_init(void);
 
-/*
- * Forward decl for frrscript_lua_call
- */
-int frrscript_lua_call(struct frrscript *fs, ...);
 
 /*
- * Call FRR script.
- *
- * Call it like this:
+ * Call script.
  *
- *   frrscript_call(fs, FRRSCRIPT_ARGS("cool_prefix", "prefix", p),
- *                  FRRSCRIPT_RESULTS("result1", "result2"))
- */
-#define frrscript_call(fs, ...) frrscript_lua_call((fs), __VA_ARGS__)
-
-/*
- * Macro that defines the arguments to a script.
+ * fs
+ *    The script to call; this is obtained from frrscript_load().
  *
- * For each argument you want to pass to a script, pass *three* arguments to
- * this function. The first should be name of the variable to bind the argument
- * to in the script's environment. The second should be the type, as registered
- * by frrscript_register_type_encoder(). The third should be the argument
- * itself.
+ * env
+ *    The script's environment. Specify this as an array of frrscript_env.
  *
- * This macro itself should be used as the second argument to frrscript_call().
+ * Returns:
+ *    0 if the script ran successfully, nonzero otherwise.
  */
-#define FRRSCRIPT_ARGS(...) PP_NARG(__VA_ARGS__), ##__VA_ARGS__
+int frrscript_call(struct frrscript *fs, struct frrscript_env *env);
+
 
 /*
- * Macro that defines the results from a script.
+ * Get result from finished script.
  *
- * Similar to FRRSCRIPT_ARGS, except this defines the results from a script.
+ * fs
+ *    The script. This script must have been run already.
  *
- * The first argument should be the name to bind the first result to and will
- * be used after the script finishes to get that particular result value.
+ * result
+ *    The result to extract from the script.
+ *    This reuses the frrscript_env type, but only the typename and name fields
+ *    need to be set. The value is returned directly.
  *
- * This macro itself should be used as the third argument to frrscript_call().
- * It may not be omitted.
+ * Returns:
+ *    The script result of the specified name and type, or NULL.
  */
-#define FRRSCRIPT_RESULTS(...) PP_NARG(__VA_ARGS__), ##__VA_ARGS__
+void *frrscript_get_result(struct frrscript *fs,
+                          const struct frrscript_env *result);
 
 #ifdef __cplusplus
 }