/*
 * Copyright (c) 2000-2017 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
/*
 * Mach Operating System
 * Copyright (c) 1987 Carnegie-Mellon University
 * All rights reserved.  The CMU software License Agreement specifies
 * the terms and conditions for use and redistribution.
 */

#include <sys/param.h>

#include <mach/boolean.h>
#include <mach/exception.h>
#include <mach/kern_return.h>

#include <sys/proc.h>
#include <sys/user.h>
#include <sys/systm.h>
#include <sys/vmparam.h>        /* MAXSSIZ */

#include <sys/ux_exception.h>

/*
 * Translate Mach exceptions to UNIX signals.
 *
 * ux_exception translates a mach exception, code and subcode to
 * a signal.  Calls machine_exception (machine dependent)
 * to attempt translation first.
 */
static int
ux_exception(int                        exception,
    mach_exception_code_t      code,
    mach_exception_subcode_t   subcode)
{
	int machine_signal = 0;

	/* Try machine-dependent translation first. */
	if ((machine_signal = machine_exception(exception, code, subcode)) != 0) {
		return machine_signal;
	}

	switch (exception) {
	case EXC_BAD_ACCESS:
		if (code == KERN_INVALID_ADDRESS) {
			return SIGSEGV;
		} else {
			return SIGBUS;
		}

	case EXC_SYSCALL:
		if (send_sigsys) {
			return SIGSYS;
		}
		break;

	case EXC_BAD_INSTRUCTION:
		return SIGILL;

	case EXC_ARITHMETIC:
		return SIGFPE;

	case EXC_EMULATION:
		return SIGEMT;

	case EXC_SOFTWARE:
		switch (code) {
		case EXC_UNIX_BAD_SYSCALL:
			return SIGSYS;
		case EXC_UNIX_BAD_PIPE:
			return SIGPIPE;
		case EXC_UNIX_ABORT:
			return SIGABRT;
		case EXC_SOFT_SIGNAL:
			return SIGKILL;
		}
		break;

	case EXC_BREAKPOINT:
		return SIGTRAP;
	}

	return 0;
}

/*
 * Sends the corresponding UNIX signal to a thread that has triggered a Mach exception.
 */
kern_return_t
handle_ux_exception(thread_t                    thread,
    int                         exception,
    mach_exception_code_t       code,
    mach_exception_subcode_t    subcode)
{
	/* Returns +1 proc reference */
	proc_t p = proc_findthread(thread);

	/* Can't deliver a signal without a bsd process reference */
	if (p == NULL) {
		return KERN_FAILURE;
	}

	/* Translate exception and code to signal type */
	int ux_signal = ux_exception(exception, code, subcode);

	uthread_t ut = get_bsdthread_info(thread);

	/*
	 * Stack overflow should result in a SIGSEGV signal
	 * on the alternate stack.
	 * but we have one or more guard pages after the
	 * stack top, so we would get a KERN_PROTECTION_FAILURE
	 * exception instead of KERN_INVALID_ADDRESS, resulting in
	 * a SIGBUS signal.
	 * Detect that situation and select the correct signal.
	 */
	if (code == KERN_PROTECTION_FAILURE &&
	    ux_signal == SIGBUS) {
		user_addr_t sp = subcode;

		user_addr_t stack_max = p->user_stack;
		user_addr_t stack_min = p->user_stack - MAXSSIZ;
		if (sp >= stack_min && sp < stack_max) {
			/*
			 * This is indeed a stack overflow.  Deliver a
			 * SIGSEGV signal.
			 */
			ux_signal = SIGSEGV;

			/*
			 * If the thread/process is not ready to handle
			 * SIGSEGV on an alternate stack, force-deliver
			 * SIGSEGV with a SIG_DFL handler.
			 */
			int mask = sigmask(ux_signal);
			struct sigacts *ps = &p->p_sigacts;
			if ((p->p_sigignore & mask) ||
			    (ut->uu_sigwait & mask) ||
			    (ut->uu_sigmask & mask) ||
			    (SIGACTION(p, SIGSEGV) == SIG_IGN) ||
			    (!(ps->ps_sigonstack & mask))) {
				p->p_sigignore &= ~mask;
				p->p_sigcatch &= ~mask;
				proc_set_sigact(p, SIGSEGV, SIG_DFL);
				ut->uu_sigwait &= ~mask;
				ut->uu_sigmask &= ~mask;
			}
		}
	}

	/* Send signal to thread */
	if (ux_signal != 0) {
		ut->uu_exception = exception;
		//ut->uu_code = code; // filled in by threadsignal
		ut->uu_subcode = subcode;
		threadsignal(thread, ux_signal, code, TRUE);
	}

	proc_rele(p);

	return KERN_SUCCESS;
}
