/*  Linker front end for BeOS.

    Copyright (C) 1996 Free Software Foundation, Inc.

    BeOS uses a proprietary Apple executable format called PEF.  However the
    Metrowerks linker that is supplied with the system also reads XCOFF so
    the gcc port generates XCOFF object files, which can be linked together,
    along with other XCOFF objects from libraries, using the GNU linker with
    the "-r" option to generate a single relocatable object file.  That file
    can then be passed to the Metrowerks linker to add the BeOS runtime
    support and generate a PEF executable.

    This front end is compiled and installed in place of the regular GNU
    linker, which is moved to ld-coff.  It first runs ld-coff with the args
    that it is given, except that -o options are redirected to a temporary
    file, and then it runs mwld to covert the relocatable temporary file
    into a PEF executable in the file specified by the -o option (or "a.out"
    as the default).

    This front end was written by Fred Fish (fnf@cygnus.com) using various
    pieces from gcc's "collect2.c".  Contributers to collect2.c include
    Chris Smith (csmith@convex.com), Michael Meissner (meissner@cygnus.com),
    Per Bothner (bothner@cygnus.com), and John Gilmore (gnu@cygnus.com).

    This is expected to be only a temporary solution.  I have formally
    requested (9/96) a license from Apple to incorporate PEF support into
    BFD using information from their licensed PEF documentation, and to have
    that code covered *only* by the GNU General Public License.  That
    request is still pending.  If PEF support is installed, then this useful
    hack will go away.  Or perhaps Be will migrate to more open standards
    first.

This file is part of GLD, the Gnu Linker.

GLD is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GLD 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with GLD; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

#include "ansidecl.h"
#include "config.h"

#ifdef CROSS_LINKER
#define RUN_MWLD 0
#else
#define RUN_MWLD 1
#endif

#define TEMP_FILE_TEMPLATE "/tmp/ldhackXXXXXX"

#define LD_COFF "ld-coff"		/* The renamed GNU linker */
#define MW_LD "mwld"			/* The Metrowerks linker */
#define DEFAULT_OFILE "a.out"		/* Default output file name */

static int vflag = 0;			/* Non-zero if user specified -v flag */
static int rflag = 0;			/* Non-zero if user specified -r flag or we are a cross linker */
static char *ofile = NULL;		/* Output file name */
static char *scratchfile;		/* Temporary file name */

static void do_wait PARAMS ((char *));
static void fork_execute PARAMS ((char *, char **));
static void collect_exit PARAMS ((int));

extern void *malloc ();
extern char *getenv ();


/* Die when sys call fails.  */

static void
fatal_perror (string, arg1, arg2, arg3)
     char *string, *arg1, *arg2, *arg3;
{
  int e = errno;

  fprintf (stderr, "ld: ");
  fprintf (stderr, string, arg1, arg2, arg3);
  fprintf (stderr, ": %s\n", strerror (e));
  collect_exit (1);
}

/* Just die.  */

static void
fatal (string, arg1, arg2, arg3)
     char *string, *arg1, *arg2, *arg3;
{
  fprintf (stderr, "ld: ");
  fprintf (stderr, string, arg1, arg2, arg3);
  fprintf (stderr, "\n");
  collect_exit (1);
}

/* Write error message.  */

static void
error (string, arg1, arg2, arg3, arg4)
     char *string, *arg1, *arg2, *arg3, *arg4;
{
  fprintf (stderr, "ld: ");
  fprintf (stderr, string, arg1, arg2, arg3, arg4);
  fprintf (stderr, "\n");
}

main (argc, argv)
     int argc;
     char *argv[];
{
  char **argp;
  char **ldargs;
  char **ldargp;
  char **tmp_ofile;
  int offset;

  argp = &argv[1];

  /* Build the temporary file name, being careful to ensure that the
     template is writable and not in const data. */

  scratchfile = malloc (strlen (TEMP_FILE_TEMPLATE));
  strcpy (scratchfile, TEMP_FILE_TEMPLATE);
  mktemp (scratchfile);

  ldargs = malloc ((argc + 4) * sizeof (char *));
  ldargp = ldargs;

  /* Find the real linker. */
  *ldargp = getenv ("LD_COFF");
  if (*ldargp == NULL)
    {
      *ldargp = LD_COFF;
    }
  ldargp++;

  while (*argp)
    {
      if (strcmp ("-v", *argp) == 0)
	{
	  vflag = 1;
	}
      else if (strcmp ("-r", *argp) == 0)
	{
	  rflag = 1;
	}
      else if (strcmp ("-o", *argp) == 0)
	{
	  /* Save the real output filename in ofile and
	     substitute our temporary file name. */
	  *ldargp++ = *argp++;
	  ofile = *argp++;
	  tmp_ofile = ldargp;
	  *ldargp++ = scratchfile;
	  /* Already handled args, skip to next */
	  continue;
	}
      *ldargp++ = *argp++;
    }

  /* Now check to see if we saw an explicit -o option.  If so, it currently
     points to the temporary file.  Leave it alone unless we also saw a -r
     option, in which case redirect it back to the real output file since
     we won't be running mwld.  If no explicit -o, need to add one unless
     we are only running the GNU linker and not mwld. */

  if (ofile == NULL)
    {
      /* No explicit -o option.  If not generating a relocatable
	 file then need to use the temporary file between passes. */
      if (!rflag && RUN_MWLD)
	{
	  *ldargp++ = "-o";
	  *ldargp++ = scratchfile;
	}
    }
  else
    {
      /* Got explicit -o option so it currently points to our scratch file.
	 If also got -r option, need to point it back to our real output
	 file. */
      if (rflag || !RUN_MWLD)
	{
	  *tmp_ofile = ofile;
	}
    }
  /* If we are not running mwld then we must be cross linking, so
     be sure to give the GNU linker a -r flag if we haven't already. */
  if (!rflag && getenv ("BEOS_FORCE_LINK") == NULL && !RUN_MWLD)
    {
      *ldargp++ = "-r";
    }

  /* Terminate the arg list to the GNU ld and run it. */

  *ldargp++ = NULL;
  fork_execute (ldargs[0], ldargs);

  /* If we are not generating a relocatable output file, then we need
     to run the Metrowerks linker to generate the final executable. */

  if (!rflag && RUN_MWLD)
    {
      ldargp = ldargs;
      *ldargp++ = MW_LD;
      *ldargp++ = "-o";
      *ldargp++ = ofile;
      *ldargp++ = scratchfile;
      *ldargp++ = NULL;
      fork_execute (ldargs[0], ldargs);
    }

  collect_exit (0);
}

/* Wait for a process to finish, and exit if a non-zero status is found.  */

int
collect_wait (prog)
     char *prog;
{
  int status;

  wait (&status);
  if (status)
    {
      if (WIFSIGNALED (status))
	{
	  int sig = WTERMSIG (status);
	  error ("%s terminated with signal %d %s",
		 prog,
		 sig,
		 (status & 0200) ? ", core dumped" : "");
	  collect_exit (127);
	}

      if (WIFEXITED (status))
	return WEXITSTATUS (status);
    }
  return 0;
}

static void
do_wait (prog)
     char *prog;
{
  int ret = collect_wait (prog);
  if (ret != 0)
    {
      error ("%s returned %d exit status", prog, ret);
      collect_exit (ret);
    }
}

/* Fork and execute a program, and wait for the reply.  */

static void
collect_execute (prog, argv, redir)
     char *prog;
     char **argv;
     char *redir;
{
  int pid;

  if (vflag)
    {
      char **p_argv;
      char *str;

      if (argv[0])
	fprintf (stderr, "%s", argv[0]);
      else
	fprintf (stderr, "[cannot find %s]", prog);

      for (p_argv = &argv[1]; (str = *p_argv) != (char *) 0; p_argv++)
	fprintf (stderr, " %s", str);

      fprintf (stderr, "\n");
    }

  fflush (stdout);
  fflush (stderr);

  /* If we can't find a program we need, complain error.  Do this here
     since we might not end up needing something that we couldn't find.  */

  if (argv[0] == 0)
    fatal ("cannot find `%s'", prog);

  pid = fork ();
  if (pid == -1)
    {
      fatal_perror ("fork");
    }

  if (pid == 0)			/* child context */
    {
      if (redir)
	{
	  unlink (redir);
	  if (freopen (redir, "a", stdout) == NULL)
	    fatal_perror ("redirecting stdout");
	  if (freopen (redir, "a", stderr) == NULL)
	    fatal_perror ("redirecting stderr");
	}

      execvp (argv[0], argv);
      fatal_perror ("executing %s", prog);
    }
}

static void
fork_execute (prog, argv)
     char *prog;
     char **argv;
{
  collect_execute (prog, argv, NULL);
  do_wait (prog);
}

/* Delete tempfiles and exit function.  */

void
collect_exit (status)
     int status;
{
  if (status > 0)
    {
      unlink (ofile);
    }
  unlink (scratchfile);
  exit (status);
}

