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