]>
Commit | Line | Data |
---|---|---|
5f98c815 QY |
1 | /* Scripting foo |
2 | * Copyright (C) 2020 NVIDIA Corporation | |
3 | * Quentin Young | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the Free | |
7 | * Software Foundation; either version 2 of the License, or (at your option) | |
8 | * any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License along | |
16 | * with this program; see the file COPYING; if not, write to the Free Software | |
17 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
18 | */ | |
5f98c815 | 19 | #include <zebra.h> |
fa22080d QY |
20 | |
21 | #ifdef HAVE_SCRIPTING | |
22 | ||
3b002f19 | 23 | #include <stdarg.h> |
1a3a91e2 | 24 | #include <lua.h> |
5f98c815 QY |
25 | |
26 | #include "frrscript.h" | |
1a3a91e2 | 27 | #include "frrlua.h" |
5f98c815 QY |
28 | #include "memory.h" |
29 | #include "hash.h" | |
30 | #include "log.h" | |
31 | ||
fa22080d | 32 | |
1a3a91e2 | 33 | DEFINE_MTYPE_STATIC(LIB, SCRIPT, "Scripting"); |
5f98c815 | 34 | |
a3ef7ae8 DL |
35 | /* |
36 | * Script name hash utilities | |
37 | */ | |
38 | ||
39 | struct frrscript_names_head frrscript_names_hash; | |
40 | ||
41 | /* | |
42 | * Wrapper for frrscript_names_add | |
43 | * Use this to register hook calls when a daemon starts up | |
44 | */ | |
45 | int frrscript_names_add_function_name(const char *function_name) | |
46 | { | |
47 | struct frrscript_names_entry *insert = | |
48 | XCALLOC(MTYPE_SCRIPT, sizeof(*insert)); | |
49 | strlcpy(insert->function_name, function_name, | |
50 | sizeof(insert->function_name)); | |
51 | ||
52 | if (frrscript_names_add(&frrscript_names_hash, insert)) { | |
53 | zlog_warn( | |
54 | "Failed to add hook call function name to script_names"); | |
55 | return 1; | |
56 | } | |
57 | return 0; | |
58 | } | |
59 | ||
60 | void frrscript_names_destroy(void) | |
61 | { | |
62 | struct frrscript_names_entry *ne; | |
63 | ||
64 | while ((ne = frrscript_names_pop(&frrscript_names_hash))) | |
65 | XFREE(MTYPE_SCRIPT, ne); | |
66 | } | |
67 | ||
68 | /* | |
69 | * Given a function_name, set its script_name. function_names and script_names | |
70 | * are one-to-one. Each set will wipe the previous script_name. | |
71 | * Return 0 if set was successful, else 1. | |
72 | * | |
73 | * script_name is the base name of the file, without .lua. | |
74 | */ | |
75 | int frrscript_names_set_script_name(const char *function_name, | |
76 | const char *script_name) | |
77 | { | |
78 | struct frrscript_names_entry lookup; | |
79 | ||
80 | strlcpy(lookup.function_name, function_name, | |
81 | sizeof(lookup.function_name)); | |
82 | struct frrscript_names_entry *snhe = | |
83 | frrscript_names_find(&frrscript_names_hash, &lookup); | |
84 | if (!snhe) | |
85 | return 1; | |
86 | strlcpy(snhe->script_name, script_name, sizeof(snhe->script_name)); | |
87 | return 0; | |
88 | } | |
89 | ||
90 | /* | |
91 | * Given a function_name, get its script_name. | |
92 | * Return NULL if function_name not found. | |
93 | * | |
94 | * script_name is the base name of the file, without .lua. | |
95 | */ | |
96 | char *frrscript_names_get_script_name(const char *function_name) | |
97 | { | |
98 | struct frrscript_names_entry lookup; | |
99 | ||
100 | strlcpy(lookup.function_name, function_name, | |
101 | sizeof(lookup.function_name)); | |
102 | struct frrscript_names_entry *snhe = | |
103 | frrscript_names_find(&frrscript_names_hash, &lookup); | |
104 | if (!snhe) | |
105 | return NULL; | |
821a877f DS |
106 | |
107 | if (snhe->script_name[0] == '\0') | |
108 | return NULL; | |
109 | ||
a3ef7ae8 DL |
110 | return snhe->script_name; |
111 | } | |
112 | ||
113 | uint32_t frrscript_names_hash_key(const struct frrscript_names_entry *snhe) | |
114 | { | |
115 | return string_hash_make(snhe->function_name); | |
116 | } | |
117 | ||
118 | int frrscript_names_hash_cmp(const struct frrscript_names_entry *snhe1, | |
119 | const struct frrscript_names_entry *snhe2) | |
120 | { | |
121 | return strncmp(snhe1->function_name, snhe2->function_name, | |
122 | sizeof(snhe1->function_name)); | |
123 | } | |
124 | ||
f869ab17 QY |
125 | /* Codecs */ |
126 | ||
127 | struct frrscript_codec frrscript_codecs_lib[] = { | |
128 | {.typename = "integer", | |
129 | .encoder = (encoder_func)lua_pushintegerp, | |
130 | .decoder = lua_tointegerp}, | |
131 | {.typename = "string", | |
ea6caa1f | 132 | .encoder = (encoder_func)lua_pushstring_wrapper, |
f869ab17 QY |
133 | .decoder = lua_tostringp}, |
134 | {.typename = "prefix", | |
135 | .encoder = (encoder_func)lua_pushprefix, | |
136 | .decoder = lua_toprefix}, | |
137 | {.typename = "interface", | |
138 | .encoder = (encoder_func)lua_pushinterface, | |
139 | .decoder = lua_tointerface}, | |
140 | {.typename = "in_addr", | |
141 | .encoder = (encoder_func)lua_pushinaddr, | |
142 | .decoder = lua_toinaddr}, | |
143 | {.typename = "in6_addr", | |
144 | .encoder = (encoder_func)lua_pushin6addr, | |
145 | .decoder = lua_toin6addr}, | |
146 | {.typename = "sockunion", | |
147 | .encoder = (encoder_func)lua_pushsockunion, | |
148 | .decoder = lua_tosockunion}, | |
149 | {.typename = "time_t", | |
150 | .encoder = (encoder_func)lua_pushtimet, | |
151 | .decoder = lua_totimet}, | |
152 | {}}; | |
153 | ||
154 | /* Type codecs */ | |
155 | ||
156 | struct hash *codec_hash; | |
e4e0229a | 157 | char scriptdir[MAXPATHLEN]; |
f869ab17 QY |
158 | |
159 | static unsigned int codec_hash_key(const void *data) | |
5f98c815 | 160 | { |
f869ab17 | 161 | const struct frrscript_codec *c = data; |
5f98c815 | 162 | |
f869ab17 | 163 | return string_hash_make(c->typename); |
5f98c815 QY |
164 | } |
165 | ||
f869ab17 | 166 | static bool codec_hash_cmp(const void *d1, const void *d2) |
5f98c815 | 167 | { |
f869ab17 QY |
168 | const struct frrscript_codec *e1 = d1; |
169 | const struct frrscript_codec *e2 = d2; | |
923431ef QY |
170 | |
171 | return strmatch(e1->typename, e2->typename); | |
5f98c815 QY |
172 | } |
173 | ||
f869ab17 | 174 | static void *codec_alloc(void *arg) |
5f98c815 | 175 | { |
f869ab17 | 176 | struct frrscript_codec *tmp = arg; |
5f98c815 | 177 | |
f869ab17 | 178 | struct frrscript_codec *e = |
1a3a91e2 QY |
179 | XCALLOC(MTYPE_SCRIPT, sizeof(struct frrscript_codec)); |
180 | e->typename = XSTRDUP(MTYPE_SCRIPT, tmp->typename); | |
923431ef | 181 | e->encoder = tmp->encoder; |
f869ab17 | 182 | e->decoder = tmp->decoder; |
5f98c815 QY |
183 | |
184 | return e; | |
185 | } | |
186 | ||
ca28a0f6 | 187 | static void codec_free(void *data) |
5f98c815 | 188 | { |
ca28a0f6 DS |
189 | struct frrscript_codec *c = data; |
190 | char *constworkaroundandihateit = (char *)c->typename; | |
191 | ||
192 | XFREE(MTYPE_SCRIPT, constworkaroundandihateit); | |
193 | XFREE(MTYPE_SCRIPT, c); | |
5f98c815 | 194 | } |
5f98c815 | 195 | |
fae19fa5 | 196 | /* Lua function hash utils */ |
105ba9af DL |
197 | |
198 | unsigned int lua_function_hash_key(const void *data) | |
199 | { | |
200 | const struct lua_function_state *lfs = data; | |
201 | ||
202 | return string_hash_make(lfs->name); | |
203 | } | |
204 | ||
205 | bool lua_function_hash_cmp(const void *d1, const void *d2) | |
206 | { | |
207 | const struct lua_function_state *lfs1 = d1; | |
208 | const struct lua_function_state *lfs2 = d2; | |
209 | ||
210 | return strmatch(lfs1->name, lfs2->name); | |
211 | } | |
212 | ||
213 | void *lua_function_alloc(void *arg) | |
214 | { | |
215 | struct lua_function_state *tmp = arg; | |
105ba9af DL |
216 | struct lua_function_state *lfs = |
217 | XCALLOC(MTYPE_SCRIPT, sizeof(struct lua_function_state)); | |
ca28a0f6 | 218 | |
105ba9af DL |
219 | lfs->name = tmp->name; |
220 | lfs->L = tmp->L; | |
221 | return lfs; | |
222 | } | |
223 | ||
ca28a0f6 | 224 | static void lua_function_free(void *data) |
105ba9af | 225 | { |
ca28a0f6 DS |
226 | struct lua_function_state *lfs = data; |
227 | ||
105ba9af | 228 | lua_close(lfs->L); |
fae19fa5 | 229 | XFREE(MTYPE_SCRIPT, lfs); |
105ba9af DL |
230 | } |
231 | ||
b6640929 | 232 | /* internal frrscript APIs */ |
5f98c815 | 233 | |
40d038d2 | 234 | int _frrscript_call_lua(struct lua_function_state *lfs, int nargs) |
5f98c815 | 235 | { |
3b002f19 | 236 | |
40d038d2 DL |
237 | int ret; |
238 | ret = lua_pcall(lfs->L, nargs, 1, 0); | |
3b002f19 QY |
239 | |
240 | switch (ret) { | |
646b0cce QY |
241 | case LUA_OK: |
242 | break; | |
3b002f19 | 243 | case LUA_ERRRUN: |
40d038d2 DL |
244 | zlog_err("Lua hook call '%s' : runtime error: %s", lfs->name, |
245 | lua_tostring(lfs->L, -1)); | |
3b002f19 QY |
246 | break; |
247 | case LUA_ERRMEM: | |
40d038d2 DL |
248 | zlog_err("Lua hook call '%s' : memory error: %s", lfs->name, |
249 | lua_tostring(lfs->L, -1)); | |
3b002f19 QY |
250 | break; |
251 | case LUA_ERRERR: | |
40d038d2 DL |
252 | zlog_err("Lua hook call '%s' : error handler error: %s", |
253 | lfs->name, lua_tostring(lfs->L, -1)); | |
3b002f19 QY |
254 | break; |
255 | case LUA_ERRGCMM: | |
40d038d2 DL |
256 | zlog_err("Lua hook call '%s' : garbage collector error: %s", |
257 | lfs->name, lua_tostring(lfs->L, -1)); | |
3b002f19 QY |
258 | break; |
259 | default: | |
40d038d2 DL |
260 | zlog_err("Lua hook call '%s' : unknown error: %s", lfs->name, |
261 | lua_tostring(lfs->L, -1)); | |
3b002f19 QY |
262 | break; |
263 | } | |
264 | ||
f869ab17 | 265 | if (ret != LUA_OK) { |
40d038d2 | 266 | lua_pop(lfs->L, 1); |
f869ab17 | 267 | goto done; |
3b002f19 | 268 | } |
5f98c815 | 269 | |
40d038d2 DL |
270 | if (lua_gettop(lfs->L) != 1) { |
271 | zlog_err( | |
272 | "Lua hook call '%s': Lua function should return only 1 result", | |
273 | lfs->name); | |
274 | ret = 1; | |
275 | goto done; | |
276 | } | |
277 | ||
278 | if (lua_istable(lfs->L, 1) != 1) { | |
279 | zlog_err( | |
280 | "Lua hook call '%s': Lua function should return a Lua table", | |
281 | lfs->name); | |
282 | ret = 1; | |
283 | } | |
284 | ||
f869ab17 | 285 | done: |
5f98c815 QY |
286 | /* LUA_OK is 0, so we can just return lua_pcall's result directly */ |
287 | return ret; | |
288 | } | |
289 | ||
06947dde DL |
290 | void *frrscript_get_result(struct frrscript *fs, const char *function_name, |
291 | const char *name, | |
292 | void *(*lua_to)(lua_State *L, int idx)) | |
5f98c815 | 293 | { |
06947dde DL |
294 | void *p; |
295 | struct lua_function_state *lfs; | |
296 | struct lua_function_state lookup = {.name = function_name}; | |
f869ab17 | 297 | |
06947dde | 298 | lfs = hash_lookup(fs->lua_function_hash, &lookup); |
1a3a91e2 | 299 | |
2ce634e2 | 300 | if (lfs == NULL) |
1a3a91e2 | 301 | return NULL; |
f869ab17 | 302 | |
1da9c4bd DL |
303 | /* At this point, the Lua state should have only the returned table. |
304 | * We will then search the table for the key/value we're interested in. | |
305 | * Then if the value is present (i.e. non-nil), call the lua_to* | |
306 | * decoder. | |
2ce634e2 | 307 | */ |
1da9c4bd DL |
308 | assert(lua_gettop(lfs->L) == 1); |
309 | assert(lua_istable(lfs->L, -1) == 1); | |
310 | lua_getfield(lfs->L, -1, name); | |
06947dde DL |
311 | if (lua_isnil(lfs->L, -1)) { |
312 | lua_pop(lfs->L, 1); | |
b6640929 DL |
313 | zlog_warn( |
314 | "frrscript: '%s.lua': '%s': tried to decode '%s' as result but failed", | |
315 | fs->name, function_name, name); | |
06947dde DL |
316 | return NULL; |
317 | } | |
318 | p = lua_to(lfs->L, 2); | |
f869ab17 | 319 | |
1da9c4bd | 320 | /* At the end, the Lua state should be same as it was at the start |
214d8a60 | 321 | * i.e. containing solely the returned table. |
84c92002 | 322 | */ |
1da9c4bd DL |
323 | assert(lua_gettop(lfs->L) == 1); |
324 | assert(lua_istable(lfs->L, -1) == 1); | |
325 | ||
06947dde | 326 | return p; |
f869ab17 | 327 | } |
5f98c815 | 328 | |
f869ab17 QY |
329 | void frrscript_register_type_codec(struct frrscript_codec *codec) |
330 | { | |
331 | struct frrscript_codec c = *codec; | |
332 | ||
f869ab17 | 333 | if (hash_lookup(codec_hash, &c)) { |
5f98c815 | 334 | zlog_backtrace(LOG_ERR); |
f869ab17 | 335 | assert(!"Type codec double-registered."); |
5f98c815 QY |
336 | } |
337 | ||
8e3aae66 | 338 | (void)hash_get(codec_hash, &c, codec_alloc); |
5f98c815 QY |
339 | } |
340 | ||
f869ab17 QY |
341 | void frrscript_register_type_codecs(struct frrscript_codec *codecs) |
342 | { | |
343 | for (int i = 0; codecs[i].typename != NULL; i++) | |
344 | frrscript_register_type_codec(&codecs[i]); | |
345 | } | |
5f98c815 | 346 | |
f0cddf95 | 347 | struct frrscript *frrscript_new(const char *name) |
5f98c815 | 348 | { |
1a3a91e2 | 349 | struct frrscript *fs = XCALLOC(MTYPE_SCRIPT, sizeof(struct frrscript)); |
5f98c815 | 350 | |
1a3a91e2 | 351 | fs->name = XSTRDUP(MTYPE_SCRIPT, name); |
f0cddf95 DL |
352 | fs->lua_function_hash = |
353 | hash_create(lua_function_hash_key, lua_function_hash_cmp, | |
354 | "Lua function state hash"); | |
355 | return fs; | |
356 | } | |
357 | ||
358 | int frrscript_load(struct frrscript *fs, const char *function_name, | |
359 | int (*load_cb)(struct frrscript *)) | |
360 | { | |
361 | ||
362 | /* Set up the Lua script */ | |
363 | lua_State *L = luaL_newstate(); | |
2ce634e2 | 364 | |
f0cddf95 | 365 | frrlua_export_logging(L); |
5f98c815 | 366 | |
1763ed26 DL |
367 | char script_name[MAXPATHLEN]; |
368 | ||
369 | if (snprintf(script_name, sizeof(script_name), "%s/%s.lua", scriptdir, | |
370 | fs->name) | |
9e3a277b | 371 | >= (int)sizeof(script_name)) { |
1763ed26 DL |
372 | zlog_err("frrscript: path to script %s/%s.lua is too long", |
373 | scriptdir, fs->name); | |
374 | goto fail; | |
375 | } | |
646b0cce | 376 | |
868b41d9 | 377 | if (luaL_dofile(L, script_name) != 0) { |
93b2d38a | 378 | zlog_err("frrscript: failed loading script '%s': error: %s", |
868b41d9 | 379 | script_name, lua_tostring(L, -1)); |
5f98c815 | 380 | goto fail; |
868b41d9 | 381 | } |
5f98c815 | 382 | |
2ed598ba | 383 | /* To check the Lua function, we get it from the global table */ |
f0cddf95 | 384 | lua_getglobal(L, function_name); |
4093300e | 385 | if (lua_isfunction(L, lua_gettop(L)) == 0) { |
93b2d38a | 386 | zlog_err("frrscript: loaded script '%s' but %s not found", |
4093300e | 387 | script_name, function_name); |
f0cddf95 | 388 | goto fail; |
4093300e | 389 | } |
2ed598ba DL |
390 | /* Then pop the function (frrscript_call will push it when it needs it) |
391 | */ | |
392 | lua_pop(L, 1); | |
f0cddf95 | 393 | |
4093300e DL |
394 | if (load_cb && (*load_cb)(fs) != 0) { |
395 | zlog_err( | |
93b2d38a | 396 | "frrscript: '%s': %s: loaded but callback returned non-zero exit code", |
4093300e | 397 | script_name, function_name); |
5f98c815 | 398 | goto fail; |
4093300e | 399 | } |
5f98c815 | 400 | |
f0cddf95 DL |
401 | /* Add the Lua function state to frrscript */ |
402 | struct lua_function_state key = {.name = function_name, .L = L}; | |
403 | ||
8e3aae66 | 404 | (void)hash_get(fs->lua_function_hash, &key, lua_function_alloc); |
f0cddf95 DL |
405 | |
406 | return 0; | |
5f98c815 | 407 | fail: |
f0cddf95 DL |
408 | lua_close(L); |
409 | return 1; | |
5f98c815 QY |
410 | } |
411 | ||
64d457d7 | 412 | void frrscript_delete(struct frrscript *fs) |
5f98c815 | 413 | { |
ca28a0f6 DS |
414 | hash_clean(fs->lua_function_hash, lua_function_free); |
415 | hash_free(fs->lua_function_hash); | |
1a3a91e2 QY |
416 | XFREE(MTYPE_SCRIPT, fs->name); |
417 | XFREE(MTYPE_SCRIPT, fs); | |
5f98c815 QY |
418 | } |
419 | ||
e4e0229a | 420 | void frrscript_init(const char *sd) |
5f98c815 | 421 | { |
f869ab17 QY |
422 | codec_hash = hash_create(codec_hash_key, codec_hash_cmp, |
423 | "Lua type encoders"); | |
3b002f19 | 424 | |
e4e0229a QY |
425 | strlcpy(scriptdir, sd, sizeof(scriptdir)); |
426 | ||
3b002f19 | 427 | /* Register core library types */ |
f869ab17 | 428 | frrscript_register_type_codecs(frrscript_codecs_lib); |
5f98c815 | 429 | } |
fa22080d | 430 | |
ca28a0f6 DS |
431 | void frrscript_fini(void) |
432 | { | |
433 | hash_clean(codec_hash, codec_free); | |
434 | hash_free(codec_hash); | |
435 | ||
436 | frrscript_names_destroy(); | |
437 | } | |
fa22080d | 438 | #endif /* HAVE_SCRIPTING */ |