/*
 * POSIX library for Lua 5.1, 5.2 & 5.3.
 * (c) Gary V. Vaughan <gary@vaughan.pe>, 2013-2015
 * (c) Reuben Thomas <rrt@sc3d.org> 2010-2013
 * (c) Natanael Copa <natanael.copa@gmail.com> 2008-2010
 * Clean up and bug fixes by Leo Razoumov <slonik.az@gmail.com> 2006-10-11
 * Luiz Henrique de Figueiredo <lhf@tecgraf.puc-rio.br> 07 Apr 2006 23:17:49
 * Based on original by Claudio Terra for Lua 3.x.
 * With contributions by Roberto Ierusalimschy.
 * With documentation from Steve Donovan 2012
 */
/***
 Software Signal Facilities.

 Constants and functions for propagating signals among processes.

 Note that `posix.signal.signal` is implemented with sigaction(2) for
 consistent semantics across platforms.

@module posix.signal
*/

#include <config.h>

#include <signal.h>

#include "_helpers.c"


/***
Send a signal to the given process.
@function kill
@int pid process to act on
@int[opt=`SIGTERM` sig signal to send
@treturn[1] int `0`, if successful
@return[2] nil
@treturn[2] string error message
@treturn[2] int errnum
@see kill(2)
*/
static int
Pkill(lua_State *L)
{
	pid_t pid = checkint(L, 1);
	int sig = optint(L, 2, SIGTERM);
	checknargs(L, 2);
	return pushresult(L, kill(pid, sig), NULL);
}


/***
Send a signal to the given process group.
@function killpg
@int pgrp group id to act on, or `0` for the sending process`s group
@int[opt=`SIGTERM`] sig signal to send
@treturn[1] int `0`, if successful
@return[2] nil
@treturn[2] string error message
@treturn[2] int errnum
@see killpg(2)
*/
static int
Pkillpg(lua_State *L)
{
	int pgrp = checkint(L, 1);
	int sig = optint(L, 2, SIGTERM);
	checknargs(L, 2);
	return pushresult(L, killpg(pgrp, sig), NULL);
}


/***
Raise a signal on this process.
@function raise
@int sig signal to send
@treturn[1] int `0`, if successful
@return[2] nil
@treturn[2] string error message
@treturn[2] int errnum
@see raise(3)
*/
static int
Praise(lua_State *L)
{
	int sig = checkint(L, 1);
	checknargs(L, 1);
	lua_pop(L, 1);
	return pushintresult(raise(sig));
}


static lua_State *signalL;

#define SIGNAL_QUEUE_MAX 25
static volatile sig_atomic_t signal_pending, defer_signal;
static volatile sig_atomic_t signal_count = 0;
static volatile sig_atomic_t signals[SIGNAL_QUEUE_MAX];

#define sigmacros_map \
	MENTRY( _DFL ) \
	MENTRY( _IGN )

static const char *const Ssigmacros[] =
{
#define MENTRY(_s) LPOSIX_STR_1(LPOSIX_SPLICE(_SIG, _s)),
	sigmacros_map
#undef MENTRY
	NULL
};

static void (*Fsigmacros[])(int) =
{
#define MENTRY(_s) LPOSIX_SPLICE(SIG, _s),
	sigmacros_map
#undef MENTRY
	NULL
};


static void
sig_handle (lua_State *L, lua_Debug *LPOSIX_UNUSED (ar))
{
	/* Block all signals until we have run the Lua signal handler */
	sigset_t mask, oldmask;
	sigfillset(&mask);
	sigprocmask(SIG_SETMASK, &mask, &oldmask);

	lua_sethook(L, NULL, 0, 0);

	/* Get signal handlers table */
	lua_pushlightuserdata(L, &signalL);
	lua_rawget(L, LUA_REGISTRYINDEX);

	/* Empty the signal queue */
	while (signal_count--)
	{
		sig_atomic_t signalno = signals[signal_count];
		/* Get handler */
		lua_pushinteger(L, signalno);
		lua_gettable(L, -2);

		/* Call handler with signal number */
		lua_pushinteger(L, signalno);
		if (lua_pcall(L, 1, 0, 0) != 0)
			fprintf(stderr,"error in signal handler %ld: %s\n", (long)signalno, lua_tostring(L,-1));
	}
	signal_count = 0;  /* reset global to initial state */

	/* Having run the Lua signal handler, restore original signal mask */
	sigprocmask(SIG_SETMASK, &oldmask, NULL);
}


static void
sig_postpone (int i)
{
	if (defer_signal)
	{
		signal_pending = i;
		return;
	}
	if (signal_count == SIGNAL_QUEUE_MAX)
		return;
	defer_signal++;
	/* Queue signals */
	signals[signal_count] = i;
	signal_count ++;
	lua_sethook(signalL, sig_handle, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1);
	defer_signal--;
	/* re-raise any pending signals */
	if (defer_signal == 0 && signal_pending != 0)
		raise (signal_pending);
}


static int
sig_handler_wrap (lua_State *L)
{
	int sig = luaL_checkinteger(L, lua_upvalueindex(1));
	void (*handler)(int) = lua_touserdata(L, lua_upvalueindex(2));
	handler(sig);
	return 0;
}


/***
Install a signal handler for this signal number.
Although this is the same API as signal(2), it uses sigaction for guaranteed semantics.
@function signal
@see signal.lua
@int signum
@tparam[opt=SIG_DFL] function handler function, or `SIG_IGN` or `SIG_DFL` constants
@param[opt] flags the `sa_flags` element of `struct sigaction`
@treturn function previous handler function
@see sigaction(2)
*/
static int
Psignal (lua_State *L)
{
	struct sigaction sa, oldsa;
	int sig = checkint(L, 1), ret;
	void (*handler)(int) = sig_postpone;

	checknargs(L, 3);

	/* Check handler is OK */
	switch (lua_type(L, 2))
	{
		case LUA_TNIL:
		case LUA_TSTRING:
			handler = Fsigmacros[luaL_checkoption(L, 2, "SIG_DFL", Ssigmacros)];
			break;
		case LUA_TFUNCTION:
			if (lua_tocfunction(L, 2) == sig_handler_wrap)
			{
				lua_getupvalue(L, 2, 1);
				handler = lua_touserdata(L, -1);
				lua_pop(L, 1);
			}
			break;
		default:
			argtypeerror(L, 2, "function, string or nil");
			break;
	}

	/* Set up C signal handler, getting old handler */
	sa.sa_handler = handler;
	sa.sa_flags = optint(L, 3, 0);
	sigfillset(&sa.sa_mask);
	ret = sigaction(sig, &sa, &oldsa);
	if (ret == -1)
		return 0;

	/* Set Lua handler if necessary */
	if (handler == sig_postpone)
	{
		lua_pushlightuserdata(L, &signalL); /* We could use an upvalue, but we need this for sig_handle anyway. */
		lua_rawget(L, LUA_REGISTRYINDEX);
		lua_pushvalue(L, 1);
		lua_pushvalue(L, 2);
		lua_rawset(L, -3);
		lua_pop(L, 1);
	}

	/* Push old handler as result */
	if (oldsa.sa_handler == sig_postpone)
	{
		lua_pushlightuserdata(L, &signalL);
		lua_rawget(L, LUA_REGISTRYINDEX);
		lua_pushvalue(L, 1);
		lua_rawget(L, -2);
	} else if (oldsa.sa_handler == SIG_DFL)
		lua_pushstring(L, "SIG_DFL");
	else if (oldsa.sa_handler == SIG_IGN)
		lua_pushstring(L, "SIG_IGN");
	else
	{
		lua_pushinteger(L, sig);
		lua_pushlightuserdata(L, oldsa.sa_handler);
		lua_pushcclosure(L, sig_handler_wrap, 2);
	}
	return 1;
}


static const luaL_Reg posix_signal_fns[] =
{
	LPOSIX_FUNC( Pkill		),
	LPOSIX_FUNC( Pkillpg		),
	LPOSIX_FUNC( Praise		),
	LPOSIX_FUNC( Psignal		),
	{NULL, NULL}
};


/***
Constants.
@section constants
*/

/***
Signal constants.
Any constants not available in the underlying system will be `nil` valued.
@table posix.signal
@int SIGABRT abort ()
@int SIGALRM alarm clock
@int SIGBUS bus error
@int SIGCHLD to parent on child stop or exit
@int SIGCONT continue a stopped process
@int SIGFPE floating point error
@int SIGHUP hangup
@int SIGILL illegal instruction
@int SIGINT interrupt
@int SIGKILL kill
@int SIGPIPE write on pipe with no reader
@int SIGQUIT quit
@int SIGSEGV segmentation violation
@int SIGSTOP stop
@int SIGTERM terminate
@int SIGTSTP stop signal from tty
@int SIGTTIN to readers process group on background tty read
@int SIGTTOU to readers process group on background tty output
@int SIGUSR1 user defined
@int SIGUSR2 user defined
@int SIGSYS bad argument to system call
@int SIGTRAP trace trap
@int SIGURG urgent condition on i/o channel
@int SIGVTALRM virtual time alarm
@int SIGXCPU exceeded cpu time limit
@int SIGXFSZ exceeded file size limit
@int SA_NOCLDSTOP do not generate a SIGCHLD on child stop
@int SA_NOCLDWAIT don't keep zombies child processes
@int SA_RESETHAND reset to SIG_DFL when taking a signal
@int SA_NODEFER don't mask the signal we're delivering
@usage
  -- Print signal constants supported on this host.
  for name, value in pairs (require "posix.signal") do
    if type (value) == "number" then
      print (name, value)
     end
  end
*/

LUALIB_API int
luaopen_posix_signal(lua_State *L)
{
	luaL_register(L, "posix.signal", posix_signal_fns);
	lua_pushliteral(L, "posix.signal for " LUA_VERSION " / " PACKAGE_STRING);
	lua_setfield(L, -2, "version");

	/* Signals table stored in registry for Psignal and sig_handle */
	lua_pushlightuserdata(L, &signalL);
	lua_newtable(L);
	lua_rawset(L, LUA_REGISTRYINDEX);

	signalL = L; /* For sig_postpone */

	/* Signals */
#ifdef SIGABRT
	LPOSIX_CONST( SIGABRT		);
#endif
#ifdef SIGALRM
	LPOSIX_CONST( SIGALRM		);
#endif
#ifdef SIGBUS
	LPOSIX_CONST( SIGBUS		);
#endif
#ifdef SIGCHLD
	LPOSIX_CONST( SIGCHLD		);
#endif
#ifdef SIGCONT
	LPOSIX_CONST( SIGCONT		);
#endif
#ifdef SIGFPE
	LPOSIX_CONST( SIGFPE		);
#endif
#ifdef SIGHUP
	LPOSIX_CONST( SIGHUP		);
#endif
#ifdef SIGILL
	LPOSIX_CONST( SIGILL		);
#endif
#ifdef SIGINT
	LPOSIX_CONST( SIGINT		);
#endif
#ifdef SIGKILL
	LPOSIX_CONST( SIGKILL		);
#endif
#ifdef SIGPIPE
	LPOSIX_CONST( SIGPIPE		);
#endif
#ifdef SIGQUIT
	LPOSIX_CONST( SIGQUIT		);
#endif
#ifdef SIGSEGV
	LPOSIX_CONST( SIGSEGV		);
#endif
#ifdef SIGSTOP
	LPOSIX_CONST( SIGSTOP		);
#endif
#ifdef SIGTERM
	LPOSIX_CONST( SIGTERM		);
#endif
#ifdef SIGTSTP
	LPOSIX_CONST( SIGTSTP		);
#endif
#ifdef SIGTTIN
	LPOSIX_CONST( SIGTTIN		);
#endif
#ifdef SIGTTOU
	LPOSIX_CONST( SIGTTOU		);
#endif
#ifdef SIGUSR1
	LPOSIX_CONST( SIGUSR1		);
#endif
#ifdef SIGUSR2
	LPOSIX_CONST( SIGUSR2		);
#endif
#ifdef SIGSYS
	LPOSIX_CONST( SIGSYS		);
#endif
#ifdef SIGTRAP
	LPOSIX_CONST( SIGTRAP		);
#endif
#ifdef SIGURG
	LPOSIX_CONST( SIGURG		);
#endif
#ifdef SIGVTALRM
	LPOSIX_CONST( SIGVTALRM	);
#endif
#ifdef SIGXCPU
	LPOSIX_CONST( SIGXCPU		);
#endif
#ifdef SIGXFSZ
	LPOSIX_CONST( SIGXFSZ		);
#endif

	/* String constants */
	lua_pushliteral(L, "SIG_DFL");
	lua_setfield(L, -2, "SIG_DFL");

	lua_pushliteral(L, "SIG_IGN");
	lua_setfield(L, -2, "SIG_IGN");


	/* Signal flags */
#ifdef SA_NOCLDSTOP
	LPOSIX_CONST( SA_NOCLDSTOP	);
#endif
#ifdef SA_NOCLDWAIT
	LPOSIX_CONST( SA_NOCLDWAIT	);
#endif
#ifdef SA_RESETHAND
	LPOSIX_CONST( SA_RESETHAND	);
#endif
#ifdef SA_NODEFER
	LPOSIX_CONST( SA_NODEFER	);
#endif

	return 1;
}
