/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * External Application Invoker for Lua
 *
 * 2021/12/22
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <poll.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/inotify.h>
#include <sched.h>
#include <regex.h>
#include <glob.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

#define INVOKER_OUTPUT     0x01
#define INVOKER_NOWAIT     0x02
#define INVOKER_BUFSIZ     0x04
#define INVOKER_NOSTDIO    0x08
#define INVOKER_TRIMEND    0x10
#define INVOKER_CLOSEFD    0x20
#define INVOKER_NOFORK     0x40
#define INVOKER_LOWPRI     0x80

#define INVOKER_MAX_ARGS   48
#define INVOKER_BUFSIZE    16384
extern int luaopen_invoker(lua_State * L);
static pid_t invoke_args(int flags, char * args[],
	int * pout, int pipeSize) __attribute__((noinline));
static int global_urandfd; /* file descriptor for /dev/urandom */

#ifdef USE_PRIVATE_ISINTEGER
static int lua_isinteger(lua_State * L, int idx)
{
	lua_Number n_lua;
	lua_Integer i_lua;

	if (lua_isnumber(L, idx) == 0)
		return 0;
	n_lua = lua_tonumber(L, idx);
	i_lua = lua_tointeger(L, idx);
	if (n_lua == (lua_Number) i_lua)
		return 1;
	return 0;
}
#endif

static int setclear_cloexec(int pfd, int doset)
{
	int ret, flag;
	int error = 0;

	ret = fcntl(pfd, F_GETFD, 0);
	if (ret == -1) {
		error = errno;
err0:
		fprintf(stderr, "Error, failed to set/get fd flags for %d: %s\n",
			pfd, strerror(error));
		fflush(stderr);
		return -1;
	}

	flag = ret;
	if (doset)
		flag |= FD_CLOEXEC;
	else
		flag &= ~FD_CLOEXEC;
	if (ret == flag)
		return 0;

	ret = fcntl(pfd, F_SETFD, flag);
	if (ret == -1) {
		error = errno;
		goto err0;
	}
	return 0;
}

static int setclear_nonblock(int pfd, int nonblock)
{
	int ret, flag;
	int error = 0;

	ret = fcntl(pfd, F_GETFL, 0);
	if (ret == -1) {
		error = errno;
err0:
		fprintf(stderr, "Error, failed to set/get status flags for %d: %s\n",
			pfd, strerror(error));
		fflush(stderr);
		return -1;
	}

	flag = ret;
	if (nonblock)
		flag |= O_NONBLOCK;
	else
		flag &= ~O_NONBLOCK;
	if (ret == flag)
		return 0;

	ret = fcntl(pfd, F_SETFL, flag);
	if (ret == -1) {
		error = errno;
		goto err0;
	}
	return 0;
}

static int set_pipe_size(int pfd, int psize)
{
	int error = 0;
	int ret, cursize;

	ret = fcntl(pfd, F_GETPIPE_SZ, 0);
	if (ret == -1) {
		error = errno;
err0:
		fprintf(stderr, "Error, failed to get/set pipe size for %d: %s\n",
			pfd, strerror(error));
		fflush(stderr);
		errno = error;
		return -1;
	}

	cursize = ret;
	if (psize == 0)
		return cursize;
	if (psize < 0 || psize > (8 * 1024 * 1024)) {
		fprintf(stderr, "Error, invalid new pipe size: %d\n", psize);
		fflush(stderr);
		errno = EINVAL;
		return -1;
	}

	if (psize < cursize)
		return cursize;
	ret = fcntl(pfd, F_SETPIPE_SZ, psize);
	if (ret == -1) {
		error = errno;
		goto err0;
	}

	return psize;
}

pid_t invoke_args(int flags, char * args[], int * pout, int pipeSize)
{
	pid_t pid;
	int pfds[2], ret;

	pfds[0] = -1;
	pfds[1] = -1;

	if (flags & INVOKER_NOFORK)
		flags &= ~INVOKER_OUTPUT;

	if (flags & INVOKER_OUTPUT) {
		ret = pipe2(pfds, O_CLOEXEC);
		if (ret == -1) {
			fprintf(stderr, "Error, failed to create pipes: %s\n",
				strerror(errno));
			fflush(stderr);
			return -1;
		}

		set_pipe_size(pfds[1], pipeSize);
	}

	pid = (flags & INVOKER_NOFORK) ? 0 : fork();
	if (pid == -1) {
		int error = errno;
		if (pfds[0] != -1) {
			close(pfds[0]);
			close(pfds[1]);
		}
		fprintf(stderr, "Error, failed to fork child process: %s\n",
			strerror(error));
		fflush(stderr);
		return -1;
	}

	if (pid == 0) {
		if (flags & INVOKER_NOSTDIO) {
			int nfd = open("/dev/null", O_RDWR);
			if (nfd != -1) {
				ret = 0;
				if (nfd != 0)
					ret = dup2(nfd, 0);
				if (ret != -1 && nfd != 1)
					ret = dup2(nfd, 1);
				if (ret != -1 && nfd != 2)
					ret = dup2(nfd, 2);
				if (ret == -1) {
					fprintf(stderr, "Error, failed to replace stdio: %s\n",
						strerror(errno));
					fflush(stderr);
				}
				if (nfd > 2)
					close(nfd);
				setclear_cloexec(0, 0);
				setclear_cloexec(1, 0);
				setclear_cloexec(2, 0);
			} else {
				fprintf(stderr, "Error, failed to open /dev/null: %s\n",
					strerror(errno));
				fflush(stderr);
			}
		}

		if (pfds[1] != -1) {
			close(pfds[0]);
			if (pfds[1] != STDOUT_FILENO) {
				ret = dup2(pfds[1], STDOUT_FILENO);
				if (ret == -1) {
					fprintf(stderr, "Error, failed replace stdout: %s\n",
						strerror(errno));
					fflush(stderr);
					_exit(90);
				}
				close(pfds[1]);
			}
			setclear_cloexec(STDOUT_FILENO, 0);
			setclear_nonblock(STDOUT_FILENO, 0);
		}

		if (flags & INVOKER_CLOSEFD) {
			int maxfd, fd;
			maxfd = (int) sysconf(_SC_OPEN_MAX);
			if (maxfd <= 0x2)
				maxfd = 512;
			/* close unused file descriptors for new process */
			for (fd = 3; fd < maxfd; ++fd)
				close(fd);
		}

		/* run child process with very low priority */
		if (flags & INVOKER_LOWPRI) {
			int nval;
			struct sched_param param;

			errno = 0;
			nval = nice(0);
			if (errno == 0 && nval < 19)
				nice(19 - nval);
			param.sched_priority = 0;
			sched_setscheduler(0, SCHED_IDLE, &param);
		}

		if (args[0][0] == '/')
			execv(args[0], args); /* absolute path given, use `execv */
		else
			execvp(args[0], args);
		fprintf(stderr, "Error, failed invoke %s: %s\n",
			args[0], strerror(errno));
		fflush(stderr);
		_exit(91);
	}

	if (pfds[0] != -1) {
		*pout = pfds[0];
		close(pfds[1]);
	}
	return pid;
}

static void invoke_free_args(char * argv[])
{
	int idx;
	for (idx = 0; idx < INVOKER_MAX_ARGS; ++idx) {
		if (argv[idx] != NULL) {
			free(argv[idx]);
			argv[idx] = NULL;
		}
	}
}

static int invoke_app(lua_State * L)
{
	pid_t pid;
	int ntop, idx, argc;
	int rfd, flags, eval, bufsize;
	char * args[INVOKER_MAX_ARGS + 1];

	rfd = -1;
	eval = 0;
	bufsize = INVOKER_BUFSIZE;
	/* check lua stack slot space */
	if (lua_checkstack(L, 3) == 0)
		return 0;

	/* get the number of arguments */
	ntop = lua_gettop(L);
	if (ntop <= 1) {
		lua_pushboolean(L, 0);
		lua_pushfstring(L, "Error, invalid number of argument: %d", ntop);
		return 2;
	}

	/* get the invocation flags */
	flags = lua_isinteger(L, 1) ? lua_tointeger(L, 1) : -1;
	if (flags < 0) {
		lua_pushboolean(L, 0);
		lua_pushfstring(L, "Error, invalid flags: %d", flags);
		return 2;
	}

	/* determine buffer size */
	if (flags & INVOKER_BUFSIZ) {
		bufsize = ((flags >> 16) & 0x7FF) + 1;
		bufsize <<= 12; /* bufsize *= 4096; */
	}

	argc = 0;
	/* clear the argument array */
	for (idx = 0; idx <= INVOKER_MAX_ARGS; ++idx)
		args[idx] = NULL;

	if (lua_isstring(L, 2) != 0) {
		for (idx = 2; idx <= ntop; ++idx) {
			const char * arg;
			if (lua_isstring(L, idx) == 0)
				break;

			arg = lua_tolstring(L, idx, NULL);
			if (arg == NULL)
				break;

			args[argc] = strdup(arg);
			if (args[argc] == NULL) {
				fprintf(stderr, "Error, system out of memory: %s\n",
					strerror(errno));
				fflush(stderr);
				break;
			}

			argc++;
			if (argc >= INVOKER_MAX_ARGS)
				break;
		}
	} else if (lua_istable(L, 2) != 0) {
		for (idx = 1; idx < INVOKER_MAX_ARGS; ++idx) {
			const char * arg;
			lua_pushinteger(L, idx);
			lua_gettable(L, 2);

			if (lua_isstring(L, -1) == 0)
				break;

			arg = lua_tolstring(L, -1, NULL);
			if (arg == NULL)
				break;

			args[argc] = strdup(arg);
			if (args[argc] == NULL) {
				fprintf(stderr, "Error, strdup(%s) has failed: %s\n",
					arg, strerror(errno));
				fflush(stderr);
				break;
			}

			argc++;
			if (argc >= INVOKER_MAX_ARGS)
				break;
		}

		/* clear any extra stack values */
		if (lua_gettop(L) != ntop)
			lua_settop(L, ntop);
	} else {
		lua_pushboolean(L, 0);
		lua_pushfstring(L, "Error, invalid type for invoke: %d", lua_type(L, 2));
		return 2;
	}

	if (argc == 0) {
		lua_pushboolean(L, 0);
		lua_pushstring(L, "Error, no argument given for invoke");
		return 2;
	}

	pid = invoke_args(flags, args, &rfd, bufsize);
	if (pid == -1) {
		lua_pushboolean(L, 0);
		lua_pushfstring(L, "Error, failed to invoke '%s'", args[0]);

		if (rfd != -1)
			close(rfd);
		invoke_free_args(args);
		return 2;
	}

	if (flags & INVOKER_NOWAIT) {
		lua_pushinteger(L, pid);
		lua_pushinteger(L, rfd);
		invoke_free_args(args);
		return 2;
	}

	do {
		int est;
		pid_t rp;
again:
		est = 0;
		/* wait for application to exit */
		rp = waitpid(pid, &est, 0);
		if (rp == -1) {
			int error = errno;
			if (error == EINTR)
				goto again;
			fprintf(stderr, "Error, failed to waitpid(%ld): %s\n",
				(long) pid, strerror(error));
			fflush(stderr);

			lua_pushnil(L);
			lua_pushfstring(L, "Error, failed to wait for '%s'", args[0]);

			if (rfd != -1) 
				close(rfd);
			invoke_free_args(args);
			return 2;
		}

		/* store the exit status of child process */
		eval = est;
	} while (0);

	if (rfd != -1) {
		char * rbuf;
		ssize_t rl1 = 0;

		setclear_nonblock(rfd, 1);
		/* allocate buffer to read the output from child process */
		rbuf = (char *) malloc(bufsize + 1);
		if (rbuf == NULL) {
			fprintf(stderr, "Error, system out of memory: %s\n",
				strerror(errno));
			fflush(stderr);
		} else {
			/* read the pipe */
			rl1 = read(rfd, rbuf, bufsize);
		}

		/* close pipe file descriptor */
		close(rfd);
		rfd = -1;

		lua_pushinteger(L, eval);
		if (rl1 > 0 && (flags & INVOKER_TRIMEND) != 0) {
			while (rl1 > 0) {
				char cha = rbuf[rl1 - 1];
				if (cha != '\r' && cha != '\n' && cha != ' ' && cha != '\t')
					break;
				rl1--;
			}
		}

		if (rl1 >= 0)
			lua_pushlstring(L, rbuf, (size_t) rl1);
		/* free allocated buffer */
		if (rbuf != NULL)
			free(rbuf);

		invoke_free_args(args);
		return (rl1 >= 0) ? 2 : 1;
	}

	lua_pushinteger(L, eval);
	invoke_free_args(args);
	return 1;
}

static int invoke_exited(lua_State * L)
{
	int eval, ntop;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop == 0 || lua_isinteger(L, 1) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument for exited");
		return 2;
	}

	eval = lua_tointeger(L, 1);
	lua_pushboolean(L, WIFEXITED(eval) != 0);
	lua_pushinteger(L, WEXITSTATUS(eval));
	return 2;
}

static int invoke_signaled(lua_State * L)
{
	int eval, ntop;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop == 0 || lua_isinteger(L, 1) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument for exited");
		return 2;
	}

	eval = lua_tointeger(L, 1);
	lua_pushboolean(L, WIFSIGNALED(eval) != 0);
	lua_pushinteger(L, WTERMSIG(eval));
	return 2;
}

static int invoke_pipesize(lua_State * L)
{
	int pfd;
	int psize, nsize, ntop;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop <= 1 ||
		lua_isinteger(L, 1) == 0 ||
		lua_isinteger(L, 2) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument for pipesize");
		return 2;
	}

	pfd = lua_tointeger(L, 1);
	psize = lua_tointeger(L, 2);
	nsize = set_pipe_size(pfd, psize);
	if (nsize < 0) {
		int error = errno;
		lua_pushboolean(L, 0);
		lua_pushfstring(L, "Error, failed to set pipesize: %d", error);
		return 2;
	}

	lua_pushinteger(L, nsize);
	return 1;
}

static int invoke_read(lua_State * L)
{
	ssize_t rl1;
	off_t myoffs;
	char * rbuf = NULL;
	int ntop, pfd, rdlen, flags;

	flags = 0;
	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop <= 1 ||
		lua_isinteger(L, 1) == 0 ||
		lua_isinteger(L, 2) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument for readfd");
		return 2;
	}

	if (ntop >= 3 && lua_isinteger(L, 3))
		flags = (int) lua_tointeger(L, 3);

	pfd = lua_tointeger(L, 1);
	rdlen = (int) lua_tointeger(L, 2);
	if (rdlen <= 0)
		rdlen = INVOKER_BUFSIZE;
	if (rdlen > (32 * 1024 * 1024)) {
		lua_pushnil(L);
		lua_pushfstring(L, "Error, invalid buffer size: %d", rdlen);
		return 2;
	}

	rbuf = (char *) malloc((size_t) (rdlen + 0x1));
	if (rbuf == NULL) {
		lua_pushnil(L);
		lua_pushfstring(L, "Error, system out of memory: %d", rdlen);
		return 2;
	}

	// read file descriptor from an offset
	if (lua_type(L, 4) == LUA_TNUMBER) {
		myoffs = (off_t) lua_tonumber(L, 4);
		if (myoffs >= 0 && lseek(pfd, myoffs, SEEK_SET) != myoffs) {
			int error = errno;
			lua_pushnil(L);
			lua_pushfstring(L, "Error, cannot lseek(%d): %s", pfd, strerror(error));

			free(rbuf);
			return 2;
		}
	}

	rl1 = read(pfd, rbuf, (size_t) rdlen);
	if (rl1 < 0) {
		int error = errno;

		lua_pushnil(L);
		lua_pushfstring(L, "Error, failed to read %d: %s", pfd, strerror(error));
		free(rbuf);
		return 2;
	}

	if (rl1 > 0 && (flags & INVOKER_TRIMEND) != 0) {
		while (rl1 > 0) {
			char cha = rbuf[rl1 - 1];
			if (cha != '\r' && cha != '\n' && cha != ' ' && cha != '\t')
				break;
			rl1--;
		}
	}

	lua_pushlstring(L, rbuf, (size_t) rl1);
	free(rbuf);

	myoffs = lseek(pfd, 0, SEEK_CUR);
	if (myoffs > 0x7FFFFFFF)
		lua_pushnumber(L, (lua_Number) myoffs);
	else if (myoffs < 0)
		lua_pushinteger(L, 0);
	else
		lua_pushinteger(L, (lua_Integer) myoffs);
	return 2;
}

static int invoke_close(lua_State * L)
{
	int pfd, ntop;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop == 0 || lua_isinteger(L, 1) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument for closefd");
		return 2;
	}

	pfd = lua_tointeger(L, 1);
	if (close(pfd) == -1) {
		int error = errno;
		lua_pushboolean(L, 0);
		lua_pushfstring(L, "Error, failed to close(%d): %s", pfd, strerror(error));
		return 2;
	}

	lua_pushboolean(L, 1);
	return 1;
}

static int invoke_wait(lua_State * L)
{
	pid_t pid, rp;
	int ntop, nohang, est;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop <= 1 ||
		lua_isinteger(L, 1) == 0 ||
		lua_isboolean(L, 2) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument for waitpid");
		return 2;
	}

	pid = (pid_t) lua_tointeger(L, 1);
	nohang = lua_toboolean(L, 2);

again:
	est = 0;
	rp = waitpid(pid, &est, nohang ? WNOHANG : 0);
	if (rp == -1) {
		int error = errno;
		if (error == EINTR)
			goto again;

		lua_pushnil(L);
		lua_pushfstring(L, "Error, failed to waitpid(%d): %s",
			(int) pid, strerror(error));
		return 2;
	}

	if (rp == 0) {
		lua_pushboolean(L, 0); /* child process still running, exited = false */
		return 1;
	}

	lua_pushboolean(L, 1); /* child process has exited */
	lua_pushinteger(L, est);
	return 2;
}

static int invoke_readlink(lua_State * L)
{
	ssize_t rl1;
	char * rbuf;
	int ntop, ret;
	const char * sym;
	struct stat linkst;

	rbuf = NULL;
	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop == 0 || lua_isstring(L, 1) == 0) {
err0:
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument for readlink");
		return 2;
	}

	sym = lua_tolstring(L, 1, NULL);
	if (sym == NULL || sym[0] == '\0')
		goto err0;

	ret = lstat(sym, &linkst);
	if (ret == -1) {
		int error = errno;
		lua_pushnil(L);
		lua_pushfstring(L, "Error, failed to check %s: %s",
			sym, strerror(error));
		return 2;
	}

	if (S_ISLNK(linkst.st_mode) == 0) {
		lua_settop(L, 1);
		return 1;
	}

#define INVOKER_LINKSIZE 1536
	rbuf = (char *) malloc(INVOKER_LINKSIZE + 1);
	if (rbuf == NULL) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, system out of memory for readlink");
		return 2;
	}

	rl1 = readlink(sym, rbuf, INVOKER_LINKSIZE);
	if (rl1 == -1) {
		int error = errno;
		free(rbuf);
		lua_pushnil(L);
		lua_pushfstring(L, "Error, failed to find %s: %s",
			sym, strerror(error));
		return 2;
	}

	if (rl1 >= INVOKER_LINKSIZE)
		rl1 = INVOKER_LINKSIZE - 1;
#undef INVOKER_LINKSIZE
	rbuf[rl1] = '\0';

	lua_pushlstring(L, rbuf, (size_t) rl1);
	free(rbuf);
	return 1;
}

static int invoke_realpath(lua_State * L)
{
	int ntop;
	char * rbuf;
	const char * filp;

	rbuf = NULL;
	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop == 0 || lua_isstring(L, 1) == 0) {
err0:
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument for realpath");
		return 2;
	}

	filp = lua_tolstring(L, 1, NULL);
	if (filp == NULL || filp[0] == '\0')
		goto err0;

	rbuf = (char *) calloc(0x1, 4096);
	if (rbuf == NULL) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, system out of memory for readlink");
		return 2;
	}

	if (realpath(filp, rbuf) == NULL) {
		int error = errno;
		free(rbuf);
		lua_pushnil(L);
		lua_pushfstring(L, "Error, failed to find %s: %s",
			filp, strerror(error));
		return 2;
	}

	lua_pushstring(L, rbuf);
	free(rbuf);
	return 1;
}

static int invoke_uptime(lua_State * L)
{
	int ret;
	struct timespec tspec;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	tspec.tv_sec = 0;
	tspec.tv_nsec = 0;
	ret = clock_gettime(CLOCK_BOOTTIME, &tspec);
	if (ret == -1)
		ret = clock_gettime(CLOCK_MONOTONIC, &tspec);
	if (ret == -1) {
		int error = errno;
		fprintf(stderr, "Error, failed to get boottime: %s\n",
			strerror(error));
		fflush(stderr);
	}

	lua_pushinteger(L, (lua_Integer) tspec.tv_sec);
	lua_pushinteger(L, (lua_Integer) (tspec.tv_nsec / 1000000));
	return 2;
}

static int invoke_waitsec(lua_State * L)
{
	int ntop, sec;
	int ret, error = 0;
	struct timespec now, then;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop < 1 || lua_isinteger(L, 1) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument given for waitsec");
		return 2;
	}

	sec = (int) lua_tointeger(L, 1);
	if (sec <= 0 || sec >= 30 * 24 * 3600) {
		lua_pushnil(L);
		lua_pushfstring(L, "Error, invalid argument %d for waitsec", sec);
		return 2;
	}

	now.tv_sec = 0;
	now.tv_nsec = 0;
	ret = clock_gettime(CLOCK_REALTIME, &now);
	if (ret == -1) {
		error = errno;
		fprintf(stderr, "Error, failed to get bootime: %s\n",
			strerror(error));
		fflush(stderr);
		lua_pushnil(L);
		return 1;
	}

	if (now.tv_nsec == 0) {
		then.tv_sec = sec;
		then.tv_nsec = 0;
	} else {
		then.tv_sec = (time_t) (sec - 1);
		then.tv_nsec = 1000000000 - now.tv_nsec;
	}

	ret = nanosleep(&then, NULL);
	if (ret == -1) {
		error = errno;
		if (error == EINTR) {
			lua_pushboolean(L, 0);
			lua_pushstring(L, "waitsec has been Interrupted");
			return 2;
		}

		fprintf(stderr, "Error, waitsec has failed to sleep: %s\n",
			strerror(error));
		fflush(stderr);
		lua_pushnil(L);
		return 1;
	}

	lua_pushboolean(L, 1);
	return 1;
}

static int invoke_readfile(lua_State * L)
{
	ssize_t rl1;
	int error = 0;
	int ntop, rfd, flags;
	const char * filp;
	char * rbuf = NULL;
	size_t filesize = 0;

	rfd = -1;
	flags = 0;
	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop < 1 || lua_isstring(L, 1) == 0) {
err0:
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid argument for readfile");
		return 2;
	}

	filp = lua_tolstring(L, 1, NULL);
	if (filp == NULL || filp[0] == '\0')
		goto err0;

	if (ntop >= 2 && lua_isinteger(L, 2))
		flags = (int) lua_tointeger(L, 2);

	if (ntop >= 3 && lua_isinteger(L, 3) != 0) {
		filesize = (size_t) lua_tointeger(L, 3);
	} else {
		int ret;
		struct stat rst;

		ret = stat(filp, &rst);
		if (ret == -1) {
			error = errno;
err1:
			lua_pushnil(L);
			lua_pushfstring(L, "Error, cannot read %s: %s", filp, strerror(error));
			return 2;
		}
		filesize = (size_t) rst.st_size;
	}

	if (filesize == 0)
		filesize = INVOKER_BUFSIZE;
	else if (filesize > (8 * 1024 * 1024)) {
		lua_pushnil(L);
		lua_pushfstring(L, "Error, invalid read size for %s: %d",
			filp, (int) filesize);
		return 2;
	}

	rfd = open(filp, O_RDONLY | O_CLOEXEC);
	if (rfd == -1) {
		error = errno;
		goto err1;
	}

	rbuf = (char *) malloc(filesize + 0x1);
	if (rbuf == NULL) {
		close(rfd);
		lua_pushnil(L);
		lua_pushstring(L, "Error, system out of memory for readfile");
		return 2;
	}

	rl1 = read(rfd, rbuf, filesize);
	if (rl1 == -1) {
		error = errno;
		free(rbuf);
		close(rfd);
		goto err1;
	}

	close(rfd);
	if (rl1 > 0 && (flags & INVOKER_TRIMEND) != 0) {
		while (rl1 > 0) {
			char cha = rbuf[rl1 - 1];
			if (cha != '\r' && cha != '\n' && cha != ' ' && cha != '\t')
				break;
			rl1--;
		}
	}

	lua_pushlstring(L, rbuf, (size_t) rl1);
	free(rbuf);
	return 1;
}

/**
 * Poll an in-progress TCP connection.
 */
static int ipv4_ipv6_poll(int sockfd, int timeout)
{
	socklen_t slt;
	int ret, error;
	struct pollfd pfd;

	error = 0;
again:
	pfd.fd = sockfd;
	pfd.events = POLLOUT | POLLPRI | POLLERR;
	pfd.revents = 0;
	ret = poll(&pfd, 0x1, timeout);
	if (ret < 0) {
		error = errno;
		if (error == EINTR)
			goto again;
		fprintf(stderr, "Error, failed to poll(%d): %s\n", sockfd, strerror(error));
		fflush(stderr);
		return error;
	}

	if (ret == 0)
		return ETIMEDOUT;

	error = 0;
	slt = sizeof(error);
	ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &slt);
	if (ret == -1) {
		error = errno;
		fprintf(stderr, "Error, failed to get error flag for socket %d: %s\n",
			sockfd, strerror(error));
		fflush(stderr);
	}
	return error;
}

static int ipv4_ipv6_connect(int ipv4fd, int ipv6fd,
	const char * ipaddr, unsigned short port, int timeout)
{
	int ret, error;
	struct sockaddr_in addr_v4;
	struct sockaddr_in6 addr_v6;

	memset(&addr_v4, 0, sizeof(addr_v4));
	addr_v4.sin_family = AF_INET;
	addr_v4.sin_port = htons(port);
	if (inet_pton(AF_INET, ipaddr, (void *) &addr_v4.sin_addr) == 1) {
		ret = connect(ipv4fd, (const struct sockaddr *) &addr_v4, sizeof(addr_v4));
		if (ret == 0)
			return 0; /* connected successfully */
		error = errno;
		if (error == EINPROGRESS)
			return ipv4_ipv6_poll(ipv4fd, timeout);
		return error;
	}

	memset(&addr_v6, 0, sizeof(addr_v6));
	addr_v6.sin6_family = AF_INET6;
	addr_v6.sin6_port = htons(port);
	if (inet_pton(AF_INET6, ipaddr, (void *) &addr_v6.sin6_addr) == 1) {
		ret = connect(ipv6fd, (const struct sockaddr *) &addr_v6, sizeof(addr_v6));
		if (ret == 0)
			return 0; /* connected successfully */
		error = errno;
		if (error == EINPROGRESS)
			return ipv4_ipv6_poll(ipv6fd, timeout);
		return error;
	}

	/* bad IPv4/IPv6 address */
	return EADDRNOTAVAIL;
}

static int create_tcp_socket(int * pfd, int ipv6)
{
	int sockfd;

	sockfd = *pfd;
	if (sockfd != -1) {
		close(sockfd);
		*pfd = -1;
	}

	sockfd = socket(ipv6 ? AF_INET6 : AF_INET,
		SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
	if (sockfd == -1) {
		int error = errno;
		fprintf(stderr, "Error, failed to create ipv%d TCP socket: %s\n",
			ipv6 ? 6 : 4, strerror(error));
		fflush(stderr);
		if (error == 0) /* utterly impossible */
			error = -1;
		return error > 0 ? -error : error;
	}
	*pfd = sockfd;
	return sockfd;
}

static int invoke_tcpcheck(lua_State * L)
{
	int ret, ntop;
	const char * url;
	int result, refused;
	unsigned short portno;
	int ipv4fd, ipv6fd, timeout;
	struct addrinfo * ainfo, * pai;

	ainfo = pai = NULL;
	result = refused = 0;
	ipv4fd = ipv6fd = -1;
	timeout = 3000; /* default 3 seconds */
	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop < 2 ||
		lua_isstring(L, 1) == 0 ||
		lua_isinteger(L, 2) == 0) {
err0:
		lua_pushnil(L);
		return 1;
	}

	url = lua_tolstring(L, 1, NULL);
	if (url == NULL || url[0] == '\0')
		goto err0;
	portno = (unsigned short) lua_tointeger(L, 2);
	if (ntop >= 3 && lua_isinteger(L, 3))
		timeout = (int) lua_tointeger(L, 3);

	/* create ipv4 and ipv6 sockets */
	ret = create_tcp_socket(&ipv4fd, 0);
	if (ret >= 0)
		create_tcp_socket(&ipv6fd, 1);
	if (ret < 0) {
		close(ipv4fd);
		close(ipv6fd);
		lua_pushnil(L);
		lua_pushboolean(L, 0);
		return 2;
	}

	/* deem `url as an ipv4/ipv6 address */
	ret = ipv4_ipv6_connect(ipv4fd, ipv6fd, url, portno, timeout);
	if (ret != EADDRNOTAVAIL) {
		result = (ret == 0);
		refused = (ret == ECONNREFUSED);
		goto err1;
	}

	/* we have to call `getaddrinfo(...) to resolve `url */
	ret = getaddrinfo(url, NULL, NULL, &ainfo);
	if (ret) {
		result = 0;
		refused = 0;
		goto err1;
	}

	portno = htons(portno);
	for (pai = ainfo; pai != NULL; pai = pai->ai_next) {
		int error = 0;
		struct sockaddr_in    addrv4;
		struct sockaddr_in6   addrv6;

		if (pai->ai_addrlen == sizeof(addrv4)) {
			const struct sockaddr_in * inp;

			inp = (const struct sockaddr_in *) pai->ai_addr;
			if (inp == NULL)
				continue;

			memset(&addrv4, 0, sizeof(addrv4));
			addrv4.sin_family = AF_INET;
			addrv4.sin_port = portno;
			addrv4.sin_addr = inp->sin_addr;
			ret = connect(ipv4fd, (const struct sockaddr *) &addrv4, sizeof(addrv4));
			if (ret == 0) {
				result = 1;
				refused = 0;
				goto err1;
			}
			error = errno;
			if (error == EINPROGRESS) {
				ret = ipv4_ipv6_poll(ipv4fd, timeout);
				if (ret == 0 || ret == ECONNREFUSED) {
					result = (ret == 0);
					refused = (ret == ECONNREFUSED);
					goto err1;
				}
			} else if (error == ECONNREFUSED) {
				result = 0;
				refused = 1;
				goto err1;
			} /* else: other errors should be ignored */
			create_tcp_socket(&ipv4fd, 0);
		} else if (pai->ai_addrlen == sizeof(addrv6)) {
			const struct sockaddr_in6 * inp;

			inp = (const struct sockaddr_in6 *) pai->ai_addr;
			if (inp == NULL)
				continue;

			memset(&addrv4, 0, sizeof(addrv4));
			addrv6.sin6_family = AF_INET6;
			addrv6.sin6_port = portno;
			addrv6.sin6_addr = inp->sin6_addr;
			ret = connect(ipv6fd, (const struct sockaddr *) &addrv6, sizeof(addrv6));
			if (ret == 0) {
				result = 1;
				refused = 0;
				goto err1;
			}
			error = errno;
			if (error == EINPROGRESS) {
				ret = ipv4_ipv6_poll(ipv6fd, timeout);
				if (ret == 0 || ret == ECONNREFUSED) {
					result = (ret == 0);
					refused = (ret == ECONNREFUSED);
					goto err1;
				}
			} else if (error == ECONNREFUSED) {
				result = 0;
				refused = 1;
				goto err1;
			} /* ignore all other errors */
			create_tcp_socket(&ipv6fd, 1);
		} else {
			fprintf(stderr, "Error, unknown host address length: %u\n",
				(unsigned int) pai->ai_addrlen);
			fflush(stderr);
		}
	}

err1:
	if (ipv4fd != -1)
		close(ipv4fd);
	if (ipv6fd != -1)
		close(ipv6fd);
	if (ainfo != NULL) {
		freeaddrinfo(ainfo);
		ainfo = NULL;
	}
	lua_pushboolean(L, result);
	lua_pushboolean(L, refused);
	return 2;
}

static int invoke_kill(lua_State * L)
{
	pid_t pid;
	int ret, ntop, signo;

	if (lua_checkstack(L, 2) == 0)
		return 0;
	ntop = lua_gettop(L);
	if (ntop < 2 ||
		lua_isinteger(L, 1) == 0 ||
		lua_isinteger(L, 2) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "invalid argument(s) for invoker.kill");
		return 2;
	}

	pid = (pid_t) lua_tointeger(L, 1);
	signo = (int) lua_tointeger(L, 2);
	ret = kill(pid, signo);
	if (ret == -1) {
		ret = errno;
		lua_pushboolean(L, 0);
		lua_pushinteger(L, ret);
		return 2;
	}

	lua_pushboolean(L, 1);
	return 1;
}

static int invoke_setname(lua_State * L)
{
	int ret, ntop;
	char tname[16];
	const char * name;

	if (lua_checkstack(L, 2) == 0)
		return 0;
	ntop = lua_gettop(L);
	if (ntop < 1 || lua_isstring(L, 1) == 0) {
err0:
		lua_pushnil(L);
		lua_pushstring(L, "invalid argument for setname");
		return 2;
	}

	name = lua_tolstring(L, 1, NULL);
	if (name == NULL)
		goto err0;
	memset(tname, 0, sizeof(tname));
	strncpy(tname, name, sizeof(tname) - 1);
	ret = prctl(PR_SET_NAME, (unsigned long) tname, 0, 0, 0);
	if (ret == -1) {
		int errn = errno;
		lua_pushnil(L);
		lua_pushfstring(L, "cannot set thread name: %d", errn);
		return 2;
	}
	lua_pushboolean(L, 1);
	return 1;
}

static int invoke_random(lua_State * L)
{
	int ntop, ufd, owned;
	unsigned int maxval, rval;

	owned = 0;
	maxval = 0x80000000u;
	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop >= 1 && lua_isinteger(L, 1))
		maxval = (unsigned int) lua_tointeger(L, 1);

	ufd = global_urandfd;
	if (ufd == -1) {
		owned = 1;
		ufd = open("/dev/urandom", O_RDWR | O_CLOEXEC);
		if (ufd == -1) {
			lua_pushnil(L);
			lua_pushstring(L, "failed to open random device");
			return 2;
		}
	}

	rval = 0;
	if (read(ufd, &rval, sizeof(rval)) != (ssize_t) sizeof(rval)) {
		if (owned != 0)
			close(ufd);
		lua_pushnil(L);
		lua_pushstring(L, "failed to read random device");
		return 2;
	}

	if (owned != 0)
		close(ufd);
	if (maxval && rval >= maxval)
		rval = rval % maxval;
	lua_pushinteger(L, (lua_Integer) rval);
	return 1;
}

static int invoke_randstr(lua_State * L)
{
	char * buf;
	size_t rlen, slen;
	int ntop, ufd, owned;
	const char * randstr;

	owned = 0;
	buf = NULL;
	randstr = NULL;
	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop < 1 || lua_isinteger(L, 1) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "invalid argument for randstr");
		return 2;
	}

	rlen = (size_t) lua_tointeger(L, 1);
	if (rlen == 0 || rlen >= 0x80000) {
		lua_pushnil(L);
		lua_pushstring(L, "invalid random length");
		return 2;
	}

	ufd = global_urandfd;
	if (ufd == -1) {
		owned = 1;
		ufd = open("/dev/urandom", O_RDWR | O_CLOEXEC);
		if (ufd == -1) {
			lua_pushnil(L);
			lua_pushstring(L, "failed to open random device");
			return 2;
		}
	}

	buf = (char *) malloc(rlen + 0x8);
	if (buf == NULL) {
		if (owned != 0)
			close(ufd);
		lua_pushnil(L);
		lua_pushstring(L, "system out of memory");
		return 2;
	}

	if (read(ufd, buf, rlen) != (ssize_t) rlen) {
		free(buf);
		if (owned != 0)
			close(ufd);
		lua_pushnil(L);
		lua_pushstring(L, "failed to read random device");
		return 2;
	}
	if (owned != 0) {
		close(ufd);
		ufd = -1;
	}

	slen = 0;
	if (ntop > 1) {
		if (lua_type(L, 2) == LUA_TBOOLEAN && lua_toboolean(L, 2)) {
			randstr = "0123456789abcdefghijklmnopqrstvuwxyzABCDEFGHIJKLMNOPQRSTVUWXYZ_&";
			slen = strlen(randstr);
		} else if (lua_type(L, 2) == LUA_TSTRING)
			randstr = lua_tolstring(L, 2, &slen);
	}

	if (randstr && slen > 0) {
		size_t idx;
		for (idx = 0; idx < rlen; ++idx) {
			unsigned int jdx;
			jdx = (unsigned int) buf[idx];
			buf[idx] = randstr[jdx % slen];
		}
	}

	lua_pushlstring(L, buf, rlen);
	free(buf);
	return 1;
}

static int invoke_openrand(lua_State * L)
{
	int idx;
	int ret, ufd, vfd;
	const char * randev;
	struct stat fst;

	randev = NULL;
	if (lua_checkstack(L, 0) == 0)
		return 0;

	ret = lua_gettop(L);
	if (ret >= 1 && lua_type(L, 1) == LUA_TSTRING)
		randev = lua_tolstring(L, 1, NULL);
	if (randev == NULL || randev[0] == '\0')
		randev = "/dev/urandom";

	if (global_urandfd != -1) {
		close(global_urandfd);
		global_urandfd = -1;
	}

	ufd = open(randev, O_RDWR | O_CLOEXEC);
	if (ufd == -1) {
		lua_pushnil(L);
		lua_pushstring(L, "failed to open random device");
		return 2;
	}

	vfd = -1;
	for (idx = 128; idx < 1024; ++idx) {
		ret = fstat(idx, &fst);
		if (ret == -1 && errno == EBADF) {
			vfd = idx;
			break;
		}
	}

	if (vfd == -1) {
		close(ufd);
		lua_pushnil(L);
		lua_pushstring(L, "cannot find unused file descriptor");
		return 2;
	}

	ret = dup3(ufd, vfd, O_CLOEXEC);
	if (ret == -1) {
		close(ufd);
		lua_pushnil(L);
		lua_pushstring(L, "cannot duplicate file descriptor");
		return 2;
	}
	close(ufd);
	lua_pushinteger(L, vfd);
	return 1;
}

static int invoke_mountpoint(lua_State * L)
{
	char * pdir;
	dev_t st_dev;
	ino_t st_ino;
	int ntop, ret, rval;
	struct stat pathst;
	const char * dirpath;

	rval = 0;
	pdir = NULL;
	if (lua_checkstack(L, 2) == 0)
		return 0;
	ntop = lua_gettop(L);
	if (ntop <= 0 || lua_isstring(L, 1) == 0) {
err0:
		lua_pushnil(L);
		lua_pushstring(L, "invalid argument for mountpoint");
		return 2;
	}

	dirpath = lua_tolstring(L, 1, NULL);
	if (dirpath == NULL || dirpath[0] == '\0')
		goto err0;

	ret = lstat(dirpath, &pathst);
	if (ret == -1 || S_ISDIR(pathst.st_mode) == 0)
		goto err1;
	st_dev = pathst.st_dev;
	st_ino = pathst.st_ino;

	ret = asprintf(&pdir, "%s/..", dirpath);
	if (ret > 0) {
		ret = stat(pdir, &pathst);
		if (ret == -1)
			goto err1;
	} else
		goto err1;

	rval = 1;
	if ((st_dev == pathst.st_dev) && (st_ino != pathst.st_ino))
		rval = 0;
err1:
	if (pdir != NULL)
		free(pdir);
	lua_pushboolean(L, rval);
	return 1;
}

static int invoke_msleep(lua_State * L)
{
	int ntop;
	long long msec;
	struct timespec spec;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	msec = -1;
	ntop = lua_gettop(L);
	if (ntop >= 1) {
		if (lua_isinteger(L, 1)) {
			msec = (long long) lua_tointeger(L, 1);
		} else if (lua_isnumber(L, 1)) {
			msec = (long long) lua_tonumber(L, 1);
		}
	}

	if (msec < 0) {
		lua_pushnil(L);
		lua_pushfstring(L, "invalid argument for msleep => %d", msec);
		return 2;
	}

	if (msec == 0) {
		lua_pushinteger(L, 0);
		return 1;
	}

	spec.tv_sec = (time_t) (msec / 1000);
	spec.tv_nsec = (long) ((msec % 1000) * 1000000);
	ntop = nanosleep(&spec, NULL);
	if (ntop == -1)
		ntop = errno;
	lua_pushinteger(L, ntop);
	return 1;
}

static int invoke_uptimsec(lua_State * L)
{
	struct timespec spec;
	unsigned long long uptm;

	spec.tv_sec = 0;
	spec.tv_nsec = 0;
	if (lua_checkstack(L, 2) == 0)
		return 0;

	if (clock_gettime(CLOCK_BOOTTIME, &spec) == -1 &&
		clock_gettime(CLOCK_MONOTONIC, &spec) == -1) {
		int error = errno;
		fprintf(stderr, "Error, failed to get system uptime: %s\n",
			strerror(error));
		fflush(stderr);
		errno = error;
		abort(); /* terminate application */
	}

	uptm = (unsigned long long) spec.tv_sec;
	uptm = uptm * 1000 + (unsigned long long) (spec.tv_nsec / 1000000);
	lua_pushnumber(L, (lua_Number) uptm);
	return 1;
}

static int invoke_setsid(lua_State * L)
{
	pid_t rval;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	rval = setsid();
	if (rval == (pid_t) -1) {
		int error = errno;
		lua_pushboolean(L, 0);
		lua_pushfstring(L, "Error, failed to create new session: %s",
			strerror(error));
		return 2;
	}

	lua_pushinteger(L, (lua_Integer) rval);
	return 1;
}

static int invoke_killpg(lua_State * L)
{
	int pid;
	int ret, ntop, signo;

	if (lua_checkstack(L, 2) == 0)
		return 0;

	ntop = lua_gettop(L);
	if (ntop < 2 ||
		lua_isinteger(L, 1) == 0 ||
		lua_isinteger(L, 2) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "invalid argument(s) for invoker.killpg");
		return 2;
	}

	pid = (int) lua_tointeger(L, 1);
	signo = (int) lua_tointeger(L, 2);
	ret = killpg(pid, signo);
	if (ret == -1) {
		ret = errno;
		lua_pushboolean(L, 0);
		lua_pushinteger(L, ret);
		return 2;
	}

	lua_pushboolean(L, 1);
	return 1;
}

static int invoke_setguid(lua_State * L)
{
	int ntop;
	uid_t vuid = 0;
	gid_t vgid = 0;

	if (lua_checkstack(L, 2) == 0)
		return 0;
	ntop = lua_gettop(L);
	if (ntop < 1 || lua_isinteger(L, 1) == 0) {
		lua_pushnil(L);
		lua_pushfstring(L, "invalid number or type of argument: %d", ntop);
		return 2;
	}

	vgid = (gid_t) lua_tointeger(L, 1);
	if (setgid(vgid) == -1) {
		int error = errno;
		lua_pushnil(L);
		lua_pushfstring(L, "setgid(%d) has failed: %s",
			(int) vgid, strerror(error));
		return 2;
	}

	if (ntop > 1 && lua_isinteger(L, 2)) {
		vuid = (uid_t) lua_tointeger(L, 2);
		if (setuid(vuid) == -1) {
			int error = errno;
			lua_pushnil(L);
			lua_pushfstring(L, "setuid(%d) has failed: %s",
				(int) vgid, strerror(error));
			return 2;
		}
	}

	lua_pushboolean(L, 1);
	return 1;
}

static int invoke_watchfile(lua_State * L)
{
	regex_t reg_ex;
	struct pollfd pfd;
	unsigned int wmask;
	const char * regstr;
	unsigned char * pbuf;
	int timeo, ret, idx;
	int wfd, ntop, nfiles, rval;
	char * ffile; /* pointer to the very first file */

	rval = 0;
	wfd = -1;
	wmask = 0;
	nfiles = 0;
	timeo = -1;
	pbuf = NULL;
	ffile = NULL;
	regstr = NULL;
	memset(&reg_ex, 0, sizeof(reg_ex));

	if (lua_checkstack(L, 4) == 0)
		return 0;
	ntop = lua_gettop(L);
	if (ntop < 4) {
		lua_pushnil(L);
		lua_pushfstring(L, "Error, invalid number of arguments: %d", ntop);
		return 2;
	}

	/* check the type of first two arugments */
	if (lua_isinteger(L, 1) == 0 ||
		lua_isinteger(L, 2) == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "Error, invalid type of watch mask");
		return 2;
	}

	/* mask for `inotify_add_watch */
	wmask = (unsigned int) lua_tointeger(L, 1);
	/* timeout for poll */
	timeo = (int) lua_tointeger(L, 2);

	/* check for regular express for matching */
	if (lua_type(L, 3) == LUA_TSTRING) {
		regstr = lua_tolstring(L, 3, NULL);
		if (regstr && regstr[0]) {
			ret = regcomp(&reg_ex, regstr, REG_EXTENDED);
			if (ret != 0) {
				lua_pushnil(L);
				lua_pushfstring(L, "Error, failed to compile regex '%s': %d",
					regstr, ret);

				rval = 2;
				goto err0;
			}
		} else
			regstr = NULL;
	}

	/* create inotify file descriptor */
	wfd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
	if (wfd == -1) {
		int error = errno;
		lua_pushnil(L);
		lua_pushfstring(L, "Error, failed to init inotify: %s", strerror(error));

		rval = 2;
		goto err0;
	}

	/* add paths to watch one by one */
	for (idx = 4; idx <= ntop; ++idx) {
		const char * pstr = lua_type(L, idx) == LUA_TSTRING ?
			lua_tolstring(L, idx, NULL) : NULL;
		if (pstr && pstr[0]) {
			ret = inotify_add_watch(wfd, pstr, wmask);
			if (ret == -1) {
				int error = errno;
				lua_pushnil(L);
				lua_pushfstring(L, "Error, failed to watch '%s': %s\n",
					pstr, strerror(error));

				rval = 2;
				goto err0;
			}

			nfiles++;
			if (ffile == NULL)
				ffile = strdup(pstr);
		}
	}

	/* no path has been successfully add to inotify */
	if (nfiles == 0) {
		lua_pushnil(L);
		lua_pushstring(L, "No file to watch.");
		rval = 2;
		goto err0;
	}

	/* poll the inotify file descriptor */
	pfd.fd = wfd;
	pfd.events = POLLIN;
	pfd.revents = 0;
	ret = poll(&pfd, 0x1, timeo);
	if (ret == 0) {
		/* no activity for inotify instance */
		rval = 1;
		lua_pushinteger(L, 0);
		goto err0;
	}

	if (ret == -1) {
		int error = errno;
		/* interrupted ? */
		if (error != EINTR) {
			fprintf(stderr, "Error, failed to poll inotify: %s\n",
				strerror(error));
			fflush(stderr);
		}
		lua_pushinteger(L, -1);
		lua_pushfstring(L, "Error, poll has failed: %s", strerror(error));
		rval = 2;
		goto err0;
	}

#define LOCAL_INOTIFY_BUFSIZE 65536 /* 64K */
	/* allocate memory for read */
	ret = posix_memalign((void * *) &pbuf, 4096, LOCAL_INOTIFY_BUFSIZE);
	if (ret != 0) {
		fputs("Error, system out of memory for inotify!\n", stderr);
		fflush(stderr);
		lua_pushnil(L);
		lua_pushstring(L, "System out of memory!");
		rval = 2;
		goto err0;
	}

	do {
		ssize_t rl1;
		size_t rlen, rl0;
		int nidx, numfiles;
		const struct inotify_event * evp;

		rlen = 0;
		nidx = 0;
		numfiles = 0;
		/* read the events */
		rl1 = read(wfd, pbuf, LOCAL_INOTIFY_BUFSIZE - 1);
		if (rl1 < 0) {
			int error = errno;
			lua_pushinteger(L, -1);
			lua_pushfstring(L, "Error, inotify read %d has failed: %s",
				wfd, strerror(error));
			rval = 2;
			goto err0;
		}

		pbuf[rl1] = '\0';
		rl0 = (size_t) rl1;
		lua_pushnil(L); /* for `lua_replace(...) */
		nidx = lua_gettop(L);

		while ((rlen + sizeof(*evp)) <= rl0) {
			int dop = -1;
			unsigned int nlen;
			const char * nstr = NULL;

			evp = (const struct inotify_event *) (pbuf + rlen);
			nlen = (unsigned int) evp->len;
			if (nlen > 0 && nlen < 1024)
				nstr = evp->name;
			else if (nlen == 0 && nfiles == 1)
				nstr = ffile;
			if (nstr == NULL || nstr[0] == '\0')
				dop = 0;

			if (nstr && regstr) {
				regmatch_t regm[1];
				regm[0].rm_so = regm[0].rm_eo = 0;
				if (regexec(&reg_ex, nstr, 0x1, regm, 0) != 0)
					dop = 0;
			}

			if (dop != 0) {
				numfiles++;
				lua_pushstring(L, nstr);
			}

			rlen += sizeof(*evp);
			rlen += (size_t) nlen;
		}

		if (numfiles > 0) {
			lua_pushinteger(L, numfiles);
			lua_replace(L, nidx);
		} else {
			lua_settop(L, nidx - 1);
			lua_pushinteger(L, 0);
		}
		rval = numfiles + 1;
	} while (0);

err0:
	if (wfd != -1)
		close(wfd);
	if (pbuf != NULL)
		free(pbuf);
	if (ffile != NULL)
		free(ffile);
	if (regstr != NULL)
		regfree(&reg_ex);
	return rval;
}

static int invoke_pause(lua_State * L)
{
	int ret;
	ret = pause();
	lua_pushinteger(L, ret);
	return 1;
}

static int invoke_isip(lua_State * L, int family)
{
	int ret;
	char addrbuf[32];
	const char * ipaddr;

	ipaddr = NULL;
	if (lua_checkstack(L, 1) == 0)
		return 0;

	ret = lua_gettop(L);
	if (ret >= 1 && lua_type(L, 1) == LUA_TSTRING)
		ipaddr = lua_tolstring(L, 1, NULL);

	if (ipaddr == NULL || ipaddr[0] == '\0') {
		lua_pushboolean(L, 0);
		return 1;
	}

	memset(addrbuf, 0, sizeof(addrbuf));
	ret = inet_pton(family, ipaddr, (void *) addrbuf);
	lua_pushboolean(L, ret == 1);
	return 1;
}

static int invoke_isipv4(lua_State * L)
{
	return invoke_isip(L, AF_INET);
}

static int invoke_isipv6(lua_State * L)
{
	return invoke_isip(L, AF_INET6);
}

static int invoke_ismaskv4(lua_State * L)
{
	int ret, idx;
	const char * ipaddr;
	unsigned int ip_bin;

	ipaddr = NULL;
	if (lua_checkstack(L, 1) == 0)
		return 0;

	ret = lua_gettop(L);
	if (ret >= 1 && lua_type(L, 1) == LUA_TSTRING)
		ipaddr = lua_tolstring(L, 1, NULL);

	if (ipaddr == NULL || ipaddr[0] == '\0') {
		lua_pushnil(L);
		return 1;
	}

	ip_bin = 0;
	if (inet_pton(AF_INET, ipaddr, &ip_bin) == 1) {
		ret = 0;
		ip_bin = __builtin_bswap32(ip_bin);
		for (idx = 31; idx >= 0; --idx) {
			if ((ip_bin & (0x1u << idx)) == 0)
				break;
			ret++;
			ip_bin &= ~(0x1u << idx);
		}

		if (ip_bin == 0) {
			lua_pushinteger(L, ret);
			return 1;
		}
	}

	lua_pushnil(L);
	return 1;
}

static int invoke_waitlock(lua_State * L)
{
	char tbuf[128];
	const char * filp;
	int fd, ret, error;

	fd = -1;
	error = 0;
	filp = NULL;
	ret = lua_gettop(L);
	if (ret >= 1 && lua_type(L, 1) == LUA_TSTRING)
		filp = lua_tolstring(L, 1, NULL);

	if (filp == NULL || filp[0] == '\0') {
		lua_pushnil(L);
		return 1;
	}

	for (;;) {
		struct timespec delay;
		fd = open(filp, O_RDWR | O_CLOEXEC, 0644);
		if (fd >= 0) {
			struct stat fst;
			memset(&fst, 0, sizeof(fst));
			ret = fstat(fd, &fst);
			if (ret != 0 || fst.st_size < 10) {
				delay.tv_sec = 0; delay.tv_nsec = 150 * 1000000;
				nanosleep(&delay, NULL);
			}
			break;
		}

		fd = open(filp, O_CREAT | O_WRONLY | O_EXCL | O_CLOEXEC, 0644);
		if (fd >= 0) {
			delay.tv_sec = 0; delay.tv_nsec = 0;
			clock_gettime(CLOCK_MONOTONIC, &delay);
			ret = snprintf(tbuf, sizeof(tbuf), "locked by: %ld, at: %ld\n",
				(long) getpid(), (long) delay.tv_sec);
			if (ret > 0) {
				write(fd, tbuf, (size_t) ret);
				fsync(fd);
				lseek(fd, 0, SEEK_SET);
			}

			break;
		}

		error = errno;
		if (error != EEXIST) {
			lua_pushnil(L);
			lua_pushfstring(L, "cannot create '%s': %s", filp, strerror(error));
			return 2;
		}

		delay.tv_sec = 0; delay.tv_nsec = 200 * 1000000;
		nanosleep(&delay, NULL);
	}

again:
	ret = lockf(fd, F_LOCK, 10);
	if (ret == -1) {
		error = errno;
		if (error == EINTR)
			goto again;

		close(fd);
		lua_pushnil(L);
		lua_pushfstring(L, "failed to lock file %s: %s", filp, strerror(error));
		return 2;
	}

	lua_pushinteger(L, fd);
	return 1;
}

static int invoke_open(lua_State * L)
{
	int ret, oflag, omode;
	const char * filp;

	filp = NULL;
	ret = lua_type(L, 1);
	if (ret == LUA_TSTRING)
		filp = lua_tolstring(L, 1, NULL);
	if (filp == NULL || filp[0] == '\0') {
		lua_pushnil(L);
		lua_pushinteger(L, EINVAL);
		return 2;
	}

	oflag = lua_isinteger(L, 2) ? (int) lua_tointeger(L, 2) : O_RDONLY | O_CLOEXEC | O_LARGEFILE;
	omode = lua_isinteger(L, 3) ? (int) lua_tointeger(L, 3) : 0644;

	ret = open(filp, oflag, (mode_t) omode);
	if (ret < 0) {
		ret = errno;
		lua_pushnil(L);
		lua_pushinteger(L, ret);
		return 2;
	}

	lua_pushinteger(L, ret);
	return 1;
}

static int invoke_write(lua_State * L)
{
	int fd;
	ssize_t rl1;
	size_t dlen;
	off_t myoffs;
	const char * datp;

	dlen = 0;
	datp = NULL;
	fd = lua_isinteger(L, 1) ? (int) lua_tointeger(L, 1) : -1;
	if (fd < 0) {
		lua_pushnil(L);
		lua_pushinteger(L, EBADF);
		return 2;
	}

	if (lua_type(L, 2) == LUA_TSTRING)
		datp = lua_tolstring(L, 2, &dlen);
	if (datp == NULL || dlen == 0) {
		lua_pushnil(L);
		lua_pushinteger(L, EINVAL);
		return 2;
	}

	// read file descriptor from an offset
	if (lua_type(L, 3) == LUA_TNUMBER) {
		myoffs = (off_t) lua_tonumber(L, 3);
		if (myoffs >= 0 && lseek(fd, myoffs, SEEK_SET) != myoffs) {
			int error = errno;
			lua_pushnil(L);
			lua_pushfstring(L, "Error, cannot lseek(%d): %s", fd, strerror(error));
			return 2;
		}
	}

	errno = 0;
	rl1 = write(fd, datp, dlen);
	if (rl1 <= 0) {
		int error = errno;
		lua_pushnil(L);
		lua_pushfstring(L, "Error, failed to write(%d): %s", fd, strerror(error));
		return 2;
	}

	lua_pushinteger(L, (lua_Integer) rl1);
	myoffs = lseek(fd, 0, SEEK_CUR);
	if (myoffs > 0x7FFFFFFF)
		lua_pushnumber(L, (lua_Number) myoffs);
	else if (myoffs < 0)
		lua_pushinteger(L, 0);
	else
		lua_pushinteger(L, (lua_Integer) myoffs);
	return 2;
}

static int invoke_utctime(lua_State * L)
{
	int ret;
	struct timespec ts;

	ret = clock_gettime(CLOCK_REALTIME, &ts);
	if (ret < 0) {
		ts.tv_sec = time(NULL);
		ts.tv_nsec = 0;
	}

	lua_pushnumber(L, (lua_Number) ts.tv_sec);
	lua_pushinteger(L, (lua_Integer) ts.tv_nsec);
	return 2;
}

static int invoke_statfile(lua_State * L)
{
	int fd, ret, lst;
	const char * filp;
	struct stat fst;

	fd = -1;
	filp = NULL;
	if (lua_type(L, 1) == LUA_TSTRING)
		filp = lua_tolstring(L, 1, NULL);
	else if (lua_isinteger(L, 1))
		fd = (int) lua_tointeger(L, 1);

	// use `lstat ?
	lst = lua_type(L, 2);
	if (lst == LUA_TBOOLEAN)
		lst = lua_toboolean(L, 2);
	if (lst == LUA_TNUMBER)
		lst = (int) lua_tointeger(L, 2);
	else
		lst = 0;

	if (fd < 0 && (filp == NULL || filp[0] == '\0')) {
		lua_pushnil(L);
		lua_pushinteger(L, EINVAL);
		return 2;
	}

	memset(&fst, 0, sizeof(fst));
	if (fd >= 0)
		ret = fstat(fd, &fst);
	else
		ret = lst ? lstat(filp, &fst) : stat(filp, &fst);
	if (ret < 0) {
		ret = errno;
		lua_pushnil(L);
		lua_pushinteger(L, (lua_Integer) ret);
		return 2;
	}

	// 1, file type:
	if (S_ISREG(fst.st_mode))
		lua_pushstring(L, "regular");
	else if (S_ISDIR(fst.st_mode))
		lua_pushstring(L, "directory");
	else if (S_ISCHR(fst.st_mode))
		lua_pushstring(L, "character");
	else if (S_ISBLK(fst.st_mode))
		lua_pushstring(L, "blockdev");
	else if (S_ISFIFO(fst.st_mode))
		lua_pushstring(L, "pipe");
	else if (S_ISLNK(fst.st_mode))
		lua_pushstring(L, "symlink");
	else if (S_ISSOCK(fst.st_mode))
		lua_pushstring(L, "socket");
	else
		lua_pushstring(L, "unknown");

	// 2, file size:
	if (fst.st_size > 0x7FFFFFFF)
		lua_pushnumber(L, (lua_Number) fst.st_size);
	else
		lua_pushinteger(L, (lua_Integer) fst.st_size);

	// 3, file mode:
	lua_pushinteger(L, (lua_Integer) (fst.st_mode & 07777));

	// 4, access time:
	lua_pushnumber(L, (lua_Number) fst.st_atime);

	// 5, modification time:
	lua_pushnumber(L, (lua_Number) fst.st_mtime);

	// 6, change time:
	lua_pushnumber(L, (lua_Number) fst.st_ctime);

	// 7, st_ino:
	lua_pushnumber(L, (lua_Number) fst.st_ino);

	// 8, st_dev:
	lua_pushnumber(L, (lua_Number) fst.st_dev);

	// 9, user ID:
	lua_pushinteger(L, (lua_Integer) fst.st_uid);

	// 10, group ID:
	lua_pushinteger(L, (lua_Integer) fst.st_gid);

	// 11, number of hardlinks:
	lua_pushinteger(L, (lua_Integer) fst.st_nlink);

	return 11;
}

static int invoke_sync(lua_State * L)
{
	int ret = 0, fd;
	fd = lua_isinteger(L, 1) ? (int) lua_tointeger(L, 1) : -1;
	if (fd < 0)
		sync();
	else {
		ret = syncfs(fd);
		if (ret < 0) {
			ret = errno;
			lua_pushnil(L);
			lua_pushinteger(L, ret);
			return 2;
		}
	}
	lua_pushinteger(L, ret);
	return 1;
}

static int invoke_setenv(lua_State * L)
{
	int ret;
	const char * key, * val;
	key = val = NULL;

	if (lua_type(L, 1) == LUA_TSTRING)
		key = lua_tolstring(L, 1, NULL);
	if (key == NULL || key[0] == '\0') {
		lua_pushnil(L);
		lua_pushinteger(L, EINVAL);
		return 2;
	}

	if (lua_type(L, 2) == LUA_TSTRING)
		val = lua_tolstring(L, 2, NULL);

	if (val != NULL)
		ret = setenv(key, val, 1);
	else
		ret = unsetenv(key);
	if (ret != 0) {
		ret = errno;
		lua_pushnil(L);
		lua_pushinteger(L, ret);
		return 2;
	}

	lua_pushinteger(L, ret);
	return 1;
}

static int invoke_chdir(lua_State * L)
{
	int ret;
	const char * pdir;

	pdir = NULL;
	if (lua_type(L, 1) == LUA_TSTRING)
		pdir = lua_tolstring(L, 1, NULL);
	if (pdir == NULL || pdir[0] == '\0') {
		lua_pushnil(L);
		lua_pushinteger(L, EINVAL);
		return 2;
	}

	ret = chdir(pdir);
	if (ret < 0) {
		ret = errno;
		lua_pushnil(L);
		lua_pushinteger(L, ret);
		return 2;
	}

	lua_pushinteger(L, 0);
	return 1;
}

static int invoke_getcwd(lua_State * L)
{
	int ret;
	char tmpdir[768];

	memset(tmpdir, 0, sizeof(tmpdir));
	if (getcwd(tmpdir, sizeof(tmpdir) - 1) == NULL) {
		ret = errno;
		lua_pushnil(L);
		lua_pushinteger(L, ret);
		return 2;
	}

	lua_pushstring(L, tmpdir);
	return 1;
}

// glob method, modified from luaposix, ext/posix/glob.c:
static int invoke_glob(lua_State * L)
{
	unsigned int i;
	int flags = 0;
	int globret = 0;
	const char * pattern = "*";
	glob_t globres;

	if (lua_type(L, 1) == LUA_TSTRING) {
		const char * p;
		p = lua_tolstring(L, 1, NULL);
		if (p && p[0])
			pattern = p;
	}
	if (lua_isinteger(L, 2))
		flags = lua_tointeger(L, 2);

	memset(&globres, 0, sizeof(globres));
	globret = glob(pattern, flags, NULL, &globres);
	if (globret != 0) {
		lua_pushnil(L);
		lua_pushinteger(L, globret);
		return 2;
	}

	lua_createtable(L, (int) globres.gl_pathc, 0);
	for (i=1; i <= globres.gl_pathc; i++) {
		lua_pushstring(L, globres.gl_pathv[i-1]);
		lua_rawseti(L, -2, i);
	}
	globfree(&globres);
	return 1;
}

static int invoke_unlink(lua_State * L)
{
	int ret;
	const char * filp;

	filp = NULL;
	if (lua_type(L, 1) == LUA_TSTRING)
		filp = lua_tolstring(L, 1, NULL);
	if (filp == NULL || filp[0] == '\0') {
		lua_pushnil(L);
		lua_pushinteger(L, EINVAL);
		return 2;
	}

	ret = unlink(filp);
	if (ret < 0) {
		ret = errno;
		if (ret != EISDIR)
			goto next;
		ret = rmdir(filp);
	}
	if (ret < 0) {
		ret = errno;
next:
		lua_pushnil(L);
		lua_pushinteger(L, ret);
		return 2;
	}

	lua_pushinteger(L, ret);
	return 1;
}

static int invoke_mkdir(lua_State * L)
{
	int ret, mode;
	const char * dirp;

	dirp = NULL;
	if (lua_type(L, 1) == LUA_TSTRING)
		dirp = lua_tolstring(L, 1, NULL);
	if (dirp == NULL || dirp[0] == '\0') {
		lua_pushnil(L);
		lua_pushinteger(L, EINVAL);
		return 2;
	}

	mode = lua_isinteger(L, 2) ? lua_tointeger(L, 2) : 0755;
	ret = mkdir(dirp, (mode_t) mode);
	if (ret < 0) {
		ret = errno;
		lua_pushnil(L);
		lua_pushinteger(L, ret);
		return 2;
	}
	lua_pushinteger(L, ret);
	return 1;
}

static int invoke_link(lua_State * L)
{
	int ret, issym;
	const char * arg0, * arg1;

	issym = 0;
	arg0 = arg1 = NULL;
	if (lua_type(L, 1) == LUA_TSTRING)
		arg0 = lua_tolstring(L, 1, NULL);
	if (lua_type(L, 2) == LUA_TSTRING)
		arg1 = lua_tolstring(L, 2, NULL);

	if (arg0 == NULL || arg1 == NULL) {
		lua_pushnil(L);
		lua_pushinteger(L, EINVAL);
		return 2;
	}

	if (lua_isinteger(L, 3))
		issym = lua_tointeger(L, 3);
	if (issym != 0)
		ret = symlink(arg0, arg1);
	else
		ret = link(arg0, arg1);
	if (ret < 0) {
		ret = errno;
		lua_pushnil(L);
		lua_pushinteger(L, ret);
		return 2;
	}
	lua_pushinteger(L, ret);
	return 1;
}

static int invoke_rename(lua_State * L)
{
	int ret;
	const char * arg0, * arg1;

	arg0 = arg1 = NULL;
	if (lua_type(L, 1) == LUA_TSTRING)
		arg0 = lua_tolstring(L, 1, NULL);
	if (lua_type(L, 2) == LUA_TSTRING)
		arg1 = lua_tolstring(L, 2, NULL);

	if (arg0 == NULL || arg1 == NULL) {
		lua_pushnil(L);
		lua_pushinteger(L, EINVAL);
		return 2;
	}

	ret = rename(arg0, arg1);
	if (ret < 0) {
		ret = errno;
		lua_pushnil(L);
		lua_pushinteger(L, ret);
		return 2;
	}
	lua_pushinteger(L, ret);
	return 1;
}

static int invoke_nonblock(lua_State * L)
{
	int fd, isnonblock;

	fd = lua_isinteger(L, 1) ? (int) lua_tointeger(L, 1) : -1;
	if (fd < 0) {
		lua_pushnil(L);
		lua_pushstring(L, "invalid file descriptor for nonblock");
		return 2;
	}

	isnonblock = lua_gettop(L) >= 2 ? lua_toboolean(L, 2) : 0;
	if (setclear_nonblock(fd, isnonblock) < 0) {
		lua_pushnil(L);
		lua_pushfstring(L, "failed to set blocking mode for %d", fd);
		return 2;
	}
	lua_pushinteger(L, 0);
	return 1;
}

static int invoke_cloexec(lua_State * L)
{
	int fd, iscloexec;

	fd = lua_isinteger(L, 1) ? (int) lua_tointeger(L, 1) : -1;
	if (fd < 0) {
		lua_pushnil(L);
		lua_pushstring(L, "invalid file descriptor for cloexec");
		return 2;
	}

	iscloexec = lua_gettop(L) >= 2 ? lua_toboolean(L, 2) : 0;
	if (setclear_cloexec(fd, iscloexec) < 0) {
		lua_pushnil(L);
		lua_pushfstring(L, "failed to set CLOEXEC mode for %d", fd);
		return 2;
	}
	lua_pushinteger(L, 0);
	return 1;
}

static const struct luaL_Reg inv_regs[] = {
	{ "invoke",                invoke_app },
	{ "exited",                invoke_exited },
	{ "signaled",              invoke_signaled },
	{ "pipesize",              invoke_pipesize },
	{ "readfd",                invoke_read },
	{ "closefd",               invoke_close },
	{ "waitpid",               invoke_wait },
	{ "readlink",              invoke_readlink },
	{ "realpath",              invoke_realpath },
	{ "uptime",                invoke_uptime },
	{ "waitsec",               invoke_waitsec },
	{ "readfile",              invoke_readfile },
	{ "tcpcheck",              invoke_tcpcheck },
	{ "kill",                  invoke_kill },
	{ "setname",               invoke_setname },
	{ "random",                invoke_random },
	{ "randstr",               invoke_randstr },
	{ "openrandev",            invoke_openrand },
	{ "mountpoint",            invoke_mountpoint },
	{ "msleep",                invoke_msleep },
	{ "uptimsec",              invoke_uptimsec },
	{ "setsid",                invoke_setsid },
	{ "killpg",                invoke_killpg },
	{ "setguid",               invoke_setguid },
	{ "watchfile",             invoke_watchfile },
	{ "pause",                 invoke_pause },
	{ "isipv4",                invoke_isipv4 },
	{ "isipv6",                invoke_isipv6 },
	{ "ismaskv4",              invoke_ismaskv4 },
	{ "waitlock",              invoke_waitlock },
	{ "open",                  invoke_open },
	{ "writefd",               invoke_write },
	{ "utctime",               invoke_utctime },
	{ "statfile",              invoke_statfile },
	{ "sync",                  invoke_sync },
	{ "setenv",                invoke_setenv },
	{ "chdir",                 invoke_chdir },
	{ "getcwd",                invoke_getcwd },
	{ "glob",                  invoke_glob },
	{ "unlink",                invoke_unlink },
	{ "mkdir",                 invoke_mkdir },
	{ "link",                  invoke_link },
	{ "rename",                invoke_rename },
	{ "nonblock",              invoke_nonblock },
	{ "cloexec",               invoke_cloexec },
	{ NULL,                    NULL },
};

int luaopen_invoker(lua_State * L)
{
	global_urandfd = -1;

	luaL_register(L, "invoker", inv_regs);

	/* Add INVOKER_OUTPUT constant */
	lua_pushinteger(L, INVOKER_OUTPUT);
	lua_setfield(L, -2, "OUTPUT");

	/* Add INVOKER_NOWAIT constant */
	lua_pushinteger(L, INVOKER_NOWAIT);
	lua_setfield(L, -2, "NOWAIT");

	/* Add INVOKER_BUFSIZ constant */
	lua_pushinteger(L, INVOKER_BUFSIZ);
	lua_setfield(L, -2, "BUFSIZ");

	/* redirect stdio to /dev/null */
	lua_pushinteger(L, INVOKER_NOSTDIO);
	lua_setfield(L, -2, "NOSTDIO");

	/* strip whitespaces at the end of buffer */
	lua_pushinteger(L, INVOKER_TRIMEND);
	lua_setfield(L, -2, "TRIMEND");

	/* close unused file descriptor for child process */
	lua_pushinteger(L, INVOKER_CLOSEFD);
	lua_setfield(L, -2, "CLOSEFD");

	/* do not call fork */
	lua_pushinteger(L, INVOKER_NOFORK);
	lua_setfield(L, -2, "NOFORK");

	/* run application with very low priority */
	lua_pushinteger(L, INVOKER_LOWPRI);
	lua_setfield(L, -2, "LOWPRI");

	/* add flags for inotify system calls */
	lua_pushinteger(L, IN_ACCESS);
	lua_setfield(L, -2, "IN_ACCESS");

	lua_pushinteger(L, IN_ATTRIB);
	lua_setfield(L, -2, "IN_ATTRIB");

	lua_pushinteger(L, IN_CLOSE_WRITE);
	lua_setfield(L, -2, "IN_CLOSE_WRITE");

	lua_pushinteger(L, IN_CLOSE_NOWRITE);
	lua_setfield(L, -2, "IN_CLOSE_NOWRITE");

	lua_pushinteger(L, IN_CREATE);
	lua_setfield(L, -2, "IN_CREATE");

	lua_pushinteger(L, IN_DELETE);
	lua_setfield(L, -2, "IN_DELETE");

	lua_pushinteger(L, IN_DELETE_SELF);
	lua_setfield(L, -2, "IN_DELETE_SELF");

	lua_pushinteger(L, IN_MODIFY);
	lua_setfield(L, -2, "IN_MODIFY");

	lua_pushinteger(L, IN_MOVE_SELF);
	lua_setfield(L, -2, "IN_MOVE_SELF");

	lua_pushinteger(L, IN_MOVED_FROM);
	lua_setfield(L, -2, "IN_MOVED_FROM");

	lua_pushinteger(L, IN_MOVED_TO);
	lua_setfield(L, -2, "IN_MOVED_TO");

	lua_pushinteger(L, IN_OPEN);
	lua_setfield(L, -2, "IN_OPEN");

	lua_pushinteger(L, IN_ALL_EVENTS);
	lua_setfield(L, -2, "IN_ALL_EVENTS");

	lua_pushinteger(L, IN_DONT_FOLLOW);
	lua_setfield(L, -2, "IN_DONT_FOLLOW");

	lua_pushinteger(L, IN_MASK_ADD);
	lua_setfield(L, -2, "IN_MASK_ADD");

	lua_pushinteger(L, IN_ONESHOT);
	lua_setfield(L, -2, "IN_ONESHOT");

	lua_pushinteger(L, IN_ONLYDIR);
	lua_setfield(L, -2, "IN_ONLYDIR");

	return 1;
}
