librazor/lua.c
author J. Ali Harlow <ali@juiblex.co.uk>
Sat Feb 11 09:30:23 2012 +0000 (2012-02-11)
changeset 420 a7a1be2fed47
parent 376 d15a16347c77
child 442 c4bcba8023a9
permissions -rw-r--r--
Start 0.5.4
     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 #ifdef ESTALE
   176 		case ESTALE:
   177 #endif
   178 		case ENOTDIR:
   179 		case ENODEV:
   180 #ifdef ETIMEDOUT
   181 		case ETIMEDOUT:
   182 #endif
   183 			lua_pop(L, lua_gettop(L) - n);
   184 			break;
   185 		default:
   186 			free(file);
   187 			return r;
   188 		}
   189 
   190 		if (*e)
   191 			s = e + 1;
   192 		else
   193 			break;
   194 	}
   195 
   196 	if (got_eacces)
   197 		err = EACCES;
   198 
   199 	lua_pushnil(L);
   200 	lua_pushfstring(L, "%s: %s", file, strerror(err));
   201 	lua_pushinteger(L, err);
   202 
   203 	free(file);
   204 	return 3;
   205 }
   206 
   207 static int real2fake(lua_State *L, struct razor_lua *rl, int indx)
   208 {
   209 	const char *path;
   210 
   211 	if (indx < 0)
   212 		indx += lua_gettop(L) + 1;
   213 
   214 	if (lua_type(L, indx) == LUA_TSTRING) {
   215 		path = lua_tostring(L, indx);
   216 		if (path && !strncmp(path, rl->root, strlen(rl->root)) &&
   217 		   (path[strlen(rl->root)] == '/' || !path[strlen(rl->root)])) {
   218 			lua_pushstring(L, path + strlen(rl->root));
   219 			lua_replace(L, indx);
   220 			return 1;
   221 		}
   222 	}
   223 
   224 	return 0;
   225 }
   226 
   227 static int proxy_handler(lua_State *L)
   228 {
   229 	int i, retval, cond;
   230 	unsigned arg;
   231 	const char *path;
   232 	struct razor_lua *rl;
   233 	int method = lua_tointeger(L, lua_upvalueindex(1));
   234 	struct razor_proxy *rp = proxy + method;
   235 
   236 	(void)lua_getallocf(L, (void **)&rl);
   237 
   238 	for(i = 0; i < MAX_ARGS; i++) {
   239 		arg = rp->args[i];
   240 		if (arg == ARG_NONE)
   241 			continue;
   242 		switch (ARG_TYPE(arg)) {
   243 		case LUA_TSTRING:
   244 			if (ARG_FLAGS(arg) & ARG_FLAG_BYPASS)
   245 				cond = !lua_toboolean(L, ARG_SWTCH(arg));
   246 			else
   247 				cond = 1;
   248 			if (ARG_FLAGS(arg) & ARG_FLAG_OPT) {
   249 				if (lua_isstring(L, ARG_INDEX(arg)))
   250 					path = lua_tostring(L, ARG_INDEX(arg));
   251 				else
   252 					path = NULL;
   253 			} else
   254 				path = luaL_checkstring(L, ARG_INDEX(arg));
   255 			if (cond && path && *path == '/') {
   256 				lua_pushfstring(L, "%s%s", rl->root, path);
   257 				lua_replace(L, ARG_INDEX(arg));
   258 			}
   259 			break;
   260 		default:
   261 			lua_pushfstring(L, "razor proxy: Unhandled type (%d)",
   262 					ARG_TYPE(arg));
   263 			lua_error(L);
   264 		}
   265 	}
   266 
   267 	retval = rl->handler[method](L);
   268 
   269 	if (rp->result != LUA_TNONE && !lua_isnil(L, 1)) {
   270 		switch (rp->result) {
   271 		case LUA_TNONE:
   272 			break;
   273 		case LUA_TSTRING:
   274 			real2fake(L, rl, -1);
   275 			break;
   276 		case LUA_TTABLE:
   277 			lua_pushnil(L);
   278 			while (lua_next(L, -2)) {
   279 				if (real2fake(L, rl, -1)) {
   280 					i = lua_tonumber(L, -2);
   281 					lua_rawseti(L, -3, i);
   282 				} else
   283 					lua_pop(L, 1);
   284 			}
   285 			break;
   286 		default:
   287 			lua_pushfstring(L, "razor proxy: Unhandled type (%d)",
   288 					rp->result);
   289 			lua_error(L);
   290 		}
   291 	}
   292 
   293 	return retval;
   294 }
   295 
   296 struct razor_lua_loader {
   297 	uint32_t name;
   298 	lua_CFunction func;
   299 };
   300 
   301 static struct razor_preload {
   302 	int init;
   303 	struct hashtable modules;
   304 	struct array name_pool;
   305 	struct array loaders;
   306 } razor_preload = {0};
   307 
   308 RAZOR_EXPORT void razor_set_lua_loader(const char *modname, void (*loader)())
   309 {
   310 	uint32_t name;
   311 	struct razor_lua_loader *ploader, *end;
   312 
   313 	if (!razor_preload.init) {
   314 		razor_preload.init = 1;
   315 		array_init(&razor_preload.name_pool);
   316 		array_init(&razor_preload.loaders);
   317 		hashtable_init(&razor_preload.modules,
   318 			       &razor_preload.name_pool);
   319 	}
   320 
   321 	name = hashtable_tokenize(&razor_preload.modules, modname);
   322 
   323 	end = razor_preload.loaders.data + razor_preload.loaders.size;
   324 	for(ploader = razor_preload.loaders.data; ploader < end; ploader++)
   325 		if (ploader->name == name) {
   326 			ploader->func = loader;
   327 			return;
   328 		}
   329 
   330 	ploader = array_add(&razor_preload.loaders, sizeof(*ploader));
   331 	ploader->name = name;
   332 	ploader->func = loader;
   333 }
   334 
   335 RAZOR_EXPORT void (*razor_get_lua_loader(const char *modname))()
   336 {
   337 	uint32_t name;
   338 	struct razor_lua_loader *ploader, *end;
   339 
   340 	if (!razor_preload.init)
   341 		return 0;
   342 
   343 	name = hashtable_lookup(&razor_preload.modules, modname);
   344 
   345 	end = razor_preload.loaders.data + razor_preload.loaders.size;
   346 	for(ploader = razor_preload.loaders.data; ploader < end; ploader++)
   347 		if (ploader->name == name)
   348 			return ploader->func;
   349 
   350 	return 0;
   351 }
   352 
   353 static void razor_lua_preload(lua_State *L)
   354 {
   355 	struct razor_lua_loader *ploader, *end;
   356 
   357 	if (!razor_preload.init)
   358 		return;
   359 
   360 	lua_getfield(L, LUA_GLOBALSINDEX, "package");
   361 	lua_getfield(L, -1, "preload");
   362 	lua_remove(L, -2);
   363 
   364 	end = razor_preload.loaders.data + razor_preload.loaders.size;
   365 	for(ploader = razor_preload.loaders.data; ploader < end; ploader++) {
   366 		lua_pushcfunction(L, ploader->func);
   367 		lua_setfield(L, -2,
   368 			     razor_preload.name_pool.data + ploader->name);
   369 	}
   370 
   371 	lua_pop(L, 1);
   372 }
   373 
   374 int run_lua_script(const char *root, const char *name, const char *body,
   375 		   ssize_t len, int arg1)
   376 {
   377 	int i, n;
   378 	lua_State *L;
   379 	struct razor_lua rl;
   380 
   381 	if (root && strcmp(root, "/"))
   382 		rl.root = root;
   383 	else
   384 		rl.root = NULL;
   385 
   386 	L = lua_newstate(alloc_lua, &rl);
   387 	luaL_openlibs(L);
   388 	razor_lua_preload(L);
   389 	lua_getglobal(L, "require");
   390 	lua_pushstring(L, "posix");
   391 	if (lua_pcall(L, 1, 1, 0)) {
   392 		fprintf(stderr, "lua posix: %s\n", lua_tostring(L, -1));
   393 		lua_pop(L, 1);
   394 		lua_close(L);
   395 		return -1;
   396 	}
   397 
   398 	if (rl.root)
   399 		for(i = 0; i < ARRAY_SIZE(proxy); i++) {
   400 			if (proxy[i].table) {
   401 				lua_getglobal(L, proxy[i].table);
   402 				n = lua_gettop(L);
   403 			} else
   404 				n = LUA_GLOBALSINDEX;
   405 			lua_getfield(L, n, proxy[i].name);
   406 			rl.handler[i] = lua_tocfunction(L, -1);
   407 			lua_getfenv(L, -1);
   408 			lua_remove(L, -2);
   409 			if (proxy[i].table &&
   410 			    !strcmp(proxy[i].table, "posix") &&
   411 			    !strcmp(proxy[i].name, "execp")) {
   412 				execp_handler = rl.handler[i];
   413 				rl.handler[i] = proxy_execp;
   414 			}
   415 			lua_pushinteger(L, i);
   416 			lua_pushcclosure(L, proxy_handler, 1);
   417 			lua_pushvalue(L, -2);
   418 			lua_setfenv(L, -2);
   419 			lua_setfield(L, n, proxy[i].name);
   420 			if (proxy[i].table)
   421 				lua_pop(L, 2);
   422 			else
   423 				lua_pop(L, 1);
   424 		}
   425 
   426 	if (len < 0)
   427 		len = strlen(body);
   428 
   429 	if (luaL_loadbuffer(L, body, len, name)) {
   430 		fprintf(stderr, "failed to load lua script\n");
   431 		lua_close(L);
   432 		return -1;
   433 	}
   434 
   435 	lua_newtable(L);
   436 	lua_pushvalue(L, LUA_GLOBALSINDEX);
   437 	lua_pushliteral(L, "arg");
   438 	lua_pushvalue(L, -3);
   439 	lua_rawset(L, -3);
   440 	lua_pop(L, 1);
   441 	lua_pushliteral(L, "<lua>");
   442 	lua_rawseti(L, -2, 1);
   443 	if (arg1 >= 0) {
   444 		lua_pushinteger(L, arg1);
   445 		lua_rawseti(L, -2, 2);
   446 	}
   447 	lua_pop(L, 1);
   448 
   449 	if (lua_pcall(L, 0, 0, 0)) {
   450 		fprintf(stderr, "lua script failed: %s\n", lua_tostring(L, -1));
   451 		lua_pop(L, 1);
   452 		lua_close(L);
   453 		return -1;
   454 	}
   455 
   456 	lua_close(L);
   457 	return 0;
   458 }