libs/lucid-http: use SERVER_ADDR as fallback
[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 }