/* Startup code for libRILc.

    This file is part of libRILc, a standard C library for GCC on Amiga OS.
    Copyright © 1998  Rask Ingemann Lambertsen

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    As a special exception, if you link this library with files compiled
    with a GNU compiler to produce an executable, this does not cause the
    resulting executable to be covered by the GNU General Public License.
    This exception does not however invalidate any other reasons why the
    executable file might be covered by the GNU General Public License.
*/

/* FIXME: WB startup is not handled. */

/* Work around the problem of the m68k compiler placing globals before the code. */
#ifndef __PPC__
__asm__(".text\n"
        "\tjmp ___entry(pc)\n");
#endif

/* Work around problem with ppc.library versions before v45.19 starting execution
   af offset 4 instead of offset 0. A simple "nop" won't work with GCC 2.7.2.1
   because GCC 2.7.2.1 inserts a traceback longword before each function. */
#ifdef __PPC__
__asm__(".text\n"
        "\tb __entry\n"
        "\tb __entry\n");
#endif

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/execbase.h>
#include <dos/dosextens.h>
#include <dos/rdargs.h>
#include <utility/tagitem.h>
#include <libraries/locale.h>
#include <workbench/startup.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <limits.h>

#include "globals.h"
#include "startupdata.h"
#include "resourcetracking.h"
#include "systemcalls.h"

#ifdef __PPC__
#include <powerup/ppclib/interface.h>
#include <powerup/ppclib/message.h>
#include <powerup/ppclib/tasks.h>
#include <powerup/gcclib/powerup_protos.h>

/* Work around proto file bugs. */
struct ExecBase *SysBase;
struct DosLibrary *DOSBase;
#define __NOLIBBASE__
#include <ppcproto/exec.h>
#include <ppcproto/dos.h>
#undef __NOLIBBASE__
#include <ppcproto/locale.h>

#undef  NewList
#define NewList(a) PPCNewList(a)
#undef  Open
#define Open(a,b) PPCOpen(a,b)
#undef  Close
#define Close(a) PPCClose(a)
#undef  FreeMem
#define FreeMem(a,b) PPCFreeMem(a,b)
#undef  Remove()
#define Remove(a) PPCRemove(a)

#else /* not __PPC__ */

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/locale.h>

#endif /* not __PPC__ */

#include "stdio/startup.h"
#include "memory/startup.h"
#include "unix/startup.h"

struct ExecBase *SysBase;
struct DosLibrary *DOSBase;
struct LocaleBase *LocaleBase;
struct Library *UtilityBase;


/*****
       Public globals.
*****/

int errno;
LONG __ioerr;


/*****
       Private global data for startup code and library.
*****/

/* exit() has to use longjmp() :-( */
jmp_buf __exit_jmpbuf;

/* Locale support. */
struct Locale *__ctype_locale;
unsigned char *__decimalpoint;

/* Signal handling. */
void (*(__signal_handlers[NSIG - 1]))(int) =
{
  /* SIGINT   */  SIG_DFL,
  /* SIGKILL  */  SIG_DFL,
  /* SIGHUP   */  SIG_DFL,
  /* SIGABRT  */  SIG_DFL,
  /* SIGSEGV  */  SIG_DFL
};
struct MinList __atexit_handlers;


/*****
       Startup code starts here.
*****/

#ifdef __PPC__
LONG __entry (STRPTR cmdLine, struct WBStartup *wbmsg)
{
  struct StartupData	*startupMsg;
#else
LONG __entry (STRPTR cmdLine __asm__("a0"), ULONG cmdLineLen __asm__("d0"))
{
#endif

  /* Some variables are declared volatile because of setjmp()/longjmp(). */

           int       returncode;
           struct RDArgs     *volatile rdargs = NULL;
           STRPTR            *rdargv = NULL;
           STRPTR            *volatile argv = NULL;
  volatile ULONG              argc;

  SysBase = *(struct ExecBase **) 4;

#ifdef __PPC__
  startupMsg = (struct StartupData *) PPCGetTaskAttr (PPCTASKTAG_STARTUP_MSGDATA);
#endif

  if ((DOSBase = (struct DosLibrary *) OpenLibrary ("dos.library", 37UL)))
  {
    /* Set up malloc() and friends. */
    /* if */ __memory_startup ();

    /* Set up stdio. */
#ifdef __PPC__
    /* if */ __stdio_startup (startupMsg->stdin, startupMsg->stdout, startupMsg->stderr);
#else
    /* if */ __stdio_startup (Input (), Output (), ((struct Process *) FindTask (NULL))->pr_CES);
#endif

    /* if */ __unix_startup ();

    if ((LocaleBase = (struct LocaleBase *) OpenLibrary ("locale.library", 38UL)))
    {
      if ((UtilityBase = OpenLibrary ("utility.library", 36UL)))
      {
        /* Prepare for resource tracking and exit handling. */
        NewList ((struct List *) &__atexit_handlers);

        /* ctype needs this. Guaranteed not to fail. */
        __ctype_locale = OpenLocale (NULL);
        
        /* __float_*printf()/scanf() needs this. */
#if 0
        __decimalpoint = (unsigned char *) __ctype_locale->loc_DecimalPoint;
#else
        __decimalpoint = ".";
#endif


        /* Argument parsing. */
        /* Allocate and initialise an RDArgs structure so ReadArgs can
           parse arguments from cmdLine. */
        if ((rdargs = AllocDosObject (DOS_RDARGS, NULL)))
        {
#ifdef __PPC__

          rdargs->RDA_Source.CS_Buffer = cmdLine;
          /* See below about strlen() + 1! */
          rdargs->RDA_Source.CS_Length = cmdLine ? strlen (cmdLine) + 1 : 0;
          rdargs->RDA_Source.CS_CurChr = 0;
          /* We don't want reprompting - or do we? */
          rdargs->RDA_Flags |= RDAF_NOPROMPT;

          /* Hack: ReadArgs() needs a '\n' at the end of the string.
           * Since it knows the length of the string, this should be "safe".
           */
          if (cmdLine)
            cmdLine[rdargs->RDA_Source.CS_Length - 1] = '\n';

#else /* not __PPC__ */

          STRPTR tmp_cmdline;
          ULONG  tmp_cmdlen;

          /* There seems to be no way of finding out if the buffer was large
          ** enough for GetProgramName(), so assume 256 bytes is enough.
          ** FIXME: I really should do full BCPL escaping on the command name.
          ** Two quotes, a separating space and a terminating '\n' = 4 chars.
          */
          if ((tmp_cmdline = AllocVec (256 + 4 + strlen (cmdLine), MEMF_ANY)))
          {
            *tmp_cmdline = '\"';
            GetProgramName (&tmp_cmdline[1], 256);
            strcat (tmp_cmdline, "\" ");
            strcat (tmp_cmdline, cmdLine);
            tmp_cmdlen = strlen (tmp_cmdline);
            tmp_cmdline [tmp_cmdlen] = '\n';
          }

          rdargs->RDA_Source.CS_Buffer = tmp_cmdline;
          rdargs->RDA_Source.CS_Length = tmp_cmdline ? tmp_cmdlen : 0;
          rdargs->RDA_Source.CS_CurChr = 0;
          /* We don't want reprompting - or do we? */
          rdargs->RDA_Flags |= RDAF_NOPROMPT;

#endif /* not __PPC__ */

          if ((ReadArgs ("/M", (LONG *) &rdargv, rdargs)))
          {
            if (rdargv)
            {
              /* Count arguments. */
              for (argc = 0; rdargv[argc]; argc ++)
                ;
            }
            else
              argc = 0;

            if (argc)
              argv = rdargv;
            else
              /* This is a bit of a hack, but also a convenient way of pro-
                 viding the address of a NULL pointer _and_ the NULL pointer. */
              argv = (STRPTR *) "\0\0\0";
          }
          else
            argv = 0;

#ifdef __PPC__

          /* Restore the argument line after the abuse above. */
          if(cmdLine)
            cmdLine[rdargs->RDA_Source.CS_Length - 1] = '0';

#else /* not __PPC__ */

          /* The temporary command line is not needed any more. */
          if (tmp_cmdline)
            FreeVec (tmp_cmdline);

#endif /* not __PPC__ */

          if (argv) /* emergency FIXME */
          {
            volatile __syscall_handle sh;

            /* This is my attempt at providing a clean way of implementing exit()
             * (as clean as it can be when setjmp()/longjmp() is used). */

            /* exit() needs this. */
            if (0 == (returncode = setjmp (__exit_jmpbuf)))
            {
              /* Call main() if we didn't return from exit(). */
              /* Enable Ctrl-C handling and the like. */
              sh = __syscall_init ();
              returncode = main ((int) argc, (char **) argv);
            }
            else
              /* We returned from exit(), fix return code (hack). */
              returncode = (returncode == INT_MIN) ? 0 : returncode;

            /* Disable Ctrl-C and the like while exiting. */
            __syscall_shutdown (sh);

          } /* Emergency FIX, see above. */
          else
            returncode = RETURN_FAIL;

          /* We don't need the arguments any more. */
          FreeArgs (rdargs);
          FreeDosObject (DOS_RDARGS, rdargs);
        }

        /* Clean up the mess left by the application. *Sigh*. */
        {
          struct MinNode *node, *next;

          /* Call atexit() handlers here. */
          for (node = __atexit_handlers.mlh_Head; (next = node->mln_Succ); node = next)
          {
            /* Debug code:
            ULONG args[1];
            args[0] = (ULONG) ((struct __atexit_node *) node)->func;
            VFPrintf (stderr->fh, "Program calls atexit() function at 0x%lx.\n", args);
            */
            (* ((struct __atexit_node *) node)->func) ();
            Remove ((struct Node *) node);
            FreeMem (node, sizeof (struct __atexit_node));
          }
        }

        /* Can be called with NULL safely. */
        CloseLocale (__ctype_locale);

        CloseLibrary (UtilityBase);
      }
      else
      {
        FPuts (stderr->fh, "Couldn't open utility.library v36!\n");
        returncode = RETURN_ERROR;
      }
      CloseLibrary ((struct Library *) LocaleBase);
    }
    else
    {
      FPuts (stderr->fh, "Couldn't open locale.library v38!\n");
      returncode = RETURN_ERROR;
    }
    __unix_shutdown ();
    __stdio_shutdown ();
    __memory_shutdown ();

    CloseLibrary ((struct Library *) DOSBase);
  }
  else
    returncode = RETURN_ERROR;

#ifdef __PPC__
  /* Originally, the exit code was returned in the startup code. */
  startupMsg->Status = returncode;
#endif

  return (returncode);
}

/* Never returns. */
void exit (int returncode)
{
  longjmp (__exit_jmpbuf, (returncode == 0) ? INT_MIN : returncode);
}
