librazor/lua.c
author J. Ali Harlow <ali@juiblex.co.uk>
Fri Oct 17 09:57:19 2014 +0100 (2014-10-17)
changeset 456 bae5adee8c8c
parent 400 eb6f3496b8e5
permissions -rw-r--r--
Add facility to specify razor command when running tests.

This makes it easy to run under eg., valgrind as:

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