/* Copyright 1996 Acorn Computers Ltd
 *
 * 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.
 */
/* signal.c: ANSI draft (X3J11 Oct 86) library code, section 4.7 */
/* Copyright (C) Codemist Ltd, 1988                              */
/* version 0.01d */

/* N.B. machine dependent messages (only) below. */

#include "hostsys.h"
#include <signal.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>                             /* for exit()            */
#include <errno.h>
#include "kernel.h"

extern int _fprintf_lf(FILE *fp, const char *fmt, ...);
extern int _sprintf_lf(char *buff, const char *fmt, ...);

extern int _SignalNumber(int errnum);
extern _kernel_oserror *_kernel_peek_last_oserror(void);

#define SIGLAST 11  /* one after highest signal number (see <signal.h>) */

static void (*_signalvector[SIGLAST+1])(int);

/* HIDDEN EXPORTS - for compatibility with previous shared C libraries */
extern void __ignore_signal_handler(int sig);
extern void __error_signal_marker(int sig);
extern void __default_signal_handler(int sig);

extern void __ignore_signal_handler(int sig)
{
    /* do this in case called because of SharedCLibrary botch... */
    signal(sig, SIG_IGN);
}

static void _real_default_signal_handler(int sig)
{
    char *s, v[128];
    _kernel_oserror *e = _kernel_peek_last_oserror();

    if (((sig == SIGSEGV || sig == SIGILL) && _SignalNumber(e->errnum) == sig) ||
        sig == SIGFPE || sig == SIGOSERROR)
    {
        _postmortem(e->errmess, *((int *)"mesg"));
        return;
    }

    switch (sig)
    {
case SIGABRT:
#ifdef DEFAULT_TEXT
        s = "Abnormal termination (e.g. abort() function)";
#endif
        s = _kernel_getmessage(s, "C39");
        break;
case SIGILL:
#ifdef DEFAULT_TEXT
#ifdef __arm
        s = "Illegal instruction (call to non-function/code corrupted) [is the floating point emulator loaded?]";
#else
        s = "Illegal instruction (call to non-function/code corrupted)";
#endif
#endif
        s = _kernel_getmessage(s, "C40");
        break;
case SIGINT:
#ifdef DEFAULT_TEXT
        s = "Interrupt received from user - program terminated";
#endif
        _sys_msg(_kernel_getmessage(s, "C41"));
        exit(EXIT_FAILURE);
        break;
case SIGSEGV:
#ifdef DEFAULT_TEXT
        s = "Illegal address (e.g. wildly outside array bounds)";
#endif
        s = _kernel_getmessage(s, "C42");
        break;
case SIGTERM:
#ifdef DEFAULT_TEXT
        s = "Termination request received";
#endif
        s = _kernel_getmessage(s, "C43");
        break;
default:
#ifdef DEFAULT_TEXT
        s = "Unknown signal number %d";
#endif
        s = _kernel_getmessage(s, "C44");
        break;
    }
    _sprintf_lf(v, s, sig);
    _postmortem(v, *((int *)"mesg"));
}

#pragma -s1

#if 0
/* Stack overflow is either a normal exception in which case it should just
 * exit with an error return or a fatal system error in which case it shoul
 * generate an external error. I choose the latter. ECN
 */
static void _default_sigstak_handler()
{
    char *s = "Stack overflow\n\r";
    while (*s!=0) _kernel_oswrch(*s++);
    _kernel_setreturncode(100);
    _kernel_exit(100);
}
#else
extern void _default_sigstak_handler();
#endif

extern void __default_signal_handler(int sig)
{
    if (_kernel_processor_mode() & 0xF)
        _kernel_exit((int)(_kernel_last_oserror()));
    else if (sig==SIGSTAK)
      _default_sigstak_handler();
    else
      _real_default_signal_handler(sig);
}

extern void __error_signal_marker(int sig)
/* This function should NEVER be called - its value is used as a marker     */
/* return from signal (SIG_ERR).   If someone manages to use pass this      */
/* value back to signal and thence get it invoked we make it behave as      */
/* if signal got SIG_DFL:                                                   */
{
    __default_signal_handler(sig);
}

int raise(int sig)
{
    void (*handler)(int);
    if (sig<=0 || sig>=SIGLAST) return (errno = ESIGNUM);
    handler = _signalvector[sig];
    /* Mustn't call default_signal_handler via _kernel_call_client unless */
    /* it really is on the other side of the compatibility veneer.        */
    if (handler==__SIG_DFL)
        (*__default_signal_handler)(sig);
    else if (handler!=__SIG_IGN)
    {   _signalvector[sig] = __SIG_DFL;
        /* And this will fail for old clients setting SIG_IGN across */
        /* the compatibility veneer... Oh yuk yuk yuk...             */
        _call_client_1(handler, sig);
    }
    return 0;
}

int _signal_real_handler(int sig)
{
    if (sig<=0 || sig>=SIGLAST) return 0;
    return (_signalvector[sig]!=__SIG_DFL);
}

#pragma -s0

void (*signal(int sig, void (*func)(int)))(int)
{
    void (*oldf)(int);
    if (sig<=0 || sig>=SIGLAST) return __SIG_ERR;
    oldf = _signalvector[sig];
    _signalvector[sig] = func;
    return oldf;
}

void _signal_init()
{
    int i;
    /* do the following initialisation explicitly so code restartable */
    for (i=1; i<SIGLAST; i++) signal(i, __SIG_DFL);
}

/* end of signal.c */