librazor/lua.c
author J. Ali Harlow <ali@juiblex.co.uk>
Wed Apr 29 17:00:01 2009 +0100 (2009-04-29)
changeset 361 2523d03a840e
parent 352 4866573c6944
child 368 ea743486ba6f
permissions -rw-r--r--
Add support for preloading lua modules. This is useful both when
providing lua bindings to applications based on librazor and when
producing static binaries using librazor (where otherwise the lua
POSIX library would need to be included as an additional dynamic
object).
     1 /*
     2  * Copyright (C) 2009  J. Ali Harlow <ali@juiblex.co.uk>
     3  *
     4  * This program is free software; you can redistribute it and/or modify
     5  * it under the terms of the GNU General Public License as published by
     6  * the Free Software Foundation; either version 2 of the License, or
     7  * (at your option) any later version.
     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  * You should have received a copy of the GNU General Public License along
    15  * with this program; if not, write to the Free Software Foundation, Inc.,
    16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    17  */
    18 
    19 #include "config.h"
    20 
    21 #include <stdlib.h>
    22 #include <string.h>
    23 #include <stdio.h>
    24 #include <errno.h>
    25 #include <sys/stat.h>
    26 #include <sys/types.h>
    27 #include <lua.h>
    28 #include <lualib.h>
    29 #include <lauxlib.h>
    30 
    31 #include "razor.h"
    32 #include "razor-internal.h"
    33 
    34 #define MAX_ARGS		2
    35 
    36 #define ARG_FLAG_OPT		0x1
    37 #define ARG_FLAG_BYPASS		0x2
    38 
    39 #define ARG(flags, swtch, index, type) \
    40 				((flags) << 26 | (swtch) << 21 | \
    41 				 (index) << 16 | type)
    42 #define ARG_FLAGS(arg)		((unsigned)(arg) >> 26)
    43 #define ARG_SWTCH(arg)		((unsigned)(arg) >> 21 & 0x1F)
    44 #define ARG_INDEX(arg)		((unsigned)(arg) >> 16 & 0x1F)
    45 #define ARG_TYPE(arg)		((unsigned)(arg) & 0xFFFF)
    46 
    47 #define ARG_NONE		0
    48 #define ARG_STRING(n)		ARG(0, 0, n, LUA_TSTRING)
    49 #define ARG_OPT_STRING(n)	ARG(ARG_FLAG_OPT, 0, n, LUA_TSTRING)
    50 #define ARG_BYP_STRING(n, s)	ARG(ARG_FLAG_BYPASS, s, n, LUA_TSTRING)
    51 
    52 struct razor_proxy {
    53 	char *table, *name;
    54 	int result;
    55 	unsigned args[MAX_ARGS];
    56 };
    57 
    58 static struct razor_proxy proxy[] = {
    59 	{ NULL, "dofile", LUA_TNONE, { ARG_STRING(1) } },
    60 	{ NULL, "loadfile", LUA_TNONE, { ARG_OPT_STRING(1) } },
    61 	{ "io", "input", LUA_TNONE, { ARG_OPT_STRING(1) } },
    62 	{ "io", "output", LUA_TNONE, { ARG_OPT_STRING(1) } },
    63 	{ "io", "lines", LUA_TNONE, { ARG_OPT_STRING(1) } },
    64 	{ "io", "open", LUA_TNONE, { ARG_STRING(1) } },
    65 	{ "io", "popen", LUA_TNONE, { ARG_STRING(1) } },
    66 	/*
    67 	 * Note: We do not proxy os.execute
    68 	 * We can't do it properly (there is no way to distinguish filenames
    69 	 * in arguments from any other strings) so it's better not to try.
    70 	 * Scripts that attempt to be portable should use posix.exec[p]
    71 	 */
    72 	{ "os", "remove", LUA_TNONE, { ARG_STRING(1) } },
    73 	{ "os", "rename", LUA_TNONE, { ARG_STRING(1), ARG_STRING(2) } },
    74 	/*
    75 	 * Note: We do not proxy os.tmpname
    76 	 * It should never be used. scripts should use io.tmpfile instead
    77 	 * if the rather limited functionality it provides suffices. The
    78 	 * proper solution is to implement posix.mkstemp
    79 	 */
    80 	{ "posix", "access", LUA_TNONE, { ARG_STRING(1) } },
    81 	{ "posix", "chdir", LUA_TNONE, { ARG_STRING(1) } },
    82 	{ "posix", "chmod", LUA_TNONE, { ARG_STRING(1) } },
    83 	{ "posix", "chown", LUA_TNONE, { ARG_STRING(1) } },
    84 	{ "posix", "dir", LUA_TNONE, { ARG_OPT_STRING(1) } },
    85 	{ "posix", "exec", LUA_TNONE, { ARG_STRING(1) } },
    86 	{ "posix", "execp", LUA_TNONE, { ARG_STRING(1) } },
    87 	{ "posix", "files", LUA_TNONE, { ARG_OPT_STRING(1) } },
    88 	{ "posix", "getcwd", LUA_TSTRING, { ARG_NONE } },
    89 	{ "posix", "glob", LUA_TTABLE, { ARG_STRING(1) } },
    90 	{ "posix", "link", LUA_TNONE, { ARG_BYP_STRING(1, 3), ARG_STRING(2) } },
    91 	{ "posix", "mkdir", LUA_TNONE, { ARG_STRING(1) } },
    92 	{ "posix", "mkfifo", LUA_TNONE, { ARG_STRING(1) } },
    93 	{ "posix", "pathconf", LUA_TNONE, { ARG_STRING(1) } },
    94 	{ "posix", "readlink", LUA_TNONE, { ARG_STRING(1) } },
    95 	{ "posix", "rmdir", LUA_TNONE, { ARG_STRING(1) } },
    96 	{ "posix", "stat", LUA_TNONE, { ARG_STRING(1) } },
    97 	{ "posix", "unlink", LUA_TNONE, { ARG_STRING(1) } },
    98 	{ "posix", "utime", LUA_TNONE, { ARG_STRING(1) } },
    99 };
   100 
   101 static lua_CFunction execp_handler;
   102 
   103 struct razor_lua {
   104 	const char *root;
   105 	lua_CFunction handler[ARRAY_SIZE(proxy)];
   106 };
   107 
   108 static void *alloc_lua(void *user_data, void *ptr, size_t osize, size_t nsize)
   109 {
   110 	if (!nsize) {
   111 		free(ptr);
   112 		return NULL;
   113 	} else
   114 		return realloc(ptr, nsize);
   115 }
   116 
   117 static int proxy_execp(lua_State *L)
   118 {
   119 	int r, n, err, got_eacces = 0;
   120 	char *file = strdup(luaL_checkstring(L, 1));
   121 	char *exe;
   122 	const char *s, *e, *path;
   123 	struct razor_lua *rl;
   124 
   125 	if (*file == '\0' || strchr(file, '/')) {
   126 		r = execp_handler(L);
   127 		free(file);
   128 		return r;
   129 	}
   130 
   131 	(void)lua_getallocf(L, (void **)&rl);
   132 
   133 	path = getenv("PATH");
   134 	if (!path)
   135 		path = ":/bin:/usr/bin";
   136 
   137 	s = path;
   138 	for (;;) {
   139 		e = strchr(s, ':');
   140 		if (!e)
   141 			e = s + strlen(s);
   142 		if (*s == '/') {
   143 			n = strlen(rl->root);
   144 			exe = malloc(n + (e - s) + 1 + strlen(file) + 1);
   145 			memcpy(exe, rl->root, n);
   146 			memcpy(exe + n, s, e - s);
   147 			n += e - s;
   148 			exe[n++] = '/';
   149 			strcpy(exe + n, file);
   150 		} else if (e != s) {
   151 			exe = malloc((e - s) + 1 + strlen(file) + 1);
   152 			memcpy(exe, s, e - s);
   153 			n = e - s;
   154 			exe[n++] = '/';
   155 			strcpy(exe + n, file);
   156 		} else {
   157 			/* Ensure exe contains a slash to avoid PATH search */
   158 			exe = malloc(2 + strlen(file) + 1);
   159 			exe[0] = '.';
   160 			exe[1] = '/';
   161 			strcpy(exe + 2, file);
   162 		}
   163 		lua_pushstring(L, exe);
   164 		lua_replace(L, 1);
   165 		free(exe);
   166 		n = lua_gettop(L);
   167 		r = execp_handler(L);
   168 
   169 		err = lua_tointeger(L, -1);
   170 		switch (err) {
   171 		case EACCES:
   172 			got_eacces = 1;
   173 			/* Fall through */
   174 		case ENOENT:
   175 		case ESTALE:
   176 		case ENOTDIR:
   177 		case ENODEV:
   178 		case ETIMEDOUT:
   179 			lua_pop(L, lua_gettop(L) - n);
   180 			break;
   181 		default:
   182 			free(file);
   183 			return r;
   184 		}
   185 
   186 		if (*e)
   187 			s = e + 1;
   188 		else
   189 			break;
   190 	}
   191 
   192 	if (got_eacces)
   193 		err = EACCES;
   194 
   195 	lua_pushnil(L);
   196 	lua_pushfstring(L, "%s: %s", file, strerror(err));
   197 	lua_pushinteger(L, err);
   198 
   199 	free(file);
   200 	return 3;
   201 }
   202 
   203 static int real2fake(lua_State *L, struct razor_lua *rl, int indx)
   204 {
   205 	const char *path;
   206 
   207 	if (indx < 0)
   208 		indx += lua_gettop(L) + 1;
   209 
   210 	if (lua_type(L, indx) == LUA_TSTRING) {
   211 		path = lua_tostring(L, indx);
   212 		if (path && !strncmp(path, rl->root, strlen(rl->root)) &&
   213 		   (path[strlen(rl->root)] == '/' || !path[strlen(rl->root)])) {
   214 			lua_pushstring(L, path + strlen(rl->root));
   215 			lua_replace(L, indx);
   216 			return 1;
   217 		}
   218 	}
   219 
   220 	return 0;
   221 }
   222 
   223 static int proxy_handler(lua_State *L)
   224 {
   225 	int i, retval, cond;
   226 	unsigned arg;
   227 	const char *path;
   228 	struct razor_lua *rl;
   229 	int method = lua_tointeger(L, lua_upvalueindex(1));
   230 	struct razor_proxy *rp = proxy + method;
   231 
   232 	(void)lua_getallocf(L, (void **)&rl);
   233 
   234 	for(i = 0; i < MAX_ARGS; i++) {
   235 		arg = rp->args[i];
   236 		if (arg == ARG_NONE)
   237 			continue;
   238 		switch (ARG_TYPE(arg)) {
   239 		case LUA_TSTRING:
   240 			if (ARG_FLAGS(arg) & ARG_FLAG_BYPASS)
   241 				cond = !lua_toboolean(L, ARG_SWTCH(arg));
   242 			else
   243 				cond = 1;
   244 			if (ARG_FLAGS(arg) & ARG_FLAG_OPT) {
   245 				if (lua_isstring(L, ARG_INDEX(arg)))
   246 					path = lua_tostring(L, ARG_INDEX(arg));
   247 				else
   248 					path = NULL;
   249 			} else
   250 				path = luaL_checkstring(L, ARG_INDEX(arg));
   251 			if (cond && path && *path == '/') {
   252 				lua_pushfstring(L, "%s%s", rl->root, path);
   253 				lua_replace(L, ARG_INDEX(arg));
   254 			}
   255 			break;
   256 		default:
   257 			lua_pushfstring(L, "razor proxy: Unhandled type (%d)",
   258 					ARG_TYPE(arg));
   259 			lua_error(L);
   260 		}
   261 	}
   262 
   263 	retval = rl->handler[method](L);
   264 
   265 	if (rp->result != LUA_TNONE && !lua_isnil(L, 1)) {
   266 		switch (rp->result) {
   267 		case LUA_TNONE:
   268 			break;
   269 		case LUA_TSTRING:
   270 			real2fake(L, rl, -1);
   271 			break;
   272 		case LUA_TTABLE:
   273 			lua_pushnil(L);
   274 			while (lua_next(L, -2)) {
   275 				if (real2fake(L, rl, -1)) {
   276 					i = lua_tonumber(L, -2);
   277 					lua_rawseti(L, -3, i);
   278 				} else
   279 					lua_pop(L, 1);
   280 			}
   281 			break;
   282 		default:
   283 			lua_pushfstring(L, "razor proxy: Unhandled type (%d)",
   284 					rp->result);
   285 			lua_error(L);
   286 		}
   287 	}
   288 
   289 	return retval;
   290 }
   291 
   292 struct razor_lua_loader {
   293 	uint32_t name;
   294 	lua_CFunction func;
   295 };
   296 
   297 static struct razor_preload {
   298 	int init;
   299 	struct hashtable modules;
   300 	struct array name_pool;
   301 	struct array loaders;
   302 } razor_preload = {0};
   303 
   304 RAZOR_EXPORT void razor_set_lua_loader(const char *modname, void (*loader)())
   305 {
   306 	uint32_t name;
   307 	struct razor_lua_loader *ploader, *end;
   308 
   309 	if (!razor_preload.init) {
   310 		razor_preload.init = 1;
   311 		array_init(&razor_preload.name_pool);
   312 		array_init(&razor_preload.loaders);
   313 		hashtable_init(&razor_preload.modules,
   314 			       &razor_preload.name_pool);
   315 	}
   316 
   317 	name = hashtable_tokenize(&razor_preload.modules, modname);
   318 
   319 	end = razor_preload.loaders.data + razor_preload.loaders.size;
   320 	for(ploader = razor_preload.loaders.data; ploader < end; ploader++)
   321 		if (ploader->name == name) {
   322 			ploader->func = loader;
   323 			return;
   324 		}
   325 
   326 	ploader = array_add(&razor_preload.loaders, sizeof(*ploader));
   327 	ploader->name = name;
   328 	ploader->func = loader;
   329 }
   330 
   331 static void razor_lua_preload(lua_State *L)
   332 {
   333 	struct razor_lua_loader *ploader, *end;
   334 
   335 	if (!razor_preload.init)
   336 		return;
   337 
   338 	lua_getfield(L, LUA_GLOBALSINDEX, "package");
   339 	lua_getfield(L, -1, "preload");
   340 	lua_remove(L, -2);
   341 
   342 	end = razor_preload.loaders.data + razor_preload.loaders.size;
   343 	for(ploader = razor_preload.loaders.data; ploader < end; ploader++) {
   344 		lua_pushcfunction(L, ploader->func);
   345 		lua_setfield(L, -2,
   346 			     razor_preload.name_pool.data + ploader->name);
   347 	}
   348 
   349 	lua_pop(L, 1);
   350 }
   351 
   352 int run_lua_script(const char *root, const char *name, const char *body,
   353 		   ssize_t len)
   354 {
   355 	int i, n;
   356 	lua_State *L;
   357 	struct razor_lua rl;
   358 
   359 	if (root && strcmp(root, "/"))
   360 		rl.root = root;
   361 	else
   362 		rl.root = NULL;
   363 
   364 	L = lua_newstate(alloc_lua, &rl);
   365 	luaL_openlibs(L);
   366 	razor_lua_preload(L);
   367 	lua_getglobal(L, "require");
   368 	lua_pushstring(L, "posix");
   369 	if (lua_pcall(L, 1, 1, 0)) {
   370 		fprintf(stderr, "lua posix: %s\n", lua_tostring(L, -1));
   371 		lua_pop(L, 1);
   372 		lua_close(L);
   373 		return -1;
   374 	}
   375 
   376 	if (rl.root)
   377 		for(i = 0; i < ARRAY_SIZE(proxy); i++) {
   378 			if (proxy[i].table) {
   379 				lua_getglobal(L, proxy[i].table);
   380 				n = lua_gettop(L);
   381 			} else
   382 				n = LUA_GLOBALSINDEX;
   383 			lua_getfield(L, n, proxy[i].name);
   384 			rl.handler[i] = lua_tocfunction(L, -1);
   385 			lua_getfenv(L, -1);
   386 			lua_remove(L, -2);
   387 			if (proxy[i].table &&
   388 			    !strcmp(proxy[i].table, "posix") &&
   389 			    !strcmp(proxy[i].name, "execp")) {
   390 				execp_handler = rl.handler[i];
   391 				rl.handler[i] = proxy_execp;
   392 			}
   393 			lua_pushinteger(L, i);
   394 			lua_pushcclosure(L, proxy_handler, 1);
   395 			lua_pushvalue(L, -2);
   396 			lua_setfenv(L, -2);
   397 			lua_setfield(L, n, proxy[i].name);
   398 			if (proxy[i].table)
   399 				lua_pop(L, 2);
   400 			else
   401 				lua_pop(L, 1);
   402 		}
   403 
   404 	if (len < 0)
   405 		len = strlen(body);
   406 
   407 	if (luaL_loadbuffer(L, body, len, name)) {
   408 		fprintf(stderr, "failed to load lua script\n");
   409 		lua_close(L);
   410 		return -1;
   411 	}
   412 
   413 	if (lua_pcall(L, 0, 0, 0)) {
   414 		fprintf(stderr, "lua script failed: %s\n", lua_tostring(L, -1));
   415 		lua_pop(L, 1);
   416 		lua_close(L);
   417 		return -1;
   418 	}
   419 
   420 	lua_close(L);
   421 	return 0;
   422 }