/* sys/init.c (emx+gcc) -- Copyright (c) 1992-1996 by Eberhard Mattes */

#define INCL_DOS
#include <os2emx.h>
#include <string.h>
#include <sys/builtin.h>
#include <sys/fmutex.h>
#include <emx/startup.h>
#include <emx/syscalls.h>

#define SYS_PRIVATE_HEAP_SIZE 0x100000

#define EXTERN
#define INIT(x) = x

#include "syscalls.h"

static int arg_size;
static int argc;
static int envc;
static char **vec;
static char *pool;

extern void volatile __init_ret (void *stack);
static void parse_args (const char *src);
static void parse_env (char *src);

#define PUTC(c) BEGIN ++arg_size; if (pool != NULL) *pool++ = (c); END
#define PUTV    BEGIN ++argc; if (vec != NULL) *vec++ = pool; END

#define WHITE(c) ((c) == ' ' || (c) == '\t')

static void parse_args (const char *src)
{
  int bs, quote;
  char *flag_ptr;

  argc = 0; arg_size = 0;
  /* argv[0] */
  PUTC (_ARG_NONZERO);
  PUTV;
  for (;;)
    {
      PUTC (*src);
      if (*src == 0)
        break;
      ++src;
    }
  ++src;
  for (;;)
    {
      while (WHITE (*src))
        ++src;
      if (*src == 0)
        break;
      flag_ptr = pool;
      PUTC (_ARG_NONZERO);
      PUTV;
      bs = 0; quote = 0;
      for (;;)
        {
          if (*src == '"')
            {
              while (bs >= 2)
                {
                  PUTC ('\\');
                  bs -= 2;
                }
              if (bs & 1)
                PUTC ('"');
              else
                {
                  quote = !quote;
                  if (flag_ptr != NULL)
                    *flag_ptr |= _ARG_DQUOTE;
                }
              bs = 0;
            }
          else if (*src == '\\')
            ++bs;
          else
            {
              while (bs != 0)
                {
                  PUTC ('\\');
                  --bs;
                }
              if (*src == 0 || (WHITE (*src) && !quote))
                break;
              PUTC (*src);
            }
          ++src;
        }
      PUTC (0);
    }
}


static void parse_env (char *src)
{
  envc = 0;
  while (*src != 0)
    {
      ++envc;
      if (vec != NULL)
        *vec++ = src;
      while (*src != 0)
        ++src;
      ++src;
    }
}


struct layout
{
  ULONG text_base;
  ULONG text_end;
  ULONG data_base;
  ULONG data_end;
  ULONG bss_base;
  ULONG bss_end;
  ULONG heap_base;
  ULONG heap_end;
  ULONG heap_brk;
  ULONG heap_off;
  ULONG os2_dll;
  ULONG stack_base;
  ULONG stack_end;
  ULONG flags;
  ULONG reserved[2];
  UCHAR options[64];
};


void __init (struct layout *layout)
{
  ULONG rc;
  PTIB ptib;
  PPIB ppib;
  void *stack;
  int n;
  ULONG times;
  static char init_level;

  /* In a custom C runtime DLL, this function is called at least
     twice, once from the startup code of this DLL, once from the
     startup code of the application, and once from each DLL using
     this DLL.  init_level indicates which initializations have been
     performed.  Initially init_level is 0, meaning no initializations
     have been done.  init_level 1 indicates that initializations
     common to DLLs and applications have been performed.  init_level
     2 indicates that initializations specific to applications have
     been done as well as initializations of init_level 1. */

  if (init_level == 0)
    {
      init_level = 1;

      /* Initialize the heap.  Here, we only create the semaphore
         (while having only one thread).  The heap will be created by
         __sbrk(). */

#if defined (__MT__)
      if (_fmutex_create (&_sys_heap_fmutex, 0) != 0)
        DosExit (EXIT_PROCESS, 255);
#endif

      /* Get the process ID and parent process ID.  This is also
         required for (stand-alone) DLLs.  At least, we need ptib and
         ppib. */

      rc = DosGetInfoBlocks (&ptib, &ppib);
      if (rc != 0)
        DosExit (EXIT_PROCESS, 255);
      _sys_pid = ppib->pib_ulpid;
      _sys_ppid = ppib->pib_ulppid;

      /* Initialize threads.  This is also required for (stand-alone)
         DLLs as these may call _beginthread(). */
#if defined (__MT__)
      rc = DosAllocMem (&_sys_private_heap, SYS_PRIVATE_HEAP_SIZE,
                        PAG_READ | PAG_WRITE);
      if (rc != 0)
        DosExit (EXIT_PROCESS, 255);
      rc = DosSubSetMem (_sys_private_heap,
                         DOSSUB_INIT | DOSSUB_SPARSE_OBJ | DOSSUB_SERIALIZE,
                         SYS_PRIVATE_HEAP_SIZE);
      if (rc != 0)
        DosExit (EXIT_PROCESS, 255);
      for (n = 0; n < MAX_THREADS; ++n)
        _sys_thread_table[n] = NULL;
      __newthread (ptib->tib_ptib2->tib2_ultid);
#else
      _sys_init_thread (&_sys_thread_one);
#endif

      /* Get current time for clock().  We do this also for
         (stand-alone) DLLs. */

      _sys_get_clock (&_sys_clock0_ms);
    }

  /* Stop here if called from the startup code of a DLL.  The
     remaining code is used only if this function has been called from
     the startup code of an application. */

  if (layout->flags & 1)
    return;

  /* Avoid performing the following initializations more than once. */

  if (init_level == 2)
    return;
  init_level = 2;

  /* Get ptib and ppib again, in case __init() has already been called
     for a DLL. */

  rc = DosGetInfoBlocks (&ptib, &ppib);
  if (rc != 0)
    DosExit (EXIT_PROCESS, 255);

  /* Copy command line arguments and environment. */

  vec = NULL; pool = NULL;
  parse_env (ppib->pib_pchenv);
  parse_args (ppib->pib_pchcmd);
  n = arg_size + (argc+1+envc+1) * sizeof (char *);
  if (n & 3)
    n = (n | 3) + 1;            /* alignment for EXCEPTIONREGISTRATIONRECORD */
  stack = alloca (n + sizeof (EXCEPTIONREGISTRATIONRECORD));
  _sys_xreg = stack + n;
  vec = stack;
  pool = (char *)(vec + argc+1+envc+1);
  parse_env (ppib->pib_pchenv);
  *vec++ = NULL;               /* empty environment */
  parse_args (ppib->pib_pchcmd);
  *vec++ = NULL;

  /* Initialize exception handling. */

  _sys_xreg->prev_structure = (void *)0xffffffff;
  _sys_xreg->ExceptionHandler = _sys_exception;
  rc = DosSetExceptionHandler (_sys_xreg);
  if (rc != 0)
    DosExit (EXIT_PROCESS, 255);
  /* DosSetSignalExceptionFocus fails for PM programs, error code 303 */
  DosSetSignalExceptionFocus (SIG_SETFOCUS, &times);

  /* Return to the program. */

  __init_ret (stack);
}


char const _sys_sig_valid[NSIG] =
{
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1
};

struct sig_descr const sig_info[NSIG] =
{
  {"SIG0",     ST_TERM,   ST_TERM},
  {"SIGHUP",   ST_TERM,   ST_IGNORE},
  {"SIGINT",   ST_TERM,   ST_IGNORE},
  {"SIGQUIT",  ST_TERM,   ST_IGNORE},
  {"SIGILL",   ST_NEXT,   ST_NEXT},
  {"SIGTRAP",  ST_NEXT,   ST_NEXT},
  {"SIGABRT",  ST_TERM,   ST_TERM},
  {"SIGEMT",   ST_TERM,   ST_TERM},
  {"SIGFPE",   ST_NEXT,   ST_NEXT},
  {"SIGKILL",  ST_TERM,   ST_TERM},
  {"SIGBUS",   ST_TERM,   ST_TERM},
  {"SIGSEGV",  ST_NEXT,   ST_NEXT},
  {"SIGSYS",   ST_TERM,   ST_TERM},
  {"SIGPIPE",  ST_TERM,   ST_IGNORE},
  {"SIGALRM",  ST_TERM,   ST_IGNORE},
  {"SIGTERM",  ST_NEXT,   ST_IGNORE},
  {"SIGUSR1",  ST_IGNORE, ST_IGNORE},
  {"SIGUSR2",  ST_IGNORE, ST_IGNORE},
  {"SIGCLD",   ST_IGNORE, ST_IGNORE},
  {"SIG19",    ST_TERM,   ST_TERM},
  {"SIG20",    ST_TERM,   ST_TERM},
  {"SIGBREAK", ST_TERM,   ST_IGNORE}
};


static void say (const char *msg)
{
  __write (2, msg, strlen (msg));
};


static void _sys_terminate (int signo)
{
  if (signo == SIGABRT)
    say ("\r\nAbnormal program termination\r\n");
  else
    {
      say ("\r\nProcess terminated by ");
      say (sig_info[signo].name);
      say ("\r\n");
    }
  while (1)
    DosExit (EXIT_PROCESS, 3);
}


static void _sys_signal_acknowledge (int signo)
{
  switch (signo)
    {
    case SIGINT:
      DosAcknowledgeSignalException (XCPT_SIGNAL_INTR);
      break;
    case SIGTERM:
      DosAcknowledgeSignalException (XCPT_SIGNAL_KILLPROC);
      break;
    case SIGBREAK:
      DosAcknowledgeSignalException (XCPT_SIGNAL_BREAK);
      break;
    }
}


/* Called from OS/2 exception handler for signal exceptions. */

static ULONG _sys_signal (int signo)
{
  thread_data *tp;

  tp = SYS_THREAD;
  if (tp == NULL)
    return XCPT_CONTINUE_SEARCH;
  __sigaddset (&tp->sig_pending, signo);
  _sys_signal_acknowledge (signo);
  _sys_deliver_pending_signals (tp);
  switch (sig_info[signo].fun_action)
    {
    case ST_TERM:
      _sys_terminate (signo);
    case ST_NEXT:
      return XCPT_CONTINUE_SEARCH;
    case ST_IGNORE:
      return XCPT_CONTINUE_EXECUTION;
    }
  return XCPT_CONTINUE_SEARCH;  /* Keep the optimizer happy */
}


/* Called from OS/2 exception handler for traps. */

static ULONG _sys_trap (int signo)
{
  thread_data *tp;
  void (*handler)(int signo);

  tp = SYS_THREAD;
  if (tp == NULL)
    return XCPT_CONTINUE_SEARCH;
  handler = tp->signals[signo].sa_handler;
  if (handler != SIG_IGN && handler != SIG_DFL)
    handler (signo);
  switch (sig_info[signo].fun_action)
    {
    case ST_TERM:
      _sys_terminate (signo);
    case ST_NEXT:
      return XCPT_CONTINUE_SEARCH;
    case ST_IGNORE:
      return XCPT_CONTINUE_EXECUTION;
    }
  return XCPT_CONTINUE_SEARCH;  /* Keep the optimizer happy */
}


static void _sys_deliver_signal (thread_data *tp, int signo)
{
  struct sigaction *p;
  void (*handler)(int signo);
  sigset_t mask, old_blocked;

  /* TODO: Critical section */

  mask = _SIGMASK (signo);
  p = &tp->signals[signo];

  tp->sig_pending &= ~mask;
  handler = p->sa_handler;

  if (handler == SIG_IGN)
    {
      /* Ignore the signal. */
    }
  else if (handler == SIG_DFL)
    {
      if (sig_info[signo].dfl_action != ST_IGNORE)
        _sys_terminate (signo);
    }
  else
    {
      if (p->sa_flags & SA_SYSV)
        {
          p->sa_handler = SIG_DFL;
          handler (signo);
        }
      else if (p->sa_flags & SA_ACK)
        {
          tp->sig_blocked |= mask;
          handler (signo);
        }
      else
        {
          old_blocked = tp->sig_blocked;
          SET_BLOCKED (tp, tp->sig_blocked | mask | p->sa_mask);
          handler (signo);
          tp->sig_blocked = old_blocked;
        }
    }
}


void _sys_deliver_pending_signals (thread_data *tp)
{
  int signo;

  /* TODO: Critical section */
  while ((tp->sig_pending & ~tp->sig_blocked) != 0)
    {
      for (signo = 1; signo < NSIG; ++signo)
        if (tp->sig_pending & ~tp->sig_blocked & _SIGMASK (signo))
          _sys_deliver_signal (tp, signo);
    }
}


ULONG _sys_exception (PEXCEPTIONREPORTRECORD report,
                      PEXCEPTIONREGISTRATIONRECORD registration,
                      PCONTEXTRECORD context,
                      PVOID whatever)
{
  __asm__ ("cld");              /* Don't trust */
  if (report->fHandlerFlags & (EH_UNWINDING | EH_EXIT_UNWIND))
    return XCPT_CONTINUE_SEARCH;
  switch (report->ExceptionNum)
    {
    case XCPT_SIGNAL:
      if (report->cParameters >= 1)
        {
          if (report->ExceptionInfo[0] == XCPT_SIGNAL_INTR)
            return _sys_signal (SIGINT);
          else if (report->ExceptionInfo[0] == XCPT_SIGNAL_KILLPROC)
            return _sys_signal (SIGTERM);
          else if (report->ExceptionInfo[0] == XCPT_SIGNAL_BREAK)
            return _sys_signal (SIGBREAK);
        }
      break;
    case XCPT_ACCESS_VIOLATION:
    case XCPT_DATATYPE_MISALIGNMENT:
      return _sys_trap (SIGSEGV);

    case XCPT_INTEGER_DIVIDE_BY_ZERO:
    case XCPT_INTEGER_OVERFLOW:
    case XCPT_ARRAY_BOUNDS_EXCEEDED:
    case XCPT_FLOAT_DENORMAL_OPERAND:
    case XCPT_FLOAT_DIVIDE_BY_ZERO:
    case XCPT_FLOAT_INEXACT_RESULT:
    case XCPT_FLOAT_INVALID_OPERATION:
    case XCPT_FLOAT_OVERFLOW:
    case XCPT_FLOAT_STACK_CHECK:
    case XCPT_FLOAT_UNDERFLOW:
      return _sys_trap (SIGFPE);

    case XCPT_ILLEGAL_INSTRUCTION:
    case XCPT_INVALID_LOCK_SEQUENCE:
    case XCPT_PRIVILEGED_INSTRUCTION:
      return _sys_trap (SIGILL);
    }
  return XCPT_CONTINUE_SEARCH;
}


void _sys_get_clock (unsigned long *ms)
{
  ULONG val_ms;

  DosQuerySysInfo (QSV_MS_COUNT, QSV_MS_COUNT, &val_ms, sizeof (val_ms));
  *ms = val_ms;
}


void _sys_init_thread (thread_data *tp)
{
  int n;

  __sigemptyset (&tp->sig_blocked);
  __sigemptyset (&tp->sig_pending);
  for (n = 0; n < NSIG; ++n)
    {
      tp->signals[n].sa_handler = SIG_DFL;
      tp->signals[n].sa_flags   = SA_ACK;
      __sigemptyset (&tp->signals[n].sa_mask);
    }
  tp->fd.find_handle = HDIR_CREATE;
  tp->fd.find_next = NULL;
  tp->fd.find_count = 0;
}
