/* Copyright 2000 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

#if defined (powerpc)

#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_cfm[] =
	    "$Id: cfm.c 63 2004-12-24 18:19:43Z ctm $";
#endif

/*
 * In addition to fleshing this out, it probably makes sense to use mmap
 * to pull in certain sections (including temporarily the loader section).
 */

#include "rsys/common.h"

#include <sys/mman.h>
#include <assert.h>

#include "FileMgr.h"
#include "OSUtil.h"
#include "MemoryMgr.h"
#include "SegmentLdr.h"
#include "AliasMgr.h"

#include "rsys/cfm.h"
#include "rsys/pef.h"
#include "rsys/file.h"
#include "rsys/interfacelib.h"
#include "rsys/mathlib.h"
#include "rsys/launch.h"
#include "rsys/hfs.h"
#include "rsys/string.h"

#include "ppc_stubs.h"

typedef enum
{
  process_share = 1,
  global_share = 4,
  protected_share = 5,
}
share_kind_t;

enum
{
  code_section_type = 0,
  unpacked_data_section_type,
  pattern_data_section_type,
  constant_section_type,
  loader_section_type,
  debug_section_type,
  executable_data_section_type,
  exception_section_type,
  traceback_section_type,
};

#undef roundup
#define roundup(n, m)				\
({						\
  typeof (n) __n;				\
  typeof (m) __m;				\
						\
  __n = (n);					\
  __m = (m);					\
  (__n + __m - 1) / m * m;			\
})

#undef rounddown
#define rounddown(n, m)				\
({						\
  typeof (n) __n;				\
  typeof (m) __m;				\
						\
  __n = (n);					\
  __m = (m);					\
  __n / m * m;					\
})

PRIVATE OSErr
try_to_get_memory (void **addrp, syn68k_addr_t default_syn_address,
		   uint32 total_size, int alignment)
{
  OSErr retval;
#if !defined(linux)
  retval = paramErr;
#else
  void *default_address;
  void *received_address;

  default_address = SYN68K_TO_US (default_syn_address);
  total_size = roundup (total_size, getpagesize());
  received_address = mmap (default_address, total_size,
			   PROT_EXEC|PROT_READ|PROT_WRITE,
			   MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
  if (received_address == (void *) -1)
    retval = memFullErr;
  else
    {
      retval = noErr;
      *addrp = received_address;
    }
#endif  
  return retval;
}

enum
{
  readable_section   = (1 << 0),
  writable_section   = (1 << 1),
  executable_section = (1 << 2),
};

PRIVATE OSErr
load_unpacked_section (const void *mmapped_addr,
		       syn68k_addr_t default_address, uint32 total_size,
		       uint32 packed_size, uint32 unpacked_size,
		       uint32 section_offset, share_kind_t share_kind,
		       int alignment, section_info_t *infop)
{
  OSErr retval;
  void *addr;

  if (packed_size != unpacked_size)
    warning_unexpected ("%d %d", packed_size, unpacked_size);
  if (!(infop->perms & writable_section) && total_size == unpacked_size)
    {
      retval = noErr;
      addr = (char *) mmapped_addr + section_offset;
    }
  else
    {
      retval = try_to_get_memory (&addr, default_address, total_size,
				  alignment);
      if (retval == noErr)
	{
	  memcpy (addr, (char *) mmapped_addr + section_offset, packed_size);
	  memset (addr + unpacked_size, 0, total_size - unpacked_size);
	}
    }
  if (retval == noErr)
    {
      infop->start = US_TO_SYN68K (addr);
      infop->length = total_size;
    }

  return retval;
}

PRIVATE OSErr
repeat_block (uint32 repeat_count, uint32 block_size, uint8 **destpp,
	      const uint8 **srcpp, uint32 *dest_lengthp, uint32 *src_lengthp)
{
  OSErr retval;
  uint32 total_bytes_to_write;

  total_bytes_to_write = repeat_count * block_size;
  if (total_bytes_to_write > *dest_lengthp || block_size > *src_lengthp)
    retval = paramErr;
  else
    {
      while (repeat_count-- > 0)
	{
	  memcpy (*destpp, *srcpp, block_size);
	  *destpp += block_size;
	}
      *srcpp += block_size;
      *dest_lengthp -= total_bytes_to_write;
      *src_lengthp -= block_size;
      retval = noErr;
    }
  return retval;
}

PRIVATE OSErr
extract_count (const uint8 **srcpp, uint32 *lengthp, uint32 *countp)
{
  OSErr retval;
  uint32 count;
  uint8 next_piece;
  
  count = *countp;
  do
    {
      retval = (*lengthp > 0) ? noErr : paramErr;
      if (retval == noErr)
	{
	  next_piece = **srcpp;
	  ++*srcpp;
	  --*lengthp;
	  count = (count << 7) | (next_piece & 0x7F);
	}
    }
  while (retval == noErr && (next_piece & 0x80));
  if (retval == noErr)
    *countp = count;

  return retval;
}

PRIVATE OSErr
interleave (uint32 repeat_count, uint32 custom_size, uint8 **destpp,
	    const uint8 **srcpp, uint32 *dest_lengthp, uint32 *src_lengthp,
	    uint32 common_size, const uint8 *common_mem)
{
  OSErr retval;
  uint32 total_size;

  total_size = repeat_count * (common_size + custom_size) + common_size;
  if (total_size > *dest_lengthp || custom_size * repeat_count > *src_lengthp)
    retval = paramErr;
  else
    {
      ++repeat_count;
      while (repeat_count-- > 0)
	{
	  if (common_mem)
	    memcpy (*destpp, common_mem, common_size);
	  else
	    memset (*destpp, 0, common_size);
	  *destpp += common_size;
	  *dest_lengthp -= common_size;
	  if (repeat_count > 0)
	    {
	      memcpy (*destpp, *srcpp, custom_size);
	      *destpp += custom_size;
	      *dest_lengthp -= custom_size;
	      *srcpp += custom_size;
	      *src_lengthp -= custom_size;
	    }
	}
      retval = noErr;
    }
  return retval;
}

PRIVATE OSErr
pattern_initialize (uint8 *addr, const uint8 *patmem, uint32 packed_size,
		    uint32 unpacked_size)
{
  OSErr retval;

  retval = noErr;
  while (retval == noErr && packed_size > 0)
    {
      uint8 opcode;
      uint32 count;

      opcode = *patmem >> 5;
      count = (*patmem & 0x1f);
      ++patmem;
      --packed_size;
      if (!count)
	retval = extract_count (&patmem, &packed_size, &count);
      if (retval == noErr)
	{
	  switch (opcode)
	    {
	    case 0:
	      if (count > unpacked_size)
		retval = paramErr;
	      else
		{
		  memset (addr, 0, count);
		  addr += count;
		  unpacked_size -= count;
		}
	      break;
	    case 1:
	      retval = repeat_block (1, count, &addr, &patmem, &unpacked_size,
				     &packed_size);
	      break;
	    case 2:
	      {
		uint32 repeat_count;

		repeat_count = 0;
		retval = extract_count (&patmem, &packed_size, &repeat_count);
		if (retval == noErr)
		  retval = repeat_block (repeat_count + 1, count, &addr,
					 &patmem, &unpacked_size,
					 &packed_size);
	      }
	      break;
	    case 3:
	    case 4:
	      {
		uint32 common_size;
		uint32 custom_size;

		common_size = count;
		custom_size = 0;
		retval = extract_count (&patmem, &packed_size, &custom_size);
		if (retval == noErr)
		  {
		    uint32 repeat_count;

		    repeat_count = 0;
		    retval = extract_count (&patmem, &packed_size,
					    &repeat_count);
		    if (retval == noErr)
		      {
			const uint8 *common_mem;

			if (opcode == 4)
			  common_mem = 0;
			else
			  {
			    common_mem = patmem;
			    if (common_size > packed_size)
			      retval = paramErr;
			    else
			      {
				patmem += common_size;
				packed_size -= common_size;
			      }
			  }
			if (retval == noErr)
			  interleave (repeat_count, custom_size, &addr,
				      &patmem, &unpacked_size, &packed_size,
				      common_size, common_mem);
		      }
		  }
	      }
	      break;
	    default:
	      warning_unexpected ("%d", opcode);
	      assert (0);
	      break;
	    }
	}
    }
  if (retval == noErr && (packed_size || unpacked_size))
    {
      retval = paramErr;
      warning_unexpected ("%d %d", packed_size, unpacked_size);
      assert (0); 
    }
  return retval;
}

PRIVATE OSErr
load_pattern_section (const void *mmapped_addr,
		     syn68k_addr_t default_address, uint32 total_size,
		     uint32 packed_size, uint32 unpacked_size,
		     uint32 section_offset, share_kind_t share_kind,
		     int alignment, section_info_t *infop)
{
  OSErr retval;
  void *addr;

  retval = try_to_get_memory (&addr, default_address, total_size, alignment);
  if (retval == noErr)
    {
      uint8 *patmem;


      patmem = (typeof (patmem)) ((char *) mmapped_addr + section_offset);
      retval = pattern_initialize (addr, patmem, packed_size, unpacked_size);
      if (retval == noErr)
	{
	  memset (addr + unpacked_size, 0,
		  total_size - unpacked_size);
	  infop->start = US_TO_SYN68K (addr);
	  infop->length = total_size;
	}
    }

  return retval;
}

PRIVATE void
repeatedly_relocate (uint32 count, uint8 **relocAddressp, uint32 val)
{
  uint32 *p;
  p = (uint32 *) *relocAddressp;
  while (count-- > 0)
    {
      *p = CL (CL (*p) + val);
      ++p;
    } 
  *relocAddressp = (uint8 *) p;
}

PRIVATE OSErr
check_existing_connections (Str63 library, OSType arch, LoadFlags loadflags,
			    ConnectionID *cidp, Ptr *mainaddrp, Str255 errName)
{
  /* TODO */
#warning TODO
  return fragLibNotFound;
}

PRIVATE void
get_root_and_app (INTEGER *root_vrefp, LONGINT *root_diridp,
		  INTEGER *app_vrefp , LONGINT *app_diridp)
{
#warning TODO
  /* TODO */
}

PRIVATE OSErr
check_file (INTEGER vref, LONGINT dirid, Str255 file, boolean_t shlb_test_p,
	    Str63 library, OSType arch, LoadFlags loadflags,
	    ConnectionID *cidp, Ptr *mainaddrp, Str255 errName)
{
  OSErr retval;
  FSSpec fs;

  retval = FSMakeFSSpec (vref, dirid, file, &fs);
  if (retval != noErr)
    retval = fragLibNotFound;
  else
    {
      if (shlb_test_p)
	{
	  OSErr err;
	  FInfo finfo;

	  err = FSpGetFInfo (&fs, &finfo);
	  if (err != noErr || finfo.fdType != TICK("shlb"))
	    retval = fragLibNotFound;
	}
      if (retval == noErr)
	{
	  INTEGER rn;
	  
	  rn = FSpOpenResFile (&fs, fsRdPerm);
	  if (rn == -1)
	    retval = fragLibNotFound;
	  else
	    {
	      Handle cfrg0;

	      cfrg0 = Get1Resource (T('c','f','r','g'), 0);
	      if (!cfrg0)
		retval = fragLibNotFound;
	      else
		{
		  cfir_t *cfirp;

		  cfirp = ROMlib_find_cfrg (cfrg0, arch, kImportLibraryCFrag,
					    library);
		  if (!cfirp)
		    retval = fragLibNotFound;
		  else
		    retval = GetDiskFragment (&fs,
					      CFIR_OFFSET_TO_FRAGMENT (cfirp),
					      CFIR_FRAGMENT_LENGTH (cfirp), "",
					      loadflags, cidp, mainaddrp,
					      errName);
		}
	      CloseResFile (rn);
	    }
	}
    }

  return retval;
}

PRIVATE OSErr
check_vanddir (INTEGER vref, LONGINT dirid, int descend_count, Str63 library,
	       OSType arch, LoadFlags loadflags, ConnectionID *cidp,
	       Ptr *mainaddrp, Str255 errName)
{
  CInfoPBRec pb;
  Str255 s;
  OSErr retval;
  INTEGER dirindex;
  OSErr err;
  int errcount;

  retval = fragLibNotFound;
  pb.hFileInfo.ioNamePtr = RM (&s[0]);
  pb.hFileInfo.ioVRefNum = CW (vref);
  err = noErr;
  errcount = 0;
  for (dirindex = 1;
       retval != noErr && err != fnfErr && errcount != 3;
       dirindex++)
    {
      pb.hFileInfo.ioFDirIndex = CW (dirindex);
      pb.hFileInfo.ioDirID   = CL (dirid);
      err = PBGetCatInfo(&pb, FALSE);
      if (err)
	{
	  if (err != fnfErr)
	    {
	      warning_unexpected ("PBGetCatInfo err = %d\n", err);
	      ++errcount;
	    }
	}
      else
	{
	  errcount = 0;
	  if (pb.hFileInfo.ioFlAttrib & ATTRIB_ISADIR)
	    {
	      if (descend_count > 0)
		{
		  retval = check_vanddir (vref, CL (pb.hFileInfo.ioDirID),
					  descend_count-1, library,
					  arch, loadflags, cidp, mainaddrp,
					  errName);
		}
	    }
	  else if (pb.hFileInfo.ioFlFndrInfo.fdType == TICKX ("shlb"))
	    retval = check_file (vref, dirid, s, FALSE, library, arch,
				 loadflags, cidp, mainaddrp, errName);
	}
    }
  return retval;
}

/*
 * This sort of implements the "Searching for Import Libraries" algorithm
 * described on 1-17 and 1-18 of the Mac Runtime Architectures manual.  The
 * biggest difference is that we don't search for the best fit between
 * everything available in the Extensions folder, the ROM registry and the
 * file registry.  Instead we currently stop when we find any fit.
 *
 * Additionally, we don't really have a ROM registry per-se.  Instead
 * we have the home-brewed InterfaceLib and MathLib files.  We don't
 * have anything corresponding to the file registry.
 *
 */

P6 (PUBLIC pascal trap, OSErr, GetSharedLibrary, Str63, library, OSType, arch,
    LoadFlags, loadflags, ConnectionID *, cidp, Ptr *, mainaddrp, Str255,
    errName)
{
  OSErr retval;

  warning_trace_info ("GetSharedLibrary (\"%.*s\")\n", library[0],
		      library+1);

  retval = check_existing_connections (library, arch, loadflags, cidp,
				       mainaddrp, errName);
  if (retval == fragLibNotFound)
    {
      INTEGER root_vref, app_vref;
      LONGINT root_dirid, app_dirid;
	  
      get_root_and_app (&root_vref, &root_dirid, &app_vref, &app_dirid);
      if (root_vref != app_vref || root_dirid != app_dirid)
	retval = check_vanddir (root_vref, root_dirid, 0, library, arch,
				loadflags, cidp, mainaddrp, errName);
      if (retval != noErr)
	retval = check_file (ROMlib_exevrefnum, 0, ROMlib_exefname, FALSE,
			     library, arch, loadflags, cidp, mainaddrp,
			     errName);
      if (retval != noErr)
	retval = check_vanddir (app_vref, app_dirid, 0, library, arch,
				loadflags, cidp, mainaddrp, errName);
      if (retval != noErr)
	retval = check_vanddir (ROMlib_exevrefnum, 0, 0, library, arch,
				loadflags, cidp, mainaddrp, errName);
      if (retval != noErr)
	{
	  INTEGER extensions_vref;
	  LONGINT extensions_dirid;

	  if (FindFolder (0, kExtensionFolderType, FALSE, &extensions_vref,
	                  &extensions_dirid) == noErr)
	    retval = check_vanddir (extensions_vref, extensions_dirid, 1,
				    library, arch, loadflags, cidp,
				    mainaddrp, errName);
	}

      if (retval != noErr)
	{
	  if (EqualString (library, "\7MathLib", FALSE, TRUE))
	    retval = ROMlib_GetMathLib (library, arch, loadflags, cidp,
				mainaddrp, errName);
	  else if (EqualString (library, "\14InterfaceLib", FALSE, TRUE))
	    retval = ROMlib_GetInterfaceLib (library, arch, loadflags, cidp,
				     mainaddrp, errName);
	}
    }
  return retval;
}

P1 (PUBLIC pascal trap, OSErr, CloseConnection, ConnectionID *, cidp)
{
  warning_trace_info ("cidp = %p, cid = 0x%x", cidp, (uint32) *cidp);
  return noErr;
}

enum { tracking_val_start = 0x88123456 };
PRIVATE uint32 num_tracking_vals;
PRIVATE char **tracking_symbols;

PRIVATE void
tracking_handler (int signum, struct sigcontext sc)
{
  uint32 r12;
  uint32 val;

  r12 = sc.regs->gpr[PT_R12];
  val = r12 - (uint32) tracking_val_start;
  if (val < num_tracking_vals)
    fprintf (stderr, "Need glue for '%s'\n", tracking_symbols[val]);
  ExitToShell ();
}

PUBLIC void
ROMlib_release_tracking_values (void)
{
  if (num_tracking_vals > 0)
    {
      int i;

      for (i = 0; i < num_tracking_vals; ++i)
	free (tracking_symbols[i]);
      tracking_symbols = realloc (tracking_symbols, 0);
      num_tracking_vals = 0;
    }
  signal (SIGSEGV, (void *) tracking_handler);
}

PRIVATE uint32
tracking_value (const char *symbol_name)
{
  uint32 retval;

  tracking_symbols = realloc (tracking_symbols,
			      (num_tracking_vals+1) *
			      sizeof *tracking_symbols);
  tracking_symbols[num_tracking_vals] = strdup (symbol_name);
  retval = tracking_val_start + num_tracking_vals++;
  warning_trace_info ("name = '%s' (%p)\n", symbol_name, (Ptr) retval);
  return retval;
}


PRIVATE OSErr
symbol_lookup (uint32 *indexp, Ptr *valp, uint8 imports[][4],
	       const char *symbol_names, CFragClosureID closure_id)
{
  OSErr retval;
  int index;
  uint8 flags;
  uint8 class;
  const char *symbol_name;

  index = *indexp;
  flags = imports[index][0] >> 4;
  class = imports[index][0] & 0xf;
  symbol_name = (symbol_names +
		 (imports[index][1] << 16) +
		 (imports[index][2] << 8) +
		 (imports[index][3] << 0));

  {
    int i;
    int n_libs;

    n_libs = N_LIBS (closure_id);
    for (i = 0; i < n_libs; ++i)
      {
	const lib_t *l;
	uint32 first_symbol;

	l = &closure_id->libs[i];
	first_symbol = LIB_FIRST_SYMBOL (l);
	if (index >= first_symbol &&
	    index < first_symbol + LIB_N_SYMBOLS (l))
	  {
	    Str255 sym255;
	    OSErr err;

	    sym255[0] = MIN (strlen (symbol_name), 255);
	    memcpy (sym255+1, symbol_name, sym255[0]);
	    err = FindSymbol (LIB_CID (l), sym255, valp, 0);
	    if (err != noErr)
	      {
		if (flags & 8)
		  *valp = kUnresolvedCFragSymbolAddress;
		else
		  *valp = (Ptr) tracking_value (symbol_name);
	      }
/*-->*/	    break;
	  }
      }
  }
  ++*indexp;
  retval = noErr;
  return retval;
}

PRIVATE OSErr
relocate (const PEFLoaderRelocationHeader_t reloc_headers[],
	  int section, uint32 reloc_count, uint8 reloc_instrs[][2],
	  uint8 imports[][4], const char *symbol_names,
	  CFragClosureID closure_id, ConnectionID connp)
{
  OSErr retval;
  uint8 *relocAddress;
  uint32 importIndex;
  syn68k_addr_t sectionC;
  syn68k_addr_t sectionD;
  int32 repeat_remaining;

  repeat_remaining = -1; /* i.e. not currently processing RelocSmRepeat or
			    RelocLgRepeat */
  
  relocAddress = (uint8 *) SYN68K_TO_US (connp->sects[section].start);
  importIndex = 0;
  sectionC = connp->sects[0].start;
  sectionD = connp->sects[1].start;

  retval = noErr;
  while (reloc_count-- > 0)
    {
      uint8 msb;

      msb = reloc_instrs[0][0];
      if ((msb >> 6) == 0)
	{
	  uint8 lsb;
	  int skipCount;
	  int relocCount;

	  lsb = reloc_instrs[0][1];
	  skipCount = (msb << 2) | (lsb >> 6);
	  relocCount = (lsb & 0x3f);
	  relocAddress += skipCount * 4;
	  repeatedly_relocate (relocCount, &relocAddress, sectionD);
	}
      else
	{
	  switch ((msb >> 5))
	    {
	    case 2:
	      {
		int sub_op;
		int run_length;
		sub_op = (msb >> 1) & 0xf;
		run_length = (((msb & 1) << 8)
			      | reloc_instrs[0][1]);
		++run_length;
		switch (sub_op)
		  {
		  case 0: /* RelocBySectC */
		    repeatedly_relocate (run_length, &relocAddress, sectionC);
		    break;
		  case 1: /* RelocBySectD */
		    repeatedly_relocate (run_length, &relocAddress, sectionD);
		    break;
		  case 2:
		    while (run_length-- > 0)
		      {
			repeatedly_relocate (1, &relocAddress, sectionC);
			repeatedly_relocate (1, &relocAddress, sectionD);
			relocAddress += 4;
		      }
		    break;
		  case 3: /* RelocTVector8 */
		    while (run_length-- > 0)
		      {
			repeatedly_relocate (1, &relocAddress, sectionC);
			repeatedly_relocate (1, &relocAddress, sectionD);
		      }
		    break;
		  case 4:
		    while (run_length-- > 0)
		      {
			repeatedly_relocate (1, &relocAddress, sectionD);
			relocAddress += 4;
		      }
		    break;
		  case 5: /* RelocImportRun */
		    while (retval == noErr && run_length-- > 0)
		      {
			Ptr symbol_val;

			retval = symbol_lookup (&importIndex, &symbol_val,
						 imports, symbol_names,
						closure_id);
			if (retval == noErr)
			  repeatedly_relocate (1, &relocAddress,
					       (uint32) symbol_val);
		      }
		    break;
		  default:
		    warning_unexpected ("%d", sub_op);
		    assert (0);
		    retval = paramErr;
		  }
	      }
	    break;
	    case 3:
	      {
		int sub_op;
		int index;
		Ptr symbol_val;

		sub_op = (msb >> 1) & 0xf;
		index = (((msb & 1) << 8)
			 | reloc_instrs[0][1]);
		switch (sub_op)
		  {
		  case 0:
		    importIndex = index;
		    retval = symbol_lookup (&importIndex, &symbol_val,
					    imports, symbol_names,
					    closure_id);
		    if (retval == noErr)
		      repeatedly_relocate (1, &relocAddress,
					   (uint32) symbol_val);
		    break;
		  case 1:
		    sectionC = connp->sects[index].start;
		    warning_unimplemented ("RelocSmSetSectC not tested much");
		    assert (0);
		    break;
		  case 2:
		    sectionD = connp->sects[index].start;
		    warning_unimplemented ("RelocSmSetSectD not tested much");
		    break;
		  case 3:
		    fprintf (stderr, "RelocSmBySection\n");
		    assert (0);
		    break;
		  default:
		    warning_unexpected ("sub_op");
		    fprintf (stderr, "Relocte By Index sub_op = %d\n", sub_op);
		    assert (0);
		    break;
		  }
	      }
	      break;
	    default:
	      switch ((msb >> 4))
		{
		case 8:
		  {
		    uint32 offset;

		    offset = ((msb & 0xf) << 8) | (reloc_instrs[0][1]);
		    ++offset;
		    relocAddress += offset;
		  }
		  break;
		case 9:
		  {
		    warning_unimplemented ("RelocSmRepeat not tested much");
		    if (repeat_remaining != -1)
		      --repeat_remaining;
		    else
		      {
			uint8 lsb;

			lsb = reloc_instrs[0][1];
			repeat_remaining = lsb + 1;
		      }
		    if (repeat_remaining > 0)
		      {
			int blockCount;

			blockCount = (msb & 0xF) + 2;
			reloc_count  += blockCount;
			reloc_instrs -= blockCount;
		      }
		    else
		      repeat_remaining = -1;
		  }
		  break;
		default:
		  switch (msb >> 2)
		    {
		    case 0x28:
		      {
			uint32 offset;

			offset = ((msb & 3) << 24 |
				  (reloc_instrs[0][1]) << 16 |
				  (reloc_instrs[0][2]) << 8 |
				  (reloc_instrs[0][3]));

			relocAddress
			  = (uint8 *)
			  SYN68K_TO_US (connp->sects[section].start) + offset;
		      }
		      --reloc_count;
		      ++reloc_instrs;
		      break;
		    case 0x29:
		      {
			Ptr symbol_val;

			importIndex = ((msb & 3) << 24 |
				       (reloc_instrs[0][1]) << 16 |
				       (reloc_instrs[0][2]) << 8 |
				       (reloc_instrs[0][3]));
			retval = symbol_lookup (&importIndex, &symbol_val,
						imports, symbol_names,
						closure_id);
			if (retval == noErr)
			  repeatedly_relocate (1, &relocAddress,
					       (uint32) symbol_val);
			

		      }
		      --reloc_count;
		      ++reloc_instrs;
		      break;
		    case 0x2c:
		      fprintf (stderr, "RelocLgRepeat\n");
		      assert (0);
		      --reloc_count;
		      ++reloc_instrs;
		      break;
		    case 0x2d:
		      fprintf (stderr, "RelocLgSetOrBySection\n");
		      assert (0);
		      --reloc_count;
		      ++reloc_instrs;
		      break;
		    default:
		      warning_unexpected ("0x%x", msb);
		      retval = paramErr;
		      break;
		    }
		}
	    }
	}
      ++reloc_instrs;
    }
  return retval;
}

PRIVATE CFragClosureID
begin_closure (uint32 n_libs, PEFImportedLibrary_t *libs,
	       const char *symbol_names, OSType arch)
{
  CFragClosureID retval;
  int i;
  OSErr err;

  retval = (typeof (retval)) NewPtr (sizeof *retval + n_libs * sizeof (lib_t));
  N_LIBS_X (retval) = CL (n_libs);

#warning eventually need to worry about errors  

  for (err = noErr, i = 0; /* err == noErr && */ i < n_libs; ++i)
    {
      Str63 libName;
      Ptr mainAddr;
      Str255 errName;
      int offset;
      const char *cname;

      offset = PEFIL_NAME_OFFSET (&libs[i]);
      cname = symbol_names + offset;
      libName[0] = MIN(strlen (cname), 63);
      memcpy (libName+1, cname, libName[0]);
      err = GetSharedLibrary (libName, arch, kReferenceCFrag,
			      &LIB_CID_X (&retval->libs[i]),
			      &mainAddr, errName);
      if (err != noErr)
	{
	  warning_unexpected ("%.*s", libName[0], libName+1);
	  LIB_CID_X (&retval->libs[i]) = (void *) 0x12348765;
	}
      LIB_N_SYMBOLS_X (&retval->libs[i]) = PEFIL_SYMBOL_COUNT_X (&libs[i]);
      LIB_FIRST_SYMBOL_X (&retval->libs[i]) = PEFIL_FIRST_SYMBOL_X (&libs[i]);
    }
  return retval;
}

PRIVATE OSErr
load_loader_section (const void *addr,
		     syn68k_addr_t default_address, uint32 total_size,
		     uint32 packed_size, uint32 unpacked_size,
		     uint32 section_offset, share_kind_t share_kind,
		     int alignment, syn68k_addr_t *mainAddrp, OSType arch,
		     ConnectionID connp)
{
  OSErr retval;
  char *loader_section_bytes;
  PEFLoaderInfoHeader_t *lihp;
  uint32 n_libs;
  uint32 n_imports;
  uint32 n_reloc_headers;
  PEFImportedLibrary_t *libs;
  uint8 (*imports)[4];
  PEFLoaderRelocationHeader_t *reloc_headers;
  uint8 *relocation_area;
  char *symbol_names;
  int i;
  CFragClosureID closure_id;

  loader_section_bytes = (char *)addr + section_offset;
  lihp = (PEFLoaderInfoHeader_t *) loader_section_bytes;
  connp->lihp = lihp;
  n_libs = PEFLIH_IMPORTED_LIBRARY_COUNT (lihp);
  libs = (PEFImportedLibrary_t *) &lihp[1];
  n_imports = PEFLIH_IMPORTED_SYMBOL_COUNT (lihp);
  imports = (uint8 (*)[4])&libs[n_libs];
  n_reloc_headers = PEFLIH_RELOC_SECTION_COUNT (lihp);
  reloc_headers = (PEFLoaderRelocationHeader_t *) imports[n_imports];
  
  relocation_area = (char *)
    (loader_section_bytes + PEFLIH_RELOC_INSTR_OFFSET (lihp));

  symbol_names = (typeof (symbol_names))
    (loader_section_bytes + PEFLIH_STRINGS_OFFSET (lihp));

  closure_id = begin_closure (n_libs, libs, symbol_names, arch);

  for (i = 0, retval = noErr; retval == noErr && i < n_reloc_headers; ++i)
    {
      uint32 reloc_count;
      uint8 (*reloc_instrs)[2];

      reloc_count = PEFRLH_RELOC_COUNT (&reloc_headers[i]);
      reloc_instrs = (typeof (reloc_instrs))
	(relocation_area
	 + PEFRLH_FIRST_RELOC_OFFSET (&reloc_headers[i]));
      retval = relocate (reloc_headers,
			 PEFRLH_SECTION_INDEX (&reloc_headers[i]),
			 reloc_count,
			 reloc_instrs, imports, symbol_names,
			 closure_id, connp);
    }
  if (retval == noErr && lihp->initSection != 0xffffffff)
    {
      uint32 *init_addr;
      uint32 init_toc;
      uint32 (*init_routine) (uint32);
      InitBlock init_block;

      warning_unimplemented ("register preservation, bad init_block");
      // #warning this code has a lot of problems (register preservation, bad init_block)

      init_addr = (uint32 *) SYN68K_TO_US
	(connp->sects[PEFLIH_INIT_SECTION (lihp)].start +
	 PEFLIH_INIT_OFFSET (lihp));

      memset (&init_block, 0xFA, sizeof init_block);
      init_routine = (uint32 (*)(uint32)) SYN68K_TO_US (init_addr[0]);
      init_toc = (uint32) SYN68K_TO_US (init_addr[1]);
#if defined (powerpc)
      retval = ppc_call (init_toc, init_routine, (uint32) &init_block);
#else
      warning_unexpected (NULL_STRING);
      retval = paramErr;
#endif
      if (retval)
	{
	  warning_unexpected ("%d", retval);
	  retval = noErr;
	}
    }
  if (retval == noErr)
    *mainAddrp = (connp->sects[PEFLIH_MAIN_SECTION (lihp)].start +
		  PEFLIH_MAIN_OFFSET (lihp));
  return retval;
}

PRIVATE OSErr
do_pef_section (ConnectionID connp, const void *addr,
		const PEFSectionHeader_t *sections, int i,
		boolean_t instantiate_p,
		syn68k_addr_t *mainAddrp, OSType arch)
{
  OSErr retval;
  const PEFSectionHeader_t *shp;
  syn68k_addr_t default_address;
  uint32 total_size;
  uint32 packed_size;
  uint32 unpacked_size;
  uint32 section_offset;
  int share_kind;
  int alignment;

  shp = &sections[i];

#if 1
  {
    uint32 def;

    def = PEFSH_DEFAULT_ADDRESS (shp);
    if (def)
      fprintf (stderr, "***def = 0x%x***\n", def);
  }
#endif

#if 0
  default_address = PEFSH_DEFAULT_ADDRESS (shp);
#else
  default_address = 0;
// #warning defaultAddress ignored -- dont implement without testing
#endif
  total_size = PEFSH_TOTAL_SIZE (shp);
  packed_size = PEFSH_PACKED_SIZE (shp);
  unpacked_size = PEFSH_UNPACKED_SIZE (shp);
  section_offset = PEFSH_CONTAINER_OFFSET (shp);
  share_kind = PEFSH_SHARE_KIND (shp);
  alignment = PEFSH_ALIGNMENT (shp);

  switch (PEFSH_SECTION_KIND (shp))
    {
    case code_section_type:
      connp->sects[i].perms = readable_section | executable_section;
      goto unpacked_common;

    case unpacked_data_section_type:
      connp->sects[i].perms = readable_section | writable_section;
      goto unpacked_common;

    case constant_section_type:
      connp->sects[i].perms = readable_section;
      goto unpacked_common;

    case executable_data_section_type:
      connp->sects[i].perms = (readable_section | writable_section |
			       executable_section);
      goto unpacked_common;

unpacked_common:
      retval = load_unpacked_section (addr, default_address, total_size,
				      packed_size, unpacked_size,
				      section_offset, share_kind, alignment, 
				      &connp->sects[i]);
      break;
    case pattern_data_section_type:
      connp->sects[i].perms = readable_section | writable_section;
      retval = load_pattern_section (addr, default_address, total_size,
				     packed_size, unpacked_size,
				     section_offset, share_kind, alignment,
				     &connp->sects[i]);
      break;
    case loader_section_type:
      retval = load_loader_section (addr, default_address, total_size,
				    packed_size, unpacked_size,
				    section_offset, share_kind, alignment,
				    mainAddrp, arch, connp);
      break;
    default:
      warning_unexpected ("%d", PEFSH_SECTION_KIND (shp));
      retval = noErr;
      break;
    }

  return retval;
}

/*
 * NOTE: it would be nice if someone else provided code to flush the
 * instruction cache.  I'm a little nervous that my code below will fail on 
 * multi-processor systems.
 */

typedef enum { ICACHE } flush_type_t;

PRIVATE void
cacheflush (void *start, uint32 length, flush_type_t flush)
{
#if defined (powerpc)
  enum { CACHE_LINE_SIZE = 32, };
  char *p, *ep; 

  switch (flush)
    {
    case ICACHE:
      for (p = start, ep = p + roundup (length, CACHE_LINE_SIZE);
	   p != ep;
	   p += CACHE_LINE_SIZE)
	{
	  asm volatile ("dcbf 0,%0" : : "r" (p) : "memory");
	  asm volatile ("sync" : : : "memory");
	  asm volatile ("icbi 0,%0" : : "r"(p) : "memory");
	}
      asm volatile ("isync" : : : "memory");
      break;
    default:
      warning_unexpected ("%d", flush);
      break;
    }
#endif
}

PRIVATE OSErr
do_pef_sections (ConnectionID connp, const PEFContainerHeader_t *headp,
		 syn68k_addr_t *mainAddrp, OSType arch)
{
  OSErr retval;
  PEFSectionHeader_t *sections;
  int n_sects;
  int i;

  n_sects = connp->n_sects;
  sections = (typeof (sections)) ((char *) headp + sizeof *headp);

  memset (connp->sects, 0, sizeof connp->sects[0] * n_sects);
  for (i = 0, retval = noErr; retval == noErr && i < n_sects; ++i)
    retval = do_pef_section (connp, headp, sections, i,
			     i < PEF_CONTAINER_INSTSECTION_COUNT(headp),
			     mainAddrp, arch);
// #warning need to back out cleanly if a section fails to load
  
#if defined (linux)
  if (retval == noErr)
    {
      int i;

      for (i = 0; i < n_sects; ++i)
	{
	  if (connp->sects[i].length)
	    {
	      int prot;

	      prot = 0;
	      if (connp->sects[i].perms & executable_section)
		{
		  cacheflush (SYN68K_TO_US (connp->sects[i].start),
			      connp->sects[i].length, ICACHE);
		  prot |= PROT_EXEC;
		}
	      if (connp->sects[i].perms & readable_section)
		prot |= PROT_READ;
	      if (connp->sects[i].perms & writable_section)
		prot |= PROT_WRITE;
	      if (!prot)
		prot |= PROT_NONE;
	      if (mprotect (SYN68K_TO_US (connp->sects[i].start),
			    roundup (connp->sects[i].length, getpagesize ()),
			    prot) != 0)
		warning_unexpected ("%d", errno);
	    }
	}
    }
#endif

  return retval;
}

PUBLIC ConnectionID
ROMlib_new_connection (uint32 n_sects)
{
  ConnectionID retval;
  Size n_bytes;

  n_bytes = sizeof *retval + n_sects * sizeof (section_info_t);
  retval = (ConnectionID) NewPtrSysClear (n_bytes);
  if (retval)
    retval->n_sects = n_sects;
  return retval;
}

P7 (PUBLIC pascal trap, OSErr, GetMemFragment, void *, addr,
    uint32, length, Str63, fragname, LoadFlags, flags,
    ConnectionID *, connp, Ptr *, mainAddrp, Str255, errname)
{
  OSErr retval;

  syn68k_addr_t main_addr;
  PEFContainerHeader_t *headp;

  warning_unimplemented ("ignoring flags = 0x%x\n", flags);

  main_addr = 0;
  *connp = 0;

  headp = addr;

  if (PEF_CONTAINER_TAG1_X(headp) != CLC (T('J','o','y','!')))
    warning_unexpected ("0x%x", PEF_CONTAINER_TAG1 (headp));

  if (PEF_CONTAINER_TAG2_X(headp) != CLC (T('p','e','f','f')))
    warning_unexpected ("0x%x", PEF_CONTAINER_TAG2 (headp));

  if (PEF_CONTAINER_ARCHITECTURE_X(headp)
      != CLC (T('p','w','p','c')))
    warning_unexpected ("0x%x",
			PEF_CONTAINER_ARCHITECTURE (headp));

  if (PEF_CONTAINER_FORMAT_VERSION_X(headp) != CLC (1))
    warning_unexpected ("0x%x",
			PEF_CONTAINER_FORMAT_VERSION (headp));

// #warning ignoring (old_dev, old_imp, current) version

  *connp = ROMlib_new_connection (PEF_CONTAINER_SECTION_COUNT (headp));
  if (!*connp)
    retval = fragNoMem;
  else
    retval = do_pef_sections (*connp, headp, &main_addr,
			      PEF_CONTAINER_ARCHITECTURE (headp));

  if (retval == noErr)
    *mainAddrp = (Ptr) SYN68K_TO_US (main_addr);

  return retval;
}

typedef struct
{
  void *addr;     /* virtual address */
  FSSpec fs;     /* canonicalized file tht this came from */
  LONGINT offset_req;
  LONGINT length_req;
  off_t offset_act;
  size_t length_act;
  boolean_t mapped_to_eof_p;
  int refcount;
}
context_t;

PRIVATE int n_context_slots = 0;
PRIVATE int n_active_contexts = 0;
PRIVATE context_t *contexts = 0;

PRIVATE boolean_t
fsmatch (FSSpecPtr fsp1, FSSpecPtr fsp2)
{
  boolean_t retval;

  retval = (fsp1->vRefNum == fsp2->vRefNum &&
	    fsp1->parID == fsp2->parID &&
	    EqualString (fsp1->name, fsp2->name, FALSE, TRUE));
  return retval;
}

PRIVATE boolean_t
match (FSSpecPtr fsp, LONGINT offset, LONGINT length, const context_t *cp)
{
  boolean_t retval;

  retval = (cp->refcount > 0 &&
	    fsmatch (fsp, (FSSpecPtr) &cp->fs) &&
	    offset >= cp->offset_act &&
	    ((length == kWholeFork && cp->mapped_to_eof_p) ||
	     (offset + length <= cp->offset_act + cp->length_act)));

  return retval;
}

PRIVATE OSErr
get_context (int *contextidp, boolean_t *exists_pp, FSSpecPtr fsp,
	     LONGINT offset, LONGINT length)
{
  PRIVATE OSErr retval;
  int i;
  int free_slot;

  retval = noErr;
  free_slot = -1;
  for (i = 0;
       i < n_context_slots && !match (fsp, offset, length, &contexts[i]);
       ++i)
    if (contexts[i].refcount == 0)
      free_slot = i;
  if (i < n_context_slots)
    {
      ++contexts[i].refcount;
      *contextidp = i;
      *exists_pp = TRUE;
    }
  else
    {
      if (free_slot == -1)
	{
	  size_t new_size;
	  context_t *new_contexts;

	  if (n_context_slots != n_active_contexts)
	    warning_unexpected ("%d %d", n_context_slots, n_active_contexts);
	  new_size = sizeof *new_contexts * (n_active_contexts + 1);
	  new_contexts = realloc (contexts, new_size);
	  if (new_contexts == NULL)
	      retval = memFullErr;
	  else
	    {
	      free_slot = n_active_contexts;
	      ++n_active_contexts;
	      ++n_context_slots;
	      contexts = new_contexts;
	    }
	}
      if (retval == noErr)
	{
	  context_t *cp;

	  cp = &contexts[free_slot];
	  cp->fs = *fsp;
	  cp->offset_req = offset;
	  cp->length_req = length;
	  cp->refcount = 1;
	  *contextidp = free_slot;
	  *exists_pp = FALSE;
	}
    }

  return retval;
}

PRIVATE OSErr
release_context (int context)
{
  OSErr retval;

  if (context >= n_context_slots || contexts[context].refcount <= 0)
    retval = paramErr;
  else
    {
      retval = noErr;
      if (--contexts[context].refcount == 0)
	{
	  --n_active_contexts;
	  munmap (contexts[context].addr, contexts[context].length_act);
	}
    }
  return retval;
}

PRIVATE context_t *
contextp_from_id (int context)
{
  context_t *retval;

  if (context >= n_context_slots || contexts[context].refcount <= 0)
    retval = 0;
  else
    retval = &contexts[context];
  return retval;
}

PRIVATE OSErr
try_to_mmap_file (FSSpecPtr fsp, LONGINT offset, LONGINT length,
		  int *contextidp)
{
  OSErr retval;
  INTEGER vref;

  retval = noErr;
  /* canonicalize fsp to make sure */
  vref = CW (fsp->vRefNum);
  if (ISWDNUM (vref) || pstr_index_after (fsp->name, ':', 0))
    {
      FSSpecPtr newfsp;

      newfsp = alloca (sizeof *newfsp);
      retval = FSMakeFSSpec (vref, CL (fsp->parID), fsp->name, newfsp);
      if (retval == noErr)
	fsp = newfsp;
    }

  if (retval == noErr)
    {
      boolean_t exists_p;
      int context;

      retval = get_context (&context, &exists_p, fsp, offset, length);
      if (retval == noErr)
	{
	  if (exists_p)
	    *contextidp = context;
	  else
	    {
	      INTEGER rn;

	      retval = FSpOpenDF (fsp, fsRdPerm, &rn);
	      if (retval != noErr)
		release_context (context);
	      else
		{
		  const fcbrec *fp;
		  context_t *cp;
		  size_t pagesize;

		  pagesize = getpagesize ();
		  fp = (const fcbrec *) (MR(FCBSPtr) + rn);
		  /* NOTE: right now we let them place it anywhere they
		     want -- eventually it probably makes sense to try to
		     get it placed high */
		  cp = contextp_from_id (context);
		  cp->offset_act = rounddown (offset, pagesize);
		  cp->mapped_to_eof_p = length == kWholeFork;
		  if (!cp->mapped_to_eof_p)
		    cp->length_act = length + (offset - cp->offset_act);
		  else
		    {
		      LONGINT eof;
		      OSErr err;

		      err = GetEOF (rn, &eof);
		      if (err == noErr)
			cp->length_act = eof - cp->offset_act;
		      else
			{
			  warning_unexpected ("err = %d", err);
			  cp->length_act = -1; /* force mmap to fail */
			}
		    }
		  cp->addr = mmap (0, cp->length_act, PROT_READ, MAP_PRIVATE,
				   fp->fcfd, cp->offset_act);
		  if (cp->addr != MAP_FAILED)
		    *contextidp = context;
		  else
		    {
		      retval = ROMlib_maperrno ();
		      release_context (context);
		    }
		  FSClose (rn);
		}
	    }
	}
    }

  return retval;

}

P8 (PUBLIC pascal trap, OSErr, GetDiskFragment, FSSpecPtr, fsp,
    LONGINT, offset, LONGINT, length, Str63, fragname, LoadFlags, flags,
		 ConnectionID *, connp, Ptr *, mainAddrp, Str255, errname)
{
  OSErr retval;
  int context;

  warning_unimplemented ("ignoring flags = 0x%x\n", flags);

  retval = try_to_mmap_file (fsp, offset, length, &context);
  if (retval == noErr)
    {
      const context_t *cp;
      void *addr;

      cp = contextp_from_id (context);

      addr = (char *) cp->addr + offset - cp->offset_act;
      retval = GetMemFragment (addr, length, fragname, flags, connp,
			       mainAddrp, errname);
    }
  return retval;
}

#endif
