librazor/lua.c
author J. Ali Harlow <ali@juiblex.co.uk>
Sat Aug 23 11:13:48 2014 +0100 (2014-08-23)
changeset 440 48204dea0b9f
parent 376 d15a16347c77
child 442 c4bcba8023a9
permissions -rw-r--r--
Remove INTLLIBS from librazor_la_LIBADD.

This partially reverts 611c84a3f4b4538a65d186050608c17adbf17770.
It's not clear what motivated the initial inclusion of INTLLIBS
here since the net effect is only seen in librazor.la and not
in razor.pc and librazor.la is not normally packaged. Certainly
neither the static nor the dynamic versions of librazor currently
use libintl. At best this would cause the linker to search a
static libintl for undefined symbols without finding any; at worse
it causes a static build of plover using librazor.la to fail if
no static version of libintl is installed.
     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 }