/*	$NetBSD: linux_signal.c,v 1.3 1995/03/10 22:55:04 fvdl Exp $	*/

/*
 * Copyright (c) 1995 Frank van der Linden
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed for the NetBSD Project
 *      by Frank van der Linden
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * heavily from: svr4_signal.c,v 1.7 1995/01/09 01:04:21 christos Exp
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/namei.h>
#include <sys/proc.h>
#include <sys/filedesc.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/kernel.h>
#include <sys/signal.h>
#include <sys/signalvar.h>
#include <sys/malloc.h>

#include <sys/syscallargs.h>

#include <compat/linux/linux_types.h>
#include <compat/linux/linux_syscallargs.h>
#include <compat/linux/linux_util.h>
#include <compat/linux/linux_signal.h>

/*
 * Most of ths stuff in this file is taken from Christos' SVR4 emul
 * code. The things that need to be done are largely the same, so
 * re-inventing the wheel doesn't make much sense.
 */

/*
 * Some boring signal conversion functions. Just a switch() for all signals;
 * return the converted signal number, 0 if not supported.
 */

int
bsd_to_linux_sig(sig)
	int sig;
{
	switch(sig) {
	case SIGHUP:
		return LINUX_SIGHUP;
	case SIGINT:
		return LINUX_SIGINT;
	case SIGQUIT:
		return LINUX_SIGQUIT;
	case SIGILL:
		return LINUX_SIGILL;
	case SIGTRAP:
		return LINUX_SIGTRAP;
	case SIGABRT:
		return LINUX_SIGABRT;
	case SIGFPE:
		return LINUX_SIGFPE;
	case SIGKILL:
		return LINUX_SIGKILL;
	case SIGBUS:
		return LINUX_SIGBUS;
	case SIGSEGV:
		return LINUX_SIGSEGV;
	case SIGPIPE:
		return LINUX_SIGPIPE;
	case SIGALRM:
		return LINUX_SIGALRM;
	case SIGTERM:
		return LINUX_SIGTERM;
	case SIGURG:
		return LINUX_SIGURG;
	case SIGSTOP:
		return LINUX_SIGSTOP;
	case SIGTSTP:
		return LINUX_SIGTSTP;
	case SIGCONT:
		return LINUX_SIGCONT;
	case SIGCHLD:
		return LINUX_SIGCHLD;
	case SIGTTIN:
		return LINUX_SIGTTIN;
	case SIGTTOU:
		return LINUX_SIGTTOU;
	case SIGIO:
		return LINUX_SIGIO;
	case SIGXCPU:
		return LINUX_SIGXCPU;
	case SIGXFSZ:
		return LINUX_SIGXFSZ;
	case SIGVTALRM:
		return LINUX_SIGVTALRM;
	case SIGPROF:
		return LINUX_SIGPROF;
	case SIGWINCH:
		return LINUX_SIGWINCH;
	case SIGUSR1:
		return LINUX_SIGUSR1;
	case SIGUSR2:
		return LINUX_SIGUSR2;
	/* Not supported: EMT, SYS, INFO */
	}
	return 0;
}

int
linux_to_bsd_sig(sig)
	int sig;
{
	switch(sig) {
	case LINUX_SIGHUP:
		return SIGHUP;
	case LINUX_SIGINT:
		return SIGINT;
	case LINUX_SIGQUIT:
		return SIGQUIT;
	case LINUX_SIGILL:
		return SIGILL;
	case LINUX_SIGTRAP:
		return SIGTRAP;
	case LINUX_SIGABRT:
		return SIGABRT;
	case LINUX_SIGBUS:
		return SIGBUS;
	case LINUX_SIGFPE:
		return SIGFPE;
	case LINUX_SIGKILL:
		return SIGKILL;
	case LINUX_SIGUSR1:
		return SIGUSR1;
	case LINUX_SIGSEGV:
		return SIGSEGV;
	case LINUX_SIGUSR2:
		return SIGUSR2;
	case LINUX_SIGPIPE:
		return SIGPIPE;
	case LINUX_SIGALRM:
		return SIGALRM;
	case LINUX_SIGTERM:
		return SIGTERM;
	case LINUX_SIGCHLD:
		return SIGCHLD;
	case LINUX_SIGCONT:
		return SIGCONT;
	case LINUX_SIGSTOP:
		return SIGSTOP;
	case LINUX_SIGTSTP:
		return SIGTSTP;
	case LINUX_SIGTTIN:
		return SIGTTIN;
	case LINUX_SIGTTOU:
		return SIGTTOU;
	case LINUX_SIGURG:
		return SIGURG;
	case LINUX_SIGXCPU:
		return SIGXCPU;
	case LINUX_SIGXFSZ:
		return SIGXFSZ;
	case LINUX_SIGVTALRM:
		return SIGVTALRM;
	case LINUX_SIGPROF:
		return SIGPROF;
	case LINUX_SIGWINCH:
		return SIGWINCH;
	case LINUX_SIGIO:
		return SIGIO;
	/* Not supported: STKFLT, PWR */
	}
	return 0;
}

/*
 * Ok, we know that Linux and BSD signals both are just an unsigned int.
 * Don't bother to use the sigismember() stuff for now.
 */
static void
linux_to_bsd_sigset(lss, bss)
	const linux_sigset_t *lss;
	sigset_t *bss;
{
	int i, newsig;

	*bss = (sigset_t) 0;
	for (i = 1; i <= LINUX_NSIG; i++) {
		if (*lss & sigmask(i)) {
			newsig = linux_to_bsd_sig(i);
			if (newsig)
				*bss |= sigmask(newsig);
		}
	}
}

void
bsd_to_linux_sigset(bss, lss)
	const sigset_t *bss;
	linux_sigset_t *lss;
{
	int i, newsig;
	
	*lss = (linux_sigset_t) 0;
	for (i = 1; i <= NSIG; i++) {
		if (*bss & sigmask(i)) {
			newsig = bsd_to_linux_sig(i);
			if (newsig)
				*lss |= sigmask(newsig);
		}
	}
}

/*
 * Convert between Linux and BSD sigaction structures. Linux has
 * one extra field (sa_restorer) which we don't support. The Linux
 * SA_ONESHOT and SA_NOMASK flags (which together form the old
 * SysV signal behavior) are silently ignored. XXX
 */
void
linux_to_bsd_sigaction(lsa, bsa)
	struct linux_sigaction *lsa;
	struct sigaction *bsa;
{
	bsa->sa_handler = lsa->sa_handler;
	linux_to_bsd_sigset(&bsa->sa_mask, &lsa->sa_mask);
	bsa->sa_flags = 0;
	bsa->sa_flags |= cvtto_bsd_mask(lsa->sa_flags, LINUX_SA_NOCLDSTOP,
	    SA_NOCLDSTOP);
	bsa->sa_flags |= cvtto_bsd_mask(lsa->sa_flags, LINUX_SA_ONSTACK,
	    SA_ONSTACK);
	bsa->sa_flags |= cvtto_bsd_mask(lsa->sa_flags, LINUX_SA_RESTART,
	    SA_RESTART);
}

void
bsd_to_linux_sigaction(bsa, lsa)
	struct sigaction *bsa;
	struct linux_sigaction *lsa;
{
	lsa->sa_handler = bsa->sa_handler;
	bsd_to_linux_sigset(&lsa->sa_mask, &bsa->sa_mask);
	lsa->sa_flags = 0;
	lsa->sa_flags |= cvtto_linux_mask(bsa->sa_flags, SA_NOCLDSTOP,
	    LINUX_SA_NOCLDSTOP);
	lsa->sa_flags |= cvtto_linux_mask(bsa->sa_flags, SA_ONSTACK,
	    LINUX_SA_ONSTACK);
	lsa->sa_flags |= cvtto_linux_mask(bsa->sa_flags, SA_RESTART,
	    LINUX_SA_RESTART);
	lsa->sa_restorer = NULL;
}


/*
 * The Linux sigaction() system call. Do the usual conversions,
 * and just call sigaction(). Some flags and values are silently
 * ignored (see above).
 */
int
linux_sigaction(p, uap, retval)
	register struct proc *p;
	struct linux_sigaction_args /* {
		syscallarg(int) signum;
		syscallarg(struct linux_sigaction *) nsa;
		syscallarg(struct linux_sigaction *) osa;
	} */ *uap;
	register_t *retval;
{
	struct sigaction *nbsda = NULL, *obsda = NULL, tmpbsda;
	struct linux_sigaction *nlsa, *olsa, tmplsa;
	struct sigaction_args sa;
	caddr_t sg;
	int error;

	sg = stackgap_init();
	olsa = SCARG(uap, osa);
	nlsa = SCARG(uap, nsa);

	if (olsa != NULL)
		obsda = stackgap_alloc(&sg, sizeof (struct sigaction));

	if (nlsa != NULL) {
		nbsda = stackgap_alloc(&sg, sizeof (struct sigaction));
		if ((error = copyin(nlsa, &tmplsa, sizeof tmplsa)))
			return error;
		linux_to_bsd_sigaction(&tmplsa, &tmpbsda);
		if ((error = copyout(&tmpbsda, nbsda, sizeof tmpbsda)))
			return error;
	}

	SCARG(&sa, signum) = linux_to_bsd_sig(SCARG(uap, signum));
	SCARG(&sa, nsa) = nbsda;
	SCARG(&sa, osa) = obsda;

	if ((error = sigaction(p, &sa, retval)))
		return error;

	if (olsa != NULL) {
		if ((error = copyin(obsda, &tmpbsda, sizeof tmpbsda)))
			return error;
		bsd_to_linux_sigaction(&tmpbsda, &tmplsa);
		if ((error = copyout(&tmplsa, olsa, sizeof tmplsa)))
			return error;
	}
	return 0;
}

/*
 * The Linux signal() system call. I think that the signal() in the C
 * library actually calls sigaction, so I doubt this one is ever used.
 * But hey, it can't hurt having it here. The same restrictions as for
 * sigaction() apply.
 */
int
linux_signal(p, uap, retval)
	register struct proc *p;
	struct linux_signal_args /* {
		syscallarg(int) sig;
		syscallarg(linux_handler_t) handler;
	} */ *uap;
	register_t *retval;
{
	caddr_t sg;
	struct sigaction_args sa_args;
	struct sigaction *osa, *nsa, tmpsa;
	int error;

	sg = stackgap_init();
	nsa = stackgap_alloc(&sg, sizeof *nsa);
	osa = stackgap_alloc(&sg, sizeof *osa);

	tmpsa.sa_handler = SCARG(uap, handler);
	tmpsa.sa_mask = (sigset_t) 0;
	tmpsa.sa_flags = 0;
	if ((error = copyout(&tmpsa, nsa, sizeof tmpsa)))
		return error;

	SCARG(&sa_args, signum) = linux_to_bsd_sig(SCARG(uap, sig));
	SCARG(&sa_args, osa) = osa;
	SCARG(&sa_args, nsa) = nsa;
	if ((error = sigaction(p, &sa_args, retval)))
		return error;

	if ((error = copyin(osa, &tmpsa, sizeof *osa)))
		return error;
	retval[0] = (register_t) tmpsa.sa_handler;

	return 0;
}

/*
 * This is just a copy of the svr4 compat one. I feel so creative now.
 */
int
linux_sigprocmask(p, uap, retval)
	register struct proc *p;
	register struct linux_sigprocmask_args /* {
		syscallarg(int) how;
		syscallarg(linux_sigset_t *) set;
		syscallarg(linux_sigset_t * oset;
	} */ *uap;
	register_t *retval;
{
	linux_sigset_t ss;
	sigset_t bs;
	int error = 0;

	*retval = 0;

	if (SCARG(uap, oset) != NULL) {
		/* Fix the return value first if needed */
		bsd_to_linux_sigset(&p->p_sigmask, &ss);
		if ((error = copyout(&ss, SCARG(uap, oset), sizeof(ss))) != 0)
			return error;
	}

	if (SCARG(uap, set) == NULL)
		/* Just examine */
		return 0;

	if ((error = copyin(SCARG(uap, set), &ss, sizeof(ss))) != 0)
		return error;

	linux_to_bsd_sigset(&ss, &bs);

	(void) splhigh();

	switch (SCARG(uap, how)) {
	case LINUX_SIG_BLOCK:
		p->p_sigmask |= bs & ~sigcantmask;
		break;

	case LINUX_SIG_UNBLOCK:
		p->p_sigmask &= ~bs;
		break;

	case LINUX_SIG_SETMASK:
		p->p_sigmask = bs & ~sigcantmask;
		break;

	default:
		error = EINVAL;
		break;
	}

	(void) spl0();

	return error;
}

/*
 * The functions below really make no distinction between an int
 * and [linux_]sigset_t. This is ok for now, but it might break
 * sometime. Then again, sigset_t is trusted to be an int everywhere
 * else in the kernel too.
 */
/* ARGSUSED */
int
linux_siggetmask(p, uap, retval)
	struct proc *p;
	void *uap;
	register_t *retval;
{
	bsd_to_linux_sigset(&p->p_sigmask, (linux_sigset_t *) retval);
	return 0;
}

/*
 * The following three functions fiddle with a process' signal mask.
 * Convert the signal masks because of the different signal
 * values for Linux. The need for this is the reason why
 * they are here, and have not been mapped directly.
 */
int
linux_sigsetmask(p, uap, retval)
	struct proc *p;
	struct linux_sigsetmask_args /* {
		syscallarg(linux_sigset_t) mask;
	} */ *uap;
	register_t *retval;
{
	linux_sigset_t mask;
	sigset_t bsdsig;

	bsd_to_linux_sigset(&p->p_sigmask, (linux_sigset_t *) retval);

	mask = SCARG(uap, mask);
	bsd_to_linux_sigset(&mask, &bsdsig);

	splhigh();
	p->p_sigmask = bsdsig & ~sigcantmask;
	spl0();

	return 0;
}

int
linux_sigpending(p, uap, retval)
	struct proc *p;
	struct linux_sigpending_args /* {
		syscallarg(linux_sigset_t *) mask;
	} */ *uap;
	register_t *retval;
{
	sigset_t bsdsig;
	linux_sigset_t linuxsig;

	bsdsig = p->p_siglist & p->p_sigmask;

	bsd_to_linux_sigset(&bsdsig, &linuxsig);
	return copyout(&linuxsig, SCARG(uap, mask), sizeof linuxsig);
}

int
linux_sigsuspend(p, uap, retval)
	struct proc *p;
	struct linux_sigsuspend_args /* {
		syscallarg(caddr_t) restart;
		syscallarg(int) oldmask;
		syscallarg(int) mask;
	} */ *uap;
	register_t *retval;
{
	struct sigsuspend_args ssa;

	linux_to_bsd_sigset(&SCARG(uap, mask), &SCARG(&ssa, mask));
	return sigsuspend(p, &ssa, retval);
}

/*
 * The deprecated pause(2), which is really just an instance
 * of sigsuspend(2).
 */
int
linux_pause(p, uap, retval)
	struct proc *p;
	void *uap;	
	register_t *retval;
{	
	struct sigsuspend_args bsa;

	SCARG(&bsa, mask) = p->p_sigmask;
	return sigsuspend(p, &bsa, retval);
}

/*
 * Once more: only a signal conversion is needed.
 */
int
linux_kill(p, uap, retval)
	struct proc *p;
	struct linux_kill_args /* {
		syscallarg(int) pid;
		syscallarg(int) signum;
	} */ *uap;
	register_t *retval;
{
	SCARG(uap, signum) = linux_to_bsd_sig(SCARG(uap, signum));
	return kill(p, (struct kill_args *) uap, retval);
}
