/* emximp.c  -- Manage import libraries
   Copyright (c) 1992-1998 Eberhard Mattes

This file is part of emximp.

emximp 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.

emximp 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 emximp; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <process.h>
#include <ar.h>
#include <time.h>
#include <sys/moddef.h>
#include "defs.h"
#include <sys/omflib.h>

#define VERSION "0.9d"

#define NORETURN2 __attribute__ ((noreturn))

#define MOD_PREDEF   0
#define MOD_DEF      1
#define MOD_REF      2

#define N_EXT  0x01
#define N_ABS  0x02
#define N_IMP1 0x68
#define N_IMP2 0x6a

#define PARMS_REG     (-1)
#define PARMS_FAR16   (-2)

struct lib
{
  struct lib *next;
  char *name;
  int lbl;
};

struct predef
{
  struct predef *next;
  char *name;
};

enum modes
{
  M_NONE,                       /* No mode selected */
  M_LIB_TO_IMP,                 /* .lib -> .imp */
  M_IMP_TO_S,                   /* .imp -> .s or .o */
  M_IMP_TO_DEF,                 /* .imp -> .def */
  M_LIB_TO_A,                   /* .lib -> .a */
  M_IMP_TO_A,                   /* .imp -> .a */
  M_IMP_TO_LIB,                 /* .imp -> .lib */
  M_DEF_TO_IMP,                 /* .def -> .imp */
  M_DEF_TO_A,                   /* .def -> .a */
  M_DEF_TO_LIB                  /* .def -> .lib */
};

static FILE *out_file = NULL;
static char out_fname[128];
static struct lib *libs;
static struct predef *predefs;
static char *out_base;
static int base_len;
static char *as_name;
static int pipe_flag;
static int profile_flag;
static int opt_b;
static int opt_q;
static int opt_s;
static enum modes mode = M_NONE;
static long mod_lbl;
static long seq_no = 1;
static char *first_module = NULL;
static int warnings = 0;
static struct omflib *out_lib;
static char lib_errmsg[512];
static char *module_name = NULL;


static void error (const char *fmt, ...) NORETURN2;
static void write_error (const char *fname) NORETURN2;
static void lib_error (void) NORETURN2;
static void write_a_import (const char *func_name, const char *mod_name,
    int ordinal, const char *proc_name);


static void usage (void)
{
  puts ("emximp " VERSION " -- Copyright (c) 1992-1996 by Eberhard Mattes\n");
  puts ("Usage:");
  puts ("  emximp [-a[<assembler>]] [-b <base_name>|<prefix_length>] "
        "[-p <module>] ...");
  puts ("         [-s] <input_file>.imp");
  puts ("  emximp [-m] -o <output_file>.a <input_file>.def ...");
  puts ("  emximp [-m] -o <output_file>.a <input_file>.imp ...");
  puts ("  emximp [-m] -o <output_file>.a <input_file>.lib ...");
  puts ("  emximp -o <output_file>.def <input_file>.imp ...");
  puts ("  emximp -o <output_file>.imp <input_file>.def ...");
  puts ("  emximp -o <output_file>.imp <input_file>.lib ...");
  puts ("  emximp [-p#] -o <output_file>.lib <input_file>.def ...");
  puts ("  emximp [-p#] -o <output_file>.lib <input_file>.imp...");
  puts ("Options:");
  puts ("  -p#  Set page size");
  puts ("  -q   Be quiet");
  puts ("  -m   Call _mcount for profiling");
  exit (1);
}


static void error (const char *fmt, ...)
{
  va_list arg_ptr;

  va_start (arg_ptr, fmt);
  fprintf (stderr, "emximp: ");
  vfprintf (stderr, fmt, arg_ptr);
  fputc ('\n', stderr);
  exit (2);
}


static void warning (const char *fmt, ...)
{
  va_list arg_ptr;

  va_start (arg_ptr, fmt);
  fprintf (stderr, "emximp: ");
  vfprintf (stderr, fmt, arg_ptr);
  fputc ('\n', stderr);
  ++warnings;
}


/* Like warning(), but don't increment `warnings'. */

static void information (const char *fmt, ...)
{
  va_list arg_ptr;

  va_start (arg_ptr, fmt);
  fprintf (stderr, "emximp: ");
  vfprintf (stderr, fmt, arg_ptr);
  fputc ('\n', stderr);
}


void *xmalloc (size_t n)
{
  void *p;
  
  p = malloc (n);
  if (p == NULL)
    error ("Out of memory");
  return p;
}


void *xrealloc (void *p, size_t n)
{
  void *q;
  
  q = realloc (p, n);
  if (q == NULL)
    error ("Out of memory");
  return q;
}


char *xstrdup (const char *s)
{
  char *p;
  
  p = xmalloc (strlen (s) + 1);
  strcpy (p, s);
  return p;
}


static void write_error (const char *fname)
{
  error ("Write error on output file `%s'", fname);
}


static void lib_error (void)
{
  error ("%s", lib_errmsg);
}


static void out_flush (void)
{
  char name[512];
  char *nargv[5];
  int rc;
  
  if (out_file != NULL)
    {
      if (fflush (out_file) != 0)
        write_error (out_fname);
      if (pipe_flag)
        {
          rc = pclose (out_file);
          if (rc == -1)
            error ("Error while closing pipe");
          if (rc > 0)
            error ("Assembly failed, return code = %d", rc);
        }
      else
        {
          if (fclose (out_file) != 0)
            error ("Cannot close output file `%s'", out_fname);
          if (as_name != NULL)
            {
              _splitpath (out_fname, NULL, NULL, name, NULL);
              strcat (name, ".o");
              nargv[0] = as_name;
              nargv[1] = "-o";
              nargv[2] = name;
              nargv[3] = out_fname;
              nargv[4] = NULL;
              rc = spawnvp (P_WAIT, as_name, nargv);
              if (rc < 0)
                error ("Cannot run `%s'", as_name);
              if (rc > 0)
                error ("Assembly of `%s' failed, return code = %d",
                       out_fname, rc);
              remove (out_fname);
            }
        }
      out_file = NULL;
    }
}


static void out_start (void)
{
  struct lib *lp1, *lp2;
  char name[512], cmd[512];
  
  if (pipe_flag)
    {
      _splitpath (out_fname, NULL, NULL, name, NULL);
      strcat (name, ".o");
      sprintf (cmd, "%s -o %s", as_name, name);
      out_file = popen (cmd, "wt");
      if (out_file == NULL)
        error ("Cannot open pipe to `%s'", as_name);
    }
  else
    {
      out_file = fopen (out_fname, "wt");
      if (out_file == NULL)
        error ("Cannot open output file `%s'", out_fname);
    }
  fprintf (out_file, "/ %s (emx+gcc)\n\n", out_fname);
  fprintf (out_file, "\t.text\n");
  for (lp1 = libs; lp1 != NULL; lp1 = lp2)
    {
      lp2 = lp1->next;
      free (lp1->name);
      free (lp1);
    }
  libs = NULL; mod_lbl = 1;
}


static void write_lib_import (const char *func, const char *module, long ord,
                              const char *name)
{
  byte omfbuf[1024];
  int i, len;
  word page;

  if (omflib_write_module (out_lib, func, &page, lib_errmsg) != 0)
    lib_error ();
  if (omflib_add_pub (out_lib, func, page, lib_errmsg) != 0)
    lib_error ();
  i = 0;
  omfbuf[i++] = 0x00;
  omfbuf[i++] = IMPDEF_CLASS;
  omfbuf[i++] = IMPDEF_SUBTYPE;
  omfbuf[i++] = (ord < 1 ? 0x00 : 0x01);
  len = strlen (func);
  omfbuf[i++] = (byte)len;
  memcpy (omfbuf+i, func, len); i += len;
  len = strlen (module);
  omfbuf[i++] = (byte)len;
  memcpy (omfbuf+i, module, len); i += len;
  if (ord < 1)
    {
      if (strcmp (func, name) == 0)
        len = 0;
      else
        len = strlen (name);
      omfbuf[i++] = (byte)len;
      memcpy (omfbuf+i, name, len); i += len;
    }
  else
    {
      omfbuf[i++] = (byte)ord;
      omfbuf[i++] = (byte)(ord >> 8);
    }
  if (omflib_write_record (out_lib, COMENT, i, omfbuf, TRUE,
                           lib_errmsg) != 0)
    lib_error ();
  omfbuf[0] = 0x00;
  if (omflib_write_record (out_lib, MODEND, 1, omfbuf, TRUE,
                           lib_errmsg) != 0)
    lib_error ();
}


#define DELIM(c) ((c) == 0 || isspace ((unsigned char)c))


static void read_imp (const char *fname)
{
  FILE *inp_file;
  char buf[512], func[256], name[256], module[256], mod_ref[256], *p, *q;
  char tmp[512];
  long line_no, ord, parms, file_no;
  int mod_type;
  struct lib *lp1;
  struct predef *pp1;

  libs = NULL; mod_lbl = 1;
  inp_file = fopen (fname, "rt");
  if (inp_file == NULL)
    error ("Cannot open input file `%s'", fname);
  line_no = 0;
  while (fgets (buf, sizeof (buf), inp_file) != NULL)
    {
      ++line_no;
      p = strchr (buf, '\n');
      if (p != NULL) *p = 0;
      p = buf;
      while (isspace ((unsigned char)*p)) ++p;
      if (*p == '+')
        {
          if (mode == M_IMP_TO_S)
            {
              if (opt_b)
                error ("Output file name in line %ld of %s not allowed "
                       "as -b is used", line_no, fname);
              ++p;
              while (isspace ((unsigned char)*p)) ++p;
              out_flush ();
              q = out_fname;
              while (!DELIM (*p))
                *q++ = *p++;
              *q = 0;
              while (isspace ((unsigned char)*p)) ++p;
              if (*p != 0 && *p != ';')
                error ("Invalid file name in line %ld of %s", line_no, fname);
              out_start ();
            }
        }
      else if (*p == 0 || *p == ';')
        ;           /* empty line */
      else
        {
          if (!opt_b && out_file == NULL && mode != M_IMP_TO_LIB)
            error ("No output file selected in line %ld of %s",
                   line_no, fname);
          if (DELIM (*p))
            error ("Function name expected in line %ld of %s", line_no, fname);
          q = func;
          while (!DELIM (*p))
            *q++ = *p++;
          *q = 0;
          while (isspace ((unsigned char)*p)) ++p;
          if (DELIM (*p))
            error ("Module name expected in line %ld of %s", line_no, fname);
          q = module;
          while (!DELIM (*p))
            *q++ = *p++;
          *q = 0;
          while (isspace ((unsigned char)*p)) ++p;
          if (DELIM (*p))
            error ("External name or ordinal expected in line %ld of %s",
                   line_no, fname);
          if (isdigit ((unsigned char)*p))
            {
              name[0] = 0;
              ord = strtol (p, &p, 10);
              if (ord < 1 || ord > 65535 || !DELIM (*p))
                error ("Invalid ordinal in line %ld of %s", line_no, fname);
            }
          else
            {
              if (opt_b && !opt_s)
                error ("External name in line %ld of %s cannot be used "
                       "as -b is given", line_no, fname);
              ord = -1;
              q = name;
              while (!DELIM (*p))
                *q++ = *p++;
              *q = 0;
            }
          while (isspace ((unsigned char)*p)) ++p;
          if (DELIM (*p))
            error ("Number of arguments expected in line %ld of %s",
                   line_no, fname);
          if (*p == '?')
            {
              ++p;
              parms = 0;
              if (mode == M_IMP_TO_S)
                warning ("Unknown number of arguments in line %ld of %s",
                         line_no, fname);
            }
          else if (*p == 'R')
            {
              ++p;
              parms = PARMS_REG;
            }
          else if (*p == 'F')
            {
              ++p;
              parms = PARMS_FAR16;
              if (mode == M_IMP_TO_S)
                warning ("16-bit function not supported (line %ld of %s)",
                         line_no, fname);
              if (strlen (func) + 4 >= sizeof (func))
                error ("Function name too long in line %ld of %s",
                       line_no, fname);
              strcpy (tmp, func);
              strcpy (func, "_16_");
              strcat (func, tmp);
            }
          else
            {
              parms = strtol (p, &p, 10);
              if (parms < 0 || parms > 255 || !DELIM (*p))
                error ("Invalid number of parameters in line %ld of %s",
                       line_no, fname);
            }
          while (isspace ((unsigned char)*p)) ++p;
          if (*p != 0 && *p != ';')
            error ("Unexpected characters at end of line %ld of %s",
                   line_no, fname);
          switch (mode)
            {
            case M_IMP_TO_DEF:
              if (first_module == NULL)
                {
                  first_module = xstrdup (module);
                  fprintf (out_file, "LIBRARY %s\n", module);
                  fprintf (out_file, "EXPORTS\n");
                }
              else if (strcmp (first_module, module) != 0)
                error ("All functions must be in the same module "
                       "(input file %s)", fname);
              if (ord >= 0)
                fprintf (out_file, "  %-32s @%ld\n", func, ord);
              else if (strcmp (func, name) == 0)
                fprintf (out_file, "  %-32s\n", func);
              else
                fprintf (out_file, "  %-32s = %s\n", name, func);
              break;
            case M_IMP_TO_A:
              if (ord < 1)
                write_a_import (func, module, ord, name);
              else
                write_a_import (func, module, ord, NULL);
              break;
            case M_IMP_TO_LIB:
              write_lib_import (func, module, ord, name);
              break;
            case M_IMP_TO_S:
              if (opt_b)
                {
                  out_flush ();
                  if (opt_s)
                    file_no = seq_no++;
                  else
                    file_no = ord;
                  if (out_base != NULL)
                    sprintf (out_fname, "%s%ld.s", out_base, file_no);
                  else
                    sprintf (out_fname, "%.*s%ld.s",
                             base_len, module, file_no);
                  out_start ();
                }
              for (pp1 = predefs; pp1 != NULL; pp1 = pp1->next)
                if (stricmp (module, pp1->name) == 0)
                  break;
              if (pp1 != NULL)
                {
                  mod_type = MOD_PREDEF;
                  sprintf (mod_ref, "__os2_%s", pp1->name);
                }
              else
                {
                  for (lp1 = libs; lp1 != NULL; lp1 = lp1->next)
                    if (stricmp (module, lp1->name) == 0)
                      break;
                  if (lp1 == NULL)
                    {
                      mod_type = MOD_DEF;
                      lp1 = xmalloc (sizeof (struct lib));
                      lp1->name = xstrdup (module);
                      lp1->lbl = mod_lbl++;
                      lp1->next = libs;
                      libs = lp1;
                    }
                  else
                    mod_type = MOD_REF;
                  sprintf (mod_ref, "L%d", lp1->lbl);
                }
              fprintf (out_file, "\n\t.globl\t_%s\n", func);
              fprintf (out_file, "\t.align\t2, %d\n", 0x90);
              fprintf (out_file, "_%s:\n", func);
              if (parms >= 0)
                fprintf (out_file, "\tmovb\t$%d, %%al\n", (int)parms);
              fprintf (out_file, "1:\tjmp\t__os2_bad\n");
              if (ord >= 0)
                fprintf (out_file, "2:\t.long\t1, 1b+1, %s, %d\n",
                         mod_ref, (int)ord);
              else
                fprintf (out_file, "2:\t.long\t0, 1b+1, %s, 4f\n", mod_ref);
              if (mod_type == MOD_DEF)
                fprintf (out_file, "%s:\t.asciz\t\"%s\"\n", mod_ref, module);
              if (ord < 0)
                fprintf (out_file, "4:\t.asciz\t\"%s\"\n", name);
              fprintf (out_file, "\t.stabs  \"__os2dll\", 23, 0, 0, 2b\n");
              break;
            default:
              abort ();
            }
        }
    }
  if (mode == M_IMP_TO_S)
    out_flush ();
  if (ferror (inp_file))
    error ("Read error on input file `%s'", fname);
  fclose (inp_file);
}


static void set_ar (char *dst, const char *src, int size)
{
  while (*src != 0 && size > 0)
    {
      *dst++ = *src++;
      --size;
    }
  while (size > 0)
    {
      *dst++ = ' ';
      --size;
    }
}


static long ar_member_size;

static void write_ar (const char *name, long size)
{
  struct ar_hdr ar;
  char tmp[20];

  ar_member_size = size;
  set_ar (ar.ar_name, name, sizeof (ar.ar_name));
  sprintf (tmp, "%ld", (long)time (NULL));
  set_ar (ar.ar_date, tmp, sizeof (ar.ar_date));
  set_ar (ar.ar_uid, "0", sizeof (ar.ar_uid));
  set_ar (ar.ar_gid, "0", sizeof (ar.ar_gid));
  set_ar (ar.ar_mode, "100666", sizeof (ar.ar_gid));
  sprintf (tmp, "%ld", size);
  set_ar (ar.ar_size, tmp, sizeof (ar.ar_size));
  set_ar (ar.ar_fmag, ARFMAG, sizeof (ar.ar_fmag));
  fwrite (&ar, 1, sizeof (ar), out_file);
}


static void finish_ar (void)
{
  if (ar_member_size & 1)
    fputc (0, out_file);
}


static dword aout_str_size;
static char aout_str_tab[2048];
static int aout_sym_count;
static struct nlist aout_sym_tab[6];

static byte aout_text[64];
static int aout_text_size;

static struct reloc aout_treloc_tab[2];
static int aout_treloc_count;

static int aout_size;


static void aout_init (void)
{
  aout_str_size = sizeof (dword);
  aout_sym_count = 0;
  aout_text_size = 0;
  aout_treloc_count = 0;
}

static int aout_sym (const char *name, byte type, byte other,
                     word desc, dword value)
{
  int len;

  len = strlen (name);
  if (aout_str_size + len + 1 > sizeof (aout_str_tab))
    error ("a.out string table overflow");
  if (aout_sym_count >= sizeof (aout_sym_tab) / sizeof (aout_sym_tab[0]))
    error ("a.out symbol table overflow");
  aout_sym_tab[aout_sym_count].string = aout_str_size;
  aout_sym_tab[aout_sym_count].type = type;
  aout_sym_tab[aout_sym_count].other = other;
  aout_sym_tab[aout_sym_count].desc = desc;
  aout_sym_tab[aout_sym_count].value = value;
  strcpy (aout_str_tab + aout_str_size, name);
  aout_str_size += len + 1;
  return aout_sym_count++;
}


static void aout_text_byte (byte b)
{
  if (aout_text_size >= sizeof (aout_text))
    error ("a.out text segment overflow");
  aout_text[aout_text_size++] = b;
}


static void aout_text_dword (dword d)
{
  aout_text_byte ((d >> 0) & 0xff);
  aout_text_byte ((d >> 8) & 0xff);
  aout_text_byte ((d >> 16) & 0xff);
  aout_text_byte ((d >> 24) & 0xff);
}


static void aout_treloc (dword address, int symbolnum, int pcrel, int length,
                         int ext)
{
  if (aout_treloc_count >= sizeof (aout_treloc_tab) / sizeof (struct reloc))
    error ("a.out text relocation buffer overflow");
  aout_treloc_tab[aout_treloc_count].address = address;
  aout_treloc_tab[aout_treloc_count].symbolnum = symbolnum;
  aout_treloc_tab[aout_treloc_count].pcrel = pcrel;
  aout_treloc_tab[aout_treloc_count].length = length;
  aout_treloc_tab[aout_treloc_count].ext = ext;
  aout_treloc_tab[aout_treloc_count].unused = 0;
  ++aout_treloc_count;
}


static void aout_finish (void)
{
  while (aout_text_size & 3)
    aout_text_byte (0x90);
  aout_size = (sizeof (struct a_out_header) + aout_text_size
               + aout_treloc_count * sizeof (struct reloc)
               + aout_sym_count * sizeof (aout_sym_tab[0])
               + aout_str_size);
}


static void aout_write (void)
{
  struct a_out_header ao;

  ao.magic = 0407;
  ao.machtype = 0;
  ao.flags = 0;
  ao.text_size = aout_text_size;
  ao.data_size = 0;
  ao.bss_size = 0;
  ao.sym_size = aout_sym_count * sizeof (aout_sym_tab[0]);
  ao.entry = 0;
  ao.trsize = aout_treloc_count * sizeof (struct reloc);
  ao.drsize = 0;
  fwrite (&ao, 1, sizeof (ao), out_file);
  fwrite (aout_text, 1, aout_text_size, out_file);
  fwrite (aout_treloc_tab, aout_treloc_count, sizeof (struct reloc), out_file);
  fwrite (aout_sym_tab, aout_sym_count, sizeof (aout_sym_tab[0]), out_file);
  *(dword *)aout_str_tab = aout_str_size;
  fwrite (aout_str_tab, 1, aout_str_size, out_file);
}


static void write_a_import (const char *func_name, const char *mod_name,
                            int ordinal, const char *proc_name)
{
  char tmp1[256], tmp2[257], tmp3[1024];
  int sym_mcount, sym_entry, sym_import;
  dword fixup_mcount, fixup_import;

  aout_init ();
  sprintf (tmp2, "_%s", func_name);
  if (profile_flag && strncmp (func_name, "_16_", 4) != 0)
    {
      sym_entry = aout_sym (tmp2, N_TEXT|N_EXT, 0, 0, aout_text_size);
      sym_mcount = aout_sym ("__mcount", N_EXT, 0, 0, 0);

      /* Use, say, "_$U_DosRead" for "DosRead" to import the
         non-profiled function. */
      sprintf (tmp2, "__$U_%s", func_name);
      sym_import = aout_sym (tmp2, N_EXT, 0, 0, 0);

      aout_text_byte (0x55);    /* push ebp */
      aout_text_byte (0x89);    /* mov ebp, esp */
      aout_text_byte (0xe5);
      aout_text_byte (0xe8);    /* call _mcount*/
      fixup_mcount = aout_text_size;
      aout_text_dword (0 - (aout_text_size + 4));
      aout_text_byte (0x5d);    /* pop ebp */
      aout_text_byte (0xe9);    /* jmp _$U_DosRead*/
      fixup_import = aout_text_size;
      aout_text_dword (0 - (aout_text_size + 4));

      aout_treloc (fixup_mcount, sym_mcount, 1, 2, 1);
      aout_treloc (fixup_import, sym_import, 1, 2, 1);
    }
  sprintf (tmp1, "IMPORT#%ld", seq_no);
  if (proc_name == NULL)
    sprintf (tmp3, "%s=%s.%d", tmp2, mod_name, ordinal);
  else
    sprintf (tmp3, "%s=%s.%s", tmp2, mod_name, proc_name);
  aout_sym (tmp2, N_IMP1|N_EXT, 0, 0, 0);
  aout_sym (tmp3, N_IMP2|N_EXT, 0, 0, 0);
  aout_finish ();
  write_ar (tmp1, aout_size);
  aout_write ();
  finish_ar ();
  seq_no++;
  if (ferror (out_file))
    write_error (out_fname);
}


static void read_lib (const char *fname)
{
  FILE *inp_file;
  int n, i, next, more, impure_warned, ord_flag;
  unsigned char *buf;
#pragma pack(1)
  struct record
    {
      unsigned char type;
      unsigned short length;
    } record, *rec_ptr;
#pragma pack()
  unsigned char func_name[256];
  unsigned char mod_name[256];
  unsigned char proc_name[256];
  unsigned char theadr_name[256];
  int ordinal;
  long pos, size;
  int page_size;

  if (mode == M_LIB_TO_IMP)
    fprintf (out_file, "; -------- %s --------\n", fname);
  if (ferror (out_file))
    write_error (out_fname);
  inp_file = fopen (fname, "rb");
  if (inp_file == NULL)
    error ("Cannot open input file `%s'", fname);
  if (fread (&record, sizeof (record), 1, inp_file) != 1)
    goto read_error;
  if (record.type != LIBHDR || record.length < 5)
    error ("`%s' is not a library file", fname);
  page_size = record.length + 3;
  if (fread (&pos, sizeof (pos), 1, inp_file) != 1)
    goto read_error;
  if (fseek (inp_file, 0L, SEEK_END) != 0)
    goto read_error;
  size = ftell (inp_file);
  if (pos < size)
    size = pos;
  buf = xmalloc (size);
  if (fseek (inp_file, 0L, SEEK_SET) != 0)
    goto read_error;
  size = fread (buf, 1, size, inp_file);
  if (size == 0 || ferror (inp_file))
    goto read_error;
  i = 0; more = TRUE; impure_warned = FALSE; theadr_name[0] = 0;
  while (more)
    {
      rec_ptr = (struct record *)(buf + i);
      i += sizeof (struct record);
      if (i > size) goto bad;
      next = i + rec_ptr->length;
      if (next > size) goto bad;
      switch (rec_ptr->type)
        {
        case MODEND:
        case MODEND|REC32:
          if ((next & (page_size-1)) != 0)
            next = (next | (page_size-1)) + 1;
          break;

        case THEADR:
          n = buf[i++];
          if (i + n > next) goto bad;
          memcpy (theadr_name, buf+i, n);
          theadr_name[n] = 0;
          impure_warned = FALSE;
          break;

        case COMENT:
          if (record.length >= 11 && buf[i+0] == 0x00 && buf[i+1] == 0xa0 &&
              buf[i+2] == 0x01)
            {
              ord_flag = buf[i+3];
              i += 4;
              if (i + 1 > next) goto bad;
              n = buf[i++];
              if (i + n > next) goto bad;
              memcpy (func_name, buf+i, n);
              func_name[n] = 0;
              i += n;
              if (i + 1 > next) goto bad;
              n = buf[i++];
              if (i + n > next) goto bad;
              memcpy (mod_name, buf+i, n);
              mod_name[n] = 0;
              i += n;

              if (ord_flag == 0)
                {
                  ordinal = -1;
                  if (i + 1 > next) goto bad;
                  n = buf[i++];
                  if (i + n > next) goto bad;
                  if (n == 0)
                    strcpy (proc_name, func_name);
                  else
                    {
                      memcpy (proc_name, buf+i, n);
                      proc_name[n] = 0;
                      i += n;
                    }
                }
              else
                {
                  if (i + 2 > next) goto bad;
                  ordinal = *(unsigned short *)(buf + i);
                  i += 2;
                  proc_name[0] = 0;
                }
              ++i;              /* Skip checksum */
              if (i != next) goto bad;
              switch (mode)
                {
                case M_LIB_TO_IMP:
                  if (ordinal != -1)
                    sprintf (proc_name, "%3d", ordinal);
                  if (strncmp (func_name, "_16_", 4) != 0)
                    fprintf (out_file, "%-23s %-8s %s ?\n",
                             func_name, mod_name, proc_name);
                  else
                    fprintf (out_file, "%-23s %-8s %s F\n",
                             func_name+4, mod_name, proc_name);
                  if (ferror (out_file))
                    write_error (out_fname);
                  break;
                case M_LIB_TO_A:
                  if (ordinal == -1)
                    write_a_import (func_name, mod_name, ordinal, proc_name);
                  else
                    write_a_import (func_name, mod_name, ordinal, NULL);
                  break;
                default:
                  abort ();
                }
            }
          break;

        case EXTDEF:
        case PUBDEF:
        case PUBDEF|REC32:
        case SEGDEF:
        case SEGDEF|REC32:
        case COMDEF:
        case COMDAT:
        case COMDAT|REC32:
          if (!opt_q && !impure_warned)
            {
              impure_warned = TRUE;
              information ("%s (%s) is not a pure import library",
                           fname, theadr_name);
            }
          break;

        case LIBEND:
          more = FALSE;
          break;
        }
      i = next;
    }
  free (buf);
  fclose (inp_file);
  return;

read_error:
  error ("Read error on file `%s'", fname);

bad:
  error ("Malformed import library file `%s'", fname);
}


static void create_output_file (int bin)
{
  out_file = fopen (out_fname, (bin ? "wb" : "wt"));
  if (out_file == NULL)
    error ("Cannot open output file `%s'", out_fname);
  if (!bin)
    fprintf (out_file, ";\n; %s (created by emximp)\n;\n", out_fname);
}


static void close_output_file (void)
{
  if (fflush (out_file) != 0)
    error ("Write error on output file `%s'", out_fname);
  if (fclose (out_file) != 0)
      error ("Cannot close output file `%s'", out_fname);
}


static void init_archive (void)
{
  static char ar_magic[SARMAG+1] = ARMAG;

  fwrite (ar_magic, 1, SARMAG, out_file);
}


static int md_export (struct _md *md, const _md_stmt *stmt, _md_token token,
                      void *arg)
{
  const char *internal;

  switch (token)
    {
    case _MD_LIBRARY:
      module_name = xstrdup (stmt->library.name);
      break;
    case _MD_EXPORTS:
      if (module_name == NULL)
        error ("No module name given in module definition file");
      if (stmt->export.internalname[0] != 0)
        internal = stmt->export.internalname;
      else
        internal = stmt->export.entryname;
      switch (mode)
        {
        case M_DEF_TO_IMP:
          if (stmt->export.flags & _MDEP_ORDINAL)
            fprintf (out_file, "%-23s %-8s %3u ?\n",
                     stmt->export.entryname, module_name,
                     (unsigned)stmt->export.ordinal);
          else
            fprintf (out_file, "%-23s %-8s %-23s ?\n",
                     stmt->export.entryname, module_name, internal);
          if (ferror (out_file))
            write_error (out_fname);
          break;
        case M_DEF_TO_A:
          if (stmt->export.flags & _MDEP_ORDINAL)
            write_a_import (stmt->export.entryname, module_name,
                            stmt->export.ordinal, NULL);
          else
            write_a_import (stmt->export.entryname, module_name,
                            0, internal);
          break;
        case M_DEF_TO_LIB:
          write_lib_import (stmt->export.entryname, module_name,
                            stmt->export.ordinal, internal);
          break;
        default:
          abort ();
        }
      break;
    case _MD_parseerror:
      error ("%s (line %ld of %s)", _md_errmsg (stmt->error.code),
             _md_get_linenumber (md), (const char *)arg);
      break;
    default:
      break;
    }
  return 0;
}


static void read_def (const char *fname)
{
  struct _md *md;

  module_name = NULL;
  md = _md_open (fname);
  if (md == NULL)
    error ("Cannot open input file `%s'", fname);
  if (mode == M_DEF_TO_IMP)
    {
      fprintf (out_file, "; -------- %s --------\n", fname);
      if (ferror (out_file))
        write_error (out_fname);
    }
  _md_next_token (md);
  _md_parse (md, md_export, (void *)fname);
  _md_close (md);
}


int main (int argc, char *argv[])
{
  int i, c;
  int imp_count, lib_count, def_count, page_size;
  struct predef *pp1;
  char *q, *ext, *opt_o;
  
  _response (&argc, &argv);
  predefs = NULL; out_base = NULL; as_name = NULL; pipe_flag = FALSE;
  profile_flag = FALSE; page_size = 16;
  opt_b = FALSE; opt_q = FALSE; opt_s = FALSE; base_len = 0; opt_o = NULL;
  opterr = 0;
  optswchar = "-";
  optind = 0;
  while ((c = getopt (argc, argv, "a::b:mo:p:qsP:")) != EOF)
    {
      switch (c)
        {
        case 'a':
          as_name = (optarg != NULL ? optarg : "as");
          pipe_flag = (_osmode != DOS_MODE);
          break;
        case 'b':
          opt_b = TRUE;
          base_len = strtol (optarg, &q, 10);
          if (base_len > 1 && *q == 0)
            out_base = NULL;
          else
            {
              out_base = optarg;
              base_len = 0;
            }
          break;
        case 'm':
          profile_flag = TRUE;
          break;
        case 'o':
          if (opt_o != NULL)
            usage ();
          opt_o = optarg;
          break;
        case 'p':
          pp1 = xmalloc (sizeof (struct predef));
          pp1->name = xstrdup (optarg);
          pp1->next = predefs;
          predefs = pp1;
          break;
        case 'q':
          opt_q = TRUE;
          break;
        case 's':
          opt_s = TRUE;
          break;
        default:
          error ("Invalid option");
        }
    }
  imp_count = 0; lib_count = 0; def_count = 0;
  for (i = optind; i < argc; ++i)
    {
      ext = _getext (argv[i]);
      if (ext != NULL && stricmp (ext, ".lib") == 0)
        ++lib_count;
      else if (ext != NULL && stricmp (ext, ".imp") == 0)
        ++imp_count;
      else if (ext != NULL && stricmp (ext, ".def") == 0)
        ++def_count;
      else
        error ("Input file `%s' has unknown file name extension", argv[i]);
    }
  if (imp_count == 0 && lib_count == 0 && def_count == 0)
    usage ();
  if ((imp_count != 0) + (lib_count != 0) + (def_count != 0) > 1)
    error ("More than one type of input files");
  if (opt_o == NULL)
    {
      if (lib_count != 0)
        error ("Cannot convert .lib files to %s files",
               (as_name == NULL ? ".s" : ".o"));
      if (def_count != 0)
        error ("Cannot convert .def files to %s files",
               (as_name == NULL ? ".s" : ".o"));
      mode = M_IMP_TO_S;
    }
  else
    {
      ext = _getext (opt_o);
      if (ext != NULL && stricmp (ext, ".imp") == 0)
        {
          if (imp_count != 0)
            error ("Cannot convert .imp files to .imp file");
          if (lib_count != 0)
            mode = M_LIB_TO_IMP;
          else
            mode = M_DEF_TO_IMP;
        }
      else if (ext != NULL && stricmp (ext, ".a") == 0)
        {
          if (def_count != 0)
            mode = M_DEF_TO_A;
          else if (imp_count != 0)
            mode = M_IMP_TO_A;
          else
            mode = M_LIB_TO_A;
        }
      else if (ext != NULL && stricmp (ext, ".def") == 0)
        {
          if (def_count != 0)
            error ("Cannot convert .def files to .def file");
          if (lib_count != 0)
            error ("Cannot convert .lib files to .def file");
          mode = M_IMP_TO_DEF;
        }
      else if (ext != NULL && stricmp (ext, ".lib") == 0)
        {
          if (lib_count != 0)
            error ("Cannot convert .lib files to .lib file");
          if (def_count != 0)
            mode = M_DEF_TO_LIB;
          else
            mode = M_IMP_TO_LIB;
          if (predefs != NULL)
            {
              if (predefs->next != NULL)
                usage ();
              page_size = strtol (predefs->name, &q, 10);
              if (page_size < 1 || *q != 0)
                usage ();
              predefs = NULL;   /* See below */
            }
        }
      else
        error ("File name extension of output file not supported");
      _strncpy (out_fname, opt_o, sizeof (out_fname));
    }
  if (mode != M_IMP_TO_S)
    if (as_name != NULL || opt_b || opt_s || predefs != NULL)
      usage ();
  if (profile_flag && mode != M_DEF_TO_A && mode != M_IMP_TO_A
      && mode != M_LIB_TO_A)
    usage ();
  switch (mode)
    {
    case M_LIB_TO_IMP:
      create_output_file (FALSE);
      for (i = optind; i < argc; ++i)
        read_lib (argv[i]);
      close_output_file ();
      break;
    case M_LIB_TO_A:
      create_output_file (TRUE);
      init_archive ();
      for (i = optind; i < argc; ++i)
        read_lib (argv[i]);
      close_output_file ();
      break;
    case M_IMP_TO_S:
      for (i = optind; i < argc; ++i)
        read_imp (argv[i]);
      break;
    case M_IMP_TO_DEF:
      create_output_file (FALSE);
      for (i = optind; i < argc; ++i)
        read_imp (argv[i]);
      close_output_file ();
      break;
    case M_IMP_TO_LIB:
      out_lib = omflib_create (out_fname, page_size, lib_errmsg);
      if (out_lib == NULL)
        lib_error ();
      if (omflib_header (out_lib, lib_errmsg) != 0)
        lib_error ();
      for (i = optind; i < argc; ++i)
        read_imp (argv[i]);
      if (omflib_finish (out_lib, lib_errmsg) != 0
          || omflib_close (out_lib, lib_errmsg) != 0)
        lib_error ();
      break;
    case M_IMP_TO_A:
      create_output_file (TRUE);
      init_archive ();
      for (i = optind; i < argc; ++i)
        read_imp (argv[i]);
      close_output_file ();
      break;
    case M_DEF_TO_A:
      create_output_file (TRUE);
      init_archive ();
      for (i = optind; i < argc; ++i)
        read_def (argv[i]);
      close_output_file ();
      break;
    case M_DEF_TO_IMP:
      create_output_file (FALSE);
      for (i = optind; i < argc; ++i)
        read_def (argv[i]);
      close_output_file ();
      break;
    case M_DEF_TO_LIB:
      out_lib = omflib_create (out_fname, page_size, lib_errmsg);
      if (out_lib == NULL)
        lib_error ();
      if (omflib_header (out_lib, lib_errmsg) != 0)
        lib_error ();
      for (i = optind; i < argc; ++i)
        read_def (argv[i]);
      if (omflib_finish (out_lib, lib_errmsg) != 0
          || omflib_close (out_lib, lib_errmsg) != 0)
        lib_error ();
      break;
    default:
      usage ();
    }
  return (warnings == 0 ? 0 : 1);
}
