/* emxexp.c -- Create export definitions from .o and .obj files
   Copyright (c) 1993-1996 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, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */


#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 <sys/omflib.h>
#include "demangle.h"

#define VERSION "0.9c"

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

static int ordinal = 0;
static int noname_flag = FALSE;
static int bss_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 struct bss_list *bss_list = NULL;

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-1995 by Eberhard Mattes\n\n"
         "Usage: emxexp [-n] [-u] [-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"
         "  -u          Also export uninitialized variables\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 export_bss (const char *name)
{
  struct bss_list *p;

  if (bss_flag)
    {
      for (p = bss_list; p != NULL; p = p->next)
        if (strcmp (p->name, name) == 0)
          return;
      p = xmalloc (sizeof (*p));
      p->name = xmalloc (strlen (name) + 1);
      strcpy (p->name, name);
      p->next = bss_list;
      bss_list = p;
      export (name);
    }
}


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);
      }
    else if (sym_ptr[i].type == N_EXT && sym_ptr[i].value != 0)
      {
        name = str_ptr + sym_ptr[i].string;
        if (*name == '_')
          ++name;
        export_bss (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 int get_byte (void)
{
  if (rec_idx >= rec_len)
    bad_omf ();
  return rec_buf[rec_idx++];
}


static void get_string (byte *dst)
{
  int len;

  len = get_byte ();
  get_mem (dst, len);
  dst[len] = 0;
}


static int get_index (void)
{
  int result;

  result = get_byte ();
  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 dword get_commlen (void)
{
  dword result;

  result = get_byte ();
  if (result <= 0x80)
    return result;
  switch (result)
    {
    case 0x81:
      return get_word ();
    case 0x84:
      result = get_byte ();
      result |= get_byte () << 8;
      result |= get_byte () << 16;
      return result;
    case 0x88:
      return get_dword ();
    default:
      bad_omf ();
    }
}


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 omf_comdef (void)
{
  int type_index, data_type;
  byte name[256];

  while (rec_idx < rec_len)
    {
      get_string (name);
      type_index = get_index ();
      data_type = get_byte ();
      switch (data_type)
        {
        case 0x61:
          get_commlen ();
          get_commlen ();
          break;
        case 0x62:
          get_commlen ();
          break;
        default:
          bad_omf ();
        }
      export_bss (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;
        case COMDEF:
          if (bss_flag)
            omf_comdef ();
          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, *end;
  struct ar_hdr ar;
  long size, ar_pos, index;
  int i, n;
  FILE *inp_file;
  char *long_names = NULL;
  size_t long_names_size = 0;

  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;

          /* Remove trailing blanks from the member name. */

          i = sizeof (ar.ar_name) - 1;
          while (i > 0 && ar.ar_name[i-1] == ' ')
            --i;
          ar.ar_name[i] = 0;

          if (strcmp (ar.ar_name, "ARFILENAMES/") == 0)
            {
              size_t i;

              /* The "ARFILENAMES/" member contains the long file
                 names, each one is terminated with a newline
                 character.  Member names starting with a space are
                 also considered long file names because a leading
                 space is used for names pointing into the
                 "ARFILENAMES/" table.  Read the "ARFILENAMES/" member
                 to LONG_NAMES. */

              if (size != 0)
                {
                  long_names_size = (size_t)size;
                  long_names = xmalloc (long_names_size);
                  size = fread (long_names, 1, long_names_size, inp_file);
                  if (ferror (inp_file))
                    error ("Cannot read `%s'", inp_fname);
                  if (size != long_names_size)
                    error ("%s: ARFILENAMES/ member is truncated", inp_fname);

                  /* Replace the newlines with nulls to make
                     processing a bit more convenient. */

                  for (i = 0; i < long_names_size; ++i)
                    if (long_names[i] == '\n')
                      long_names[i] = 0;
                  if (long_names[long_names_size-1] != 0)
                    error ("%s: ARFILENAMES/ member corrupt", inp_fname);
                }
            }

          /* Ignore the __.SYMDEF and __.IMPORT members. */

          else if (strcmp (ar.ar_name, "__.SYMDEF") != 0
              && strcmp (ar.ar_name, "__.IMPORT") != 0)
            {
              /* Process the current member.  First, fetch the name of
                 the member.  If the ar_name starts with a space, the
                 decimal number following that space is an offset into
                 the "ARFILENAMES/" member.  The number may be
                 followed by a space and a substring of the long file
                 name. */

              mod_name = ar.ar_name;
              if (mod_name[0] == ' ' && long_names != NULL
                  && (index = strtol (mod_name + 1, &end, 10)) >= 0
                  && index < long_names_size - 1
                  && (*end == 0 || *end == ' ')
                  && (index == 0 || long_names[index-1] == 0))
                mod_name = long_names + index;

              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::u")) != 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;
        case 'u':
          bss_flag = TRUE;
          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;
}
