/*
 * nixio - Linux I/O library for lua
 *
 *   Copyright (C) 2009 Steven Barth <steven@midlink.org>
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "nixio.h"
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>

#define NIXIO_EXECVE	0x01
#define NIXIO_EXECV		0x02
#define NIXIO_EXECVP	0x03

int nixio__exec(lua_State *L, int m) {
	const char *path = luaL_checkstring(L, 1);
	const char *arg;
	int argn, i;

	if (m == NIXIO_EXECVE) {
		luaL_checktype(L, 2, LUA_TTABLE);
		argn = lua_objlen(L, 2) + 1;
	} else {
		argn = lua_gettop(L);
	}

	char **args = lua_newuserdata(L, sizeof(char*) * (argn + 1));
	args[argn] = NULL;
	args[0] = (char *)path;

	if (m == NIXIO_EXECVE) {
		for (i = 1; i < argn; i++) {
			lua_rawgeti(L, 2, i);
			arg = lua_tostring(L, -1);
			luaL_argcheck(L, arg, 2, "invalid argument");
			args[i] = (char *)arg;
		}

		if (lua_isnoneornil(L, 3)) {
			execv(path, args);
		} else {
			luaL_checktype(L, 3, LUA_TTABLE);
			argn = 0;
			lua_pushnil(L);
			while (lua_next(L, 3)) {
				if (!lua_checkstack(L, 1)) {
					lua_settop(L, 0);
					return luaL_error(L, "stack overflow");
				}

				if (lua_type(L, -2) != LUA_TSTRING || !lua_isstring(L, -1)) {
					return luaL_argerror(L, 3, "invalid environment");
				}

				lua_pushfstring(L, "%s=%s",
						lua_tostring(L, -2), lua_tostring(L, -1));

				lua_insert(L, 5);
				lua_pop(L, 1);
				argn++;
			}

			char **env = lua_newuserdata(L, sizeof(char*) * (argn + 1));
			env[argn] = NULL;

			for (i = 1; i <= argn; i++) {
				env[i-1] = (char *)lua_tostring(L, -(i+1));
			}

			execve(path, args, env);
		}
	} else {
		for (i = 2; i <= argn; i++) {
			arg = luaL_checkstring(L, i);
			args[i-1] = (char *)arg;
		}

		if (m == NIXIO_EXECV) {
			execv(path, args);
		} else {
			execvp(path, args);
		}
	}

	return nixio__perror(L);
}

#ifndef __WINNT__
#include <sys/utsname.h>
#include <sys/times.h>
#include <sys/wait.h>
#include <pwd.h>
#include <grp.h>

static int nixio_fork(lua_State *L) {
	pid_t pid = fork();
	if (pid == -1) {
		return nixio__perror(L);
	} else {
		lua_pushinteger(L, pid);
		return 1;
	}
}

static int nixio_kill(lua_State *L) {
	return nixio__pstatus(L, !kill(luaL_checkint(L, 1), luaL_checkint(L, 2)));
}

static int nixio_getppid(lua_State *L) {
	lua_pushinteger(L, getppid());
	return 1;
}

static int nixio_getuid(lua_State *L) {
	lua_pushinteger(L, getuid());
	return 1;
}

static int nixio_getgid(lua_State *L) {
	lua_pushinteger(L, getgid());
	return 1;
}

static int nixio_setgid(lua_State *L) {
	return nixio__pstatus(L, !setgid(nixio__check_group(L, 1)));
}

static int nixio_setuid(lua_State *L) {
	return nixio__pstatus(L, !setuid(nixio__check_user(L, 1)));
}

static int nixio_nice(lua_State *L) {
	int nval = luaL_checkint(L, 1);

	errno = 0;
	nval = nice(nval);

	if (nval == -1 && errno) {
		return nixio__perror(L);
	} else {
		lua_pushinteger(L, nval);
		return 1;
	}
}

static int nixio_setsid(lua_State *L) {
	pid_t pid = setsid();

	if (pid == -1) {
		return nixio__perror(L);
	} else {
		lua_pushinteger(L, pid);
		return 1;
	}
}

static int nixio_wait(lua_State *L) {
	pid_t pidin = luaL_optinteger(L, 1, -1), pidout;
	int options = 0, status;

	const int j = lua_gettop(L);
	for (int i=2; i<=j; i++) {
		const char *flag = luaL_checkstring(L, i);
		if (!strcmp(flag, "nohang")) {
			options |= WNOHANG;
		} else if (!strcmp(flag, "untraced")) {
			options |= WUNTRACED;
		} else if (!strcmp(flag, "continued")) {
			options |= WCONTINUED;
		} else {
			return luaL_argerror(L, i,
					"supported values: nohang, untraced, continued");
		}
	}

	do {
		pidout = waitpid(pidin, &status, options);
	} while (pidout == -1 && errno == EINTR);

	if (pidout == 0) {
		lua_pushboolean(L, 0);
		return 1;
	} else if (pidout == -1) {
		return nixio__perror(L);
	} else {
		lua_pushinteger(L, pidout);
	}

	if (WIFEXITED(status)) {
		lua_pushliteral(L, "exited");
		lua_pushinteger(L, WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
    	lua_pushliteral(L, "signaled");
    	lua_pushinteger(L, WTERMSIG(status));
    } else if (WIFSTOPPED(status)) {
    	lua_pushliteral(L, "stopped");
    	lua_pushinteger(L, WSTOPSIG(status));
    } else {
    	return 1;
    }

    return 3;
}

static int nixio_times(lua_State *L) {
	struct tms buf;
	if (times(&buf) == -1) {
		return nixio__perror(L);
	} else {
		lua_createtable(L, 0, 4);
		nixio__pushnumber(L, buf.tms_cstime);
		lua_setfield(L, -2, "cstime");

		nixio__pushnumber(L, buf.tms_cutime);
		lua_setfield(L, -2, "cutime");

		nixio__pushnumber(L, buf.tms_stime);
		lua_setfield(L, -2, "stime");

		nixio__pushnumber(L, buf.tms_utime);
		lua_setfield(L, -2, "utime");

		return 1;
	}
}

static int nixio_uname(lua_State *L) {
	struct utsname buf;
	if (uname(&buf)) {
		return nixio__perror(L);
	}

	lua_createtable(L, 0, 5);

	lua_pushstring(L, buf.machine);
	lua_setfield(L, -2, "machine");

	lua_pushstring(L, buf.version);
	lua_setfield(L, -2, "version");

	lua_pushstring(L, buf.release);
	lua_setfield(L, -2, "release");

	lua_pushstring(L, buf.nodename);
	lua_setfield(L, -2, "nodename");

	lua_pushstring(L, buf.sysname);
	lua_setfield(L, -2, "sysname");

	return 1;
}

#endif /* !__WINNT__ */

static int nixio_chdir(lua_State *L) {
	return nixio__pstatus(L, !chdir(luaL_checkstring(L, 1)));
}

static int nixio_signal(lua_State *L) {
	int sig = luaL_checkinteger(L, 1);
	const char *val = luaL_checkstring(L, 2);

	if (!strcmp(val, "ign") || !strcmp(val, "ignore")) {
		return nixio__pstatus(L, signal(sig, SIG_IGN) != SIG_ERR);
	} else if (!strcmp(val, "dfl") || !strcmp(val, "default")) {
		return nixio__pstatus(L, signal(sig, SIG_DFL) != SIG_ERR);
	} else {
		return luaL_argerror(L, 2, "supported values: ign, dfl");
	}
}

static int nixio_getpid(lua_State *L) {
	lua_pushinteger(L, getpid());
	return 1;
}

static int nixio_getenv(lua_State *L) {
	const char *key = luaL_optstring(L, 1, NULL);
	if (key) {
		const char *val = getenv(key);
		if (val) {
			lua_pushstring(L, val);
		} else {
			lua_pushnil(L);
		}
	} else {
		lua_newtable(L);
		extern char **environ;
		for (char **c = environ; *c; c++) {
			const char *delim = strchr(*c, '=');
			if (!delim) {
				return luaL_error(L, "invalid environment");
			}
			lua_pushlstring(L, *c, delim-*c);
			lua_pushstring(L, delim + 1);
			lua_rawset(L, -3);
		}
	}
	return 1;
}

static int nixio_setenv(lua_State *L) {
	const char *key = luaL_checkstring(L, 1);
	const char *val = luaL_optstring(L, 2, NULL);
	return nixio__pstatus(L, (val) ? !setenv(key, val, 1) : !unsetenv(key));
}

static int nixio_exec(lua_State *L) {
	return nixio__exec(L, NIXIO_EXECV);
}

static int nixio_execp(lua_State *L) {
	return nixio__exec(L, NIXIO_EXECVP);
}

static int nixio_exece(lua_State *L) {
	return nixio__exec(L, NIXIO_EXECVE);
}

static int nixio_getcwd(lua_State *L) {
	char path[PATH_MAX];

	if (getcwd(path, sizeof(path))) {
		lua_pushstring(L, path);
		return 1;
	} else {
		return nixio__perror(L);
	}
}

static int nixio_umask(lua_State *L) {
	char mask[9];
	lua_pushinteger(L,
			nixio__mode_write(umask(nixio__check_mode(L, 1, -1)), mask));
	lua_pushlstring(L, mask, 9);
	return 2;
}

#ifdef __linux__

#include <sys/sysinfo.h>

static int nixio_sysinfo(lua_State *L) {
	struct sysinfo info;
	if (sysinfo(&info)) {
		return nixio__perror(L);
	}

	lua_createtable(L, 0, 12);

	nixio__pushnumber(L, info.bufferram);
	lua_setfield(L, -2, "bufferram");

	nixio__pushnumber(L, info.freehigh);
	lua_setfield(L, -2, "freehigh");

	nixio__pushnumber(L, info.freeram);
	lua_setfield(L, -2, "freeram");

	nixio__pushnumber(L, info.freeswap);
	lua_setfield(L, -2, "freeswap");

	lua_createtable(L, 0, 3);
	for (int i=0; i<3; i++) {
		lua_pushnumber(L, info.loads[i] / 65536.);
		lua_rawseti(L, -2, i+1);
	}
	lua_setfield(L, -2, "loads");

	lua_pushinteger(L, info.mem_unit);
	lua_setfield(L, -2, "mem_unit");

	lua_pushinteger(L, info.procs);
	lua_setfield(L, -2, "procs");

	nixio__pushnumber(L, info.sharedram);
	lua_setfield(L, -2, "sharedram");

	nixio__pushnumber(L, info.totalhigh);
	lua_setfield(L, -2, "totalhigh");

	nixio__pushnumber(L, info.totalram);
	lua_setfield(L, -2, "totalram");

	nixio__pushnumber(L, info.totalswap);
	lua_setfield(L, -2, "totalswap");

	lua_pushinteger(L, info.uptime);
	lua_setfield(L, -2, "uptime");

	return 1;
}

#endif


/* module table */
static const luaL_reg R[] = {
#ifdef __linux__
	{"sysinfo",		nixio_sysinfo},
#endif
#ifndef __WINNT__
	{"fork",		nixio_fork},
	{"kill",		nixio_kill},
	{"nice",		nixio_nice},
	{"getppid",		nixio_getppid},
	{"getuid",		nixio_getuid},
	{"getgid",		nixio_getgid},
	{"setuid",		nixio_setuid},
	{"setgid",		nixio_setgid},
	{"setsid",		nixio_setsid},
	{"wait",		nixio_wait},
	{"waitpid",		nixio_wait},
	{"times",		nixio_times},
	{"uname",		nixio_uname},
#endif
	{"chdir",		nixio_chdir},
	{"signal",		nixio_signal},
	{"getpid",		nixio_getpid},
	{"getenv",		nixio_getenv},
	{"setenv",		nixio_setenv},
	{"putenv",		nixio_setenv},
	{"exec",		nixio_exec},
	{"execp",		nixio_execp},
	{"exece",		nixio_exece},
	{"getcwd",		nixio_getcwd},
	{"umask",		nixio_umask},
	{NULL,			NULL}
};

void nixio_open_process(lua_State *L) {
	luaL_register(L, NULL, R);
}