0b2e33122bf9ea28d8ba54b2f2a9446e46bdb790
[project/luci.git] / libs / lucittpd / src / lib / luaplugin.c
1 /*
2  * luaplugin - fast lua plugin indexing
3  * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2
7  * as published by the Free Software Foundation
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include <sys/types.h>
16 #include <sys/time.h>
17 #include <sys/cdefs.h>
18
19 #ifndef _POSIX_C_SOURCE
20 #define _POSIX_C_SOURCE /* XXX: portability hack for timestamp */
21 #endif
22
23 #include <sys/stat.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <limits.h>
30 #include <glob.h>
31
32 #include <lualib.h>
33 #include <lauxlib.h>
34 #include <lib/list.h>
35 #include <lib/luaplugin.h>
36
37 //#define DEBUG 1
38 #ifdef DEBUG
39 #define DPRINTF(...) fprintf(stderr, __VA_ARGS__)
40 #else
41 #define DPRINTF(...) do {} while (0)
42 #endif
43
44 /**
45  * list_for_each_offset -       iterate over a list, start with the provided pointer
46  * @pos:        the &struct list_head to use as a loop cursor.
47  * @head:       the head for your list.
48  */
49 #define list_for_each_offset(pos, head, offset) \
50         for (pos = (offset)->next; pos != (offset); \
51              pos = ((pos->next == (head)) && ((offset) != (head)) ? (head)->next : pos->next))
52
53 static char pbuf[PATH_MAX];
54 static void load_module(struct luaplugin_ctx *ctx, struct luaplugin_entry *e);
55
56 static struct luaplugin_entry *
57 find_entry(struct luaplugin_ctx *ctx, const char *name, bool modname)
58 {
59         struct list_head *p;
60
61         if (!ctx->last)
62                 ctx->last = &ctx->entries;
63
64         list_for_each_offset(p, &ctx->entries, ctx->last) {
65                 struct luaplugin_entry *e;
66                 const char *cmp;
67
68                 e = container_of(p, struct luaplugin_entry, list);
69                 if (modname)
70                         cmp = e->module;
71                 else
72                         cmp = e->name;
73
74                 if (!strcmp(cmp, name))
75                         return e;
76         }
77         return NULL;
78 }
79
80 static struct luaplugin_entry *
81 new_entry(struct luaplugin_ctx *ctx, const char *name, const char *modname)
82 {
83         struct luaplugin_entry *e;
84         char *c;
85
86         e = malloc(sizeof(struct luaplugin_entry));
87         if (!e)
88                 goto error;
89
90         memset(e, 0, sizeof(struct luaplugin_entry));
91         INIT_LIST_HEAD(&e->list);
92         e->ctx = ctx;
93         e->loaded = false;
94
95         e->name = strdup(name);
96         if (!e->name)
97                 goto error1;
98
99         e->module = strdup(modname);
100         if (!e->module)
101                 goto error2;
102
103         /* strip filename extension */
104         c = strrchr(e->module, '.');
105         if (c)
106                 *c = 0;
107
108         /* lua namespace: replace / with . */
109         c = e->module;
110         while ((c = strchr(c, '/')) != NULL) {
111                 *c = '.';
112         }
113         return e;
114
115 error2:
116         free(e->name);
117 error1:
118         free(e);
119 error:
120         return NULL;
121 }
122
123 static const char *module_loader =
124 "loader = function (newgt, filename)\n"
125 "       setmetatable(newgt, { __index = _G })\n"
126 "       local f = loadfile(filename)\n"
127 "       if (type(f) == \"function\") then\n"
128 "               setfenv(f, newgt)\n"
129 "               f()\n"
130 "       else\n"
131 "               error(f)\n"
132 "       end\n"
133 "end\n";
134
135 static void
136 access_plugin_table (lua_State *L, const char *modname, bool set)
137 {
138         const char *e;
139
140         lua_pushvalue(L, LUA_GLOBALSINDEX);
141         do {
142                 bool _set = true;
143
144                 e = strchr(modname, '.');
145                 if (e == NULL) {
146                         e = modname + strlen(modname);
147                         _set = set;
148                 }
149
150                 lua_pushlstring(L, modname, e - modname);
151                 lua_rawget(L, -2);
152                 if (lua_isnil(L, -1) ||
153                     /* no such field or last field */
154                     (lua_istable(L, -1) && (*e != '.'))) {
155                         lua_pop(L, 1);  /* remove this result */
156
157                         if (_set) {
158                                 if (*e != '.')
159                                         lua_pushvalue(L, -2); /* use table from given index */
160                                 else
161                                         lua_createtable(L, 0, 1); /* new table for field */
162                         }
163
164                         lua_pushlstring(L, modname, e - modname);
165
166                         if (_set) {
167                                 lua_pushvalue(L, -2);
168                                 lua_settable(L, -4);  /* set new table into field */
169                         } else {
170                                 lua_gettable(L, -2);
171                         }
172                 }
173                 else if (!lua_istable(L, -1)) {  /* field has a non-table value? */
174                         lua_pop(L, 2 + !!set);  /* remove table and values */
175                         return;
176                 }
177                 lua_remove(L, -2);  /* remove previous table */
178                 modname = e + 1;
179         } while (*e == '.');
180         if (set)
181                 lua_pop(L, 2);
182 }
183
184
185 static void
186 load_module(struct luaplugin_ctx *ctx, struct luaplugin_entry *e)
187 {
188         lua_State *L = ctx->L;
189         int ret;
190
191         /* grab the loader wrapper function */
192         ret = luaL_dostring(L, module_loader);
193         if (ret)
194                 return;
195
196         lua_getglobal(L, "loader");
197         lua_pushnil(L);
198         lua_setglobal(L, "loader");
199
200         e->loaded = true;
201         e->reload = false;
202
203         /* new environment table for function call */
204         lua_newtable(L);
205
206         /* register the table globally */
207         lua_pushvalue(L, -1);
208         access_plugin_table(L, e->module, true);
209
210         lua_pushstring(L, e->name);
211
212         if (lua_pcall(L, 2, 0, 0) != 0) {
213                 const char *err = "unknown error";
214
215                 if (lua_isstring(L, -1))
216                         err = lua_tostring(L, -1);
217
218                 fprintf(stderr, "%s", err);
219         }
220 }
221
222 static void
223 free_entry(struct luaplugin_ctx *ctx, struct luaplugin_entry *e)
224 {
225         lua_State *L = ctx->L;
226
227         if (e->loaded && L) {
228                 /* allow the gc to free the module */
229                 lua_pushnil(L);
230                 access_plugin_table(L, e->module, true);
231         }
232         list_del(&e->list);
233         free(e->name);
234         free(e->module);
235         free(e);
236 }
237
238 static void
239 __luaplugin_scan(struct luaplugin_ctx *ctx, int base_len, int rec)
240 {
241         int gl_flags = GLOB_NOESCAPE | GLOB_NOSORT | GLOB_MARK;
242         glob_t gl;
243         int i;
244
245         strncpy(pbuf + base_len, "*.lua", PATH_MAX - base_len);
246         if (glob(pbuf, gl_flags, NULL, &gl) < 0) {
247                 globfree(&gl);
248                 return;
249         }
250
251         for (i = 0; i < gl.gl_pathc; i++) {
252                 const char *entry = gl.gl_pathv[i];
253                 struct luaplugin_entry *e;
254                 struct stat st;
255                 int elen;
256
257                 elen = strlen(entry);
258
259                 /* should not happen */
260                 if ((elen <= base_len) || (strncmp(entry, pbuf, base_len) != 0)) {
261                         fprintf(stderr, "[%s] sanity check failed in %s(%d)!\n", __FILE__, __func__, __LINE__);
262                         continue;
263                 }
264
265                 /* descend into subdirectories */
266                 if (entry[elen - 1] == '/') {
267                         strncpy(pbuf + base_len, entry + base_len, PATH_MAX - base_len);
268                         __luaplugin_scan(ctx, base_len, rec + 1);
269                         pbuf[base_len] = '\0';
270                         continue;
271                 }
272
273                 if (stat(gl.gl_pathv[i], &st))
274                         continue;
275
276                 if ((st.st_mode & S_IFMT) != S_IFREG)
277                         continue;
278
279                 e = find_entry(ctx, entry + base_len, false);
280                 if (!e) {
281                         e = new_entry(ctx, entry, entry + base_len);
282                         list_add_tail(&e->list, &ctx->entries);
283                 }
284                 if (!e)
285                         continue;
286
287                 e->checked = ctx->checked;
288                 e->reload = (e->timestamp < st.st_mtime);
289                 e->timestamp = st.st_mtime;
290         }
291         globfree(&gl);
292 }
293
294 int
295 luaplugin_call(struct luaplugin_entry *e, int narg)
296 {
297         struct luaplugin_ctx *ctx = e->ctx;
298         lua_State *L = ctx->L;
299         const char *func;
300         int ret;
301
302         func = luaL_checkstring(L, -1 - narg);
303
304         /* grab a reference to the plugin's table */
305         access_plugin_table(L, e->module, false);
306         lua_getfield(L, -1, func);
307         if (!lua_isfunction(L, -1)) {
308                 lua_pop(L, narg + 1);
309                 ret = -ENOENT;
310                 goto done;
311         }
312
313         /* replace function name with a ref to the function */
314         lua_replace(L, -3 - narg);
315
316         /* pop the table */
317         lua_pop(L, 1);
318         ret = lua_pcall(L, narg, 0, 0);
319
320         if (ret != 0) {
321                 fprintf(stderr, "%s", lua_tostring(L, -1));
322         }
323
324 done:
325         return ret;
326 }
327
328 void
329 luaplugin_scan(struct luaplugin_ctx *ctx)
330 {
331         struct list_head *tmp, *p;
332
333         sprintf(pbuf, "%s/", ctx->path);
334
335         ctx->checked++;
336         __luaplugin_scan(ctx, strlen(pbuf), 0);
337
338         /* expire old entries */
339         list_for_each_safe(p, tmp, &ctx->entries) {
340                 struct luaplugin_entry *e = container_of(p, struct luaplugin_entry, list);
341                 if (e->checked < ctx->checked)
342                         free_entry(ctx, e);
343                 else if (e->reload)
344                         load_module(ctx, e);
345         }
346 }
347
348 int
349 luaplugin_init(struct luaplugin_ctx *ctx, const char *path)
350 {
351         memset(ctx, 0, sizeof(struct luaplugin_ctx));
352         INIT_LIST_HEAD(&ctx->entries);
353         ctx->path = path;
354
355         ctx->L = luaL_newstate();
356         if (!ctx->L)
357                 return -ENOMEM;
358
359         luaL_openlibs(ctx->L);
360
361         /* disable the module functionality, a plugin is restricted to its own environment */
362         /*
363         lua_pushcfunction(ctx->L, luaplugin_module);
364         lua_setfield(ctx->L, LUA_GLOBALSINDEX, "module");
365         */
366
367         return 0;
368 }
369
370 void
371 luaplugin_done(struct luaplugin_ctx *ctx)
372 {
373         struct list_head *p, *tmp;
374
375         lua_close(ctx->L);
376         ctx->L = NULL;
377
378         list_for_each_safe(p, tmp, &ctx->entries) {
379                 struct luaplugin_entry *e;
380                 e = container_of(p, struct luaplugin_entry, list);
381                 free_entry(ctx, e);
382         }
383 }