/* omflibcr.c (emx+gcc) */

/* Create a new OMFLIB.

Copyright (c) 1993 Eberhard Mattes

This file is part of the emx OMFLIB library.  This library is free
software; you can redistribute it and/or modify it under the terms of
the GNU Library General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your
option) any later version.  This library 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 Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

As a special exception, programs written by Eberhard Mattes can use
this library without restrictions unless changed by someone else. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "omflib0.h"
#include "omflib.h"


static int const primes[] =
{
  2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
  61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127,
  131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
  197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 0
};


struct omflib *omflib_create (const char *fname, int page_size, char *error)
{
  struct omflib *p;
  FILE *f;

  if (page_size < 16 || page_size > 32768
      || (page_size & (page_size - 1)) != 0)
    {
      strcpy (error, "Invalid page size");
      return (NULL);
    }
  f = fopen (fname, "wb");
  if (f == NULL)
    {
      strcpy (error, strerror (errno));
      return (NULL);
    }
  p = malloc (sizeof (struct omflib));
  if (p == NULL)
    {
      strcpy (error, strerror (errno));
      fclose (f);
      remove (fname);
      return (NULL);
    }
  p->f = f;
  p->page_size = page_size;
  p->dict_offset = 0;
  p->dict_blocks = 0;
  p->flags = 1;
  p->mod_tab = NULL;
  p->mod_alloc = 0;
  p->mod_count = -1;
  p->dict = NULL;
  p->pub_tab = NULL;
  p->pub_alloc = 0;
  p->pub_count = 0;
  p->output = TRUE;
  return (p);
}


int omflib_header (struct omflib *p, char *error)
{
  fseek (p->f, 0, SEEK_SET);
  return (omflib_pad (p->f, p->page_size, TRUE, error));
}


static int omflib_add_dict (struct omflib *p, const char *name, int page,
                            char *error)
{
  int block_index, bucket_index;
  int bv, len, bucket_count;
  byte *block, *ptr;
  byte buf[257];
  int (*compare)(const void *s1, const void *s2, size_t n);

  len = strlen (name);
  if (len > 255)
    {
      strcpy (error, "Symbol name too long");
      return (-1);
    }
  buf[0] = (byte)len;
  memcpy (buf+1, name, len);
  omflib_hash (p, buf);
  block_index = p->block_index;
  bucket_index = p->bucket_index;
  compare = (p->flags & 1 ? memcmp : memicmp);
  bucket_count = 37;
  block = p->dict + 512 * block_index;
  for (;;)
    {
      bv = block[bucket_index];
      if (bv == 0)
        {
          if (block[37] == 0xff)
            bucket_count = 0;
          else if (2 * block[37] + len + 4 > 512)
            {
              block[37] = 0xff;
              bucket_count = 0;
            }
          else
            {
              block[bucket_index] = block[37];
              ptr = block + 2 * block[37];
              memcpy (ptr, buf, len+1);
              ptr[len+1] = (byte)page;
              ptr[len+2] = (byte)(page >> 8);
              block[37] = (2 * block[37] + len + 3 + 1) / 2;
              if (block[37] == 0) block[37] = 0xff;
              return (0);
            }
        }
      else
        {
          ptr = block + 2 * bv;
          if (*ptr == len && compare (ptr+1, buf+1, len) == 0)
            {
              strcpy (error, "Symbol multiply defined: ");
              strcat (error, name);
              return (-1);
            }
        }
      if (bucket_count != 0)
        {
          bucket_index += p->bucket_index_delta;
          if (bucket_index >= 37)
            bucket_index -= 37;
          --bucket_count;
        }
      if (bucket_count == 0)
        {
          block_index += p->block_index_delta;
          if (block_index >= p->dict_blocks)
            block_index -= p->dict_blocks;
          if (block_index == p->block_index)
            return (1);
          bucket_count = 37;
          block = p->dict + 512 * block_index;
        }
    }
}


static int omflib_build_dict (struct omflib *p, char *error)
{
  int i, ret;

  p->dict = realloc (p->dict, p->dict_blocks * 512);
  if (p->dict == NULL)
    return (omflib_set_error (error));
  memset (p->dict, 0, p->dict_blocks * 512);
  for (i = 0; i < p->dict_blocks; ++i)
    p->dict[i * 512 + 37] = 38 / 2;
  for (i = 0; i < p->pub_count; ++i)
    {
      ret = omflib_add_dict (p, p->pub_tab[i].name, p->pub_tab[i].page, error);
      if (ret != 0)
        return (ret);
    }
  return (0);
}


int omflib_finish (struct omflib *p, char *error)
{
  struct lib_header hdr;
  int len, i, blocks, prime_index;
  long pos;
  struct omf_rec rec;

  if (!p->output)
    return (0);
  len = 0;
  for (i = 0; i < p->pub_count; ++i)
    len += strlen (p->pub_tab[i].name) + 3;
  blocks = (len + 511) / 512;
  blocks += (blocks * 128) / 512;
  ++blocks;
  for (prime_index = 0; primes[prime_index] < blocks; ++prime_index)
    if (primes[prime_index] == 0)
      break;
  for (;;)
    {
      if (primes[prime_index] == 0)
        {
          strcpy (error, "Too many dictionary blocks");
          return (-1);
        }
      p->dict_blocks = primes[prime_index];
      i = omflib_build_dict (p, error);
      if (i < 0)
        return (i);
      if (i == 0)
        break;
      ++prime_index;
    }
  pos = ftell (p->f) + 3;
  rec.rec_type = LIBEND;
  if ((pos & 511) == 0)
    rec.rec_len = 0;
  else
    rec.rec_len = (word)(((pos | 511) + 1) - pos);
  if (fwrite (&rec, sizeof (rec), 1, p->f) != 1)
    return (omflib_set_error (error));
  if (omflib_pad (p->f, 512, FALSE, error) != 0)
    return (-1);
  hdr.rec_type = LIBHDR;
  hdr.rec_len = p->page_size - 3;
  hdr.dict_offset = ftell (p->f);
  hdr.dict_blocks = p->dict_blocks;
  hdr.flags = (byte)p->flags;
  fseek (p->f, 0, SEEK_SET);
  if (fwrite (&hdr, sizeof (hdr), 1, p->f) != 1)
    return (omflib_set_error (error));
  fseek (p->f, hdr.dict_offset, SEEK_SET);
  if (fwrite (p->dict, 512, p->dict_blocks, p->f) != p->dict_blocks)
    return (omflib_set_error (error));
  return (0);
}


int omflib_pad (FILE *f, int size, int force, char *error)
{
  long pos;

  pos = ftell (f);
  while ((pos & (size-1)) != 0 || force)
    {
      force = FALSE;
      if (fputc (0, f) != 0)
        return (omflib_set_error (error));
      ++pos;
    }
  return (0);
}
