/*
 * This file is part of Jehanne.
 *
 * Copyright (C) 2017 Giacomo Tesio <giacomo@tesio.it>
 *
 * This is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, version 3 of the License.
 *
 * Jehanne is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Jehanne.  If not, see <http://www.gnu.org/licenses/>.
 */

/* POSIX signals have weird semantics that are hard to emulate in a
 * sane system without giving up on its sanity.
 *
 * We distinguish control signals (SIGKILL, SIGSTOP, SIGTSTP, SIGCONT,
 * SIGABRT, SIGTTIN, SIGTTOU, SIGIOT), SIGCHLD/SIGCLD, timers' wakeups
 * (SIGALRM, SIGPROF, SIGVTALRM) and all the others.
 *
 * TRASMISSION
 * -----------
 * Signal transmission is done though a file server mounted /dev/posix/,
 * provided by sys/posixly. On startup and at each fork, processes
 * create a file named /dev/posix/signal with ORDWR mode and perm
 * equals to their pid. Writing and Reading such file they can
 * control signal dispatching, process groups and so on.
 *
 * When a signal is written to /dev/posix/signal, it is translated for
 * each receiver to a note, and written to the specific note file.
 *
 * If the receiver is a known process, the note is in the format
 * 	posix: si_signo si_pid si_uid si_value si_code
 * otherwise, if possible, it is translated to an idiomatic note
 * (eg "interrupt" or "alarm").
 *
 * Since notes in Jehanne are not reentrant, signals translated to
 * notes will be enqueued in kernel. A special machinery is implemented
 * for timers, so that they can be used in signal handlers to wakeup
 * the calling process.
 *
 * For all the signals except SIGCONT, the burden of interpreting the
 * signal is on the receiver: the sender just send the signal.
 *
 * The receiver will translate the note back to the appropriate signal
 * number and invoke the trampoline: if trampoline returns 0 no function
 * registered with signal() handled the signal and the library will 
 * invoke the default disposition associated to the signal.
 *
 * CONTROL MESSAGES
 * ----------------
 * Control messages are translated by the receiver to equivalent actions:
 * - SIGKILL => write "kill" to the control file of the current process
 * - SIGSTOP, SIGTSTP, SIGTTOU, SIGTTIN
 *            => write "stop" to the control file of the current process
 *               (when the signal is not handled nor ignored)
 * - SIGABRT/SIGIOT => invoke the registered signal handlers and abort
 *                     the current process
 * - SIGCONT => invoke the registered signal handlers via the signal
 *              trampoline and continue; note that (since a stopped
 *              process cannot resume itself) sys/posixly will write
 *              "start" to the control file of the receiver.
 *
 * SIGCHLD/SIGCLD
 * --------------
 * Jehanne (like Plan 9) does not support a SIGCHLD equivalent.
 * The user space emulation provided here is quite expensive, so it's
 * disabled by default.
 * Calling libposix_emulate_SIGCHLD() during libposix initialization
 * will enable this machinery for the whole life of the process.
 *
 * Such emulation changes the way POSIX_fork and POSIX_kill works.
 *
 * Each fork() will spawn an additional process that share the memory
 * of the parent, and waits for the child, so that it can send SIGCHLD:
 *
 *   parent
 *     +- nanny
 *          +- child
 *
 * Such "nannies" connect to sys/posixly by creating /dev/posix/nanny
 * so that the filesystem will handle them differently:
 * - any signal for the nanny sent from the parent is delivered to the child
 * - any signal for the nanny sent from the child is delivered to the parent
 * - any signal for the nanny sent from anybody else is delivered to the child
 *
 * Finally fork in child will return 0.
 *
 * When the child exits, the proxy will send a SIGCHLD sigchld to parent
 * and then exit with the same status.
 *
 * As the parent process will see the proxy as its child, it will send
 * any signal to it via kill or sigqueue and will wait it on SIGCHLD.
 *
 * TIMERS
 * ------
 * The functions alarm() and setitimer() generate SIGALRM, SIGPROF
 * or SIGVTALRM for the current process. We want timers to be able to
 * expire in a signal handler (interrupting a blocking syscall) but
 * without giving up the simplicity of notes.
 *
 * (TO BE IMPLEMENTED: )
 * We allocate these timers on libposix initialization. When normal
 * code is running timers will be implemented via Jehanne's alarms,
 * producing a note on expiration that will be mapped to the proper
 * signal for the trampoline by the receiving process.
 *
 * However, when a signal handler is being executed, the timers will
 * be implemented using the awake syscall that will be able
 * to interrupt blocking syscalls in the signal handler.
 * When a signal handler returns, if the timer has not be cleared and
 * did not expired, the wakeup is cleared and replaced with an alarm
 * note.
 */

#include <u.h>
#include <lib9.h>
#include <posix.h>
#include "internal.h"

int *__handling_external_signal;
int *__restart_syscall;
extern PosixSignalMask *__libposix_signal_mask;
extern int *__libposix_devsignal;

typedef union {
	PosixSignalInfo	signal;
	char		raw[sizeof(PosixSignalInfo)];
} SignalBuf;
typedef union {
	PosixSignalMask	signals;
	char		raw[sizeof(PosixSignalMask)];
} SigSetBuf;

static SignalConf signal_configurations[PosixNumberOfSignals];
SignalConf *__libposix_signals = signal_configurations;

typedef enum PosixSignalDisposition
{
	IgnoreWithNoEffect = 0,
	TerminateTheProcess,
	TerminateTheProcessAndCoreDump,
	StopTheProcess,
	ResumeTheProcess
} PosixSignalDisposition;

static PosixError
note_all_writable_processes(PosixSignalInfo* siginfo)
{
	// TODO: loop over writable note files and post note.
	return PosixEPERM;
}

static void
terminated_by_signal(int signo)
{
	char buf[64];

	__libposix_free_wait_list();

	snprint(buf, sizeof(buf), __POSIX_EXIT_SIGNAL_PREFIX "%d", signo);
	exits(buf);
}

void
__libposix_init_signal_handlers(void)
{
	__libposix_sighelper_set(PHIgnoreSignal, SIGNAL_MASK(PosixSIGCHLD));
	__libposix_sighelper_set(PHIgnoreSignal, SIGNAL_MASK(PosixSIGURG));
	__libposix_sighelper_set(PHIgnoreSignal, SIGNAL_MASK(PosixSIGWINCH));
	__libposix_signals[PosixSIGCHLD-1].handler = (void*)1;
	__libposix_signals[PosixSIGCHLD-1].sa_restart = 1;
	__libposix_signals[PosixSIGURG-1].handler = (void*)1;
	__libposix_signals[PosixSIGURG-1].sa_restart = 1;
	__libposix_signals[PosixSIGWINCH-1].handler = (void*)1;
	__libposix_signals[PosixSIGWINCH-1].sa_restart = 1;
}

int
__libposix_send_control_msg(int pid, char *msg)
{
	int fd, n;
	char buf[256];

	n = snprint(buf, sizeof(buf), "/proc/%d/ctl", pid);
	if(n < 0)
		goto ErrorBeforeOpen;
	fd = open(buf, OWRITE);
	if(fd < 0)
		goto ErrorBeforeOpen;
	n = snprint(buf, sizeof(buf), "%s", msg);
	if(n < 0)
		goto ErrorAfterOpen;
	if(write(fd, buf, n) < n)
		goto ErrorAfterOpen;
	close(fd);
	return 1;

ErrorAfterOpen:
	close(fd);
ErrorBeforeOpen:
	return 0;
}

/* Executes a PosixSignalDisposition.
 */
static int
execute_disposition(int signo, PosixSignalDisposition action)
{
	int aborted_by_signal;

	switch(action){
	case ResumeTheProcess:	// the sender resumed us already
	case IgnoreWithNoEffect:
		*__restart_syscall = 1;
		return 1;
	case TerminateTheProcess:
		terminated_by_signal(signo);
		break;
	case TerminateTheProcessAndCoreDump:
		aborted_by_signal = 0;
		assert(aborted_by_signal);
		break;
	case StopTheProcess:
		return __libposix_send_control_msg(*__libposix_pid, "stop");
	}
	return 0;
}

static PosixSignalDisposition
default_signal_disposition(PosixSignals signal)
{
	if(signal >= PosixSIGRTMIN && signal <= PosixSIGRTMAX)
		return TerminateTheProcess;

	switch(signal){
	default:
		sysfatal("libposix: undefined signal %d", signal);

	case PosixSIGALRM:
	case PosixSIGHUP:
	case PosixSIGINT:
	case PosixSIGKILL:
	case PosixSIGPIPE:
	case PosixSIGTERM:
	case PosixSIGUSR1:
	case PosixSIGUSR2:
	case PosixSIGPOLL:
	case PosixSIGPROF:
	case PosixSIGVTALRM:
		return TerminateTheProcess;
	case PosixSIGSTOP:
	case PosixSIGTSTP:
	case PosixSIGTTIN:
	case PosixSIGTTOU:
		return StopTheProcess;
	case PosixSIGABRT:
	case PosixSIGILL:
	case PosixSIGFPE:
	case PosixSIGBUS:
	case PosixSIGQUIT:
	case PosixSIGSEGV:
	case PosixSIGSYS:
	case PosixSIGTRAP:
	case PosixSIGXCPU:
	case PosixSIGXFSZ:
		return TerminateTheProcessAndCoreDump;
	case PosixSIGCHLD:
	case PosixSIGURG:
		return IgnoreWithNoEffect;
	case PosixSIGCONT:
		return ResumeTheProcess;
	}
}

/* returns 1 if the signal handling has been completed, 0 otherwise */
int
__libposix_run_signal_handler(SignalConf *c, PosixSignalInfo *siginfo)
{
	PosixSigHandler h;
	PosixSigAction a;
	PosixSignalMask m;

	switch((uintptr_t)c->handler){
	case 0:
		/* SIG_DFL */
		if(c->sa_restart)
			*__restart_syscall = 1;
		break;
	case 1:
		/* SIG_IGN */
		if(siginfo->si_signo == PosixSIGABRT)
			break;
		*__restart_syscall = 1;
		return 1;
	default:
		m = *__libposix_signal_mask;
		*__libposix_signal_mask |= c->mask;
		__libposix_sighelper_set(PHBlockSignals, *__libposix_signal_mask);
		a = c->handler;
		h = c->handler;

		if(c->sa_resethand)
			c->handler = 0;

		if(c->sa_siginfo){
			a(siginfo->si_signo, siginfo, nil);
		} else {
			h(siginfo->si_signo);
		}
		if(c->sa_restart)
			*__restart_syscall = 1;
		*__libposix_signal_mask = m;
		__libposix_sighelper_set(PHBlockSignals, *__libposix_signal_mask);
		if(siginfo->si_signo == PosixSIGABRT)
			break;
		return 1;
	}
	return 0;
}

PosixError
__libposix_receive_signal(PosixSignalInfo *siginfo)
{
	SignalConf *c;
	PosixSignalDisposition disposition;
	PosixSignals signo = siginfo->si_signo;

	if(signo == PosixSIGKILL || signo == PosixSIGSTOP)
		goto ExecuteDefaultDisposition;

	if(__libposix_signal_blocked(siginfo))
		return 0;

	c = __libposix_signals + (signo-1);
	if(__libposix_run_signal_handler(c, siginfo))
		return 0;

ExecuteDefaultDisposition:
	disposition = default_signal_disposition(signo);
	if(!execute_disposition(signo, disposition))
		return PosixEPERM;
	return 0;
}

long
__libposix_sighelper_signal(PosixHelperCommand command, int posix_process_pid, PosixSignalInfo *siginfo)
{
	union {
		PosixHelperRequest	request;
		long			raw;
	} offset;
	char buf[sizeof(PosixSignalInfo)];

	offset.request.command = command;
	offset.request.target = posix_process_pid;

	memcpy(buf, siginfo, sizeof(buf));

	return pwrite(*__libposix_devsignal, buf, sizeof(buf), offset.raw);
}

long
__libposix_sighelper_set(PosixHelperCommand command, PosixSignalMask signal_set)
{
	union {
		PosixHelperRequest	request;
		long			raw;
	} offset;
	union {
		PosixSignalMask	mask;
		char		raw[sizeof(PosixSignalMask)];
	} buffer;

	offset.request.command = command;
	offset.request.target = 0;

	buffer.mask = signal_set;

	return pwrite(*__libposix_devsignal, buffer.raw, sizeof(buffer.raw), offset.raw);
}

PosixError
__libposix_notify_signal_to_process(int pid, PosixSignalInfo *siginfo)
{
	long e = __libposix_sighelper_signal(PHSignalProcess, pid, siginfo);
	return (PosixError)e;
}

static PosixError
notify_signal_to_group(int pid, PosixSignalInfo* siginfo)
{
	long e = __libposix_sighelper_signal(PHSignalGroup, pid, siginfo);
	return (PosixError)e;
}

PosixError
__libposix_dispatch_signal(int pid, PosixSignalInfo* siginfo)
{
	PosixError error;
	switch(pid){
	case 0:
		return notify_signal_to_group(*__libposix_pid, siginfo);
	case -1:
		return note_all_writable_processes(siginfo);
	default:
		if(pid < 0)
			return notify_signal_to_group(-pid, siginfo);
		break;
	}
	error = __libposix_notify_signal_to_process(pid, siginfo);
	if(siginfo->si_signo == PosixSIGCONT && !__libposix_is_child(pid))
		__libposix_send_control_msg(pid, "start");
	return error;
}

static int
translate_jehanne_kernel_note(const char *note, PosixSignalInfo *siginfo)
{
	char *trap[3];
	char *tmp;

	assert(siginfo->si_signo == 0);

	if(strncmp("trap: fault ", note, 12) == 0){
		// trap: fault read addr=0x0 pc=0x400269
		note += 12;
		siginfo->si_signo = PosixSIGTRAP;
		tmp = strdup(note);
		if(getfields(tmp, trap, 3, 1, " ") == 3){
			if(trap[0][0] == 'r')
				siginfo->si_code = PosixSIFaultMapError;
			else
				siginfo->si_code = PosixSIFaultAccessError;
			siginfo->si_value._sival_raw = atoll(trap[1]+5);
		}
		free(tmp);
	} else if(strncmp("write on closed pipe", note, 20) == 0){
		// write on closed pipe pc=0x400269
		siginfo->si_signo = PosixSIGPIPE;
		note += 24;
		siginfo->si_value._sival_raw = atoll(note);
	} else if(strncmp("bad address in syscall", note, 22) == 0){
		// bad address in syscall pc=0x41cc54
		siginfo->si_signo = PosixSIGSEGV;
		note += 26;
		siginfo->si_value._sival_raw = atoll(note);
	}
	// TODO: implement

	return siginfo->si_signo == 0 ? 0 : 1;
}

static int
translate_jehanne_note(const char *note, PosixSignalInfo *siginfo)
{
	if(note == nil || note[0] == 0)
		return 0;

	if(strncmp("alarm", note, 5) == 0){
		siginfo->si_signo = PosixSIGALRM;
		return 1;
	}
	if(strncmp("sys: ", note, 5) == 0)
		return translate_jehanne_kernel_note(note + 5, siginfo);
	if(strncmp("interrupt", note, 9) == 0){
		siginfo->si_signo = PosixSIGINT;
		return 1;
	}
	if(strncmp("hangup", note, 6) == 0){
		siginfo->si_signo = PosixSIGHUP;
		return 1;
	}

	return 0;
}

int
__libposix_signal_to_note(const PosixSignalInfo *si, char *buf, int size)
{
	return
	snprint(buf, size, __POSIX_SIGNAL_PREFIX "%d %d %d %#p %d",
		si->si_signo, si->si_pid, si->si_uid,
		si->si_value._sival_raw, si->si_code);
}

/* The format of a note sent by libposix as a signal is
 *
 *	"posix: %d %d %d %#p %d", signo, pid, uid, value, code
 */
int
__libposix_note_to_signal(const char *note, PosixSignalInfo *siginfo)
{
	assert(siginfo->si_signo == 0); /* siginfo must be zeroed */
	if(strncmp(note, __POSIX_SIGNAL_PREFIX, __POSIX_SIGNAL_PREFIX_LEN) != 0)
		return translate_jehanne_note(note, siginfo);
	char *rest = (char*)note + __POSIX_SIGNAL_PREFIX_LEN;
	if(*rest == 0)
		return 0;
	siginfo->si_signo = strtol(rest, &rest, 0);
	if(*rest == 0)
		return 1;
	siginfo->si_pid = strtol(rest, &rest, 0);
	if(*rest == 0)
		return 1;
	siginfo->si_uid = strtoul(rest, &rest, 0);
	if(*rest == 0)
		return 1;
	siginfo->si_value._sival_raw = strtoull(rest, &rest, 0);
	if(*rest == 0)
		return 1;
	siginfo->si_code = strtoul(rest, &rest, 0);
	return 1;
}

int
__libposix_note_handler(void *ureg, char *note)
{
	PosixSignalInfo siginfo;
	PosixError error;

	memset(&siginfo, 0, sizeof(PosixSignalInfo));

	if(!__libposix_note_to_signal(note, &siginfo)
	&& (siginfo.si_signo < 1 || siginfo.si_signo > PosixNumberOfSignals))
		sysfatal("libposix: '%s' does not carry a signal", note);
	*__handling_external_signal = 1;
	error = __libposix_receive_signal(&siginfo);
	*__handling_external_signal = 0;
	werrstr("interrupted");
	return error == 0;
}

int
__libposix_restart_syscall(void)
{
	int r;

	if(*__handling_external_signal)
		return 0;

	r = *__restart_syscall;
	*__restart_syscall = 0;
	return r;
}

int
POSIX_sigaction(int *errnop, int signo, const struct sigaction *act, struct sigaction *old)
{
	SignalConf *c, oconf;

	if(signo < 1 || signo > PosixNumberOfSignals)
		goto FailWithEINVAL;

	c = __libposix_signals + (signo-1);

	if(old)
		memcpy(&oconf, c, sizeof(SignalConf));

	if(act){
		memset(c, 0, sizeof(SignalConf));
		if(signo == PosixSIGKILL || signo == PosixSIGSTOP)
			goto FailWithEINVAL;
		if(act->sa_flags & PosixSAFSigInfo){
			c->sa_siginfo = 1;
			c->handler = act->sa_sigaction;
		} else {
			c->handler = act->sa_handler;
		}
		c->mask = act->sa_mask & ~((255UL<<56) | SIGNAL_MASK(PosixSIGKILL) | SIGNAL_MASK(PosixSIGSTOP));
		if(act->sa_flags & PosixSAFResetHandler)
			c->sa_resethand = 1;
		else
			c->sa_resethand = 0;
		if(act->sa_flags & PosixSAFRestart)
			c->sa_restart = 1;
		else
			c->sa_restart = 0;
		if(signo == PosixSIGCHLD){
			if(act->sa_flags & PosixSAFNoChildrenWait)
				c->sa_nochildwait = 1;
			else
				c->sa_nochildwait = 0;
			if(c->handler == (void*)1) /* SIGCHLD && SIG_IGN => SA_NOCLDWAIT */
				c->sa_nochildwait = 1;
		}
		if(c->handler == (void*)1){
			/* ignore signal */
			__libposix_sighelper_set(PHIgnoreSignal, SIGNAL_MASK(signo));
		} else if(c->handler == (void*)0){
			/* default behavior */
			switch(signo){
			case PosixSIGCHLD:
			case PosixSIGURG:
			case PosixSIGWINCH:
				__libposix_sighelper_set(PHIgnoreSignal, SIGNAL_MASK(signo));
				break;
			default:
				__libposix_sighelper_set(PHEnableSignal, SIGNAL_MASK(signo));
				break;
			}
		} else {
			/* do not ignore signal */
			__libposix_sighelper_set(PHEnableSignal, SIGNAL_MASK(signo));
		}
	}

	if(old){
		if(oconf.sa_siginfo){
			old->sa_sigaction = oconf.handler;
			old->sa_mask = oconf.mask;
			old->sa_flags = 0;
			if(oconf.sa_siginfo)
				old->sa_flags |= PosixSAFSigInfo;
			if(oconf.sa_resethand)
				old->sa_flags |= PosixSAFResetHandler;
			if(oconf.sa_restart)
				old->sa_flags |= PosixSAFRestart;
			if(oconf.sa_nochildwait)
				old->sa_flags |= PosixSAFNoChildrenWait;
		} else {
			old->sa_handler = oconf.handler;
			old->sa_flags = 0;
			old->sa_mask = 0;
		}
	}
	return 0;

FailWithEINVAL:
	*errnop = __libposix_get_errno(PosixEINVAL);
	return -1;
}
