/* emxomf.c (emx+gcc) */

/* This program converts GNU-style a.out object files (with emx
   extensions) to OS/2-style OMF (Object Module Formats) object
   files.

Copyright (c) 1992, 1993 Eberhard Mattes

This file is part of emxomf.

emxomf 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.

emxomf 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, emxomf can be distributed with emx.  */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <getopt.h>
#include <sys/param.h>
#include <sys/emxload.h>
#include <ar.h>

/* The version number of this program.  This is printed with the usage
   message. */

#define VERSION "0.8h"

/* Insert private header files. */

#include "defs.h"               /* Common definitions */
#include "omflib.h"             /* Handling OMF libraries */
#include "stabshll.h"           /* Convert debug information */
#include "grow.h"               /* Growing objects */

/* Flag bits for FLAGS field of the struct symbol structure. */

#define SF_FAR16        0x01    /* 16:16 pointer */

/* This structure holds additional data for symbols. */

struct symbol
{
  int index;                    /* EXTDEF index */
  int flags;                    /* SF_FAR16 */
};

/* This program keeps OMF symbols (names) in a list of lname
   structures. */

struct lname
{
  struct lname *next;           /* Pointer to next element */
  char *name;                   /* Name of the symbol */
  int index;                    /* OMF index of the symbol */
};

/* Names of files to be deleted on program termination are stored in a
   list of delete structures. */

struct delete
{
  struct delete *next;          /* Pointer to next element */
  char *name;                   /* Name of the file to be deleted */
};

/* This structure holds the information about one class (set) of a.out
   set elements.  Set elements are used for constructors and
   destructors.  These structures are chained in a list.  See
   write_set_data() for details. */

struct set
{
  struct set *next;             /* Pointer to next class of set elements */
  char *name;                   /* Name of the set (the a.out symbol) */
  int seg_name[3];              /* Name indices of the three segments */
  int seg_index[3];             /* Segment indices of the three segments */
  int group_name;               /* Name index of the group */
  int group_index;              /* Group index of the group */
  int def;                      /* Non-zero if head element encountered */
  int count;                    /* Number of non-head non-tail elements */
  dword *data;                  /* Values of set elements (COUNT elements) */
};

/* Default library requests (see -i option) are stored in a list of
   libreq structures. */

struct libreq
{
  char *name;                   /* Name of the default library */
  struct libreq *next;          /* Pointer to next element */
};

/* This structure describes an OMF-style line number. */

struct line
{
  dword addr;                   /* Start address of the line */
  int line;                     /* Line number */
  int file_index;               /* File index */
};


/* The number of symbols in the a.out file. */
int sym_count = 0;

/* The sym_ptr variable points to an array of a.out symbols.  There
   are sym_count table entries.  Note that this table is complemented
   by the sym_more table. */
const struct nlist *sym_ptr = NULL;

/* The str_ptr variable points to the a.out string table.  The
   elements of sym_ptr contain offsets into this table.  The table
   starts with a 32-bit number.  The rest of the table consists of
   null-terminated strings. */
const byte *str_ptr = NULL;

/* The text_ptr variable points to the first byte of the text
   segment.  text_size bytes are available at text_ptr. */
byte *text_ptr = NULL;

/* The size of the text segment. */
long text_size = 0;

/* Public variables for communication with stabshll.c. */

struct buffer tt = BUFFER_INIT;
struct buffer sst = BUFFER_INIT;
struct buffer sst_reloc = BUFFER_INIT;
struct grow sst_boundary_grow = GROW_INIT;
struct grow tt_boundary_grow = GROW_INIT;
int *sst_boundary = NULL;
int *tt_boundary = NULL;

/* Prototypes for public functions. */

void error (const char *fmt, ...) NORETURN2;
void *xmalloc (size_t n);
void *xrealloc (void *ptr, size_t n);
char *xstrdup (const char *s);
const struct nlist *find_symbol (const char *name);


/* Private variables. */

/* The name of the current input file. */
static const char *inp_fname;

/* The name of the current output file. */
static const char *out_fname = NULL;

/* Use this file name for reporting errors.  This is either the name
   of the input file or a library name plus module name:
   library(module). */
static const char *error_fname;

/* The output directory.  This is set by the -O option. */
static const char *output_dir = "";

/* The current input file. */
static FILE *inp_file;

/* The current output file. */
static FILE *out_file = NULL;

/* While writing a LIB response file, this variable contains the
   stream pointer.  Otherwise, this variable is NULL. */
static FILE *response_file = NULL;

/* When creating an OMF library (.lib file), this variable contains
   the descriptor used by the omflib library.  Otherwise, this
   variable is NULL. */
static struct omflib *out_lib = NULL;

/* This buffer receives error messages from the omflib library. */
static char lib_errmsg[512];

/* emxomf reads a complete a.out module into memory.  inp_buf points
   to the buffer holding the current a.out module.  This pointer is
   aliased by sym_ptr etc. */
static byte *inp_buf;

/* OMF records are constructed in this buffer.  The first three bytes
   are used for the record type and record length.  One byte at the
   end is used for the checksum. */
static byte out_buf[1+2+MAX_REC_SIZE+1];

/* Index into out_buf (to be more precise: it's a pointer into
   out_data, which is an alias to out_buf+3) for the next byte of the
   OMF record. */
static int out_idx;

/* The list of OMF names.  lnames is the head of the list, lname_add
   is used to add another name to the end of the list. */
static struct lname *lnames;
static struct lname **lname_add;

/* The list of sets.  sets is the head of the list, set_add is used to
   add another set to the end of the list. */
static struct set *sets;
static struct set **set_add;

/* This variable is TRUE when converting a gcc-compiled module for
   EMX.DLL (-e option).  In that case, the DGROUP is named DATAGROUP
   instead of DGROUP and leading underscores are not removed. */
static int opt_e = FALSE;

/* If this variable is TRUE, an a.out archive is split into several
   OMF .obj files (-x option).  If this variable is FALSE, an a.out
   archive is converted into an OMF library file. */
static int opt_x = FALSE;

/* This is the page size for OMF libraries.  It is set by the -p
   option. */
static int page_size = 16;

/* The index of the next OMF name, used by find_lname().  Name indices
   are established by LNAMES records written by write_lnames(). */
static int lname_index;

/* The index of the next segment, used by seg_def() for creating
   SEGDEF records. */
static int segdef_index;

/* The index of the next group, used for creating GRPDEF records. */
static int group_index;

/* The index of the next symbol, used for creating EXTDEF and COMDEF
   records. */
static int sym_index;

/* OMF name indices: segments, classes, groups, etc. */
static int ovl_name;            /* Overlay name, "" */
static int text_seg_name;       /* Text segment, "TEXT32" */
static int data_seg_name;       /* Data segment, "DATA32" */
static int udat_seg_name;       /* Explicit data segment, see -D */
static int stack_seg_name;      /* Stack segment, "STACK" */
static int bss_seg_name;        /* Uninitialized data segment, "BSS32" */
static int symbols_seg_name;    /* Symbols segment, "$$SYMBOLS" */
static int types_seg_name;      /* Types segment */
static int code_class_name;     /* Code class, "CODE" */
static int data_class_name;     /* Data class, "DATA" */
static int stack_class_name;    /* Stack class, "STACK" */
static int bss_class_name;      /* Uninitialized data class, "BSS" */
static int debsym_class_name;   /* Symbols class, "DEBSYM" */
static int debtyp_class_name;   /* Types class, "DEBTYP" */
static int flat_group_name;     /* FLAT group, "FLAT" */
static int dgroup_group_name;   /* DGROUP group, "DGROUP" or "DATAGROUP" */

/* Segment indices for the TEXT32, DATA32, STACK, BSS32, $$SYMBOLS,
   $$TYPES and explicit data segments. */
static int text_index;          /* Text segment, TEXT32 */
static int data_index;          /* Data segment, DATA32 */
static int udat_index;          /* Data segment set by -D or same as above */
static int stack_index;         /* Stack segment, STACK */
static int bss_index;           /* Uninitialized data segment, BSS32 */
static int symbols_index;       /* Symbols segment, $$SYMBOLS */
static int types_index;         /* Types segment, $$TYPES */

/* Group indices for the FLAT and DGROUP groups. */
static int flat_index;          /* FLAT group */
static int dgroup_index;        /* DGROUP group */

/* Next FIXUPP thread index to be used for the frame and target
   thread, respectively. */
static int frame_thread_index;  /* Frame thread */
static int target_thread_index; /* Target thread */

/* We define target threads for referencing the TEXT32, DATA32 (or the
   one set by -D) and BSS32 segments and the FLAT group in FIXUPP
   records.  A thread has not been defined if the variable is -1.
   Otherwise, the value is the thread number. */
static int text_thread;         /* TEXT32 segment */
static int data_thread;         /* Data segment (DATA32 or -D) */
static int bss_thread;          /* BSS32 segment */
static int flat_thread;         /* FLAT group */

/* This variable holds the module name.  See get_mod_name(). */
static char mod_name[256];

/* This growing array holds the file name table for generating line
   number information.  */
static char **file_list;
static struct grow file_grow;

/* This variable points to the a.out header of the input module. */
static const struct a_out_header *a_out_h;

/* This table contains additional information for the a.out symbols.
   Each entry of the sym_ptr table is complemented by an entry of this
   table. */
static struct symbol *sym_more;

/* The length of the a.out string table, in bytes.  This value
   includes the 32-bit number at the start of the string table. */
static long str_size;

/* omflib_write_module() stored the page number of the module to this
   variable. */
static word mod_page;

/* This variable is TRUE until we have written the first line to the
   LIB response file.  It is used to suppress the line continuation
   character `&'. */
static int response_first = TRUE;

/* This is the list of files to be deleted on program termination.
   See delete_files(). */
static struct delete *files_to_delete = NULL;

/* The module type (-l and -m options).  It is MT_MODULE unless the -l
   or -m option is used. */
static enum {MT_MODULE, MT_MAIN, MT_LIB} mod_type = MT_MODULE;

/* The start (entry point) symbol (-m option).  This is NULL unless
   the -m option (or -l with argument) is used. */
static char *entry_name = NULL;

/* The name of the identifier manipulation DLL.  If this variable is
   NULL, no IDMDLL record is written. */
static char *idmdll_name = "GPPDEMID";

/* If this variable is TRUE (-b option), we always use the 32-bit
   variant of the OMF records.  If this variable is FALSE, we use the
   16-bit variant whenever possible.  This non-documented feature is
   used for debugging. */
static int force_big = FALSE;

/* The name of the LIB response file (-r and -R options).  This value
   is NULL unless the -r or -R option is used. */
static char *response_fname = NULL;

/* When creating a LIB response file, emit commands for replacing
   modules if this variable is TRUE (-R option).  Otherwise, emit
   commands for adding modules (-r option). */
static int response_replace;

/* Delete input files after successful conversion if this variable is
   TRUE (-d option). */
static int delete_input_files = FALSE;

/* Strip debug information if this variable is TRUE (-s option).
   Otherwise, convert DBX debugging information to HLL debugging
   information. */
static int strip_symbols = FALSE;

/* The libreq_head variable contains a pointer to the head of the list
   of default libraries.  The -i option is used to add a default
   library request.  The libreq_add pointer is used for appending new
   elements to the tail of the list. */
static struct libreq *libreq_head = NULL;
static struct libreq **libreq_add = &libreq_head;

/* Simulate the data array of an OMF record.  The data is stored along
   with the record type (1 byte) and record length (2 bytes) in a
   single buffer. */
#define out_data (out_buf+1+2)

/* The data segment name, set by the -D option.  If this value is
   non-NULL, put variables into the named data segment, which isn't a
   member of DGROUP. */

static char *udat_seg_string = NULL;

/* Prototypes for private functions. */

static void init_rec (int type);
static void write_rec (void);
static void put_str (const char *src);
static void put_sym (const char *src);
static void put_16 (int x);
static void put_24 (long x);
static void put_32 (long x);
static void put_idx (int x);
static void put_mem (const char *src, int size);
static void doesn_fit (void) NORETURN2;
static int find_lname (const char *name);
static void define_set_names (void);
static void define_set_groups (void);
static void write_set_segs (void);
static void write_set_groups (void);
static void write_set_pub (void);
static void write_set_data (void);
static void write_extdef (void);
static void write_pubdef (void);
static void write_pubdef1 (int type, int index, int big, dword start);
static void write_theadr (void);
static void write_idmdll (void);
static void write_linnum (void);
static void init_obj (void);
static int seg_def (int name_index, int class_index, long size, int stack);
static void write_seg (int index, int seg_name, byte *src, long seg_size,
    const byte *rel, long rel_size, int sst_flag, const int *boundary,
    int boundary_count);
static void request_lib (const char *name);
static void write_debug_style (void);
static void o_to_omf (long size);
static void open_output (void);
static void close_output (void);
static void convert (const char *src_fname, const char *dst_fname);
static void delete_files (void);
static void usage (void) NORETURN2;
static void make_out_fname (const char *dst_fname, const char *inp_fname,
    const char *ext);
static int handle_import (void);


/* Display an error message on stderr, delete the output file and
   quit.  This function is invoked like printf(): FMT is a
   printf-style format string, all other arguments are formatted
   according to FMT.  The message should not end with a newline. The
   exit code is 2. */

void error (const char *fmt, ...)
{
  va_list arg_ptr;

  va_start (arg_ptr, fmt);
  fprintf (stderr, "emxomf: ");
  vfprintf (stderr, fmt, arg_ptr);
  fputc ('\n', stderr);

  /* If there is an output file, close and delete it. */

  if (out_file != NULL)
    {
      fclose (out_file);
      remove (out_fname);
    }
  exit (2);
}


/* Allocate N bytes of memory.  Quit on failure.  This function is
   used like malloc(), but we don't have to check the return value. */

void *xmalloc (size_t n)
{
  void *p;
  
  p = malloc (n);
  if (p == NULL)
    error ("Out of memory");
  return (p);
}


/* Change the allocation of PTR to N bytes.  Quit on failure.  This
   function is used like realloc(), but we don't have to check the
   return value. */

void *xrealloc (void *ptr, size_t n)
{
  void *p;
  
  p = realloc (ptr, n);
  if (p == NULL)
    error ("Out of memory");
  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. */

char *xstrdup (const char *s)
{
  char *p;
  
  p = xmalloc (strlen (s) + 1);
  strcpy (p, s);
  return (p);
}


/* Find an a.out symbol.  The underscore character `_' is prepended to
   NAME.  On success, a pointer to the symbol table entry (in the
   array pointed to by sym_ptr) is returned.  If the symbol is not
   found, NULL is returned. */

const struct nlist *find_symbol (const char *name)
{
  int i, j, n, len, ok, t;
  const byte *s;
  
  i = 4; len = strlen (name);

  /* Search the string table for a matching string. */

  while (i < str_size)
    {
      ok = TRUE; s = name;
      if (str_ptr[i] == '_')
        ++i;
      else
        ok = FALSE;
      if (ok && memcmp (name, str_ptr+i, len+1) == 0)
	{

          /* Search the symbol table for an appropriate entry
             referencing the string. */

	  n = sym_count;
          --i;                  /* Move back to the underscore */
	  for (j = 0; j < n; ++j)
	    if (sym_ptr[j].string == i)
	      {
		t = sym_ptr[j].type & ~N_EXT;
		if (t == N_TEXT || t == N_DATA || t == N_BSS
                    || (t == 0 && sym_ptr[j].value != 0))
		  return (sym_ptr+j);
	      }
	}
      i += strlen (str_ptr+i) + 1;
    }
  return (NULL);                /* Symbol not found */
}


/* Initialize variables for starting a new OMF output file. */

static void init_obj (void)
{
  /* Initialize the list of OMF-style names. */

  lnames = NULL; lname_add = &lnames; lname_index = 1;

  /* Initialize segments. */

  segdef_index = 1; sym_index = 1; group_index = 1;

  /* Initialize FIXUPP threads. */

  text_thread = -1; data_thread = -1; bss_thread = -1; flat_thread = -1;
  frame_thread_index = 0; target_thread_index = 0;

  /* Initialize set elements management. */

  sets = NULL; set_add = &sets;

  /* Initialize the buffers used for converting debugging
     information. */

  buffer_init (&tt);
  buffer_init (&sst);
  buffer_init (&sst_reloc);
}


/* Find an OMF-style name.  If NAME is not yet a known OMF-style name,
   it is added to the list of OMF-style names.  The index of the name
   is returned.  All the OMF-style names will be defined in LNAMES
   records.  Note: INDEX must be assigned sequentially, append to end
   of list!  Otherwise, write_lnames() would have to sort the list */

static int find_lname (const char *name)
{
  struct lname *p;

  /* Walk through the list of known OMF-style names.  If there is a
     match, return the index of the name. */

  for (p = lnames; p != NULL; p = p->next)
    if (strcmp (name, p->name) == 0)
      return (p->index);

  /* The name is not yet known.  Create a new entry. */

  p = xmalloc (sizeof (struct lname));
  p->name = xstrdup (name);
  p->index = lname_index++;

  /* Add the new entry to the tail of the list of names. */

  p->next = NULL;
  *lname_add = p;
  lname_add = &p->next;

  /* Return the index of the new name. */

  return (p->index);
}


/* Deallocate the memory used by the list of OMF-style names. */

static void free_lnames (void)
{
  struct lname *p, *q;

  for (p = lnames; p != NULL; p = q)
    {
      q = p->next;
      free (p->name);
      free (p);
    }
}


/* Start a new OMF record.  TYPE is the record type. */

static void init_rec (int type)
{
  out_buf[0] = (byte)type;
  out_idx = 0;
}


/* Write an OMF record.  The record type has been defined by
   init_rec(), the data has been defined by put_8(), put_idx() etc.
   The checksum is computed by this function.  An OMF record has the
   following format:

   struct omf_record
   {
     byte type;                 The record type
     word length;               The record length, exclusive TYPE and LENGTH
     byte data[LENGTH-1];       The record data
     byte checksum;             The check sum.  The sum of all the bytes in
                                the record is 0 (module 256)
   };

   If we're writing a LIB file, we call omflib_write_record().
   Otherwise, we build and write the record here.  An OMF record
   usually contains multiple definitions, see write_lnames() for a
   simple instance of the loop we use for packing multiple definitions
   into OMF records. */

static void write_rec (void)
{
  byte chksum;
  int i;

  if (out_lib != NULL)
    {

      /* Write the record to the library file. */

      if (omflib_write_record (out_lib, out_buf[0], out_idx, out_data,
                               TRUE, lib_errmsg) != 0)
        error (lib_errmsg);
    }
  else
    {

      /* Store the record length, LSB first. */

      out_buf[1] = (byte)(out_idx+1);
      out_buf[2] = (byte)((out_idx+1) >> 8);

      /* Compute the check sum. */

      chksum = 0;
      for (i = 0; i < 1+2+out_idx; ++i)
        chksum += out_buf[i];
      out_data[out_idx] = (byte)(256-chksum);

      /* Write the record. */

      if (fwrite (out_buf, 1+2+out_idx+1, 1, out_file) != 1)
        error ("Write error on output file `%s'", out_fname);
    }
}


/* This macro yields TRUE iff we can put another X bytes into the
   current OMF record. */

#define fits(X) (out_idx + (X) <= MAX_REC_SIZE-4)

/* This function is called if we try to build too big an OMF record.
   This cannot happen unless there is a bug in this program.  Display
   an error message and quit. */

static void doesn_fit (void)
{
  error ("Record too long");
}


/* Put the null-terminated string SRC into the current OMF record.  In
   the OMF record, the string is preceded by a length byte.  The
   string length must not exceed 127 and there must be enough space
   left in the OMF record.  If these conditions are not met, display
   an error message and quit. */

static void put_str (const char *src)
{
  int len;

  len = strlen (src);
  if (!fits (1+len))
    doesn_fit ();
  if (len > 127)
    error ("String too long");
  out_data[out_idx++] = (byte)len;
  memcpy (out_data+out_idx, src, len);
  out_idx += len;
}


/* Put the symbol SRC, a null-terminated string, into the current OMF
   record.  If SRC starts with the underscore character `_', that
   character is skipped.  This is where the leading underscore
   character of a.out symbols is removed.  C modules for emx.dll are
   treated specially here: The leading underscore is not removed
   unless the name looks like the name of an OS/2 API function.  The
   symbol length (after removing the leading underscore) must not
   exceed 127 characters.  There must be enough space left in the OMF
   record.  If these conditions are not met, display an error message
   and quit. */

static void put_sym (const char *src)
{
  if (opt_e)
    {
      if (strncmp (src, "_Dos32", 6) == 0
          || strncmp (src, "_DOS16", 6) == 0)
        ++src;
    }
  else if (*src == '_')
    ++src;
  put_str (src);
}

/* Put an 8-bit byte into the current OMF record.  This macro does not
   test for buffer / record overflow. */

#define put_8(x) out_data[out_idx++] = (byte)(x)

/* Put a 16-bit word (least significant byte first) into the current
   OMF record.  If there is not enough space left in the OMF record,
   display an error message and quit. */

static void put_16 (int x)
{
  if (!fits (2))
    doesn_fit ();
  out_data[out_idx++] = (byte)x;
  out_data[out_idx++] = (byte)(x >> 8);
}


/* Put a 24-bit word (least significant byte first) into the current
   OMF record.  If there is not enough space left in the OMF record,
   display an error message and quit. */

static void put_24 (long x)
{
  if (!fits (3))
    doesn_fit ();
  out_data[out_idx++] = (byte)x;
  out_data[out_idx++] = (byte)(x >> 8);
  out_data[out_idx++] = (byte)(x >> 16);
}


/* Put a 32-bit word (least significant byte first) into the current
   OMF record.  If there is not enough space left in the OMF record,
   display an error message and quit. */

static void put_32 (long x)
{
  if (!fits (4))
    doesn_fit ();
  out_data[out_idx++] = (byte)x;
  out_data[out_idx++] = (byte)(x >> 8);
  out_data[out_idx++] = (byte)(x >> 16);
  out_data[out_idx++] = (byte)(x >> 24);
}


/* Put the index X into the current OMF record.  Indices are used for
   identifying names (LNAMES), segments (SEGDEF), groups (GRPDEF)
   etc.  Indices are stored in one or two bytes, depending on the
   value.  Small indices (0 through 127) are stored as single byte.
   Large indices (128 through 32767) are stored as two bytes: the
   high-order byte comes first, with bit 7 set; the low-order byte
   comes next.  If there is not enough space left in the OMF record or
   if the value exceeds 32767, display an error message and quit. */

static void put_idx (int x)
{
  if (x <= 0x7f)
    {
      if (!fits (1))
        doesn_fit ();
      out_data[out_idx++] = (byte)x;
    }
  else if (x <=0x7fff)
    {
      if (!fits (2))
        doesn_fit ();
      out_data[out_idx++] = (byte)((x >> 8) | 0x80);
      out_data[out_idx++] = (byte)x;
    }
  else
    error ("Index too large");
}


/* Put SIZE bytes from SRC into the current OMF record.  If there is
   not enough space left in the OMF record, display an error message
   and quit. */

static void put_mem (const char *src, int size)
{
  if (!fits (size))
    doesn_fit ();
  memcpy (out_data+out_idx, src, size);
  out_idx += size;
}


/* Write LNAMES records into the output file for defining all the
   names stored in the LNAMES list.

   LNAMES record:
   Ú
   ³1   String length n
   ³n   Name
   À

   Name indices are assigned sequentially. */

static void write_lnames (void)
{
  const struct lname *p;
  int started;

  started = FALSE;
  for (p = lnames; p != NULL; p = p->next)
    {
      if (started && !fits (strlen (p->name)+1))
        {
          write_rec ();
          started = FALSE;
        }
      if (!started)
        {
          init_rec (LNAMES);
          started = TRUE;
        }
      put_str (p->name);
    }
  if (started)
    write_rec ();
}


/* Write EXTDEF records into the output file for all the external
   symbols stored in the sym_ptr array.

   EXTDEF record:
   Ú
   ³1   String length n
   ³n   External name
   ³1-2 Type index
   À

   Symbol indices are assigned sequentially. */

static void write_extdef (void)
{
  const char *name;
  int i, started;

  started = FALSE;
  for (i = 0; i < sym_count; ++i)
    if (sym_ptr[i].type == N_EXT && sym_ptr[i].value == 0)
      {
        name = str_ptr + sym_ptr[i].string;
        sym_more[i].index = sym_index++;
        if (memcmp (name, "__16_", 5) == 0)
          sym_more[i].flags |= SF_FAR16;
        if (started && !fits (strlen (name)+3))
          {
            write_rec ();
            started = FALSE;
          }
        if (!started)
          {
            init_rec (EXTDEF);
            started = TRUE;
          }
        put_sym (name);
        put_idx (0);            /* type index */
      }
  if (started)
    write_rec ();
}


/* Write PUBDEF records into the output file for the public symbols of
   the a.out-style symbol type TYPE.  Symbols of that type are defined
   in the segment having the OMF segment index INDEX.  Ignore symbols
   not fitting into a 16-bit PUBDEF record if BIG is FALSE.  Ignore
   symbols fitting into a 16-bit PUBDEF record if BIG is TRUE.  START
   is the virtual start address of the segment in the a.out file.

   PUBDEF record:
    1-2 Base group
    1-2 Base segment
    0/2 Base frame
   Ú
   ³1   String length n
   ³n   Public name
   ³2/4 Public offset (4 bytes for 32-bit PUBDEF record)
   ³1-2 Type index
   À

   The base frame field is present only if the base segment field is
   0. */

static void write_pubdef1 (int type, int index, int big, dword start)
{
  int i, started;
  const char *name, *pub_name;
  dword address;

  started = FALSE;
  for (i = 0; i < sym_count; ++i)
    if (sym_ptr[i].type == type)
      {
        address = sym_ptr[i].value - start;
        if ((address >= 0x10000 || force_big) == big)
          {
            name = str_ptr + sym_ptr[i].string;
            if (out_lib != NULL)
              {
                pub_name = name;
                if (*pub_name == '_' && !opt_e)
                  ++pub_name;
                if (omflib_add_pub (out_lib, pub_name, mod_page,
                                    lib_errmsg) != 0)
                  error (lib_errmsg);
              }
            if (started && !fits (strlen (name) + 6))
              {
                write_rec ();
                started = FALSE;
              }
            if (!started)
              {
                init_rec (big ? PUBDEF|REC32 : PUBDEF);
                put_idx (flat_index);
                put_idx (index);
                started = TRUE;
              }
            put_sym (name);
            if (big)
              put_32 (address);
            else
              put_16 (address);
            put_idx (0);        /* type index */
          }
      }
  if (started)
    write_rec ();
}


/* Write PUBDEF records into the output file for all the public N_TEXT
   and N_DATA symbols stored in the sym_ptr array.  Common symbols are
   handled by write_comdef() below. */

static void write_pubdef (void)
{
  write_pubdef1 (N_TEXT|N_EXT, text_index, FALSE, 0);
  write_pubdef1 (N_TEXT|N_EXT, text_index, TRUE, 0);
  write_pubdef1 (N_DATA|N_EXT, udat_index, FALSE, text_size);
  write_pubdef1 (N_DATA|N_EXT, udat_index, TRUE, text_size);
}


/* Write COMDEF records into the output file for all the common
   symbols stored in the sym_ptr array.  Common symbols are used for
   uninitialized variables.  The TYPE field of these symbols is 0
   (plus N_EXT) and the VALUE field is non-zero (it is the size of the
   variable).

   COMDEF record:
   Ú
   ³1   String length n
   ³n   Communal name
   ³1-2 Type index
   ³1   Data type (0x61: FAR data, 0x62: NEAR data)
   ³1-5 Communal length
   À

   The length is encoded in 1 to 5 bytes, depending on the value:

   0 through 0x7f       1 byte, containing the value
   0 through 0xffff     0x81, followed by 16-bit word
   0 through 0xffffff   0x84, followed by 24-bit word
   0 through 0xffffffff 0x88, followed by 32-bit word */

static void write_comdef (void)
{
  int i, started;
  long size;
  const char *name;

  started = FALSE;
  for (i = 0; i < sym_count; ++i)
    if (sym_ptr[i].type == N_EXT && sym_ptr[i].value != 0)
      {
        name = str_ptr + sym_ptr[i].string;
        sym_more[i].index = sym_index++;
        if (memcmp (name, "__16_", 5) == 0)
          sym_more[i].flags |= SF_FAR16;
        size = sym_ptr[i].value;
        if (started && !fits (strlen (name) + 12))
          {
            write_rec ();
            started = FALSE;
          }
        if (!started)
          {
            init_rec (COMDEF);
            started = TRUE;
          }
        put_sym (name);
        put_idx (0);                  /* Type index */
        put_8 (0x62);                 /* Data type */
        if (size < 0x80)
          put_8 (size);
        else if (size < 0x10000)
          {
            put_8 (0x81);
            put_16 (size);
          }
        else if (size < 0x1000000)
          {
            put_8 (0x84);
            put_24 (size);
          }
        else
          {
            put_8 (0x88);
            put_32 (size);
          }
      }
  if (started)
    write_rec ();
}


/* Write a SEGDEF record to define a segment.  NAME_INDEX is the name
   index of the segment name.  CLASS_INDEX is the name index of the
   class name.  SIZE is the size of the segment in bytes.  STACK is
   TRUE iff the segment is the stack segment.  seg_def() returns the
   segment index of the new segment.

   SEGDEF record:
    1   Segment attributes
    0/2 Frame number (present only if A=000)
    0/1 Offset (present only if A=000)
    2/4 Segment length (4 bytes for 32-bit SEGDEF record)
    1/2 Segment name index
    1/2 Segment class index
    1/2 Overlay name index

    The segment attributes byte contains the following fields:

    A (bits 5-7)        Alignment (101=relocatable, 32-bit alignment)
    C (bits 2-4)        Combination (010=PUBLIC, 101=STACK)
    B (bit 1)           Big (segment length is 64KB)
    P (bit 0)           USE32 */

static int seg_def (int name_index, int class_index, long size, int stack)
{
  byte seg_attr;

  seg_attr = (stack ? 0xb5 : 0xa9);
  if (size > 0x10000 || force_big)
    {
      init_rec (SEGDEF|REC32);
      put_8 (seg_attr);
      put_32 (size);
    }
  else if (size == 0x10000)
    {
      init_rec (SEGDEF);
      put_8 (seg_attr|2);
      put_16 (0);
    }
  else
    {
      init_rec (SEGDEF);
      put_8 (seg_attr);
      put_16 (size);
    }
  put_idx (name_index);
  put_idx (class_index);
  put_idx (ovl_name);
  write_rec ();
  return (segdef_index++);
}


/* This function is passed to qsort() for sorting a relocation table.
   X1 and X2 point to reloc structures.  We compare the ADDRESS fields
   of the structures. */

static int reloc_compare (const void *x1, const void *x2)
{
  dword a1, a2;

  a1 = ((struct reloc *)x1)->address;
  a2 = ((struct reloc *)x2)->address;
  if (a1 < a2)
    return (-1);
  else if (a1 > a2)
    return (1);
  else
    return (0);
}


/* Write the segment contents (data) of one OMF segment to the output
   file.  Create LEDATA and FIXUPP records.  INDEX is the segment
   index.  SEG_NAME is the name index of the segment name (not used).
   SRC points to the data to be written (will be modified for
   fixups!), SEG_SIZE is the number of bytes to be written.  REL
   points to the relocation table (an array of struct reloc), REL_SIZE
   is the size of the relocation table, in bytes(!).  SST_FLAG is TRUE
   when writing the symbol segment $$SYMBOLS.  BOUNDARY points to an
   array of indices into SRC where the data can be split between
   records.  That array has BOUNDARY_COUNT entries.

   We can split the data at arbitrary points after the last BOUNDARY
   entry.  In consequence, write_seg() splits the data at arbitrary
   points if BOUNDARY_COUNT is zero.

   LEDATA record:
    1-2 Segment index
    2/4 Enumerated data offset (4 bytes for 32-bit LEDATA record)
    n   Data bytes (n is derived from record length)

   FIXUPP record:
   Ú
   ³?   THREAD subrecord or FIXUP subrecord
   À

   THREAD subrecord:
    1   Flags
    0-2 Index (present only for FRAME methods F0, F1 and F2)

   The flags byte contains the following fields:

   0 (bit 7)            always 0 to indicate THREAD subrecord
   D (bit 6)            0=target thread, 1=frame thread
   0 (bit 5)            reserved
   Method (bits 2-4)    method (T0 through T6 and F0 through F6)
   Thred (bits 0-1)     thread number

   FIXUP subrecord:
    2   Locat
    0-1 Fix data
    0-2 Frame datum
    0-2 Target datum
    2/4 target displacement (4 bytes for 32-bit FIXUPP record)

    The first locat byte contains the following fields:

    1 (bit 7)           always 1 to indicate FIXUP subrecord
    M (bit 6)           1=segment-relative fixup, 0=self-relative fixup
    Location (bit 2-5)  Type of location to fix up:
                          0010=16-bit selector
                          0011=32-bit long pointer (16:16)
                          1001=32-bit offset
    Offset (bits 0-1)   Most significant bits of offset into LEDATA record

    The second locat byte contains the least significant bits of the
    offset into the LEDATA record.

    The Fix data byte contains the following fields:

    F (bit 7)           1=frame thread, 0=methods F0 through F5
    Frame (bits 4-6)    frame thread number (F=1) or frame method (F=0)
    T (bit 3)           1=target thread, 1=methods 
    P (bit 2)           Bit 2 of target method
    Targt (bits 0-1)    target thread number (T=1) or target method (T=0)  */

static void write_seg (int index, int seg_name, byte *src, long seg_size,
                       const byte *rel, long rel_size, int sst_flag,
                       const int *boundary, int boundary_count)
{
  long n, off, tmp, end;
  int i, reloc_count, reloc_idx, target_index, ok, data_off, far16;
  int boundary_idx, started, *threadp;
  struct reloc *reloc_tab;
  const struct reloc *r;
  byte locat;
  dword start_data, start_bss;

  target_index = 0; threadp = NULL; /* keep the compiler happy */

  /* Copy and sort the relocation table. */

  reloc_count = rel_size / sizeof (struct reloc);
  reloc_tab = xmalloc (reloc_count * sizeof (struct reloc));
  memcpy (reloc_tab, rel, reloc_count * sizeof (struct reloc));
  qsort (reloc_tab, reloc_count, sizeof (struct reloc), reloc_compare);

  /* First pass: apply fixups to data.  Adjust fixup frames for OMF
     fixups.  In a.out files, frames are relative to address 0, in OMF
     files, frames are relative to the start of the segment.  The
     following two variables are used for doing these adjustments. */

  start_data = text_size;
  start_bss = start_data + a_out_h->data_size;

  /* Scan the relocation table for entries applying to this segment. */

  for (i = 0, r = reloc_tab; i < reloc_count; ++i, ++r)
    if (r->length == 2)
      {

        /* Here we have a 32-bit relocation. */

        if (r->ext)
          {

            /* The relocation refers to a symbol.  Look into the
               symbol table to find the fixup type and target
               address. */

            switch (sym_ptr[r->symbolnum].type)
              {
              case N_EXT:
                if (r->pcrel)
                  *(dword *)(src + r->address) = 0;
                break;
              case N_TEXT:
                break;
              case N_TEXT|N_EXT:
                *(dword *)(src + r->address) = 0;
                break;
              case N_DATA:
              case N_DATA|N_EXT:
                *(dword *)(src + r->address) -= start_data;
                break;
              case N_BSS:
              case N_BSS|N_EXT:
                *(dword *)(src + r->address) -= start_bss;
                break;
              default:
                error ("write_seg: Invalid symbol type");
              }
          }
        else
          {

            /* The relocation does not refer to a symbol, it's an
               internal relocation.  Get the fixup type from the
               relocation table. */

            switch (r->symbolnum & ~N_EXT)
              {
              case N_TEXT:
                break;
              case N_DATA:
                *(dword *)(src + r->address) -= start_data;
                break;
              case N_BSS:
                *(dword *)(src + r->address) -= start_bss;
                break;
              default:
                error ("write_seg: Invalid relocation type");
              }
          }
      }

  /* Second pass: write LEDATA and FIXUPP records. */

  off = 0; reloc_idx = 0; boundary_idx = 0;
  while (seg_size > 0)
    {

      /* Compute the maximum number of bytes in the next LEDATA
         record, depending on the maximum record size, the record type
         (16-bit or 32-bit) and the number of bytes remaining.  The
         number of bytes will be adjusted later to avoid splitting
         entries of the $$SYMBOLS and $$TYPES segments. */

      n = MAX_REC_SIZE - 5;
      n -= (off >= 0x10000 || force_big ? 4 : 2);
      if (seg_size < n)
        n = seg_size;

      /* Adjust the number of bytes to avoid splitting a fixup.  Find
         the last relocation table entry which applies to this chunk.
         Then, lower the chunk size to stop at the start of the
         frame. */

      i = reloc_idx; end = off + n;
      while (i < reloc_count && reloc_tab[i].address < end)
        ++i;
      if (i > reloc_idx)
        {
          --i;
          tmp = reloc_tab[i].address + (1 << reloc_tab[i].length) - off;
          if (tmp > n)
            n = reloc_tab[i].address - off;
        }

      /* Consult the BOUNDARY table to find the last point where we
         are allowed to split the data into multiple LEDATA records.
         This must be done after adjusting for relocation table
         entries. */

      end = off + n;
      while (boundary_idx < boundary_count && boundary[boundary_idx] < end)
        ++boundary_idx;
      if (boundary_idx > 0)
        {
          tmp = boundary[boundary_idx-1] - off;
          if (tmp > 0)
            n = tmp;
        }

      /* Write the LEDATA record.  This is simple. */

      if (off >= 0x10000 || force_big)
        {
          init_rec (LEDATA|REC32);
          put_idx (index);
          put_32 (off);
        }
      else
        {
          init_rec (LEDATA);
          put_idx (index);
          put_16 (off);
        }
      put_mem (src, n);
      write_rec ();

      /* Write the FIXUPP records for this LEDATA record.  Quite
         hairy. */

      end = off + n;
      started = FALSE;
      r = &reloc_tab[reloc_idx];

      /* Look at all relocation table entries which apply to the
         current LEDATA chunk. */

      while (reloc_idx < reloc_count && r->address < end)
        {

          /* Set ok to TRUE if we should create a fixup for this
             relocation table entry.  First, ignore all but 32-bit
             relocations.  In the $$SYMBOLS segment, we also have
             16-bit selector fixups.  far16 is later set to TRUE for
             16:16 fixups. */

          ok = (r->length == 2 || (sst_flag && r->length == 1));
          far16 = FALSE;

          if (r->ext)
            {

              /* The relocation refers to a symbol -- we won't use a
                 target thread.  If the symbol is a 16:16 symbol, we
                 set far16 to true to generate a 16:16 fixup. */

              threadp = NULL;
              if (sym_more[r->symbolnum].flags & SF_FAR16)
                far16 = TRUE;
            }
          else
            {

              /* The relocation does not refer to a symbol -- we use
                 an appropriate target thread.  The target thread
                 number is taken from or stored to *threadp.
                 target_index is the OMF segment index. */

              switch (r->symbolnum & ~N_EXT)
                {
                case N_TEXT:
                  threadp = &text_thread;
                  target_index = text_index;
                  break;
                case N_DATA:
                  threadp = &data_thread;
                  target_index = udat_index;
                  break;
                case N_BSS:
                  threadp = &bss_thread;
                  target_index = bss_index;
                  break;
                case N_ABS:
                default:
                  ok = FALSE;
                  break;
                }
            }

          if (ok)
            {

              /* Now we build the FIXUPP record. */

              if (started && !fits (32))
                {
                  write_rec ();
                  started = FALSE;
                }
              if (!started)
                {
                  init_rec (FIXUPP|REC32);
                  started = TRUE;
                }

              /* If no frame thread has been defined for the FLAT
                 group, define it now. */

              if (flat_thread < 0 && !far16)
                {
                  if (frame_thread_index >= 4)
                    error ("Too many frame threads");
                  /* THREAD: D=1, METHOD=F1 */
                  put_8 (0x44 | frame_thread_index);
                  put_idx (flat_index);
                  flat_thread = frame_thread_index++;
                }

              /* If we want to use a target thread and the target
                 thread is not yet defined, define it now. */

              if (threadp != NULL && *threadp < 0)
                {
                  if (target_thread_index >= 4)
                    error ("Too many target threads");
                  /* THREAD: D=0, METHOD=T4 */
                  put_8 (0x10 | target_thread_index);
                  put_idx (target_index);
                  *threadp = target_thread_index++;
                }

              /* Compute and write the locat word. */

              data_off = r->address - off;
              if (far16)
                locat = 0x8c;   /* Method 3: 16:16 far pointer */
              else if (sst_flag && r->length == 1)
                locat = 0x88;   /* Method 2: selector */
              else
                locat = 0xa4;   /* Method 9: 32-bit offset */
              locat |= ((data_off >> 8) & 0x03);
              if (!r->pcrel)
                locat |= 0x40;
              put_8 (locat);
              put_8 (data_off);

              /* Write the rest of the FIXUP subrecord. */

              if (far16)
                {
                  /* F=0, FRAME=F2, T=0, P=1, TARGT=T2 */
                  put_8 (0x26);
                  put_idx (sym_more[r->symbolnum].index);
                  put_idx (sym_more[r->symbolnum].index);
                }
              else if (r->ext)
                {
                  /* F=1, FRAME=F1, T=0, P=1, TARGT=T2 */
                  put_8 (0x86 | (flat_thread << 4));
                  put_idx (sym_more[r->symbolnum].index);
                }
              else
                {
                  /* F=1, FRAME=F1, T=1, P=1, TARGT=T4 */
                  put_8 (0x8c | (flat_thread << 4) | *threadp);
                }
            }
          ++reloc_idx; ++r;
        }
      if (started)
        write_rec ();

      /* Adjust pointers and counters for the next chunk. */

      src += n; off += n; seg_size -= n;
    }

  /* Deallocate the sorted relocation table. */

  free (reloc_tab);
}


/* Write a default library request record to the output file.  The
   linker will search the library NAME to resolve external
   references.  Create a COMENT record of class 0x9f.  The name is
   stored without leading byte count, the linker gets the length of
   the name from the record length. */

static void request_lib (const char *name)
{
  init_rec (COMENT);
  put_8 (0x40);
  put_8 (0x9f);
  put_mem (name, strlen (name));
  write_rec ();
}


/* Write default library request records for all the -i options.  The
   library names are stored in a list.  libreq_head points to the head
   of the list. */

static void write_libs (void)
{
  struct libreq *p;

  for (p = libreq_head; p != NULL; p = p->next)
    request_lib (p->name);
}


/* Write a record identifying the debug information style to the
   output file.  The style we use is called HLL version 3.  Create a
   COMENT record of class 0xa1. */

static void write_debug_style (void)
{
  init_rec (COMENT);
  put_8 (0x80);
  put_8 (0xa1);                 /* Debug info style */
  put_8 (3);                    /* Version 3 */
  put_mem ("HL", 2);            /* HLL style debug tables */
  write_rec ();
}


/* Write a link pass separator to the output file.  The linker makes
   two passes through the object modules.  The first pass stops when
   encountering the link pass separator.  This is used for improving
   linking speed.  The second pass ignores the link pass separator.
   The following records must precede the link pass separator: ALIAS,
   CEXTDEF COMDEF, EXTDEF, GRPDEF, LCOMDEF, LEXTDEF, LNAMES, LPUBDEF,
   PUBDEF, SEGDEF, TYPDEF, and most of the COMENT classes.  Create a
   COMENT record of class 0xa2. */

static void write_pass2 (void)
{
  init_rec (COMENT);
  put_8 (0x40);
  put_8 (0xa2);                 /* Link pass separator */
  put_8 (0x01);
  write_rec ();
}


/* Create segment names for all the sets.  It is here where sets are
   created.  See write_set_data() for details. */

static void define_set_names (void)
{
  int i, j;
  struct set *set_ptr;
  char tmp[512];
  const char *name;
  byte type;

  /* Loop through the symbol table. */

  for (i = 0; i < sym_count; ++i)
    {
      type = sym_ptr[i].type & ~N_EXT;
      if ((type == N_SETA && sym_ptr[i].value == 0xffffffff) || type == N_SETT)
        {

          /* This is a set element.  If type is N_SETA the symbol is
             the head of the set, otherwise it is an ordinary set
             element.  Search the table of sets to find out whether
             this set is already known. */

          name = str_ptr + sym_ptr[i].string;
          for (set_ptr = sets; set_ptr != NULL; set_ptr = set_ptr->next)
            if (strcmp (set_ptr->name, name) == 0)
              break;
          if (set_ptr == NULL)
            {

              /* The set is not yet known.  Create a new table
                 entry. */

              set_ptr = xmalloc (sizeof (struct set));
              set_ptr->name = xstrdup (name);
              for (j = 0; j < 3; ++j)
                {
                  set_ptr->seg_name[j] = -1;
                  set_ptr->seg_index[j] = -1;
                }
              set_ptr->group_name = -1;
              set_ptr->count = 0;
              set_ptr->data = NULL;
              set_ptr->def = 0;
              set_ptr->next = NULL;
              *set_add = set_ptr;
              set_add = &set_ptr->next;
            }
          else if (type == N_SETA && set_ptr->def)
            error ("Set `%s' defined more than once", name);

          if (type == N_SETA)
            set_ptr->def = 1;   /* Write SET1 and SET3 segments */
          else
            {

              /* Add the element to the set. */

              ++set_ptr->count;
              set_ptr->data = xrealloc (set_ptr->data, set_ptr->count * 4);
              set_ptr->data[set_ptr->count-1] = sym_ptr[i].value;
            }

          /* Define the OMF segment names for this set, if not yet
             done. */

          if (set_ptr->seg_name[0] < 0)
            {
              strcpy (tmp, "SET#");
              strcat (tmp, name);
              for (j = 0; j < 3; ++j)
                {
                  tmp[3] = (char)('1' + j);
                  set_ptr->seg_name[j] = find_lname (tmp);
                }
            }
        }
    }
}


/* Create the group name with group name index for each set. */

static void define_set_groups (void)
{
  struct set *set_ptr;
  char tmp[512];

  for (set_ptr = sets; set_ptr != NULL; set_ptr = set_ptr->next)
    {
      strcpy (tmp, "GROUP");
      strcat (tmp, set_ptr->name);
      set_ptr->group_name = find_lname (tmp);
    }
}


/* Define three segments for each set.  The segment names have already
   been defined, now write the SEGDEF records. */

static void write_set_segs (void)
{
  int j;
  struct set *set_ptr;

  for (set_ptr = sets; set_ptr != NULL; set_ptr = set_ptr->next)
    for (j = 0; j < 3; ++j)
      set_ptr->seg_index[j] =
        seg_def (set_ptr->seg_name[j], code_class_name,
                 4 * (j == 1 ? set_ptr->count : set_ptr->def), FALSE);
}


/* Write the GRPDEF records for all the sets.  One group consisting of
   three segments is defined for each set.

   GRPDEF record:
    1/2 Group name index
   Ú
   ³1   0xff (use segment index)
   ³1/2 Segment index
   À

   Group indices are assigned sequentially. */

static void write_set_groups (void)
{
  int j;
  struct set *set_ptr;

  for (set_ptr = sets; set_ptr != NULL; set_ptr = set_ptr->next)
    {
      set_ptr->group_index = group_index++;
      init_rec (GRPDEF);
      put_idx (set_ptr->group_name);
      for (j = 0; j < 3; ++j)
        {
          put_8 (0xff); put_idx (set_ptr->seg_index[j]);
        }
      write_rec ();
    }
}


/* Write the PUBDEF records for all the sets.  One PUBDEF record is
   generated for each set, it defines the set name. */

static void write_set_pub (void)
{
  struct set *set_ptr;

  for (set_ptr = sets; set_ptr != NULL; set_ptr = set_ptr->next)
      if (set_ptr->def)
        {
          init_rec (force_big ? PUBDEF|REC32 : PUBDEF);
          put_idx (flat_index);
          put_idx (set_ptr->seg_index[0]);
          put_sym (set_ptr->name);
          if (force_big)
            put_32 (0);
          else
            put_16 (0);
          put_idx (0);        /* Type index */
          write_rec ();
        }
}


/* Write the segment contents for all the sets.  One or three segments
   are written for each set XXX:

   SET1XXX      long start = -2
   SET2XXX      long table[]
   SET3XXX      long end = 0

   SET1XXX and SET3XXX are written only if there is a N_SETA symbol
   for the set.  The N_SETA symbol defines the start of the set and
   must have a value of -1 (see crt0.s).  SET2XXX is written for all
   modules which contain N_SETT symbols.  All three segments belong to
   the group GROUPXXX.  The linker combines all the segments of
   GROUPXXX into a single object.  SET1XXX comes first, followed by
   the SET2XXX segments of all modules of the program, followed by
   SET3XXX.  That way, we obtain a table of all set elements of a
   given set, preceded by -2 and terminated by 0.  A symbol XXX is
   defined which points to the start of SET1XXX.  See
   /emx/lib/gcc/main.c for code which uses sets. */

static void write_set_data (void)
{
  struct set *set_ptr;
  dword x;
  int i, max_count;
  dword *buf;
  struct reloc *reloc_tab;

  max_count = 0; buf = NULL; reloc_tab = NULL;
  for (set_ptr = sets; set_ptr != NULL; set_ptr = set_ptr->next)
    {

      /* Write the first segment.  It consists of a 32-bit word
         containing the number -2.  This is used by the startup code
         to detect a delimited set. */

      if (set_ptr->def)
        {
          x = 0xfffffffe;
          write_seg (set_ptr->seg_index[0], set_ptr->seg_name[0],
                     (byte *)&x, sizeof (x), NULL, 0, FALSE, NULL, 0);
        }

      /* Write the second segment.  It consists of the set elements as
         taken from the a.out module.  The elements are assumed to be
         pointers into the text segment. */

      if (set_ptr->count >= 1)
        {

          /* Enlarge the relocation table, if required. */

          if (set_ptr->count > max_count)
            {
              max_count = set_ptr->count;
              buf = xrealloc (buf, 4 * max_count);
              memset (buf, 0, 4 * max_count);
              reloc_tab = xrealloc (reloc_tab,
                                    max_count * sizeof (struct reloc));
            }

          /* Create one relocation table entry for each set
             element. */

          for (i = 0; i < set_ptr->count; ++i)
            {
              buf[i] = set_ptr->data[i];
              reloc_tab[i].address = i * 4;
              reloc_tab[i].symbolnum = N_TEXT;
              reloc_tab[i].pcrel = 0;
              reloc_tab[i].length = 2;
              reloc_tab[i].ext = 0;
              reloc_tab[i].unused = 0;
            }

          /* Write the segment data and fixups. */

          write_seg (set_ptr->seg_index[1], set_ptr->seg_name[1],
                     (byte *)buf, set_ptr->count * 4, (const byte *)reloc_tab,
                     set_ptr->count * sizeof (struct reloc), FALSE, NULL, 0);
        }

      /* Write the third segment.  It consists of a 32-bit word
         containing the value 0, marking the end of the table. */

      if (set_ptr->def)
        {
          x = 0;
          write_seg (set_ptr->seg_index[2], set_ptr->seg_name[2],
                     (byte *)&x, sizeof (x), NULL, 0, FALSE, NULL, 0);
        }
    }
  if (buf != NULL)
    free (buf);
}


/* If the input file is an import module (created by emximp), create
   an OMF-style import module and return TRUE.  Otherwise, return
   FALSE. */

static int handle_import (void)
{
  int i, len1, mod_len;
  long ord;
  const char *name1, *name2, *proc_name, *p;
  char mod[256], *q;

  /* Search the symbol table for N_IMP1 and N_IMP2 symbols.  These are
     unique to import modules. */

  name1 = NULL; name2 = NULL;
  for (i = 0; i < sym_count; ++i)
    switch (sym_ptr[i].type)
      {
      case N_IMP1|N_EXT:
        name1 = str_ptr + sym_ptr[i].string;
        break;
      case N_IMP2|N_EXT:
        name2 = str_ptr + sym_ptr[i].string;
        break;
      default:
        return (FALSE);
      }

  /* If no N_IMP1 and N_IMP2 symbols have been found, the module is
     not an import module. */

  if (name1 == NULL || name2 == NULL)
    return (FALSE);

  /* Parse the special symbols into module name and ordinal number.
     name2 should look like

       SYMBOL=MODULE.ORDINAL

     where SYMBOL is name1, MODULE is the module name and ORDINAL is
     the ordinal number. */

  len1 = strlen (name1);
  if (memcmp (name1, name2, len1) != 0)
    error ("Invalid import record: names don't match");
  name2 += len1;
  if (*name2 != '=')
    error ("Invalid import record: missing `='");
  ++name2;
  p = strchr (name2, '.');
  if (p == NULL)
    error ("Invalid import record: missing `.'");
  mod_len = p - name2;
  memcpy (mod, name2, mod_len);
  mod[mod_len] = 0;
  proc_name = NULL;
  errno = 0;
  ord = strtol (p + 1, &q, 10);
  if (q != p + 1 && *q == 0 && errno == 0)
    {
      if (ord < 1 || ord > 65535)
        error ("Invalid import record: invalid ordinal");
    }
  else
    {
      ord = -1;
      proc_name = p + 1;
      if (*proc_name == 0)
        error ("Invalid import record: invalid name");
    }

  /* Skip a leading underscore character if present. */

  if (*name1 == '_' && !opt_e)
    ++name1;

  /* Put a PUBDEF record into the output library. */

  if (omflib_add_pub (out_lib, name1, mod_page, lib_errmsg) != 0)
    error (lib_errmsg);

  /* Write the import definition record. */

  init_rec (COMENT);
  put_8 (0x00);
  put_8 (IMPDEF_CLASS);
  put_8 (IMPDEF_SUBTYPE);
  put_8 (proc_name == NULL ? 0x01 : 0x00); /* Import by ordinal or by name */
  put_str (name1);              /* Underscore already removed above */
  put_str (mod);
  if (proc_name == NULL)
    put_16 (ord);
  else if (strcmp (proc_name, name1) == 0)
    put_8 (0);
  else
    put_str (proc_name);
  write_rec ();
  init_rec (MODEND|REC32);
  put_8 (0x00);                 /* Non-main module without start address */
  write_rec ();
  return (TRUE);
}


/* Convert a filename from Unix format to OS/2 format.  All that has
   to be done is replacing slashes with backslashes as IPMD doesn't
   like slashes in file names.  The conversion is done in place. */

static void convert_filename (char *name)
{
  while (*name != 0)
    {
      if (*name == '/')
        *name = '\\';
      ++name;
    }
}


/* Derive the module name and store it to the mod_name variable.
   Search the symbol table for an N_SO symbol.  If there is no such
   symbol, use the output file name. */

static void get_mod_name (void)
{
  int i, len, ok;
  const char *p1, *p2;

  ok = FALSE;
  for (i = 0; i < sym_count; ++i)
    if (sym_ptr[i].type == N_SO)
      {
        p1 = str_ptr + sym_ptr[i].string;
        len = strlen (p1);
        if (len > 0 && p1[len-1] != '/')
          {
            ok = TRUE;
            _strncpy (mod_name, p1, sizeof (mod_name));
            convert_filename (mod_name);
            break;
          }
      }
  if (!ok && out_lib == NULL)
    {
      p1 = out_fname;
      for (p2 = p1; *p2 != 0; ++p2)
        if (*p2 == '/' || *p2 == '\\' || *p2 == ':')
          p1 = p2 + 1;
      _strncpy (mod_name, p1, sizeof (mod_name));
    }
}


/* Write the translator module header record.  When creating a .LIB
   file, this function does nothing.  Otherwise, the module name is
   expected in the mod_name variable. */

static void write_theadr (void)
{
  if (out_lib == NULL)
    {
      init_rec (THEADR);
      put_str (mod_name);
      write_rec ();
    }
}

/* Tell LINK386 what identifier manipulator DLL to call.  The name of
   the DLL is given by `idmdll_name', the default value of which is
   GPPDEMID.  The name can be changed with the -I option.  If -I- is
   given, no IDMDLL record will be written.  The IDMDLL record will
   also be suppressed if the program hasn't been compiled by the GNU
   C++ compiler. */

static void write_idmdll ()
{
  if (idmdll_name != NULL && find_symbol ("__gnu_compiled_cplusplus") != NULL)
    {
      init_rec (COMENT);
      put_8 (0x00);
      put_8 (0xaf);
      put_str (idmdll_name);
      put_str ("");             /* Initialization parameter */
      write_rec ();
    }
}


/* Display a warning message on stderr (and do not quit).  This
   function is invoked like printf(): FMT is a printf-style format
   string, all other arguments are formatted according to FMT. */

void warning (const char *fmt, ...)
{
  va_list arg_ptr;

  va_start (arg_ptr, fmt);
  fprintf (stderr, "emxomf warning: ");
  vfprintf (stderr, fmt, arg_ptr);
  fputc ('\n', stderr);
}


/* Find a file name for creating line number information. */

static int file_find (const char *name)
{
  int i;

  for (i = 0; i < file_grow.count; ++i)
    if (strcmp (file_list[i], name) == 0)
      return (i);
  if (file_grow.count >= 255)
    {
      warning ("Too many source files, cannot convert line number info");
      return (file_grow.count);
    }
  grow_by (&file_grow, 1);
  i = file_grow.count++;
  file_list[i] = xstrdup (name);
  return (i);
}


/* This function is passed to qsort() for sorting line numbers.  X1
   and X2 point to struct line structures.  Line number entries are
   sorted by address, file and line number. */

static int line_compare (const void *x1, const void *x2)
{
  dword a1, a2;
  int i1, i2;

  a1 = ((const struct line *)x1)->addr;
  a2 = ((const struct line *)x2)->addr;
  if (a1 < a2)
    return (-1);
  else if (a1 > a2)
    return (1);
  i1 = ((const struct line *)x1)->file_index;
  i2 = ((const struct line *)x2)->file_index;
  if (i1 < i2)
    return (-1);
  else if (i1 > i2)
    return (1);
  i1 = ((const struct line *)x1)->line;
  i2 = ((const struct line *)x2)->line;
  if (i1 < i2)
    return (-1);
  else if (i1 > i2)
    return (1);
  return (0);
}


/* Write line number information to the output file.  Unfortunately,
   the line number information format currently used by IPMD is not
   documented.  This code is based on experimentation. */

static void write_linnum (void)
{
  int i, started, len, file_index;
  int valid_lines;
  struct line *line_list;
  struct grow line_grow;
  char buf[256];

  /* Initialize growing arrays for file names and line numbers. */

  grow_init (&file_grow, &file_list, sizeof (*file_list), 8);
  grow_init (&line_grow, &line_list, sizeof (*line_list), 64);

  /* Define file index for main source file. */

  file_index = file_find (mod_name);
  started = FALSE;

  /* Go through the symbol table, defining line numbers and additional
     source files. */

  for (i = 0; i < sym_count; ++i)
    switch (sym_ptr[i].type)
      {
      case N_SOL:
        file_index = file_find (str_ptr + sym_ptr[i].string);
        break;
      case N_SLINE:
        grow_by (&line_grow, 1);
        line_list[line_grow.count].file_index = file_index;
        line_list[line_grow.count].line = sym_ptr[i].desc;
        line_list[line_grow.count].addr = sym_ptr[i].value;
        ++line_grow.count;
      }

  /* Sort the line numbers by address, file and line number. */

  qsort (line_list, line_grow.count, sizeof (*line_list), line_compare);

  /* If there are multiple line numbers assigned to the same address,
     keep but the last line number.  Delete line numbers by setting
     the line number to -1. */

  for (i = 0; i < line_grow.count - 1; ++i)
    if (line_list[i].line >= 0 && line_list[i+1].line >= 0
        && line_list[i].addr == line_list[i+1].addr)
      line_list[i].line = -1;

  /* Count the number of valid line numbers, that is, non-negative
     line numbers. */

  valid_lines = 0;
  for (i = 0; i < line_grow.count; ++i)
    if (line_list[i].line >= 0)
      ++valid_lines;

  /* Compute the size of the file names table. */

  len = 3 * 4;
  for (i = 0; i < file_grow.count; ++i)
    len += 1 + strlen (file_list[i]);

  /* Write the line number table. */

  init_rec (LINNUM|REC32);
  started = TRUE;
  put_idx (0);                  /* Base Group */
  put_idx (text_index);         /* Base Segment */

  put_16 (0);                   /* Line number = 0 (special entry) */
  put_8 (0);                    /* Entry type: source and offset */
  put_8 (0);                    /* Reserved */
  put_16 (valid_lines);         /* Count of line number entries */
  put_16 (0);                   /* Segment number */
  put_32 (len);                 /* Size of file names table */

  for (i = 0; i < line_grow.count; ++i)
    if (line_list[i].line >= 0)
      {
        if (started && !fits (8))
          {
            write_rec ();
            started = FALSE;
          }
        if (!started)
          {
            init_rec (LINNUM|REC32);
            put_idx (0);          /* Base Group */
            put_idx (text_index); /* Base Segment */
            started = TRUE;
          }
        put_16 (line_list[i].line);
        put_16(line_list[i].file_index + 1);
        put_32 (line_list[i].addr);
      }

  /* Now write the file names table. */

  if (started && !fits (12))
    {
      write_rec ();
      started = FALSE;
    }
  if (!started)
    {
      init_rec (LINNUM|REC32);
      put_idx (0);          /* Base Group */
      put_idx (text_index); /* Base Segment */
      started = TRUE;
    }
  put_32 (0);                   /* First column */
  put_32 (0);                   /* Number of columns */
  put_32 (file_grow.count);     /* Number of source and listing files */

  for (i = 0; i < file_grow.count; ++i)
    {
      len = strlen (file_list[i]);
      if (len > sizeof (buf) - 1)
        len = sizeof (buf) - 1;
      memcpy (buf, file_list[i], len);
      buf[len] = 0;
      convert_filename (buf);
      if (started && !fits (1 + len))
        {
          write_rec ();
          started = FALSE;
        }
      if (!started)
        {
          init_rec (LINNUM|REC32);
          put_idx (0);          /* Base Group */
          put_idx (text_index); /* Base Segment */
          started = TRUE;
        }
      put_8 (len);
      put_mem (buf, len);
    }

  if (started)
    write_rec ();

  grow_free (&line_grow);
  grow_free (&file_grow);
}


/* Convert an a.out module to an OMF module.  SIZE is the number of
   bytes in the input file inp_file. */

static void o_to_omf (long size)
{
  byte *data_ptr;
  const byte *text_rel;
  const byte *data_rel;
  const byte *sym;
  const struct nlist *entry_symbol;
  int i;

  /* Simplify things by reading the complete a.out module into
     memory. */

  inp_buf = xmalloc (size);
  size = fread (inp_buf, 1, size, inp_file);
  if (ferror (inp_file))
    goto read_error;

  /* Set up pointers to various sections of the module read into
     memory. */

  a_out_h = (struct a_out_header *)inp_buf;
  if (size < sizeof (struct a_out_header) || a_out_h->magic != 0407)
    error ("Input file `%s' is not an a.out file", error_fname);
  text_size = a_out_h->text_size;
  text_ptr = inp_buf + sizeof (struct a_out_header);
  data_ptr = text_ptr + text_size;
  text_rel = data_ptr + a_out_h->data_size;
  data_rel = text_rel + a_out_h->trsize;
  sym = data_rel + a_out_h->drsize;
  str_ptr = sym + a_out_h->sym_size;
  if (str_ptr + 4 - inp_buf > size)
    goto invalid;
  str_size = *(long *)str_ptr;
  sym_ptr = (struct nlist *)sym;
  sym_count = a_out_h->sym_size / sizeof (struct nlist);
  if (str_ptr + str_size - inp_buf > size)
    goto invalid;

  /* Build the complementary array of symbol data. */

  sym_more = xmalloc (sym_count * sizeof (struct symbol));
  for (i = 0; i < sym_count; ++i)
    {
      sym_more[i].index = 0;
      sym_more[i].flags = 0;
    }

  /* Find the start symbol if converting a main module. */

  if (entry_name != NULL)
    {
      entry_symbol = find_symbol (entry_name);
      if (entry_symbol == NULL)
        error ("Entry symbol not found");
      if ((entry_symbol->type & ~N_EXT) != N_TEXT)
        error ("Entry symbol not in text segment");
    }
  else
    entry_symbol = NULL;        /* keep the compiler happy */

  /* Initialize variables for a new OMF module. */

  init_obj ();

  /* If the a.out module is an import module as created by emximp,
     create an OMF import module.  Everything is done by
     handle_import() in that case, therefore we can simply return. */

  if (handle_import ())
    return;

  /* Initialize growing arrays for debug information conversion.
     These two arrays contain indices at which the $$SYMBOLS and
     $$TYPES segments, respectively, can be split into LEDATA records
     by write_seg(). */

  grow_init (&sst_boundary_grow, &sst_boundary, sizeof (*sst_boundary), 32);
  grow_init (&tt_boundary_grow, &tt_boundary, sizeof (*tt_boundary), 32);

  /* Create some OMF names. */

  ovl_name = find_lname ("");
  text_seg_name = find_lname ("TEXT32");
  data_seg_name = find_lname ("DATA32");
  bss_seg_name = find_lname ("BSS32");
  if (udat_seg_string != NULL)
    udat_seg_name = find_lname (udat_seg_string);
  else
    udat_seg_name = data_seg_name;
  if (!strip_symbols)
    {
      symbols_seg_name = find_lname ("$$SYMBOLS");
      types_seg_name = find_lname ("$$TYPES");
    }
  code_class_name = find_lname ("CODE");
  data_class_name = find_lname ("DATA");
  bss_class_name = find_lname ("BSS");
  if (!strip_symbols)
    {
      debsym_class_name = find_lname ("DEBSYM");
      debtyp_class_name = find_lname ("DEBTYP");
    }
  define_set_names ();
  if (mod_type == MT_MAIN)
    {
      stack_seg_name = find_lname ("STACK");
      stack_class_name = find_lname ("STACK");
    }
  flat_group_name = find_lname ("FLAT");
  dgroup_group_name = find_lname (opt_e ? "DATAGROUP" : "DGROUP");
  define_set_groups ();

  /* Write the THREADR record. */

  get_mod_name ();
  write_theadr ();

  /* Tell LINK386 what identifier manipulator DLL to use. */

  write_idmdll ();

  /* Write default library requests and the debug information style
     record (COMENT records). */

  write_libs ();
  write_debug_style ();

  /* Define all the OMF names (LNAMES record).  Of course, we must not
     define new OMF names after this point. */

  write_lnames ();

  /* Define segments (SEGDEF records).  This must be done after
     defining the names and before defining the groups. */

  text_index = seg_def (text_seg_name, code_class_name, text_size,
                        FALSE);
  write_set_segs ();

  if (udat_seg_string != NULL)
    udat_index = seg_def (udat_seg_name, data_class_name, a_out_h->data_size,
                          FALSE);
  data_index = seg_def (data_seg_name, data_class_name,
                        (udat_seg_string == NULL ? a_out_h->data_size : 0),
                        FALSE);
  if (udat_seg_string == NULL)
    udat_index = data_index;

  bss_index = seg_def (bss_seg_name, bss_class_name, a_out_h->bss_size,
                       FALSE);

  if (mod_type == MT_MAIN)
    stack_index = seg_def (stack_seg_name, stack_class_name, 0x8000, TRUE);

  if (!strip_symbols)
    {
      convert_debug ();         /* After seg_def of text, data & bss */
      symbols_index = seg_def (symbols_seg_name, debsym_class_name,
                               sst.size, FALSE);
      types_index = seg_def (types_seg_name, debtyp_class_name,
                             tt.size, FALSE);
    }

  /* Define groups (GRPDEF records).  This must be done after defining
     segments. */

  flat_index = group_index++;
  init_rec (GRPDEF);
  put_idx (flat_group_name);
  write_rec ();

  dgroup_index = group_index++;
  init_rec (GRPDEF);
  put_idx (dgroup_group_name);
  put_8 (0xff); put_idx (bss_index);
  put_8 (0xff); put_idx (data_index);
  write_rec ();

  write_set_groups ();

  /* Define external, communal and public symbols (EXTDEF, COMDEF and
     PUBDEF records).  This must be done after defining the groups. */

  write_extdef ();
  write_comdef ();
  write_pubdef ();
  write_set_pub ();

  /* Write link pass separator. */

  write_pass2 ();

  /* Write segment contents (LEDATA and FIXUPP records) and line
     number information (LINNUM record). */

  write_seg (text_index, text_seg_name, text_ptr, text_size,
             text_rel, a_out_h->trsize, FALSE, NULL, 0);
  write_seg (udat_index, udat_seg_name, data_ptr, a_out_h->data_size,
             data_rel, a_out_h->drsize, FALSE, NULL, 0);

  write_set_data ();

  if (!strip_symbols)
    {
      write_seg (types_index, types_seg_name, tt.buf, tt.size, NULL, 0, FALSE,
                 tt_boundary, tt_boundary_grow.count);
      write_seg (symbols_index, symbols_seg_name, sst.buf, sst.size,
                 sst_reloc.buf, sst_reloc.size, TRUE,
                 sst_boundary, sst_boundary_grow.count);
      write_linnum ();
    }

  /* End of module. */

  init_rec (MODEND|REC32);
  if (entry_name != NULL)
    {
      put_8 (0xc1);             /* Main module with start address */
      put_8 (0x50);             /* ENDDAT: F5, T0 */
      put_idx (text_index);
      put_32 (entry_symbol->value);
    }
  else
    put_8 (0x00);               /* Non-main module without start address */
  write_rec ();

  /* Clean up memory. */

  free_lnames ();
  free (inp_buf);
  free (sym_more);
  buffer_free (&tt);
  buffer_free (&sst);
  buffer_free (&sst_reloc);
  grow_free (&sst_boundary_grow);
  grow_free (&tt_boundary_grow);
  return;

read_error:
  error ("Cannot read `%s'", error_fname);

invalid:
  error ("Malformed a.out file `%s'", error_fname);
}


/* Display some hints on using this program, then quit. */

static void usage (void)
{
  puts ("emxomf " VERSION " -- Copyright (c) 1992-1993 by Eberhard Mattes\n");
  puts ("Usage:");
  puts ("  emxomf [-ds] [-l[<symbol>]] [-m <symbol>] [-p <page_size>]");
  puts ("         [-i <default_lib>] [-I <idmdll>] [-D <dataseg>]");
  puts ("         -o <output_file> <input_file>");
  puts ("  emxomf [-dsx] [-l[<symbol>]] [-m <symbol>] [-p <page_size>]");
  puts ("         [-O <directory>] [-r|R <response_file>] [-i <default_lib>]");
  puts ("         [-I <idmdll>] [-D <dataseg>] <input_file>...");
  puts ("\nOptions:");
  puts ("  -d                 Delete input files except for archives");
  puts ("  -i <default_lib>   Add default library request");
  puts ("  -l[<symbol>]       Convert library modules, with optional entrypoint");
  puts ("  -m <symbol>        Convert main module with entrypoint <symbol>");
  puts ("  -o <output_file>   Write output to <output_file>");
  puts ("  -p <page_size>     Set page size for LIB files");
  puts ("  -r <response_file> Write response file for adding modules");
  puts ("  -R <response_file> Write response file for replacing modules");
  puts ("  -s                 Strip debugging information");
  puts ("  -x                 Extract archive members");
  puts ("  -D <dataseg>       Change the name of the data segment");
  puts ("  -I <idmdll>        Name the identifier manipulation DLL");
  puts ("  -O <directory>     Extract files to <directory>");
  exit (1);
}


/* Create the output file.  If the -r or -R option is used, create the
   LIB resonse file. */

static void open_output (void)
{
  char *tmp, *p;

  out_file = fopen (out_fname, "wb");
  if (out_file == NULL)
    error ("Cannot create output file `%s'", out_fname);
  if (response_file != NULL)
    {
      if (response_first)
        response_first = FALSE;
      else
        fprintf (response_file, " &\n");
      tmp = alloca (strlen (out_fname) + 1);
      strcpy (tmp, out_fname);
      for (p = tmp; *p != 0; ++p)
        if (*p == '/')
          *p = '\\';
      fprintf (response_file, "%s %s", (response_replace ? "-+" : "+"), tmp);
    }
}


/* Close the output file.  Display an error message and quit on
   failure. */

static void close_output (void)
{
  if (fflush (out_file) != 0 || fclose (out_file) != 0)
    {
      out_file = NULL;
      error ("Write error on output file `%s'", out_fname);
    }
  out_file = NULL;
}


/* Define or build the name of the output file.  If DST_FNAME is not
   NULL, use it as name of the output file.  Otherwise, build the name
   from INP_FNAME (the input file name) and EXT (the extension).  If
   an output directory has been specified with the -O option, remove
   the directory part of INP_FNAME and use output_dir instead. */

static void make_out_fname (const char *dst_fname, const char *inp_fname,
                            const char *ext)
{
  static char tmp1[MAXPATHLEN+3];
  char tmp2[MAXPATHLEN+3];

  if (dst_fname == NULL)
    {
      if (*output_dir != 0)
        _splitpath (inp_fname, NULL, NULL, tmp2, NULL);
      else
        _strncpy (tmp2, inp_fname, MAXPATHLEN);
      if (strlen (output_dir) + strlen (tmp2) + 5 > MAXPATHLEN)
        error ("File name `%s' too long", inp_fname);
      strcpy (tmp1, output_dir);
      strcat (tmp1, tmp2);
      _remext (tmp1);
      strcat (tmp1, ext);
      out_fname = tmp1;
    }
  else
    out_fname = dst_fname;
}


/* Convert the a.out file SRC_FNAME to an OMF file named DST_FNAME.
   If DST_FNAME is NULL, the output file name is derived from
   SRC_FNAME. */

static void convert (const char *src_fname, const char *dst_fname)
{
  char tmp1[MAXPATHLEN+3], tmp2[MAXPATHLEN+3], *p;
  int i;
  long size;
  static char ar_magic[SARMAG+1] = ARMAG;
  char ar_test[SARMAG];
  struct ar_hdr ar;
  long ar_pos;
  struct delete *del;

  inp_fname = src_fname;
  error_fname = inp_fname;
  mod_name[0] = 0;
  inp_file = fopen (inp_fname, "rb");
  if (inp_file == NULL)
    error ("Cannot open input file `%s'", inp_fname);

  /* Read some bytes from the start of the file to find out whether
     this is an archive (.a) file or not. */

  if (fread (ar_test, sizeof (ar_test), 1, inp_file) != 1)
    error ("Cannot read input file `%s'", inp_fname);

  /* Create a LIB response file if requested and not yet done. */

  if (response_fname != NULL && response_file == NULL)
    {
      response_file = fopen (response_fname, "wt");
      if (response_file == NULL)
        error ("Cannot create response file `%s'", response_fname);
    }

  /* The rest of this function (save closing the input file) depends
     on whether the input file is an archive or not. */

  if (memcmp (ar_test, ar_magic, SARMAG) == 0)
    {

      /* The input file is an archive.  We cannot procede if the user
         has specified an output file name and wants one OMF file to
         be written for each module in the archive (well, we could do
         it if there's exactly one module in the archive, but we
         don't). */

      if (dst_fname != NULL && opt_x)
        error ("Cannot process archive if -o and -x are used");
      ar_pos = SARMAG;

      /* Create an OMF library file (.LIB file) if the -x option is
         not given. */

      if (!opt_x)
        {
          make_out_fname (dst_fname, inp_fname, ".lib");
          out_lib = omflib_create (out_fname, page_size, lib_errmsg);
          if (out_lib == NULL)
            error (lib_errmsg);
          if (omflib_header (out_lib, lib_errmsg) != 0)
            error (lib_errmsg);
        }

      /* Loop over all the members of the archive. */

      for (;;)
        {

          /* Read the header of the member. */

          fseek (inp_file, ar_pos, SEEK_SET);
          size = fread  (&ar, 1, sizeof (ar), inp_file);
          if (size == 0)
            break;
          else if (size != sizeof (ar))
            error ("Malformed archive `%s'", inp_fname);

          /* Decode the header. */

          errno = 0;
          size = strtol (ar.ar_size, &p, 10);
          if (p == ar.ar_size || errno != 0 || size <= 0 || *p != ' ')
            error ("Malformed archive header in `%s'", inp_fname);
          ar_pos += (sizeof (ar) + size + 1) & -2;
          i = 0;
          while (i < sizeof (ar.ar_name)-1 && ar.ar_name[i] != ' ')
            ++i;
          ar.ar_name[i] = 0;

          /* Ignore the __.SYMDEF and __.IMPORT members.  Ignore
             import modules unless creating a library file. */

          if (strcmp (ar.ar_name, "__.SYMDEF") != 0
              && strcmp (ar.ar_name, "__.IMPORT") != 0
              && (memcmp (ar.ar_name, "IMPORT#", 7) != 0 || !opt_x))
            {

              /* Convert the current member to OMF.  Extract the base
                 name of the member. */

              _splitpath (ar.ar_name, NULL, NULL, tmp2, NULL);
              if (strlen (output_dir) + strlen (tmp2) + 5 > MAXPATHLEN)
                error ("File name `%s' too long", tmp2);

              /* Provide for informative error message.  Memory leak. */

              p = xmalloc (strlen (inp_fname) + 3 + strlen (ar.ar_name));
              sprintf (p, "%s(%s)", inp_fname, ar.ar_name);
              error_fname = p;

              /* Construct the module name for the THREADR record
                 etc. */

              _strncpy (mod_name, tmp2, sizeof (mod_name));
              strcat (tmp2, ".obj");

              /* Create the output file (-x option) or add a new
                 module to the output library (no -x option). */

              if (opt_x)
                {
                  strcpy (tmp1, output_dir);
                  strcat (tmp1, tmp2);
                  out_fname = tmp1;
                  open_output ();
                }
              else
                {
                  if (omflib_write_module (out_lib, tmp2, &mod_page,
                                           lib_errmsg) != 0)
                    error (lib_errmsg);
                }

              /* Convert the member and close the output file. */

              o_to_omf (size);
              if (opt_x)
                close_output ();
            }
          free (p);
        }

      /* Finish and close the library library if creaeting a library
         file. */

      if (!opt_x)
        {
          if (omflib_finish (out_lib, lib_errmsg) != 0
              || omflib_close (out_lib, lib_errmsg) != 0)
            error (lib_errmsg);
          out_lib = NULL;
        }
    }
  else
    {

      /* The input file is not an archive.  We assume it being an
         a.out object file.  Get the size of the file for
         o_to_omf(). */

      if (fseek (inp_file, 0L, SEEK_END) != 0)
        error ("Input file `%s' is not seekable", inp_fname);
      size = ftell (inp_file);
      fseek (inp_file, 0L, SEEK_SET);

      /* Convert the file. */

      make_out_fname (dst_fname, inp_fname, ".obj");
      open_output ();
      o_to_omf (size);
      close_output ();

      /* If input files are to be deleted (-d option), add the current
         input file to the list of files to be deleted. */

      if (delete_input_files)
        {
          del = xmalloc (sizeof (struct delete));
          del->name = xstrdup (inp_fname);
          del->next = files_to_delete;
          files_to_delete = del;
        }
    }
  fclose (inp_file);
}


/* Delete all the files stored in the FILES_TO_DELETE list.  While
   deleting the files, the list elements are deallocated. */

static void delete_files (void)
{
  struct delete *p, *q;

  for (p = files_to_delete; p != NULL; p = q)
    {
      q = p->next;
      remove (p->name);
      free (p->name);
      free (p);
    }
}


/* Main function of emxomf.  Parse the command line and perform the
   requested actions. */

int main (int argc, char *argv[])
{
  int c, i;
  char *opt_o, *opt_O, *tmp;
  struct libreq *lrp;

  /* Keep emxomf in memory for the number of minutes indicated by the
     GCCLOAD environment variable. */

  _emxload_env ("GCCLOAD");

  /* Expand response files (@filename) and wildcard (*.o) on the
     command line. */

  _response (&argc, &argv);
  _wildcard (&argc, &argv);

  /* Set default values of some options. */

  opt_o = NULL; opt_O = NULL;
  opterr = FALSE;

  /* Parse the command line options. */

  while ((c = getopt (argc, argv, "bdD:ei:I:m:l::o:p:O:r:R:sx")) != EOF)
    switch (c)
      {
      case 'b':
        force_big = TRUE;
        break;
      case 'd':
        delete_input_files = TRUE;
        break;
      case 'D':
        udat_seg_string = optarg;
        break;
      case 'e':
        opt_e = TRUE;
        break;
      case 'i':
        lrp = xmalloc (sizeof (*lrp));
        lrp->next = NULL;
        lrp->name = xstrdup (optarg);
        *libreq_add = lrp;
        libreq_add = &lrp->next;
        break;
      case 'I':
        if (strcmp (optarg, "-") == 0)
          idmdll_name = NULL;
        else
          idmdll_name = optarg;
        break;
      case 'l':
        if (mod_type != MT_MODULE)
          usage ();
        mod_type = MT_LIB;
        entry_name = optarg;
        break;
      case 'm':
        if (mod_type != MT_MODULE)
          usage ();
        mod_type = MT_MAIN;
        entry_name = optarg;
        break;
      case 'o':
        if (opt_o != NULL || opt_O != NULL)
          usage ();
        opt_o = optarg;
        break;
      case 'p':
        errno = 0;
        page_size = (int)strtol (optarg, &tmp, 0);
        if (tmp == optarg || errno != 0 || *tmp != 0
            || page_size < 16 || page_size > 32768
            || (page_size & (page_size - 1)) != 0)
          usage ();
        break;
      case 'O':
        if (opt_o != NULL || opt_O != NULL)
          usage ();
        opt_O = optarg;
        break;
      case 'r':
      case 'R':
        if (response_fname != NULL)
          usage ();
        response_fname = optarg;
        response_replace = (c == 'R');
        break;
      case 's':
        strip_symbols = TRUE;
        break;
      case 'x':
        opt_x = TRUE;
        break;
      default:
        usage ();
      }

  /* Check for non-option arguments. */

  if (argc - optind == 0)
    usage ();

  if (opt_o != NULL)
    {

      /* If the -o option is used, there must be exactly one input
         file name. */

      if (argc - optind != 1)
        usage ();
      convert (argv[optind], opt_o);
    }
  else
    {

      /* The -o option is not used.  If the -O option is used, set up
         output_dir. */

      if (opt_O != NULL)
        {
          i = strlen (opt_O);
          tmp = xmalloc (i + 2);
          strcpy (tmp, opt_O);
          if (i > 0 && strchr (":\\/", tmp[i-1]) == NULL)
            strcat (tmp, "/");
          output_dir = tmp;
        }

      /* Convert all the files named on the command line. */

      for (i = optind; i < argc; ++i)
        convert (argv[i], NULL);
    }

  /* If a LIB response file has been created, finish and close it. */

  if (response_file != NULL)
    {
      if (!response_first)
        fprintf (response_file, "\n");
      if (fflush (response_file) != 0 || fclose (response_file) != 0)
        error ("Write error on response file `%s'", response_fname);
    }

  /* If the user wants input files to be deleted, delete them now. */

  if (delete_input_files)
    delete_files ();
  return (0);
}
