librazor/lua.c
author J. Ali Harlow <ali@juiblex.co.uk>
Wed Jul 08 22:14:16 2009 +0100 (2009-07-08)
changeset 377 5549419824b4
parent 368 ea743486ba6f
child 400 eb6f3496b8e5
permissions -rw-r--r--
Fix bugs when removing files and directories
     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 RAZOR_EXPORT void (*razor_get_lua_loader(const char *modname))()
   332 {
   333 	uint32_t name;
   334 	struct razor_lua_loader *ploader, *end;
   335 
   336 	if (!razor_preload.init)
   337 		return 0;
   338 
   339 	name = hashtable_lookup(&razor_preload.modules, modname);
   340 
   341 	end = razor_preload.loaders.data + razor_preload.loaders.size;
   342 	for(ploader = razor_preload.loaders.data; ploader < end; ploader++)
   343 		if (ploader->name == name)
   344 			return ploader->func;
   345 
   346 	return 0;
   347 }
   348 
   349 static void razor_lua_preload(lua_State *L)
   350 {
   351 	struct razor_lua_loader *ploader, *end;
   352 
   353 	if (!razor_preload.init)
   354 		return;
   355 
   356 	lua_getfield(L, LUA_GLOBALSINDEX, "package");
   357 	lua_getfield(L, -1, "preload");
   358 	lua_remove(L, -2);
   359 
   360 	end = razor_preload.loaders.data + razor_preload.loaders.size;
   361 	for(ploader = razor_preload.loaders.data; ploader < end; ploader++) {
   362 		lua_pushcfunction(L, ploader->func);
   363 		lua_setfield(L, -2,
   364 			     razor_preload.name_pool.data + ploader->name);
   365 	}
   366 
   367 	lua_pop(L, 1);
   368 }
   369 
   370 int run_lua_script(const char *root, const char *name, const char *body,
   371 		   ssize_t len, int arg1)
   372 {
   373 	int i, n;
   374 	lua_State *L;
   375 	struct razor_lua rl;
   376 
   377 	if (root && strcmp(root, "/"))
   378 		rl.root = root;
   379 	else
   380 		rl.root = NULL;
   381 
   382 	L = lua_newstate(alloc_lua, &rl);
   383 	luaL_openlibs(L);
   384 	razor_lua_preload(L);
   385 	lua_getglobal(L, "require");
   386 	lua_pushstring(L, "posix");
   387 	if (lua_pcall(L, 1, 1, 0)) {
   388 		fprintf(stderr, "lua posix: %s\n", lua_tostring(L, -1));
   389 		lua_pop(L, 1);
   390 		lua_close(L);
   391 		return -1;
   392 	}
   393 
   394 	if (rl.root)
   395 		for(i = 0; i < ARRAY_SIZE(proxy); i++) {
   396 			if (proxy[i].table) {
   397 				lua_getglobal(L, proxy[i].table);
   398 				n = lua_gettop(L);
   399 			} else
   400 				n = LUA_GLOBALSINDEX;
   401 			lua_getfield(L, n, proxy[i].name);
   402 			rl.handler[i] = lua_tocfunction(L, -1);
   403 			lua_getfenv(L, -1);
   404 			lua_remove(L, -2);
   405 			if (proxy[i].table &&
   406 			    !strcmp(proxy[i].table, "posix") &&
   407 			    !strcmp(proxy[i].name, "execp")) {
   408 				execp_handler = rl.handler[i];
   409 				rl.handler[i] = proxy_execp;
   410 			}
   411 			lua_pushinteger(L, i);
   412 			lua_pushcclosure(L, proxy_handler, 1);
   413 			lua_pushvalue(L, -2);
   414 			lua_setfenv(L, -2);
   415 			lua_setfield(L, n, proxy[i].name);
   416 			if (proxy[i].table)
   417 				lua_pop(L, 2);
   418 			else
   419 				lua_pop(L, 1);
   420 		}
   421 
   422 	if (len < 0)
   423 		len = strlen(body);
   424 
   425 	if (luaL_loadbuffer(L, body, len, name)) {
   426 		fprintf(stderr, "failed to load lua script\n");
   427 		lua_close(L);
   428 		return -1;
   429 	}
   430 
   431 	lua_newtable(L);
   432 	lua_pushvalue(L, LUA_GLOBALSINDEX);
   433 	lua_pushliteral(L, "arg");
   434 	lua_pushvalue(L, -3);
   435 	lua_rawset(L, -3);
   436 	lua_pop(L, 1);
   437 	lua_pushliteral(L, "<lua>");
   438 	lua_rawseti(L, -2, 1);
   439 	if (arg1 >= 0) {
   440 		lua_pushinteger(L, arg1);
   441 		lua_rawseti(L, -2, 2);
   442 	}
   443 	lua_pop(L, 1);
   444 
   445 	if (lua_pcall(L, 0, 0, 0)) {
   446 		fprintf(stderr, "lua script failed: %s\n", lua_tostring(L, -1));
   447 		lua_pop(L, 1);
   448 		lua_close(L);
   449 		return -1;
   450 	}
   451 
   452 	lua_close(L);
   453 	return 0;
   454 }