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