/* emxexp.c (emx+gcc) */

/* This program creates export definitions from .o and .obj files.

Copyright (c) 1993 Eberhard Mattes

This file is part of emxexp.

emxexp 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.

emxexp 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 emxexp; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

As a special exception, emxexp can be distributed with emx. */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <ar.h>
#include "defs.h"
#include "omflib.h"
#include "demangle.h"

#define VERSION "0.8h"

static int ordinal = 0;
static int noname_flag = FALSE;

static FILE *out_file;
static const char *inp_fname;
static const char *mod_name;
static int new_mod = FALSE;
static int last_dem = FALSE;

static void error (const char *fmt, ...) NORETURN2;
static void usage (void) NORETURN2;
static void bad_omf (void) NORETURN2;


static void error (const char *fmt, ...)
{
  va_list arg_ptr;

  va_start (arg_ptr, fmt);
  fprintf (stderr, "emxexp: ");
  vfprintf (stderr, fmt, arg_ptr);
  fputc ('\n', stderr);
  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);
}


/* How to call this program. */

static void usage (void)
{
  fputs ("emxexp " VERSION " -- Copyright (c) 1993 by Eberhard Mattes\n\n"
         "Usage: emxexp [-n] [-o[<ordinal>] <input_file>...\n\n"
         "Options:\n"
         "  -n          Output NONAME keyword for each exported symbol\n"
         "  -o          Output ordinal numbers, starting at 1\n"
         "  -o<ordinal> Output ordinal numbers, starting at <ordinal>\n",
         stderr);
  exit (1);
}


static void export (const char *name)
{
  char *dem;

  if (new_mod)
    {
      fprintf (out_file, "\n; From %s", inp_fname);
      if (mod_name != NULL)
        fprintf (out_file, "(%s)", mod_name);
      fputc ('\n', out_file);
      new_mod = FALSE;
    }
  dem = cplus_demangle (name, DMGL_ANSI | DMGL_PARAMS);
  if (dem != NULL)
    {
      fprintf (out_file, "\n  ; %s\n", dem);
      free (dem);
      last_dem = TRUE;
    }
  else if (last_dem)
    {
      fputc ('\n', out_file);
      last_dem = FALSE;
    }
  fprintf (out_file, "  \"%s\"", name);
  if (ordinal != 0)
    fprintf (out_file, " @%d", ordinal++);
  if (noname_flag)
    fprintf (out_file, " NONAME");
  fputc ('\n', out_file);
}



static void process_aout (FILE *inp_file, long size)
{
  byte *inp_buf;
  const struct a_out_header *a_out_h;
  const byte *sym;
  const struct nlist *sym_ptr;
  const byte *str_ptr;
  long str_size;
  int sym_count, i;
  const char *name;

  new_mod = TRUE;

  inp_buf = xmalloc (size);
  size = fread (inp_buf, 1, size, inp_file);

  a_out_h = (struct a_out_header *)inp_buf;
  if (size < sizeof (struct a_out_header) || a_out_h->magic != 0407)
    error ("Malformed input file `%s'", inp_fname);
  sym = (inp_buf + sizeof (struct a_out_header) + a_out_h->text_size
         + a_out_h->data_size + a_out_h->trsize + a_out_h->drsize);
  str_ptr = sym + a_out_h->sym_size;
  if (str_ptr + 4 - inp_buf > size)
    error ("Malformed input file `%s'", inp_fname);
  str_size = *(long *)str_ptr;
  sym_ptr = (const struct nlist *)sym;
  sym_count = a_out_h->sym_size / sizeof (struct nlist);
  if (str_ptr + str_size - inp_buf > size)
    error ("Malformed input file `%s'", inp_fname);

  for (i = 0; i < sym_count; ++i)
    if (sym_ptr[i].type == (N_TEXT|N_EXT) || sym_ptr[i].type == (N_DATA|N_EXT))
      {
        name = str_ptr + sym_ptr[i].string;
        if (*name == '_')
          ++name;
        export (name);
      }

  free (inp_buf);
}


static byte rec_buf[MAX_REC_SIZE+8];
static int rec_type;
static int rec_len;
static int rec_idx;


static void bad_omf (void)
{
  error ("Malformed OMF file `%s'", inp_fname);
}


static void get_mem (void *dst, int len)
{
  if (rec_idx + len > rec_len)
    bad_omf ();
  memcpy (dst, rec_buf + rec_idx, len);
  rec_idx += len;
}


static void get_string (byte *dst)
{
  int len;

  if (rec_idx >= rec_len)
    bad_omf ();
  len = rec_buf[rec_idx++];
  get_mem (dst, len);
  dst[len] = 0;
}


static int get_index (void)
{
  int result;

  if (rec_idx >= rec_len)
    bad_omf ();
  result = rec_buf[rec_idx++];
  if (result & 0x80)
    {
      if (rec_idx >= rec_len)
        bad_omf ();
      result = ((result & 0x7f) << 8) | rec_buf[rec_idx++];
    }
  return (result);
}


static word get_dword (void)
{
  dword result;

  if (rec_idx + 4 > rec_len)
    bad_omf ();
  result = rec_buf[rec_idx++];
  result |= rec_buf[rec_idx++] << 8;
  result |= rec_buf[rec_idx++] << 16;
  result |= rec_buf[rec_idx++] << 24;
  return (result);
}


static word get_word (void)
{
  word result;

  if (rec_idx + 2 > rec_len)
    bad_omf ();
  result = rec_buf[rec_idx++];
  result |= rec_buf[rec_idx++] << 8;
  return (result);
}


static dword get_word_or_dword (void)
{
  return (rec_type & REC32 ? get_dword () : get_word ());
}


static void omf_pubdef (void)
{
  int type, group, seg;
  word frame;
  dword offset;
  byte name[256];

  group = get_index ();
  seg = get_index ();
  if (seg == 0)
    frame = get_word ();

  while (rec_idx < rec_len)
    {
      get_string (name);
      offset = get_word_or_dword ();
      type = get_index ();
      export (name);
    }
}


static void process_omf (FILE *inp_file)
{
  struct omf_rec rec;

  new_mod = TRUE;
  do
    {
      if (fread (&rec, sizeof (rec), 1, inp_file) != 1)
        error ("Unexpected end of file on input file `%s'", inp_fname);
      rec_type = rec.rec_type;
      rec_len = rec.rec_len;
      rec_idx = 0;
      if (rec_len > sizeof (rec_buf))
        error ("OMF record too long in `%s'", inp_fname);
      if (fread (rec_buf, rec_len, 1, inp_file) != 1)
        error ("Unexpected end of file on input file `%s'", inp_fname);
      --rec_len;                /* Remove checksum */
      switch (rec_type)
        {
        case PUBDEF:
        case PUBDEF|REC32:
          omf_pubdef ();
          break;
        }
    } while (rec.rec_type != MODEND && rec_type != (MODEND|REC32));
}


/* Process one input file. */

static void process (void)
{
  static char ar_magic[SARMAG+1] = ARMAG;
  char ar_test[SARMAG], *p;
  struct ar_hdr ar;
  long size, ar_pos;
  int i, n;
  FILE *inp_file;

  mod_name = NULL;
  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);

  if (memcmp (ar_test, ar_magic, SARMAG) == 0)
    {

      /* The input file is an archive. Loop over all the members of
         the archive. */

      ar_pos = SARMAG;
      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. */

          if (strcmp (ar.ar_name, "__.SYMDEF") != 0
              && strcmp (ar.ar_name, "__.IMPORT") != 0)
            {
              mod_name = ar.ar_name;
              process_aout (inp_file, size);
            }
        }
    }
  else
    {
      if (*(word *)ar_test == 0407)
        {
          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);
          process_aout (inp_file, size);
        }
      else if (*(byte *)ar_test == LIBHDR)
        {
          struct omflib *lib;
          char errmsg[512], name[257];
          int page;

          lib = omflib_open (inp_fname, errmsg);
          if (lib == NULL)
            error ("%s: %s", inp_fname, errmsg);

          n = omflib_module_count (lib, errmsg);
          if (n == -1)
            error ("%s: %s", inp_fname, errmsg);
          for (i = 0; i < n; ++i)
            {
              if (omflib_module_info (lib, i, name, &page, errmsg) != 0)
                error ("%s: %s", inp_fname, errmsg);
              else
                {
                  fseek (inp_file, omflib_page_pos (lib, page), SEEK_SET);
                  mod_name = name;
                  process_omf (inp_file);
                }
            }
          if (omflib_close (lib, errmsg) != 0)
            error ("%s: %s", inp_fname, errmsg);
        }
      else
        {
          fseek (inp_file, 0L, SEEK_SET);
          process_omf (inp_file);
        }
    }
  fclose (inp_file);
}


/* Main line. */

int main (int argc, char **argv)
{
  int c, i;
  char *p;

  _response (&argc, &argv);
  _wildcard (&argc, &argv);
  opterr = 0;
  optswchar = "-";
  optind = 0;
  while ((c = getopt (argc, argv, "no::")) != EOF)
    {
      switch (c)
        {
        case 'n':
          noname_flag = TRUE;
          break;
        case 'o':
          if (optarg == NULL)
            ordinal = 1;
          else
            {
              errno = 0;
              ordinal = (int)strtol (optarg, &p, 0);
              if (p == optarg || errno != 0 || *p != 0
                  || ordinal < 1 || ordinal > 65535)
                usage ();
            }
          break;
        default:
          error ("Invalid option");
        }
    }

  out_file = stdout;

  if (optind >= argc)
    usage ();

  for (i = optind; i < argc; ++i)
    {
      inp_fname = argv[i];
      process ();
    }

  if (fflush (out_file) != 0 || (out_file != stdout && fclose (out_file) != 0))
    error ("Write error");

  return (0);
}
