/* Modified by Lal.George@att.com for use in the SML/NJ compiler */

/* Modified by Andrew.Vignaux@comp.vuw.ac.nz to get it to work :-) */

/* Copyright (C) 1985, 1986, 1987, 1988 Free Software Foundation, Inc.

    This program 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 1, or (at your option)
    any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */

/*
 * unexec.c - Convert a running program into an a.out file.
 *
 * Author:	Spencer W. Thomas
 * 		Computer Science Dept.
 * 		University of Utah
 * Date:	Tue Mar  2 1982
 * Modified heavily since then.
 *
 * Synopsis:
 *	unexec (new_name, a_name, data_start, bss_start, entry_address)
 *	char *new_name, *a_name;
 *	unsigned data_start, bss_start, entry_address;
 *
 * Takes a snapshot of the program and makes an a.out format file in the
 * file named by the string argument new_name.
 * If a_name is non-NULL, the symbol table will be taken from the given file.
 * On some machines, an existing a_name file is required.
 *
 * The boundaries within the a.out file may be adjusted with the data_start
 * and bss_start arguments.  Either or both may be given as 0 for defaults.
 *
 * Data_start gives the boundary between the text segment and the data
 * segment of the program.  The text segment can contain shared, read-only
 * program code and literal data, while the data segment is always unshared
 * and unprotected.  Data_start gives the lowest unprotected address.
 * The value you specify may be rounded down to a suitable boundary
 * as required by the machine you are using.
 *
 * Specifying zero for data_start means the boundary between text and data
 * should not be the same as when the program was loaded.
 * If NO_REMAP is defined, the argument data_start is ignored and the
 * segment boundaries are never changed.
 *
 * Bss_start indicates how much of the data segment is to be saved in the
 * a.out file and restored when the program is executed.  It gives the lowest
 * unsaved address, and is rounded up to a page boundary.  The default when 0
 * is given assumes that the entire data segment is to be stored, including
 * the previous data and bss as well as any additional storage allocated with
 * break (2).
 *
 * The new file is set up to start at entry_address.
 */

#include <a.out.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>

#define PERROR(arg) {perror(arg); return -1;}

extern int 	_data;
extern int 	_edata;
extern int 	_text;
extern int 	_etext;
extern int 	_end;


static long 		block_copy_start;
static struct filehdr	f_hdr;
static struct aouthdr 	f_ohdr;		 
long 			bias;			/* bias to add for growth */
long 			lnnoptr;		/* pointer to line no. info */
#define SYMS_START 	block_copy_start

static long 	text_scnptr;
static long 	data_scnptr;

static long 	load_scnptr;
static long 	orig_load_scnptr;
static long 	orig_data_scnptr;

#define ADDR_CORRECT(x) ((int)(x))
#define MAX_SECTIONS 16
static int pagemask;


extern char 	** global_argv;
extern int 	old_high;


static char * start_of_text()
	{
	return ((char *) 0x10000000);
	}


static char * start_of_data ()
	{
	return ((char *) 0x20000000);
	}


static report_error (file, fd)
char *file;
int fd;
	{
	if (fd) close (fd);
	chatting ("Failure operating on %s", file);
	}



static report_error_1 (fd, msg, a1, a2)
int 	fd;
char 	*msg;
int 	a1, a2;
	{
  	close (fd);
  	chatting (msg, a1, a2);
	}



int sys_write (fildes, buf, nbyte)
int 	 fildes;
char 	 *buf;
unsigned int nbyte;
	{
  	register int rtnval;

  	while ((rtnval = write (fildes, buf, nbyte)) == -1
		 && (errno == 4));
	return (rtnval);
	}


unexec (new, a_name, data_start, bss_start, entry_address)
int		new;
char 		*a_name;
unsigned	data_start, bss_start, entry_address;
	{
	int a_out = -1;

	if (a_name && (a_out = open (a_name, 0)) < 0){ PERROR (a_name); }

  	if (make_hdr (new,a_out,data_start,bss_start,entry_address,a_name)<0 ||
      	    copy_text_and_data (new) < 0 ||
      	    copy_sym (new, a_out, a_name) < 0 ||
	    adjust_lnnoptrs (new, a_out) < 0  ||
    	    unrelocate_symbols (new, a_out, a_name) < 0)
	    {
	    return -1;	
    	    }

  	if (a_out >= 0)  close (a_out);
	fchmod(new,0755);
  	return 0;
	}



#define CHECK_SCNHDR(ptr, name, flags) 					\
  if (strcmp(s->s_name, name) == 0) { 					\
    if (s->s_flags != flags) { 						\
      chatting("unexec: %x flags where %x expected in %s section.\n", \
	      s->s_flags, flags, name); 				\
    } 									\
    if (ptr) { 								\
      chatting("unexec: duplicate section header for section %s.\n", \
	      name); 							\
    } 									\
    ptr = s; 								\
  }

static int make_hdr (new, a_out, data_start, bss_start, 
		     entry_address, a_name)
int 		new, a_out;
unsigned 	data_start, bss_start, entry_address;
char 		*a_name;
#define ERROR0(msg) 	report_error_1 (new, msg, 0, 0); return -1
#define ERROR1(msg,x) 	report_error_1 (new, msg, x, 0); return -1
#define ERROR2(msg,x,y) report_error_1 (new, msg, x, y); return -1
	{
  	register int scns;
  	unsigned int bss_end;

  	struct scnhdr section[MAX_SECTIONS];
  	struct scnhdr * f_thdr;		/* Text section header */
  	struct scnhdr * f_dhdr;		/* Data section header */
  	struct scnhdr * f_bhdr;		/* Bss section header */
  	struct scnhdr * f_lhdr;		/* Loader section header */
  	struct scnhdr * f_tchdr;	/* Typechk section header */
  	struct scnhdr * f_dbhdr;	/* Debug section header */
  	struct scnhdr * f_xhdr;		/* Except section header */

  	load_scnptr = orig_load_scnptr = lnnoptr = 0;
  	
	pagemask = getpagesize () - 1;

  	/* Adjust text/data boundary. */
  	data_start = (long) start_of_data ();
	data_start = ADDR_CORRECT (data_start);

  	data_start = data_start & ~pagemask; 

/*  	
**	bss_end = ADDR_CORRECT (sbrk (0)) + pagemask;
*/
	bss_end	= ADDR_CORRECT (old_high) + pagemask;
  	bss_end &= ~ pagemask;

	/* Adjust data/bss boundary. */
  	if (bss_start != 0)
    		{
      		bss_start = (ADDR_CORRECT (bss_start) + pagemask);
      		/* (Up) to page bdry. */
      		bss_start &= ~ pagemask;
      		if (bss_start > bss_end)
			{
	  		ERROR1("unexec: bss_start (%u) is past end of program",
		  	bss_start);
			}
    		}
  	else
    		bss_start = bss_end;

  	if (data_start > bss_start)	/* Can't have negative data size. */
    		{
      		ERROR2 ("unexec: data_start (%u) > bss_start (%u)",
	      		data_start, bss_start);
   		}

  	/* Salvage as much info from the existing file as possible */
  	block_copy_start = 0;
  	f_thdr = NULL; f_dhdr = NULL; f_bhdr = NULL;
  	f_lhdr = NULL; f_tchdr = NULL; f_dbhdr = NULL; f_xhdr = NULL;
  	
	if (a_out >= 0)
    		{
      		if (read (a_out, &f_hdr, sizeof (f_hdr)) != sizeof (f_hdr))
			{
	  		PERROR (a_name);
			}
      		block_copy_start += sizeof (f_hdr);
      		
		if (f_hdr.f_opthdr > 0)
			{
	  		if (read (a_out, &f_ohdr, sizeof (f_ohdr)) != 
			    sizeof (f_ohdr))
	    			{
	      			PERROR (a_name);
	    			}
	  		block_copy_start += sizeof (f_ohdr);
			}
      		if (f_hdr.f_nscns > MAX_SECTIONS)
			{
	  		ERROR0 ("unexec: too many section headers");
			}
      		/* Loop through section headers */
      		for (scns = 0; scns < f_hdr.f_nscns; scns++) 
			{
			struct scnhdr *s = &section[scns];
			if (read (a_out, s, sizeof (*s)) != sizeof (*s))
	  			{
	    			PERROR (a_name);
		  		}
			if (s->s_scnptr > 0L)
	  			{
            			if (block_copy_start < s->s_scnptr + s->s_size)
	      				block_copy_start = s->s_scnptr + s->s_size;
	  			}

			CHECK_SCNHDR(f_thdr, _TEXT, STYP_TEXT);
			CHECK_SCNHDR(f_dhdr, _DATA, STYP_DATA);
			CHECK_SCNHDR(f_bhdr, _BSS, STYP_BSS);
			CHECK_SCNHDR(f_lhdr, _LOADER, STYP_LOADER);
			CHECK_SCNHDR(f_dbhdr, _DEBUG,  STYP_DEBUG);
			CHECK_SCNHDR(f_tchdr, _TYPCHK,  STYP_TYPCHK);
			CHECK_SCNHDR(f_xhdr, _EXCEPT,  STYP_EXCEPT);
		      	}

		if (f_thdr == 0)
			{
		  	ERROR1 ("unexec: couldn't find \"%s\" section", _TEXT);
			}
		if (f_dhdr == 0)
			{
	  		ERROR1 ("unexec: couldn't find \"%s\" section", _DATA);
			}
	      	if (f_bhdr == 0)
			{
	  		ERROR1 ("unexec: couldn't find \"%s\" section", _BSS);
			}
    		}
	  	else
    			{
      			ERROR0 ("can't build a COFF file from scratch yet");
	    		}

  		orig_data_scnptr = f_dhdr->s_scnptr;
		orig_load_scnptr = f_lhdr ? f_lhdr->s_scnptr : 0;

		/* Now we alter the contents of all the f_*hdr variables
     		** to correspond to what we want to dump. 
		*/
  		f_hdr.f_flags |= (F_RELFLG | F_EXEC);		/* Why? */

		f_ohdr.dsize = bss_start - ((unsigned) &_data);
		f_ohdr.bsize = bss_end - bss_start;

		f_dhdr->s_size = f_ohdr.dsize;
		f_bhdr->s_size = f_ohdr.bsize;
		f_bhdr->s_paddr = f_ohdr.dsize;
		f_bhdr->s_vaddr = f_ohdr.dsize;

		/* fix scnptr's */
		{
		long ptr;

    		for (scns = 0; scns < f_hdr.f_nscns; scns++) 
			{
	      		struct scnhdr *s = &section[scns];
			if (scns == 0)
			ptr = s->s_scnptr;

			if (s->s_scnptr != 0)
				{
				s->s_scnptr = ptr;
				}

			if ((s->s_flags & 0xffff) == STYP_PAD)
				{
				/*
				** the text_start should probably be o_algntext 
				** but that doesn't seem to change
				*/
				if (f_ohdr.text_start != 0) /* && scns != 0 */
					{
					s->s_size = 512 - (s->s_scnptr % 512);
					if (s->s_size == 512)
					s->s_size = 0;
					}
				}

			ptr = ptr + s->s_size;
			}

		bias = ptr - block_copy_start;
		}

		/* fix other pointers */
  	for (scns = 0; scns < f_hdr.f_nscns; scns++) 
		{
    		struct scnhdr *s = &section[scns];

    		if (s->s_relptr != 0)
      			{
			s->s_relptr += bias;
      			}
    		if (s->s_lnnoptr != 0)
      			{
			if (lnnoptr == 0) lnnoptr = s->s_lnnoptr;
			s->s_lnnoptr += bias;
      			}
  		}

	if (f_hdr.f_symptr > 0L)
    		{
      		f_hdr.f_symptr += bias;
    		}

  	text_scnptr = f_thdr->s_scnptr;
  	data_scnptr = f_dhdr->s_scnptr;
  	load_scnptr = f_lhdr ? f_lhdr->s_scnptr : 0;
  	block_copy_start = orig_load_scnptr;

  	if (write (new, &f_hdr, sizeof (f_hdr)) != sizeof (f_hdr))
    		{
      		
    		}

  	if (f_hdr.f_opthdr > 0)
    		{
      		if (write (new, &f_ohdr, sizeof (f_ohdr)) != sizeof (f_ohdr))
			{
			report_error("reading filehdr", new);
			return -1;
			}
	    	}

  	for (scns = 0; scns < f_hdr.f_nscns; scns++) 
		{
    		struct scnhdr *s = &section[scns];
    		if (write (new, s, sizeof (*s)) != sizeof (*s))
      			{
			report_error("reading section header",new);
			return -1;
      			}
  		}

  	return (0);
	}


#define write_segment(fd,start,end) bulletproofWrite0(fd,start,end-start)

static int copy_text_and_data (new)
int new;
	{
  	register char *end;
  	register char *ptr;

  	lseek (new, (long) text_scnptr, 0);
  	ptr = start_of_text () + text_scnptr;
  	end = ptr + f_ohdr.tsize;
  	write_segment (new, ptr, end);

  	lseek (new, (long) data_scnptr, 0);
  	ptr = (char *) &_data;
  	end = ptr + f_ohdr.dsize;
  	write_segment (new, ptr, end);

	return 0;
	}



static int copy_sym (new, a_out, a_name)
int new, a_out;
char *a_name;
	{
  	char page[1024];
  	int n;

  	if (a_out < 0) return 0;

  	if (SYMS_START == 0L) return 0;

  	if (lnnoptr && lnnoptr < SYMS_START)
    		lseek (a_out, lnnoptr, 0);	/* start copying from there */
  	else
    		lseek (a_out, SYMS_START, 0);	/* Position a.out to symtab. */

  	while ((n = read (a_out, page, sizeof page)) > 0)
    		{
      		if (write (new, page, n) != n) 
			{ 
			chatting("copy_sym: Error in reading a.out\n");
			return -1;
			}
  		if (n < 0) 
			{ 
			chatting("copy_sym: cannot write -ve number of bytes\n");
			return -1;
			}
		}
  	return 0;
	}


adjust_lnnoptrs (writedesc, readdesc)
int 	writedesc;
int 	readdesc;
	{
  	register int 	nsyms;
  	register int	new = writedesc;
	struct syment 	symentry;
  	union auxent 	auxentry;

  	if (!lnnoptr || !f_hdr.f_symptr)  return 0;

	lseek (new, f_hdr.f_symptr, 0);
	for (nsyms = 0; nsyms < f_hdr.f_nsyms; nsyms++)
		{
		read (new, &symentry, SYMESZ);
		if (symentry.n_numaux)
			{
			read (new, &auxentry, AUXESZ);
	  		nsyms++;
	  		if (ISFCN (symentry.n_type)) 
				{
	    			auxentry.x_sym.x_fcnary.x_fcn.x_lnnoptr += bias;
	    			lseek (new, -AUXESZ, 1);
	    			write (new, &auxentry, AUXESZ);
	  			}
			}
    		}
	}

unrelocate_symbols (new, a_out, a_name)
int new, a_out;
char *a_name;
	{
  	register int i;
  	register int l;
  	register LDREL *ldrel;
  	LDHDR ldhdr;
  	LDREL ldrel_buf [20];
  	ulong t_start = &_text;
  	ulong d_start = &_data;
  	int * p;
  	int dirty;

  	if (load_scnptr == 0) return 0;

  	lseek (a_out, orig_load_scnptr, 0);
  	if (read (a_out, &ldhdr, sizeof (ldhdr)) != sizeof (ldhdr))
    		{
		chatting("Could not read loader section header\n");
		return -1;
    		}

#define SYMNDX_TEXT	0
#define SYMNDX_DATA	1
#define SYMNDX_BSS	2
  	l = 0;
  	for (i = 0; i < ldhdr.l_nreloc; i++, l--, ldrel++)
    		{
      		if (l == 0) 
			{
			lseek (a_out,
	       		       orig_load_scnptr+LDHDRSZ+LDSYMSZ*ldhdr.l_nsyms 
				+ LDRELSZ*i,
			       0);

			l = ldhdr.l_nreloc - i;
			if (l > sizeof (ldrel_buf) / LDRELSZ)
	  			l = sizeof (ldrel_buf) / LDRELSZ;

			if (read (a_out, ldrel_buf, l * LDRELSZ) != l * LDRELSZ)
	  			{
	    			PERROR (a_name);
	  			}
			ldrel = ldrel_buf;
      		}
      		dirty = 0;

      		/* this code may not be necessary */
      		/* I originally had == in the "assignment" and it 	
		  still unrelocated */

      		/* move the BSS loader symbols to the DATA segment */
      		if (ldrel->l_rsecnm == f_ohdr.o_snbss)
		ldrel->l_rsecnm = f_ohdr.o_sndata, dirty++;

      		if (ldrel->l_symndx == SYMNDX_BSS)
		ldrel->l_symndx = SYMNDX_DATA, dirty++;

      		if (dirty)
			{
	  		lseek (new,
		 		load_scnptr + LDHDRSZ + LDSYMSZ*ldhdr.l_nsyms + LDRELSZ*i,
		 		0);

	  		if (write (new, ldrel, LDRELSZ) != LDRELSZ)
	    			{
				chatting("could not write to output file\n");
				return -1;
	    			}
			}

      		if (ldrel->l_rsecnm == f_ohdr.o_sndata)
			{
	  		int orig_int;

	  		lseek (a_out, orig_data_scnptr + ldrel->l_vaddr, 0);

	  		if (read (a_out, (void *) &orig_int, sizeof (orig_int))
			    != sizeof (orig_int))
	    			{
	      			PERROR (a_name);
	   			}

	  		switch (ldrel->l_symndx) 
			    	{
	  			case SYMNDX_TEXT:
	    				p = (int *) (d_start + ldrel->l_vaddr);
	    				orig_int = * p - (t_start - f_ohdr.text_start);
	    				break;

	  			case SYMNDX_DATA:
	  			case SYMNDX_BSS:
	    			p = (int *) (d_start + ldrel->l_vaddr);
	    			orig_int = * p - (d_start - f_ohdr.data_start);
	   			break;
	  			}

	  		lseek (new, data_scnptr + ldrel->l_vaddr, 0);
	  		if (write (new, (void *) &orig_int, sizeof (orig_int)) !=
				  	sizeof (orig_int))
	    			{
	      			chatting("could not write to output file\n");
				return -1;
	    			}
			}
    		}
	}



#define MAXPATHLEN 	512
#define isabsolutePath(name) (name[0] == '/')


hasSlash (name) char * name;
{
    int n = strlen(name);
    int i;

    for (i = 0; i<n; i++) 
	if (name[i] == '/') return 1;

    return 0;
}


/*
** searchPath (name) -- search PATH environment variable
**	- modifies name 
*	- returns 1 on success and 0 on failure.
*/
searchPath(name) char * name;
{
    char * path;
    int  n;
    char fileName[256];

    int i,j;

    path = getenv("PATH");
    n = strlen (path);

    j = 0;
    for (i=0; i < n; i++) 
	{
	    if (path[i] != ':') fileName[j++] = path[i];
	    else 
		{
		    struct stat statBuff;

		    fileName[j] = '/';
		    fileName[j+1] = '\0';
		    strcat(fileName,name);
		    if (stat(fileName, & statBuff) == 0)
			{
			    strcpy(name,fileName);
			    return 1;
			}
		    else j = 0;
		}
	}
    return 0;
}


baseName (name) char * name;
	{
	int i, n = strlen(name);

	for (i=n; i>=0 && name[i] != '/'; i--) 
		name[i] = '\0';
	}

/*
** chaseLinks (buff) -- chases links until the executable is found.
**	- modifies buff
**	- returns 1 on success and 0 on failure.
**	ToDo: - Should probably check that the file returned is executable
*/
char * chaseLinks(buff) char buff[];
{
    int 	n;
    char	ans[MAXPATHLEN];

    while (1) 
	{
	    if ((n=readlink(buff,ans,MAXPATHLEN-1)) == -1) 
		{
		    switch (errno) 
			{
			case EINVAL:	return 1;
			case ENOENT:	chatting("chaseLinks: invalid file\n");
					return 0;
			default:	chatting("chaseLinks: impossible\n");
					return 0;
			}
		}
	    else
		ans[n] = '\0';
		if (isabsolutePath(ans)) 
		    {
			strncpy(buff,ans,n+1);
		    }
		else
		    {
			baseName(buff);
			if (strlen(buff) + strlen(ans) >= MAXPATHLEN)
			    {
				chatting("chaseLinks: path name too long\n");
				return 0;
			    }
			strncat(buff,ans,n);
		    }
	}
}


getaoutname(name) char *name;
{
    if (!(isabsolutePath(name) || hasSlash(name)))
	if (searchPath(name) == 0) 
	    {
		chatting("chaseLinks: Could not find executable in path!\n");
		return 0;
	    }
    return(chaseLinks(name));
}


    

export(filid)
int filid;
{
    char 	buff[MAXPATHLEN];
    
    strcpy(buff,global_argv[0]);

    if (getaoutname(buff) == 0) 
	{
	    chatting("export: getaoutname failed\n");
	    return 1;
	}
	
    if (unexec(filid,buff,0,0,0) == 0) return 0;
    else 
	{
	    chatting ("export: Unexec failed\n");
	    return 1;
	}
}

