/* emxomfld.c (emx+gcc) */

/* This program provides an ld-like interface to LINK386.

Copyright (c) 1992, 1993 Eberhard Mattes

This file is part of emxomld.

emxomfld 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.

emxomfld 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.

As a special exception, emxomfld can be distributed with emx.  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <process.h>
#include <io.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <errno.h>
#include <sys/moddef.h>
#include "defs.h"

#define VERSION "0.8h"

#define FALSE 0
#define TRUE  1

/* A member of a linked list of strings such as file names. */
typedef struct name_list
{
  struct name_list *next;
  char *name;
} name_list;


/* The output file name, specified by the -o option.  */
static const char *output_fname = NULL;

/* The map file name (output), set if a file matching *.map is given
   on the command line. */
static const char *map_fname = NULL;

/* The module definition file name (input), set if a file matching
   *.def is given on the command line. */
static const char *def_fname = NULL;

/* The binary resource file name (input), set if a file matching *.res
   is given on the command line. */
static const char *res_fname = NULL;

/* Base address of the excecutable file, specified by the -T
   option. */
static const char *base = NULL;

/* List of directories searched for libraries.  Each -L option adds a
   directory to this list.  add_libdirs is used to add another entry
   at the end of the list. */
static name_list *libdirs = NULL;
static name_list **add_libdirs = &libdirs;

/* List of object files.  Each file given on the command line which
   does not match *.def, *.lib, *.map and *.res is added to this
   list.  add_obj_fnames is used to add another entry at the end of
   the list. */
static name_list *obj_fnames = NULL;
static name_list **add_obj_fnames = &obj_fnames;

/* List of library files.  Each file matching *.lib given on the
   command line is added to this list.  The -l option also adds an
   entry to this list. add_lib_fnames is used to add another entry at
   the end of the list. */
static name_list *lib_fnames = NULL;
static name_list **add_lib_fnames = &lib_fnames;

/* List of LINK386 options.  LINK386 options can be specified with the
   -O option.  add_options is used to add another entry at the end of
   the list. */
static name_list *options = NULL;
static name_list **add_options = &options;

/* The command line passed to LINK386. */
static char command_line[260];

/* The current length of the command line. */
static int line_len;

/* Non-zero if arguments go into the response file instead of
   command_line. */
static int response_flag;

/* The name of the response file. */
static char response_fname[L_tmpnam] = "";

/* The response file. */
static FILE *response_file = NULL;

/* Non-zero if debugging information is to be omitted.  Set by the -s
   and -S options. */
static int strip_symbols = FALSE;

/* Non-zero if emxomfld should create an .exe file and touch the
   output file.  Set by the -Zexe option. */
static int exe_flag = FALSE;

/* Non-zero when creating a dynamic link library.  Set by the -Zdll
   option. */
static int dll_flag = FALSE;

/* The stack size, specified by the -Zstack option, in Kbyte.  If the
   -Zstack option is not used, this variable is 0. */
static long stack_size = 0;

/* Prototypes. */

static void usage (void) NORETURN2;
static void *xmalloc (size_t n);
static char *xstrdup (const char *s);
static void add_name_list (name_list ***add, const char *src);
static void conv_path (char *name);
static void put_arg (const char *src, int path);
static void put_args (const name_list *list, int paths);
static void make_env (void);
static void cleanup (void);
static void arg_init (int rsp);
static void arg_end (void);
int main (int argc, char *argv[]);


/* Tell the user how to run this program. */

static void usage (void)
{
  fprintf (stderr, "emxomfld " VERSION " -- "
           "Copyright (c) 1992-1993 by Eberhard Mattes\n\n");
  fprintf (stderr,
           "Usage: emxomfld -o <file> [-l <lib>] [-L <libdir>] [-T <base>] [-sS]\n"
           "                [-Zexe] [-Zdll] [-Zstack <size>] [-O <option>] <file>...\n");
  exit (1);
}


/* Allocate N bytes of memory.  Quit on failure.  This function is
   used like malloc(), but we don't have to check the return value. */

static void *xmalloc (size_t n)
{
  void *p;
  
  p = malloc (n);
  if (p == NULL)
    {
      fprintf (stderr, "emxomfld: out of memory\n");
      exit (2);
    }
  return (p);
}


/* Create a duplicate of the string S on the heap.  Quit on failure.
   This function is used like strdup(), but we don't have to check the
   return value. */

static char *xstrdup (const char *s)
{
  char *p;
  
  p = xmalloc (strlen (s) + 1);
  strcpy (p, s);
  return (p);
}


/* Add the name SRC to a list.  ADD is a pointer to the pointer of the
   end of the list.  We duplicate the string before adding it to the
   list. */

static void add_name_list (name_list ***add, const char *src)
{
  name_list *new;

  new = xmalloc (sizeof (name_list));
  new->next = NULL;
  new->name = xstrdup (src);
  *(*add) = new;
  (*add) = &new->next;
}


/* Replace forward slashes `/' in NAME with backslashes `\'.  LINK386
   needs backslashes in path names. */

static void conv_path (char *name)
{
  char *p;

  for (p = name; *p != 0; ++p)
    if (*p == '/')
      *p = '\\';
}


/* Add the argument SRC to the command line or to the response file.
   If PATH is non-zero, SRC is a path name and slashes are to be
   replaced by backslashes.  If the command line gets too long, a
   response file is created. */

static void put_arg (const char *src, int path)
{
  int len, max_len;
  char *tmp;

  if (src != NULL)
    {

      /* Instead of a comma, we write a newline to the response
         file. */

      if (response_file != NULL && strcmp (src, ",") == 0)
        {
          fputc ('\n', response_file);
          line_len = 0;
          return;
        }

      /* Make a local copy of SRC to be able to modify it.  Then,
         translate forward slashes to backslashes if PATH is
         non-zero. */

      len = strlen (src);
      tmp = alloca (len + 1);
      strcpy (tmp, src);
      if (path)
        conv_path (tmp);

      /* Check if we've reached the maximum line length.  If the
         maximum command line length is exceeded, create a response
         file and write the remaining arguments to that file instead
         of putting them on the command line. */

      max_len = (response_file == NULL ? 110 : 52);
      if (line_len + len + 1 > max_len)
        {

          /* If SRC is a single comma or a single semicolon, copy it
             to the output, ignoring the maximum line length.  This is
             to meet the LINK386 command syntax. The maximum line
             length allows for enough commas and semicolons added this
             way. */

          if ((*tmp == ',' || *tmp == ';') && tmp[1] == 0)
            {
              if (response_file == NULL)
                {
                  command_line[line_len+0] = *tmp;
                  command_line[line_len+1] = 0;
                }
              else
                fputc (*tmp, response_file);
              ++line_len;
              return;
            }

          /* If a response file has not yet been opened, open it. */

          if (response_file == NULL)
            {

              /* Complain if we are not allowed to use a response
                 file. */

              if (!response_flag)
                {
                  fprintf (stderr, "emxomfld: command line too long\n");
                  exit (2);
                }

              /* Choose a unique file name and create the response
                 file. */

              strcpy (response_fname, "ldXXXXXX");
              if (mktemp (response_fname) == NULL)
                {
                  perror ("emxomfld");
                  exit (2);
                }
              response_file = fopen (response_fname, "wt");
              if (response_file == NULL)
                {
                  perror ("emxomfld");
                  exit (2);
                }

              /* Add the name of the response file to the command
                 line. */

              command_line[line_len++] = ' ';
              command_line[line_len++] = '@';
              strcpy (command_line+line_len, response_fname);
            }
          else
            {

              /* Start a new line in the response file. */

              fputs (" +\n", response_file);
            }
          line_len = 0;
        }

      /* Separate command line arguments by spaces (unless the
         argument to be added starts with a delimiter. */

      if (line_len != 0 && *src != ',' && *src != ';')
        {
          if (response_file == NULL)
            command_line[line_len++] = ' ';
          else
            fputc (' ', response_file);
        }

      /* Finally write the argument to the command line or to the
         response file and adjust the current line length. */

      if (response_file == NULL)
        strcpy (command_line + line_len, tmp);
      else
        fputs (tmp, response_file);
      line_len += len;
    }
}


/* Put a list of arguments onto the command line or into the response
   file.  If PATHS is non-zero, the arguments are path names and
   slashes are to be replaced by backslashes. */

static void put_args (const name_list *list, int paths)
{
  while (list != NULL)
    {
      put_arg (list->name, paths);
      list = list->next;
    }
}


/* Build the environment for LINK386: define the LIB environment
   variable. */

static void make_env (void)
{
  static char tmp[4096];
  char *p;
  int len;
  const name_list *list;

  /* Create a string for putenv(). */

  strcpy (tmp, "LIB=");
  len = strlen (tmp);

  /* Add the library directories to LIB, using `;' as separator. */

  for (list = libdirs; list != NULL; list = list->next)
    {
      if (tmp[len-1] != ';')
        tmp[len++] = ';';
      strcpy (tmp+len, list->name);
      conv_path (tmp+len);
      len += strlen (list->name);
    }

  /* Append to the end the previous definition of LIB. */

  p = getenv ("LIB");
  if (p != NULL)
    {
      if (tmp[len-1] != ';')
        tmp[len++] = ';';
      strcpy (tmp+len, p);
    }


  /* Put the new value of LIB into the environment. */

  putenv (tmp);
}


/* Start a new set of command line arguments.  If RSP is non-zero, we
   are allowed to use a response file. */

static void arg_init (int rsp)
{
  command_line[0] = 0;
  line_len = 0;
  response_flag = rsp;
}


/* Call this after adding all the command line arguments.  If a
   response file has been created, add a newline and close it. */

static void arg_end (void)
{
  if (response_file != NULL)
    {
      fputc ('\n', response_file);
      if (fflush (response_file) != 0 || fclose (response_file) != 0)
        {
          perror ("emxomfld");
          exit (2);
        }
      response_file = NULL;
    }
}


/* Cleanup by closing (if open) and deleting (if pressent) the
   response file.  This function is used with atexit(). */

static void cleanup (void)
{
  if (response_file != NULL)
    {
      fclose (response_file);
      response_file = NULL;
    }
  if (response_fname[0] != 0)
    {
      remove (response_fname);
      response_fname[0] = 0;
    }
}


/* Main function of emxomf.  Parse the command line and call LINK386
   (and optionally RC). */

int main (int argc, char *argv[])
{
  int c, rc, files;
  int opt_i;
  const char *ext;
  char tmp[512], *t;

  /* Close and delete the response file on exit. */

  atexit (cleanup);

  /* Prepare parsing of the command line. */

  opt_i = FALSE; files = 0;
  opterr = FALSE; optmode = GETOPT_KEEP;
  if (argc < 2)
    usage ();

  /* Parse the command line options and other arguments. */

  while ((c = getopt (argc, argv, "o:O:il:vL:T:sSxXZ:")) != EOF)
    switch (c)
      {
      case 'i':                 /* Use /INFORMATION option of LINK386 */
        opt_i = TRUE;
        break;

      case 'l':                 /* Add library */
        add_name_list (&add_lib_fnames, optarg);
        break;

      case 'o':                 /* Set output file name */
        output_fname = optarg;
        break;

      case 'L':                 /* Add library directory */
        add_name_list (&add_libdirs, optarg);
        break;

      case 'T':                 /* Set base address */
        base = optarg;
        break;

      case 's':                 /* Strip all symbols */
      case 'S':                 /* Strip debugging symbols */
        strip_symbols = TRUE;
        break;

      case 'x':                 /* Discard all local symbols */
      case 'X':                 /* Discard local symbols starting with L */
        break;

      case 'v':                 /* For compatibility */
        break;

      case 'O':                 /* Specify LINK386 option */
        add_name_list (&add_options, optarg);
        break;

      case 'Z':                 /* -Zdll, -Zexe and -Zstack */
        if (strcmp (optarg, "dll") == 0)
          dll_flag = TRUE;
        else if (strcmp (optarg, "exe") == 0)
          exe_flag = TRUE;
        else if (strcmp (optarg, "stack") == 0)
          {
            /* This makes assumptions on the internals of getopt(). */

            if (optind >= argc || argv[optind][0] == '-')
              usage ();
            errno = 0;
            stack_size = strtol (argv[optind], &t, 0);
            if (errno != 0 || *t != 0 || t == argv[optind])
              usage ();
            ++optind;
          }
        else
          {
            fprintf (stderr, "emxomfld: invalid option\n");
            usage ();
          }
        break;

      case 0:                   /* Non-option argument */

        /* Extract the extension to see what to do with this
           argument. */

        ext = _getext (optarg);
        if (ext == NULL)
          ext = "";

        /* If it's a .def file, use it as module definition file
           (input). */

        if (stricmp (ext, ".def") == 0)
          {
            if (def_fname != NULL)
              {
                fprintf (stderr,
                         "emxomfld: multiple module definition files\n");
                usage ();
              }
            def_fname = optarg;
          }

        /* If it's a .map file, use it as map file (output). */

        else if (stricmp (ext, ".map") == 0)
          {
            if (map_fname != NULL)
              {
                fprintf (stderr, "emxomfld: multiple map files files\n");
                usage ();
              }
            map_fname = optarg;
          }

        /* If it's a .res file, use it as binary resource file
           (input). */

        else if (stricmp (ext, ".res") == 0)
          {
            if (res_fname != NULL)
              {
                fprintf (stderr,
                         "emxomfld: multiple binary resource files\n");
                usage ();
              }
            res_fname = optarg;
          }

        /* If it's a .lib file, use it as library file. */

        else if (stricmp (ext, ".lib") == 0)
          add_name_list (&add_lib_fnames, optarg);

        /* Otherwise, assume it's an object file. */

        else
          add_name_list (&add_obj_fnames, optarg);
        ++files;
        break;

      default:
        fprintf (stderr, "emxomfld: invalid option\n");
        usage ();
      }

  /* Set default value for output file. */

  if (output_fname == NULL)
    {
      fprintf (stderr,
               "emxomfld: no output file, creating $$$.exe or $$$.dll\n");
      output_fname = "$$$";
    }

  /* Check if there are any input files. */

  if (files == 0)
    {
      fprintf (stderr, "emxomfld: no input files\n");
      usage ();
    }

  /* Remove the output file if -Zexe is given. */

  if (exe_flag)
    remove (output_fname);

  /* If there's no .map file, pass "nul" to LINK386 in the map file
     field. */

  if (map_fname == NULL)
    map_fname = "nul";

  /* Start building the LINK386 command line.  We can use a response
     file if the command line gets too long. */

  arg_init (TRUE);

  /* Default options are:

     /BATCH             Run in batch mode (disable prompting, don't
                        echo response file)

     /NOLOGO            Don't display sign-on banner

     /NOEXTDICTIONARY   Don't use extended dictionary (redefining
                        library symbols is quite common)

     /NOIGNORECASE      Make symbols case-sensitive */

  put_arg ("link386", TRUE);
  put_arg ("/bat", FALSE);
  put_arg ("/nol", FALSE);
  put_arg ("/noe", FALSE);
  put_arg ("/noi", FALSE);

  /* Add the /INFORMATION option if the -i option was given.  This is
     for debugging. */

  if (opt_i)
    put_arg ("/i", FALSE);

  /* Add the /DEBUG option if the -s option was not given.  Without
     this, LINK386 throws away debugging information. */

  if (!strip_symbols)
    put_arg ("/de", FALSE);

  /* Add the /BASE:n option to set the base address.  This specifies
     the preferred load address of object 1.  The base address being
     used is 0x10000 unless a DLL is generated or the -T option was
     given. */

  if (base == NULL && !dll_flag)
    {
      struct _md *md;

      if (def_fname != NULL)
        {
          md = _md_open (def_fname);
          if (md == NULL)
            {
              fprintf (stderr, "emxomfld: cannot open `%s'\n", def_fname);
              exit (2);
            }
          if (_md_next_token (md) == _MD_LIBRARY)
            dll_flag = TRUE;
          _md_close (md);
        }
    }
  if (base == NULL && !dll_flag)
    base = "0x10000";
  if (base != NULL)
    {
      sprintf (tmp, "/bas:%s", base);
      put_arg (tmp, FALSE);
    }

  /* Add the /STACK:n option if the -Zstack option was given. */

  if (stack_size != 0)
    {
      sprintf (tmp, "/st:0x%lx", stack_size * 1024);
      put_arg (tmp, FALSE);
    }

  /* Add the LINK386 options specified with -O. */

  put_args (options, FALSE);

  /* Put the object file names onto the command line. */

  put_args (obj_fnames, TRUE);
  put_arg (",", FALSE);

  /* Put the output file name onto the command line. */

  put_arg (output_fname, TRUE);
  put_arg (",", FALSE);

  /* Put the map file name onto the command line. */

  put_arg (map_fname, TRUE);
  put_arg (",", FALSE);

  /* Put the library file names onto the command line. */

  put_args (lib_fnames, TRUE);
  put_arg (",", FALSE);

  /* Put the name of the module definition file onto the command line. */

  put_arg (def_fname, TRUE);
  put_arg (";", FALSE);
  arg_end ();

  /* Build the environment for LINK386. */

  make_env ();

  /* Call LINK386 via CMD.EXE (what a waste -- but I'm lazy) and abort
     on failure. */

  rc = system (command_line);
  if (rc < 0)
    {
      perror ("emxomfld: link386");
      exit (2);
    }

  /* Run RC if LINK386 completed successfully and a binary resource
     file was given on the command line. */

  if (rc == 0 && res_fname != NULL)
    {
      arg_init (TRUE);
      put_arg ("rc", TRUE);
      put_arg (res_fname, TRUE);
      put_arg (output_fname, TRUE);
      arg_end ();
      rc = system (command_line);
      if (rc < 0)
        {
          perror ("emxomfld: rc");
          exit (2);
        }
    }

  /* If both LINK386 and RC competed successfully and the -Zexe option
     was given, touch the output file (without .exe) to keep `make'
     happy. */

  if (rc == 0 && exe_flag)
    {
      int h;

      h = open (output_fname,
                O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
      if (h < 0)
        {
          perror ("emxomfld");
          exit (2);
        }
      close (h);
    }

  /* Return the return code of LINK386 (or RC). */

  return (rc);
}
