/* 
 * Copyright (C) 1991 Free Software Foundation, Inc.
 * collect3.c - derived from collect2.c by Heinz Seidl (hgs@cygnus.com).
 */

/*
  This file is part of GNU CC.
  
  GNU CC 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.
  
  GNU CC 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 GNU CC; see the file COPYING.  If not, write to
  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/*
  C++ allows to initialize global variables with arbitrary expressions, 
  in particular with constructors. This means, that arbitrary pieces of
  code have to be executed _before_ `main()' is called and that constructed
  objects must be destructed at the end of the program before `exit()' is
  called.
  
  For each file with global initialization gcc generates now initialization
  and finalization code:
  
  The initialization code has the label: `__GLOBAL_$I$<name>', where <name> is
  the name of the first variable to be initialized.
  The finitialization code has the label: `__GLOBAL_$D$<name>', where <name> 
  is the name of the first variable to be destructed.
  
  Since the C runtime system (and the UNIX linkers) don't know about this kind
  of initialization, we have to deal with them:
  
  This program "collects" all the entrypoints for initialization and 
  finalization code and associates them with the labels `__CTOR_LIST__' and
  `__DTOR_LIST__' respectively.
  
  Gcc generates code for the call to `main()' witch invokes the initialization
  and finalization code.
  */


/* 
 * The __C/DTOR_LIST__ have the folowing form:
 * 
 * entry_pt * __C/DTOR_LIST__[] = {
 *	_first_entry_point,
 *	_second_entry_point,
 *	...
 *	0
 * }
 *
 * Both lists are in _proper_ order; i.e. the destructor list is in reverse
 * order of the constructor list.
 *
 * In case OLD_FORMAT is defined, init.c understands the format generated by
 * gld 1.x and used by gnulib3.
 *
 * The order is determined by the order of the .o files on the command line
 * and how the linker arranges them in the output file.
 * It is expected that the linker arranges the con/destructors in reverse
 * order as the command line.
 * There is no predicable order for files from archives.
 */

static char * rcsId = "$Id: collect3.c,v 1.2 1991/10/03 00:37:07 hgs Exp $";

#if defined (__GNUC__)
#define alloca __builtin_alloca
#else
#define alloca malloc
#endif

#include "config.h"

 /* This means no additional `_' is prepended; 
    but have at least one `_' at the beginning */
#ifdef NO_UNDERSCORES
#define CTOR_MARKER_NAME	 "_GLOBAL_$I$"
#define DTOR_MARKER_NAME	 "_GLOBAL_$D$"
#define CDTOR_MARKER_LEN	 11
#define FUNCTION_LIST_ADDR	 "_function_list_addr"
#define FUNCTION_LIST_ADDR_LEN	 19
#else
#define CTOR_MARKER_NAME	 "__GLOBAL_$I$"
#define DTOR_MARKER_NAME	 "__GLOBAL_$D$"
#define CDTOR_MARKER_LEN	 12
#define FUNCTION_LIST_ADDR	 "__function_list_addr"
#define FUNCTION_LIST_ADDR_LEN	 20
#endif

#include <stdio.h>

#define USAGE()  do fprintf( stderr, \
			    "Usage: %s <link-command options>" ##            \
			    "[-- [-t] [-d tmp_dir] [-g gcc-path]]",          \
			    argv[0]);                                        \
  while(0);

#define GREP_CMD " grep -v TOR_LIST__"
#define NM_CMD   " nm -pg"

/* convert empty string into blank (stupidity of ?printf) */
#define S(str) ((str && *str) ? str : " ")

extern char *mktemp (char *template);

static void error( const char * msg, int line );
#define ERROR(msg) error( msg, __LINE__)

#ifndef PREFIX
#define PREFIX "/usr/unsupported"
#endif

#define const static
/* causes strange bugs */
const char * TMP_DIR   = "/usr/tmp";
const char * AOUTXXX   = "AOUTXXXXXX";
const char * CDTORXXX  = "CDTORXXXXXX";
const char * GCC_FLAGS = "-g -ftraditional";
const char * GCC       = PREFIX "/bin/gcc";
#undef const

static char * prog_name = NULL;

/* Linked lists of constructor and destructor names.
 */
struct id 
{
    char *name;
    struct id *next;
};

/* if we link ccrt0 to an object we always need the C/DTOR_LISTS even if they are empty */
static int need_cdtor_list = 0;

main (int argc, char *argv[])
{
    char /*const causes strange errors*/* tmp_dir = TMP_DIR;
    char CDTOR_filename_c[BUFSIZ], CDTOR_filename_o[BUFSIZ], buf[BUFSIZ];
    char AOUT_filename[BUFSIZ];
    FILE * pipe_in;
    char * outfile = "a.out";
    char * arg, ldargs1[BUFSIZ*2], ldargs2[BUFSIZ*2], cmd[BUFSIZ*4];
    char * s = ldargs1;
    char * debug_str = NULL;	/* or `set -x; ' */
    int    no_rm = 0; 
    char /* const */* gcc = GCC;
    int    ret = 0;
    
    ldargs1[0] = '\0'; ldargs2[0] = '\0';
    
    prog_name = argv[0];
    /* Parse arguments for the linker
     */
    while (arg = *++argv)
      {
	  if (! strcmp (arg, "--"))
	    {
		/* parse the options for collect
		 */
		while (arg = *++argv)
		  {
		      switch  ( (int) arg[1]) 
			{
			  case 'v':
			    debug_str = "set -x;";
			    tmp_dir = ".";
			    break;
			  case 'd':
			    tmp_dir = *++argv;
			    break;
			  case 'g':
			    gcc = *++argv;
			    break;
			  case 'r':
			    no_rm = 1;
			    tmp_dir = ".";
			    break;
			  default:
			    USAGE();
			    exit(1);
			}
		  }
		break;		/* get out of arg parsing loop */
	    }
	  
	  
	  /* remove output file from command line
	   */
	  if (! strcmp (arg, "-o"))
	    {
		outfile = *++argv;
		continue;
	    }
	  
	  /* Split the arguments after *rt0.o and before the first .o file 
	   */
	  if ( !strcmp (arg + strlen(arg) -2, ".o") )
	    if (! (strlen( arg) >= 5 && !strcmp (arg+strlen(arg)-5, "rt0.o")) )
	      s = ldargs2;
	  
	  strcat (s , " '"); strcat (s, arg); strcat (s, "'");
      }
    
    /* Make temp file names.
     */
    if (debug_str)
      {
	  tmp_dir = ".";
	  sprintf( AOUT_filename, "%s", AOUTXXX);
	  (void) mktemp (AOUT_filename);
      }
    else
      sprintf( AOUT_filename, "%s", outfile); /* we reuse the outfile */
    
    sprintf( buf, "%s/%s", tmp_dir, CDTORXXX);
    (void) mktemp (buf);
    sprintf (CDTOR_filename_c, "%s.c", buf);
    sprintf (CDTOR_filename_o, "%s.o", buf);
    
    
    
    /* Load the program, searching all libraries.
     * (The introduction of shared libraries fucked the -r option of
     * the SunOS linker; therefore we link twice; unless we don't have 
     * de/constructors).
     */
    if ( debug_str)
      sprintf (cmd, "%s ld -o %s %s %s",
	       S(debug_str), AOUT_filename, S(ldargs1), S(ldargs2));
    else
      sprintf (cmd, "ld -o %s %s %s 2>&1 |" GREP_CMD,
	       AOUT_filename, S(ldargs1), S(ldargs2));
    system( cmd);
    
    /* Examine the namelist with nm and search it for static constructors
     * and destructors to call.
     * Write the constructor and destructor tables to a .c file.
     */
    sprintf (cmd, "%s " NM_CMD " %s", S(debug_str), AOUT_filename);  
    if (! (pipe_in = popen (cmd, "r"))) ERROR( "popen failed");
    
    ret = write_CDTOR_list (pipe_in, CDTOR_filename_c);
    
    if (pclose (pipe_in)) ERROR( "pclose failed");
    
    if ( ret == 0 && ! need_cdtor_list) exit(0);       /* no de/constructors */
    
    /* Compile the constructor and destructor tables.
     */
    if ( debug_str )
      sprintf (cmd, "%s %s -v -c %s -o %s %s",
	       S(debug_str), gcc, GCC_FLAGS,
	       CDTOR_filename_o, CDTOR_filename_c);
    else
      sprintf (cmd,"%s -c %s -o %s %s",
	       gcc, GCC_FLAGS, CDTOR_filename_o, CDTOR_filename_c);
    system( cmd);
    
    /* Link the tables in with the rest of the program.
     */
    sprintf (cmd, "%s ld -o %s %s %s %s",
	     S(debug_str), outfile, S(ldargs1), CDTOR_filename_o,
	     S(ldargs2));
    system( cmd);
    
    if ( ! debug_str && ! no_rm)
      {
	  unlink (CDTOR_filename_c);
	  unlink (CDTOR_filename_o);
      }
    
    exit(0);
}

int  write_CDTOR_list (FILE * pipe_in, char * CDTOR_filename_c)
     /*
      * Scan the name list of the loaded program for the symbols g++ uses
      * for static constructors and destructors.  Write their addresses
      * into tables which __main() and exit() will call.
      * returns 0 iff no de/constructors - otherwise > 0
      */
{
    register char *p;
    char buf[BUFSIZ];
    struct id *newid;
    FILE * CDTOR_file;
    
    struct id *constructors = 0;
    struct id *destructors = 0, *d_anchor = 0;
#ifdef OLD_FORMAT
    int nb_constructors = 0, nb_destructors = 0;
#endif
    
    while (! feof (pipe_in))
      {
	  /* read a line and strip trailing newline */
	  fgets (buf, sizeof buf, pipe_in);
	  p = buf + strlen( buf) - 1;
	  if ( *p == '\n') *p = '\0'; 
	  
	  /* If it contains a constructor or destructor name, add the name
	   * to the appropriate list.
	   */
	  for (p = buf; *p && *p != '_'; p++);
	  
	  if (! strncmp (p, CTOR_MARKER_NAME, CDTOR_MARKER_LEN))
	    {
		newid = alloca (sizeof *newid);
		newid->name = alloca (strlen (p) + 1);
#ifdef NO_UNDERSCORES
		strcpy (newid->name, p);
#else
		strcpy (newid->name, p+1);
#endif
		newid->next = constructors;
		constructors = newid;
#ifdef OLD_FORMAT
		nb_constructors ++;
#endif
	    }
	  else if (! strncmp (p, DTOR_MARKER_NAME, CDTOR_MARKER_LEN))
	    {
		newid = alloca (sizeof *newid);
		newid->name = alloca (strlen (p) + 1);
#ifdef NO_UNDERSCORES
		strcpy (newid->name, p);
#else
		strcpy (newid->name, p+1);
#endif
#ifdef OLD_FORMAT
		newid->next = destructors;
		destructors = newid;
		nb_destructors ++;
#else
		newid->next = 0;
		if( !d_anchor)
		  { d_anchor = destructors = newid; }
		else
		  {
		      destructors->next = newid;
		      destructors = newid;
		  }
#endif
	    }
	  else if ( ! need_cdtor_list &&
		   /* this shows we have ccrt0 and need the C/DTOR_LISTs */
		   (! strncmp (p, FUNCTION_LIST_ADDR, FUNCTION_LIST_ADDR_LEN)))
	    need_cdtor_list ++;
      }

    if ( ! need_cdtor_list && constructors == 0 && destructors == 0 ) return 0;
    
    if (! (CDTOR_file = fopen (CDTOR_filename_c, "w")))
      ERROR( "fopen failed");
    
    /* Write the tables as C code  */
    fprintf (CDTOR_file, "typedef void entry_pt();\n\n");
    
    write_list (CDTOR_file, "entry_pt ", constructors, ";\n");
    
    fprintf (CDTOR_file, "\nentry_pt * __CTOR_LIST__[] = {\n");
#ifdef OLD_FORMAT
    fprintf (CDTOR_file, "\t(entry_pt *) %d,\n", nb_constructors);
    write_list (CDTOR_file, "\t", constructors, ",\n");
#else
    write_list (CDTOR_file, "\t", constructors, ",\n");
#endif
    fprintf (CDTOR_file, "\t0\n};\n\n");

#ifdef OLD_FORMAT    
    write_list (CDTOR_file, "entry_pt ", destructors, ";\n");
#else
     write_list (CDTOR_file, "entry_pt ", d_anchor, ";\n");
#endif

    fprintf (CDTOR_file, "\nentry_pt * __DTOR_LIST__[] = {\n");
#ifdef OLD_FORMAT
    fprintf (CDTOR_file, "\t(entry_pt *) %d,\n", nb_destructors);
    write_list (CDTOR_file, "\t", destructors, ",\n");
#else
    write_list (CDTOR_file, "\t", d_anchor, ",\n");
#endif
    fprintf (CDTOR_file, "\t0\n};\n\n");
    
    if (fclose (CDTOR_file))
      ERROR( "fclose failed");
    
    return 1;
}


write_list ( FILE *CDTOR_file, char * prefix, struct id *list, char * suffix)
     /* 
      * Write `prefix', the names on list LIST, `suffix'.
      */
{
    if (! list)
      return;
    write_list ( CDTOR_file, prefix, list->next, suffix);
    fprintf ( CDTOR_file, "%s%s%s", prefix, list->name, suffix);
}


static void error( const char * msg, int line )
{
    static char buf[BUFSIZ];
    sprintf( buf, "%s: %s at %d", prog_name, S(msg), line);
    perror( buf);
    exit( 1);
}


/* FIXME: the -r option should be enabled (maybe with a flag) on certain machines */
/* FIXME: no fixed size arrays should be used, but xmalloc (see collect-osf) */
/* FIXME: check why const does not work */
/* FIXME: encapsulate prototypes */
/* FIXME: make all external interface names configureable */
