/*	UNEXAPOLLO -- COFF File UNEXEC for GNU Emacs on Apollo SR10.x	     */
/*									     */
/*	Copyright (C) 1988 by Leonard N. Zubkoff, All Rights Reserved	     */
/*									     */
/*	This software is provided free and without any warranty.	     */
/*	Permission to copy for any purpose is hereby granted so		     */
/*	long as this copyright notice remains intact.			     */
/*									     */
/*	Revision:	18-Aug-92 13:28:33				     */


#define hidden	    static
#define visible
#define procedure   void


#include "config.h"
#include <fcntl.h>


#include <a.out.h>
#include <sys/file.h>
#include <apollo/base.h>
#include <apollo/ios.h>
#include <apollo/type_uids.h>
#include <apollo/dst.h>


#define DST_RECORD_HDR_SIZE	2
#define LONG_ALIGN(X)		(((X)+3)&(~3))


visible procedure unexec(char *TargetFileName,
			 char *SourceFileName)
{
  struct filehdr FileHeader;
  struct aouthdr DomainHeader;
  struct scnhdr *Section, *Sections, *SectionsLimit;
  struct scnhdr *FirstDataSection, *LastDataSection;
  struct scnhdr *RwdiSection, *BlocksSection;
  struct reloc RelocEntry;
  unsigned long DataSize, OldDataSectionSize, SourceFileOffsetPastRwdi;
  unsigned char Buffer[4096];
  long DeltaBeforeRwdi, DeltaAfterRwdi, ByteCount;
  long FirstChangedVaddr, OldRwdiVaddr, i;
  ios_$id_t TargetFile, SourceFile;
  status_$t Status;
  /* Open the Source File. */
  if ((SourceFile = open(SourceFileName,O_RDONLY)) < 0)
    error("cannot open source file for input");
  /* Read the File Header. */
  if (read(SourceFile,&FileHeader,sizeof(FileHeader)) != sizeof(FileHeader))
    error("cannot read file header");

  /* Read the Domain Header. */
  if (read(SourceFile,&DomainHeader,sizeof(DomainHeader))
      != sizeof(DomainHeader))
    error("cannot read domain header");
  /* Read the Section Headers. */
  Sections =
    (struct scnhdr *) malloc(FileHeader.f_nscns*sizeof(struct scnhdr));
  if (Sections == (struct scnhdr *) 0)
    error("cannot allocate section header storage");
  SectionsLimit = Sections + FileHeader.f_nscns;
  if (read(SourceFile,Sections,FileHeader.f_nscns*sizeof(struct scnhdr))
      != FileHeader.f_nscns*sizeof(struct scnhdr))
    error("cannot read section headers");
  /* Compute the new Size of the Data Section. */
  DataSize = sbrk(0) - DomainHeader.data_start;
  DeltaBeforeRwdi = DeltaAfterRwdi = DataSize - DomainHeader.dsize;
  OldRwdiVaddr = 0;
  /* Find and Deallocate the .rwdi Section Information. */
  for (RwdiSection=Sections; RwdiSection != SectionsLimit; RwdiSection++)
    if (strcmp(RwdiSection->s_name,".rwdi") == 0)
      {
	/* If there are relocation entries, we cannot "unrelocate" them. */
	if (RwdiSection->s_nreloc > 0)
	  error(".rwdi section needs relocation - cannot dump Emacs");
	DeltaAfterRwdi = DeltaBeforeRwdi - RwdiSection->s_size;
	OldRwdiVaddr = RwdiSection->s_vaddr;
	RwdiSection->s_paddr = 0;
	RwdiSection->s_vaddr = 0;
	RwdiSection->s_scnptr = 0;
	RwdiSection->s_size = 0;
	SourceFileOffsetPastRwdi = (RwdiSection+1)->s_scnptr;
	break;
      }
  /* Skip over the Text Section Headers. */
  for (Section=Sections; (Section->s_flags & STYP_TEXT) != 0; Section++) ;
  /*
    Find the First and Last Data Sections and Fixup
    Section Header Relocation Pointers.
  */
  FirstDataSection = LastDataSection = (struct scnhdr *) 0;
  for (; Section != SectionsLimit; Section++)
    {
      if ((Section->s_flags & STYP_DATA) != 0)
	{
	  if (FirstDataSection == (struct scnhdr *) 0)
	    FirstDataSection = Section;
	  LastDataSection = Section;
	}
      if (Section->s_relptr != 0)
	Section->s_relptr += DeltaAfterRwdi;
    }
  /* Increment the Size of the Last Data Section. */
  OldDataSectionSize = LastDataSection->s_size;
  LastDataSection->s_size += DeltaBeforeRwdi;

  /* Update the File Header and Domain Header. */
  FileHeader.f_symptr += DeltaAfterRwdi;
  DomainHeader.dsize = DataSize;
  DomainHeader.bsize = 0;
  /* Skip over subsequent Bss Section Headers. */
  for (Section=LastDataSection+1;
       (Section->s_flags & STYP_BSS) != 0; Section++) ;
  /* Update the remaining Section Headers. */
  BlocksSection = (struct scnhdr *) 0;
  FirstChangedVaddr = 0;
  for (; Section != SectionsLimit; Section++)
    {
      long Delta = (Section < RwdiSection ? DeltaBeforeRwdi : DeltaAfterRwdi);
      if (Section->s_paddr != 0)
	Section->s_paddr += Delta;
      if (Section->s_vaddr != 0)
	{
	  if (FirstChangedVaddr == 0)
	    FirstChangedVaddr = Section->s_vaddr;
	  Section->s_vaddr += Delta;
	}
      if (Section->s_scnptr != 0)
	Section->s_scnptr += Delta;
      if (strcmp(Section->s_name,".blocks") == 0)
	BlocksSection = Section;
      else if (strcmp(Section->s_name,".sri") == 0 &&
	       DomainHeader.o_sri != 0)
	DomainHeader.o_sri += Delta;
      else if (strcmp(Section->s_name,".inlib") == 0 &&
	       DomainHeader.o_inlib != 0)
	DomainHeader.o_inlib += Delta;
    }
  /* Open the Target File. */
  ios_$create(TargetFileName,strlen(TargetFileName),coff_$uid,
	      ios_$recreate_mode,ios_$write_opt,&TargetFile,&Status);
  if (Status.all != status_$ok)
    error("cannot open target file for output");
  /* Write the File Header. */
  if (write(TargetFile,&FileHeader,sizeof(FileHeader)) != sizeof(FileHeader))
    error("cannot write file header");
  /* Write the Domain Header. */
  if (write(TargetFile,&DomainHeader,sizeof(DomainHeader))
      != sizeof(DomainHeader))
    error("cannot write domain header");
  /* Write the Section Headers. */
  if (write(TargetFile,Sections,FileHeader.f_nscns*sizeof(struct scnhdr))
      != FileHeader.f_nscns*sizeof(struct scnhdr))
    error("cannot write section headers");
  /* Copy the Allocated Sections. */
  for (Section=Sections; Section != FirstDataSection; Section++)
    if (Section->s_scnptr != 0)
      CopyData(TargetFile,SourceFile,LONG_ALIGN(Section->s_size));
  /* Write the Expanded Data Segment. */
  if (write(TargetFile,FirstDataSection->s_vaddr,DataSize) != DataSize)
    error("cannot write new data section");

  /* Skip over the Last Data Section and Copy until the .rwdi Section. */
  if (lseek(SourceFile,LastDataSection->s_scnptr
		       +OldDataSectionSize,L_SET) == -1)
    error("cannot seek past data section");
  for (Section=LastDataSection+1; Section != RwdiSection; Section++)
    if (Section->s_scnptr != 0)
      CopyData(TargetFile,SourceFile,LONG_ALIGN(Section->s_size));
  /* Skip over the .rwdi Section and Copy Remainder of Source File. */
  if (lseek(SourceFile,SourceFileOffsetPastRwdi,L_SET) == -1)
    error("cannot seek past .rwdi section");
  while ((ByteCount = read(SourceFile,Buffer,sizeof(Buffer))) > 0)
    if (write(TargetFile,Buffer,ByteCount) != ByteCount)
      error("cannot write data");
  /* Unrelocate .data references to Global Symbols. */
  for (Section = FirstDataSection; Section <= LastDataSection; Section++)
    for (i=0; i<Section->s_nreloc; i++)
      {
	if (lseek(SourceFile,Section->s_relptr
		  +i*sizeof(struct reloc)-DeltaAfterRwdi,L_SET) == -1)
	  error("cannot seek to relocation info");
	if (read(SourceFile,&RelocEntry,sizeof(RelocEntry))
	    != sizeof(RelocEntry))
	  error("cannot read reloc entry");
	if (lseek(SourceFile,RelocEntry.r_vaddr-Section->s_vaddr
			     +Section->s_scnptr,L_SET) == -1)
	  error("cannot seek to data element");
	if (lseek(TargetFile,RelocEntry.r_vaddr-Section->s_vaddr
			     +Section->s_scnptr,L_SET) == -1)
	  error("cannot seek to data element");
	if (read(SourceFile,Buffer,4) != 4)
	  error("cannot read data element");
	if (write(TargetFile,Buffer,4) != 4)
	  error("cannot write data element");
      }

  /* Correct virtual addresses in .blocks section. */
  if (BlocksSection != (struct scnhdr *) 0)
    {
      dst_rec_t DstRecord;
      dst_rec_comp_unit_t *CompUnit;
      unsigned short NumberOfSections;
      unsigned long SectionBase;
      unsigned long SectionOffset = 0;
      /* Find section tables and update section base addresses. */
      while (SectionOffset < BlocksSection->s_size)
	{
	  if (lseek(TargetFile,
		    BlocksSection->s_scnptr+SectionOffset,L_SET) == -1)
	    error("cannot seek to comp unit record");
	  /* Handle pad records before the comp unit record. */
	  if (read(TargetFile,&DstRecord,DST_RECORD_HDR_SIZE)
	      != DST_RECORD_HDR_SIZE)
	    error("cannot read dst record tag");
	  if (DstRecord.rec_type == dst_typ_pad)
	    SectionOffset += DST_RECORD_HDR_SIZE;
	  else if (DstRecord.rec_type == dst_typ_comp_unit)
	    {
	      CompUnit = &DstRecord.rec_data.comp_unit_;
	      if (read(TargetFile,CompUnit,sizeof(*CompUnit))
		  != sizeof(*CompUnit))
		error("cannot read comp unit record");
	      if (lseek(TargetFile,BlocksSection->s_scnptr
				   +SectionOffset
#if dst_version_major == 1 && dst_version_minor < 4
				   +CompUnit->section_table
#else
				   +CompUnit->section_table.rel_offset
#endif
				   +DST_RECORD_HDR_SIZE,
			L_SET) == -1)
		error("cannot seek to section table");
	      if (read(TargetFile,&NumberOfSections,sizeof(NumberOfSections))
		  != sizeof(NumberOfSections))
		error("cannot read section table size");
	      for (i=0; i<NumberOfSections; i++)
		{
		  if (read(TargetFile,&SectionBase,sizeof(SectionBase))
		      != sizeof(SectionBase))
		    error("cannot read section base value");
		  if (SectionBase < FirstChangedVaddr)
		    continue;
		  else if (SectionBase < OldRwdiVaddr)
		    SectionBase += DeltaBeforeRwdi;
		  else SectionBase += DeltaAfterRwdi;
		  if (lseek(TargetFile,-sizeof(SectionBase),L_INCR) == -1)
		    error("cannot seek to section base value");
		  if (write(TargetFile,&SectionBase,sizeof(SectionBase))
		      != sizeof(SectionBase))
		    error("cannot write section base");
		}
	      SectionOffset += CompUnit->data_size;
	    }
	  else error("unexpected dst record type");
	}
    }

  if (close(SourceFile) == -1)
    error("cannot close source file");
  if (close(TargetFile) == -1)
    error("cannot close target file");
}


hidden CopyData(int TargetFile,
		int SourceFile,
		long TotalByteCount)
{
  unsigned char Buffer[4096];
  long ByteCount;
  while (TotalByteCount > 0)
    {
      if (TotalByteCount > sizeof(Buffer))
	ByteCount = sizeof(Buffer);
      else ByteCount = TotalByteCount;
      if (read(SourceFile,Buffer,ByteCount) != ByteCount)
	error("cannot read data");
      if (write(TargetFile,Buffer,ByteCount) != ByteCount)
	error("cannot write data");
      TotalByteCount -= ByteCount;
    }
}
