/* $VER: vlink main.c V0.6 (24.10.98)
 *
 * This file is part of vlink, a portable linker for multiple
 * object formats.
 * Copyright (c) 1997-99  Frank Wille
 *
 * vlink is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-99 by Volker Barthelmann.
 * vlink may be freely redistributed as long as no modifications are
 * made and nothing is charged for it. Non-commercial usage is allowed
 * without any restrictions.
 * EVERY PRODUCT OR PROGRAM DERIVED DIRECTLY FROM MY SOURCE MAY NOT BE
 * SOLD COMMERCIALLY WITHOUT PERMISSION FROM THE AUTHOR.
 *
 *
 * v0.6  (24.10.98) phx
 *       -baseoff will modify the base-reg section offset for the
 *       destination target.
 *       Deleted obsolete make_baserel_offsets().
 * v0.5d (22.08.98) phx
 *       Faster memory allocation can be activated by #define FASTALLOC.
 * v0.5  (27.06.98) phx
 *       -v shows default target.
 * v0.4  (05.06.98) phx
 *       -sc forces merging of all code sections in an executable.
 *       -sd forces merging of all data and bss sections in an executable.
 *       -multibase prevents auto-merging of sections, which are accessed
 *       base-relative.
 *       Call linker_relrefs() to find all relative references between
 *       sections.
 * v0.3  (16.04.98) phx
 *       -R directs vlink to generate short relocs, if the target format
 *       allows this. It is for example supported by ELF and AmigaDos.
 *       New option -F for reading a list of input files.
 * v0.2  (07.03.98) phx
 *       -v shows standard library path.
 * v0.1  (27.02.98) phx
 *       First version that seems to link AmigaOS ADOS and EHF
 *       objects and libraries. Many common features, like linking
 *       sections together which have relative references, are
 *       still missing. Also, PowerPC-ELF32 support is about to come.
 * v0.0  (04.08.97) phx
 *       File created. Project started on a beautiful summer-day
 *       at the North Sea beach of Cuxhaven. :)
 */


#define MAIN_C
#include "vlink.h"


struct GlobalVars gvars;


void cleanup(struct GlobalVars *);

static char *get_option_arg(int,char *[],int *);
static void ReadListFile(struct GlobalVars *,char *);



main(int argc,char *argv[])
{
  struct GlobalVars *gv = &gvars;
  int i,j;
  char *buf;
  struct LibPath *libp;
  struct InputFile *ifn;
  bool stdlib;
  bool dynamic = TRUE;  /* link with dynamic libraries first */
  int so_version = 0;   /* minum version for shared objects */

#ifdef FASTALLOC
  init_mem();
#endif
  /* initialize and set default values */
  memset(gv,0,sizeof(struct GlobalVars));
  initlist(&gv->libpaths);

#ifdef LIBPATH
  libp = alloc(sizeof(struct LibPath));
  libp->path = LIBPATH;  /* default search path */
  addtail(&gv->libpaths,&libp->n);
  stdlib = TRUE;
#else
  stdlib = FALSE;
#endif

#ifdef DEFTARGET
  for (j=0; fff[j]; j++) {
    if (!strcmp(fff[j]->tname,DEFTARGET)) {
      gv->dest_format = (uint8)j;
      break;
    }
  }
  if (fff[j] == NULL) {
    fprintf(stderr,"Configuration warning: Selected default target "
            "\"%s\" is not included.\nThe current default target "
            "is \"%s\".\n",DEFTARGET,fff[gv->dest_format]->tname);
    printf("\n");
  }
#endif

  initlist(&gv->inputlist);
  initlist(&gv->lnksec);
  gv->dest_name = "a.out";
  gv->maxerrors = DEF_MAXERRORS;

  if (argc<2 || (argc==2 && *argv[1]=='?')) {
    show_usage();
    exit(EXIT_SUCCESS);
  }

  for (i=1; i<argc; i++) {

    if (*argv[i] == '-') {  /* option detected */
      switch (argv[i][1]) {

        case 'b':  
          if (!strcmp(&argv[i][2],"aseoff")) {  /* set base-relative offset */
            ++i;
            if (i<argc && *argv[i]!='-') {
              long bo;
              sscanf(argv[i],"%li",&bo);
              fff[gv->dest_format]->baseoff = bo;
            }
            else
              error(34,argv[--i]);  /* option requires argument */
          }

          else {  /* select target format */
            if (buf = get_option_arg(argc,argv,&i)) {
              for (j=0; fff[j]; j++)
                if (!strcmp(fff[j]->tname,buf))
                  break;
              if (fff[j])
                gv->dest_format = (uint8)j;
              else
                error(9,buf);  /* invalid target format */
            }
            else
              error(5,'b');  /* option requires argument */
          }
          break;

        case 'd':
          if (buf = get_option_arg(argc,argv,&i)) {
            switch (*buf) {
              case 'n':  /* static */
                dynamic = FALSE;
                break;
              case 'y':  /* dynamic */
                dynamic = TRUE;
                break;
              case 'c':
                gv->alloc_common = TRUE;  /* force alloc. of common syms. */
                break;
              case 'p':
                /* @@@ something with procedures... don't know */
                break;
              default:
                error(4,*buf);  /* unknown argument for option -d */
                break;
            }
          }
          else
            error(5,'d');  /* option requires argument */
          break;

        case 'h':  /* help text */
          show_usage();
          exit(EXIT_SUCCESS);

        case 'l':  /* library specifier */
          if (buf = get_option_arg(argc,argv,&i)) {
            ifn = alloc(sizeof(struct InputFile));
            ifn->name = buf;
            ifn->lib = TRUE;
            ifn->dynamic = dynamic;
            ifn->so_ver = so_version;
            so_version = 0;
            addtail(&gv->inputlist,&ifn->n);
          }
          else
            error(5,'l');  /* option requires argument */
          break;

        case 'm':
          if (!strcmp(&argv[i][2],"ultibase"))
            gv->multibase = TRUE;
          else
            error(2,argv[i]);  /* unrecognized option */
          break;

        case 'n':
          if (!strcmp(&argv[i][2],"ostdlib")) {
            if (stdlib) {
              remhead(&gv->libpaths);
              stdlib = FALSE;
            }
          }
          else
            error(2,argv[i]);  /* unrecognized option */
          break;

        case 'o':  /* set output file name */
          if (!(gv->dest_name = get_option_arg(argc,argv,&i)))
            error(5,'o');  /* option requires argument */
          break;

        case 'r':  /* output is an relocatable object again */
          gv->dest_object = TRUE;
          break;

        case 's':
          switch (argv[i][2]) {
            case '\0':  /* strip all symbols */
              gv->strip_symbols = STRIP_ALL;
              break;
            case 'c':   /* -sc force small code */
              gv->small_code = TRUE;
              break;
            case 'd':   /* -d force small data */
              gv->small_code = TRUE;
              break;
            case 't':   /* -static */
              dynamic = FALSE;
              break;
            default:
              error(2,argv[i]);  /* unrecognized option */
              break;
          }
          break;

        case 't':  /* trace file accesses */
          gv->trace_file = stderr;
          break;

        case 'u':  /* mark symbol as undefined */
          /* @@@ ... */
          break;

        case 'v':  /* show version and target info */
          show_version();
          printf("Standard library path: %s\nDefault target: %s\n"
                 "Supported targets:", stdlib ? 
                 ((struct LibPath *)(gv->libpaths.first))->path : "none",
                 fff[gv->dest_format]->tname);
          for (j=0; fff[j]; j++)
            printf(" %s",fff[j]->tname);
          printf("\n");
          exit(EXIT_SUCCESS);

        case 'w':  /* suppress warnings */
          gv->dontwarn = TRUE;
          break;

        case 'x':  /* discard all local symbols */
          gv->discard_local = DISLOC_ALL;
          break;

        case 'y':  /* trace all accesses on a specific symbol */
          if (gv->trace_syms == NULL)
            gv->trace_syms = alloc_hashtable(TRSYMHTABSIZE);
          if (buf = get_option_arg(argc,argv,&i)) {
            struct SymNames **chain = 
                            &gv->trace_syms[elf_hash(buf)%TRSYMHTABSIZE];
            while (*chain)
              chain = &(*chain)->next;
            *chain = alloczero(sizeof(struct SymNames));
            (*chain)->name = buf;
          }
          else
            error(5,'y');  /* option requires argument */
          break;

        case 'B':  /* set link mode */
          if (buf = get_option_arg(argc,argv,&i)) {
            if (!strcmp(buf,"static")) {
              dynamic = FALSE;
            }
            else if (!strcmp(buf,"dynamic")) {
              dynamic = TRUE;
            }
            else if (!strcmp(buf,"shareable")) {
              gv->dest_object = TRUE;
              gv->dest_sharedobj = TRUE;
            }
            else if (!strcmp(buf,"forcearchive")) {
              gv->whole_archive = TRUE;
            }
            else if (!strcmp(buf,"symbolic")) {
              ;  /* don't know, what this means... */
            }
            else {
              error(3,buf);  /* unknown link mode */
            }
          }
          else
            error(5,'B');  /* option requires argument */
          break;

        case 'F':  /* read a file with object file names */
          if (buf = get_option_arg(argc,argv,&i))
            ReadListFile(gv,buf);
          else
            error(5,'F');  /* option requires argument */
          break;

        case 'L':  /* new library search path */
          if (buf = get_option_arg(argc,argv,&i)) {
            libp = alloc(sizeof(struct LibPath));
            libp->path = buf;
            addtail(&gv->libpaths,&libp->n);
          }
          else
            error(5,'L');  /* option requires argument */
          break;

        case 'M':  /* mapping output */
          gv->map_file = stdout;
          break;

        case 'R':  /* use short form for relocations */
          gv->short_rel = TRUE;
          break;

        case 'S':  /* strip debugger symbols */
          gv->strip_symbols = STRIP_DEBUG;
          break;

        case 'T':  /* define base address of a section */
          if (buf = get_option_arg(argc,argv,&i)) {
            if (++i < argc) {
              struct SecBase *sb = alloczero(sizeof(struct SecBase));
              struct SecBase *p = (struct SecBase *)&gv->secbases;

              sb->name = buf;
              sscanf(argv[i],"%li",(long *)&sb->base); /* set base address */              
              while (p->next)
                p = p->next;
              p->next = sb;
            }
          }
          error(5,'T');  /* option requires argument */
          break;

        case 'V':  /* set minimum version for next shared object */
          if (buf = get_option_arg(argc,argv,&i))
            so_version = atoi(buf);
          else
            error(5,'V');  /* option requires argument */
          break;

        case 'X':  /* discard temporary local symbols only */
          gv->discard_local = DISLOC_TMP;
          break;

        default:
          error(2,argv[i]);  /* unrecognized option */
          break;
      }
    }

    else {  /* normal input file name */
      ifn = alloc(sizeof(struct InputFile));
      ifn->name = argv[i];
      ifn->lib = FALSE;
      addtail(&gv->inputlist,&ifn->n);
    }
  }

  if (listempty(&gv->inputlist))
    error(6);  /* no input files */

  /* link them... */
  linker_init(gv);
  linker_load(gv);     /* load all objects and libraries and their symbols */
  linker_resolve(gv);  /* resolve symbol references */
  linker_relrefs(gv);  /* find all relative references between sections */
  linker_join(gv);     /* join sections with same name and type */
  linker_copy(gv);     /* copy section contents and fix symbol offsets */
  linker_relocate(gv); /* relocate addresses in joined sections */
  linker_write(gv);    /* write output file in selected target format */
  linker_cleanup(gv);

  cleanup(gv);
}


static char *get_option_arg(int argc,char *argv[],int *i)
/* get pointer to the string, which either directly follows the option
   character or is stored in the next argument */
{
  if (argv[*i][2])
    return (&argv[*i][2]);
  else {
    if (++*i<argc) {
      if (argv[*i][0]!='-' || isdigit(argv[*i][1]))  /* another option? */
        return (argv[*i]);
      else
        --*i;
    }
  }
  return NULL;
}


static void  ReadListFile(struct GlobalVars *gv,char *name)
/* read a file, which contains a list of object file names */
{
  FILE *f;
  struct InputFile *ifn;
  char buf[256],c,*p,*n=NULL;

  if (f = fopen(name,"r")) {
    while (fgets(buf,255,f)) {
      p = buf;
      do {
        c = *p++;
        if (c=='\"') {  /* read file name in quotes */
          n = p;
          while (*p && *p!='\"')
            ++p;
          c = *p++ ? 1:0;
        }
        else if (c>' ' && !n)
          n = p-1;  /* new file name */
        if (c<=' ' && n) {  /* file name ends here */
          *(p-1) = 0;
          ifn = alloc(sizeof(struct InputFile));
          ifn->name = allocstring(n);
          ifn->lib = FALSE;
          addtail(&gv->inputlist,&ifn->n);
          n = NULL;
        }
      }
      while (c);
    }
    fclose(f);
  }
  else
    error(8,name);  /* cannot open */
}


void cleanup(struct GlobalVars *gv)
{
  exit(gv->returncode);
}
