librazor/lua.c
author J. Ali Harlow <ali@juiblex.co.uk>
Wed Apr 29 17:00:01 2009 +0100 (2009-04-29)
changeset 361 2523d03a840e
parent 352 4866573c6944
child 368 ea743486ba6f
permissions -rw-r--r--
Add support for preloading lua modules. This is useful both when
providing lua bindings to applications based on librazor and when
producing static binaries using librazor (where otherwise the lua
POSIX library would need to be included as an additional dynamic
object).
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@361
   331
static void razor_lua_preload(lua_State *L)
ali@361
   332
{
ali@361
   333
	struct razor_lua_loader *ploader, *end;
ali@361
   334
ali@361
   335
	if (!razor_preload.init)
ali@361
   336
		return;
ali@361
   337
ali@361
   338
	lua_getfield(L, LUA_GLOBALSINDEX, "package");
ali@361
   339
	lua_getfield(L, -1, "preload");
ali@361
   340
	lua_remove(L, -2);
ali@361
   341
ali@361
   342
	end = razor_preload.loaders.data + razor_preload.loaders.size;
ali@361
   343
	for(ploader = razor_preload.loaders.data; ploader < end; ploader++) {
ali@361
   344
		lua_pushcfunction(L, ploader->func);
ali@361
   345
		lua_setfield(L, -2,
ali@361
   346
			     razor_preload.name_pool.data + ploader->name);
ali@361
   347
	}
ali@361
   348
ali@361
   349
	lua_pop(L, 1);
ali@361
   350
}
ali@361
   351
ali@352
   352
int run_lua_script(const char *root, const char *name, const char *body,
ali@352
   353
		   ssize_t len)
ali@352
   354
{
ali@352
   355
	int i, n;
ali@352
   356
	lua_State *L;
ali@352
   357
	struct razor_lua rl;
ali@352
   358
ali@352
   359
	if (root && strcmp(root, "/"))
ali@352
   360
		rl.root = root;
ali@352
   361
	else
ali@352
   362
		rl.root = NULL;
ali@352
   363
ali@352
   364
	L = lua_newstate(alloc_lua, &rl);
ali@352
   365
	luaL_openlibs(L);
ali@361
   366
	razor_lua_preload(L);
ali@352
   367
	lua_getglobal(L, "require");
ali@352
   368
	lua_pushstring(L, "posix");
ali@352
   369
	if (lua_pcall(L, 1, 1, 0)) {
ali@352
   370
		fprintf(stderr, "lua posix: %s\n", lua_tostring(L, -1));
ali@352
   371
		lua_pop(L, 1);
ali@352
   372
		lua_close(L);
ali@352
   373
		return -1;
ali@352
   374
	}
ali@352
   375
ali@352
   376
	if (rl.root)
ali@352
   377
		for(i = 0; i < ARRAY_SIZE(proxy); i++) {
ali@352
   378
			if (proxy[i].table) {
ali@352
   379
				lua_getglobal(L, proxy[i].table);
ali@352
   380
				n = lua_gettop(L);
ali@352
   381
			} else
ali@352
   382
				n = LUA_GLOBALSINDEX;
ali@352
   383
			lua_getfield(L, n, proxy[i].name);
ali@352
   384
			rl.handler[i] = lua_tocfunction(L, -1);
ali@352
   385
			lua_getfenv(L, -1);
ali@352
   386
			lua_remove(L, -2);
ali@352
   387
			if (proxy[i].table &&
ali@352
   388
			    !strcmp(proxy[i].table, "posix") &&
ali@352
   389
			    !strcmp(proxy[i].name, "execp")) {
ali@352
   390
				execp_handler = rl.handler[i];
ali@352
   391
				rl.handler[i] = proxy_execp;
ali@352
   392
			}
ali@352
   393
			lua_pushinteger(L, i);
ali@352
   394
			lua_pushcclosure(L, proxy_handler, 1);
ali@352
   395
			lua_pushvalue(L, -2);
ali@352
   396
			lua_setfenv(L, -2);
ali@352
   397
			lua_setfield(L, n, proxy[i].name);
ali@352
   398
			if (proxy[i].table)
ali@352
   399
				lua_pop(L, 2);
ali@352
   400
			else
ali@352
   401
				lua_pop(L, 1);
ali@352
   402
		}
ali@352
   403
ali@352
   404
	if (len < 0)
ali@352
   405
		len = strlen(body);
ali@352
   406
ali@352
   407
	if (luaL_loadbuffer(L, body, len, name)) {
ali@352
   408
		fprintf(stderr, "failed to load lua script\n");
ali@352
   409
		lua_close(L);
ali@352
   410
		return -1;
ali@352
   411
	}
ali@352
   412
ali@352
   413
	if (lua_pcall(L, 0, 0, 0)) {
ali@352
   414
		fprintf(stderr, "lua script failed: %s\n", lua_tostring(L, -1));
ali@352
   415
		lua_pop(L, 1);
ali@352
   416
		lua_close(L);
ali@352
   417
		return -1;
ali@352
   418
	}
ali@352
   419
ali@352
   420
	lua_close(L);
ali@352
   421
	return 0;
ali@352
   422
}