/*
 *	strip TOS executable format files of symbol table info
 *	usage: strip {-g | -k | -l names} [-t] files ...
 *
 *	Original version by ++jrb	bammi@dsrgsun.ces.cwru.edu
 *	(i.e. Jwahar Bammi)
 *	Modified to add extra options -g -k and -l by
 *	Michal Jaegermann		ntomczak@ualtavm.bitnet
 *	November 1st, 1990
 *
 *	-g	keep all global symbols
 *	-k	keep _stksize symbol, so stack size can be adjusted
 *		even for nearly-stripped gcc produced executables
 *	-l nms	keep all symbols listed in a file 'nms'
 *	-t	remove additional data from end of file
 *
 *	Modified sources compile both with gcc and Sozobon C
 *
 *      Added code to deal correctly with extended symbols produced
 *      by -G option of gcc
 *      Both -k and -l options convert extended symbols into
 *      regular ones.
 *
 *	++jrb 4/25/91: 
 *	Macroized the code to take care of WORD_ALIGNED cross environments.
 *	Minor fixes for a !atari !gcc compiler.   
 *
 *	AL 08/02/92:		(alexlehm@iti.informatik.th-darmstadt.de)
 *	Added -t option to strip TurboC / PureC style executables
 *	Support for little endian (BYTE_SWAPed) machines
 */

#include <stdio.h>
#ifdef atarist
#ifdef __GNUC__
#  include <stddef.h>
#  include <memory.h>
#  include <unixlib.h>
#else
#include <malloc.h>
extern long lread();
extern long lwrite();
extern long lseek();
#endif
#endif

#ifdef unix
#  include <strings.h>
#  define lwrite write
#  define lread  read
   extern char *malloc(), *realloc();
#else
#  include <string.h>
#endif

#include <fcntl.h>

#define NEWBUFSIZ	16384L

char            mybuf[NEWBUFSIZ];
char            tmpname[128];

#define SYMLEN 8
#define GST_SYMLEN 22

typedef char    symstr_t[GST_SYMLEN];
symstr_t        stklist[] = {"__stksize", "__initial_stack"};

#ifdef atarist
long            _stksize = 1L;
#endif

#ifndef __PROTO
#  if __STDC__ || __cplusplus
#   define __PROTO(s) s
#  else
#   define __PROTO(s) ()
#  endif
#endif

/* function pointer type for the select functions */
typedef long (*select_fp) __PROTO((int, int, long, symstr_t *));

void usage __PROTO((char *s ));
int main __PROTO((int argc , char **argv ));
int strip __PROTO((char *name,symstr_t *nmlist,select_fp select,int cuttrail));
long copy __PROTO((int from , int to , long bytes ));
long copy_relo __PROTO((char *name,int from,int to));
void report __PROTO((char *s ));
symstr_t *mklist __PROTO((char *fname ));
long sel_globs __PROTO((int fd , int tfd , long sbytes , symstr_t *nmlist ));
long sel_listed __PROTO((int fd , int tfd , long sbytes , symstr_t *nmlist ));
extern char    *getenv __PROTO((const char *));

int 
main (argc, argv)
    int             argc;
    char          **argv;
{
    int             status = 0;
    int             flag = -1;
    int             cuttrail = 0;
    symstr_t       *nmlist = (symstr_t *) 0;
    select_fp       select=(select_fp)NULL;
#ifdef atarist
    char           *tmpdir;
    register int    l;
#endif

    /* process arguments */
    while (argv++, --argc) {
	if ('-' != **argv)
	    break;
	(*argv)++;
	if( **argv=='t' ) {
	    cuttrail = 1;
	} else {
	    if ((-1) != flag)
		usage ("only one option at a time is accepted\n");
	    flag = **argv;
	    switch (flag) {
	    case 'g':
	        select = sel_globs;
		break;
	    case 'k':
	        nmlist = stklist;
		select = sel_listed;
		break;
	    case 'l':
		(*argv)++;
		if ('\0' == **argv) {
		    --argc;
		    argv++;
		    if( argc==0 )
		        usage("missing names file\n");
		}
		if ((symstr_t *) NULL == (nmlist = mklist (*argv)))
		    usage ("cannot create a list of reserved names\n");
		select = sel_listed;
		break;
	    default:
		usage ("");
		break;
	    }
	}
    }

    if (argc < 1) {
	usage ("");
    }

#ifdef __GNUC__
#ifdef atarist
    tmpname[0] = '\0';
    if ((tmpdir = getenv ("TEMP")) != NULL) {
	strcpy (tmpname, tmpdir);
	l = (int) strlen (tmpname) - 1;
	if (tmpname[l] == '\\')
	    tmpname[l] = '\0';
    }
    strcat (tmpname, "\\STXXXXXX");
#else
    strcpy (tmpname, "/tmp/STXXXXXX");
#endif

    mktemp (tmpname);
#else /* not __GNUC__ */
#  ifdef atarist
    if ((tmpdir = getenv ("TEMP")) != NULL) {
	strcpy (tmpname, tmpdir);
	l = (int) strlen (tmpname) - 1;
	if (tmpname[l] != '\\') {
	    l++;
	    tmpname[l] = '\\';
	}
	l++;
    }
    else {
	l = 0;
    }
    tmpnam (&tmpname[l]);
#  else
    strcpy (tmpname, "/tmp/STXXXXXX");
    mktemp(tmpname);
#  endif	
#endif /* __GNUC__ */
    do {
	status |= strip (*argv++, nmlist, select, cuttrail);
    } while (--argc > 0);

    unlink (tmpname);
    return status;
}

void
usage (s)
    char           *s;
{
    report (s);
    report ("Usage: strip {-k | -l names | -g} [-t] files ...\n");
    exit (1);
}

/* define macro for big and function for little endian machines */
#ifdef BYTE_SWAP
short swap_short __PROTO((short s));
long swap_long __PROTO((long l));
#else
#define swap_short(s) (s)
#define swap_long(l) (l)
#endif

#if (defined(__GNUC__)) && (!defined(unix))
#include <st-out.h>
#else
/* include relevant fragments of <st-out.h> file directly */

struct aexec {
	 short	a_magic;	/* magic number */
unsigned long	a_text;		/* size of text segment */
unsigned long	a_data;		/* size of initialized data */
unsigned long	a_bss;		/* size of uninitialized data */
unsigned long	a_syms;		/* size of symbol table */
unsigned long	a_AZero1;	/* always zero */
unsigned long	a_AZero2;	/* always zero */
unsigned short	a_isreloc;	/* is reloc info present */
};
#define	CMAGIC	0x601A		/* contiguous text */

/*
 * Format of a symbol table entry
 */
struct	asym 
	{
	char		a_name[SYMLEN];	/* symbol name */
	unsigned short	a_type;		/* type flag   */
	unsigned long	a_value;	/* value of this symbol
					   (or sdb offset) */
	};

#define A_GLOBL	0x2000		/* global */
#define A_LNAM  0x0048		/* extended name */

#endif /* __GNUC__ */

#if !__STDC__ && !__cplusplus
#  ifndef offsetof
#    define offsetof(TYPE, MEMBER) ((unsigned long) &((TYPE *)0L)->MEMBER)
#  endif
#  ifndef CROSSHPUX
     typedef unsigned long size_t;
#  endif
#endif

#ifdef WORD_ALIGNED
# define SIZEOF_AEXEC ((2*sizeof(short)) + (6*sizeof(long)))
# define SIZEOF_ASYM  ((SYMLEN*sizeof(char)) + sizeof(short) + sizeof(long))
# define SYM_OFFSET   (sizeof(short) + (3*sizeof(long)))
  int read_head __PROTO((int fd, struct aexec *a));
#else
# define SIZEOF_AEXEC (sizeof(struct aexec))
# define SIZEOF_ASYM  (sizeof(struct asym))
# define SYM_OFFSET   (offsetof (struct aexec, a_syms))
#endif

int 
strip (name, nmlist, select, cuttrail)
char *name;
symstr_t * nmlist;
select_fp select;
int cuttrail;
{
    register int    fd;
    register int    tfd;
    register long   count, rbytes, sbytes;
    long            lbytes, swap_lbytes;
    struct aexec    ahead;

    if ((fd = open (name, O_RDONLY, 0666)) < 0) {
	perror (name);
	return 2;
    }
    if ((tfd = open (tmpname, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) {
	perror (tmpname);
	close (fd);
	return 4;
    }
#ifndef WORD_ALIGNED
    if ((count = lread (fd, &ahead, (long) sizeof (ahead))) 
    				!= (long) sizeof (ahead)) {
#else
    if (read_head (fd, &ahead)) {
#endif
	perror (name);
	close (tfd);
	close (fd);
	return 8;
    }
    if (swap_short(ahead.a_magic)!=CMAGIC) {
	report (name);
	report (": Bad Magic number\n");
	close (tfd);
	close (fd);
	return 0x10;
    }
    sbytes = swap_long(ahead.a_syms);
    if (0L == sbytes && !cuttrail) {
	report (name);
	report (": Already Stripped\n");
	close (tfd);
	close (fd);
	return 0x20;
    }
    if (lseek (fd, 0L, 0) < 0) {
	report (name);
	report (": seek error\n");
	close (tfd);
	close (fd);
	return 0x40;
    }
    count = SIZEOF_AEXEC + swap_long(ahead.a_text) + swap_long(ahead.a_data);
    if (copy (fd, tfd, count) != count) {
	close (tfd);
	close (fd);
	return 0x80;
    }
    if (NULL == select) {	 /* remove whole symbol table */
	lbytes = 0L;
	if (lseek (fd, sbytes, 1) < 0) {
	    report (name);
	    report (": seek error\n");
	    close (tfd);
	    close (fd);
	    return 0x100;
	}
    }
    else {
	lbytes = ( *select )(fd, tfd, sbytes, nmlist);
    }
    if( cuttrail ) {
	if((rbytes = copy_relo(name,fd,tfd))<0) {
	     close(tfd);
	     close(fd);
	     return 0x8000;
	}

	if( lread( fd, &cuttrail, 1 )!=1 ) {
	    report (name);
	    report (": Already Stripped\n");
	    close (tfd);
	    close (fd);
	    return 0x20;
	}
    } else {
	if ((rbytes = copy (fd, tfd, 0x7fffffffL)) < 0) {
	    close (tfd);
	    close (fd);
	    return 0x200;
	}
    }
    if (lseek (tfd, (long)(SYM_OFFSET), 0) < 0) {
	close (tfd);
	close (fd);
	return 0x400;
    }
    swap_lbytes=swap_long(lbytes);
    if (lwrite (tfd, &swap_lbytes, (long)sizeof (lbytes)) != 
    						(long) sizeof (lbytes)) {
	close (tfd);
	close (fd);
	return 0x800;
    }
    close (tfd);
    close (fd);
    if (rename(tmpname, name) == 0) return 0; /* try to rename it */
    if ((fd = open (name, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0) {
	perror (name);
	return 0x1000;
    }
    if ((tfd = open (tmpname, O_RDONLY, 0666)) < 0) {
	perror (tmpname);
	close (fd);
	return 0x2000;
    }

    count = SIZEOF_AEXEC + swap_long(ahead.a_text) + swap_long(ahead.a_data) + rbytes + lbytes;
    if (copy (tfd, fd, count) != count) {
	close (tfd);
	close (fd);
	return 0x4000;
    }
    close (tfd);
    close (fd);
    return 0;
}

/*
 * copy from, to in NEWBUFSIZ chunks upto bytes or EOF whichever occurs first
 * returns # of bytes copied
 */
long 
copy (from, to, bytes)
int from, to;
long bytes;
{
    register long   todo, done = 0L, remaining = bytes, actual;

    while (done != bytes) {
	todo = (remaining > NEWBUFSIZ) ? NEWBUFSIZ : remaining;
	if ((actual = lread (from, mybuf, todo)) != todo) {
	    if (actual < 0) {
		report ("Error Reading\n");
		return -done;
	    }
	}
	if (lwrite (to, mybuf, actual) != actual) {
	    report ("Error Writing\n");
	    return -done;
	}
	done += actual;
	if (actual != todo)	/* eof reached */
	    return done;
	remaining -= actual;
    }
    return done;
}

/* copy TOS relocation table from `from` to `to`. Copy bytes until NUL byte or
   first 4 bytes if == 0l.
   returns length of relocation table or -1 in case of an error */

long 
copy_relo(name,from,to)
char *name;
int from,to;
{
  long res=0;
  long bytes;
  char *end;
  int finished=0;

  res=lread( from, mybuf, sizeof(long) );
  if( res!=0 && res!=sizeof(long) ) {
    report( "Error reading\n" );
    return -1;
  }

  if( res==0 ) {
    report("Warning: ");  /* I think empty relocation tables are allowed,
    report(name);            but could cause trouble with certain programs */
    report( ": No relocation table\n" );
    return 0; 
  }
  if( lwrite(to,mybuf,res)!=res ) {
    report(name);
    report( ": Error writing\n" );
    return -1;
  }

  res=sizeof(long);
  if( *(long*)mybuf == 0 ) return res; /* This is a clean version of an empty
  					  relocation table                   */
  while(!finished) {
    if( (bytes=lread(from,mybuf,NEWBUFSIZ))<0 ) {
      report(name);
      report( ": Error reading\n" );
      return -1;
    }
    if( bytes==0 ) {
      report( "Warning: " );
      report(name);
      report( ": Unterminated relocation table\n" );
      return res;
    }
/* dont know if sozobon has memchr */
    end=memchr(mybuf,0,bytes);
    if(end) {
      bytes=end-mybuf+1;
      finished=1;
    }
    if( lwrite(to,mybuf,bytes)!=bytes ) {
      report(name);
      report(": Error writing\n");
      return -1;
    }
  }
}

void 
report (s)
char * s;
{
    lwrite (2, s, (long) strlen (s));
}

/*
 * Given a name of a file with reserved symbols create an array of
 * reserved symbol names.  To terminate create an entry which starts
 * with a null character.
 */

#define LBUFSIZE 128
#define NMSTEP 10

symstr_t       *
mklist (fname)
char * fname;
{
    FILE           *fp;
    symstr_t       *list = (symstr_t *) NULL;
    int             left = 0;
    int             pos = 0;
    int             i;
    size_t          max_size = 1;
    char            lbuf[LBUFSIZE];
    char           *in, *out;

#ifdef atarist
    if (NULL == (fp = fopen (fname, "rt"))) {
#else
    if (NULL == (fp = fopen (fname, "r"))) {
#endif
	report (fname);
	report (" -- ");
	usage ("cannot open this file\n");
    }

    while (NULL != fgets (lbuf, LBUFSIZE, fp)) {
	if (0 == left) {
	    max_size += NMSTEP;
	    if ((symstr_t *) NULL == list) {
		list = (symstr_t *) malloc ( max_size * sizeof (symstr_t));
	    }
	    else {
		list = (symstr_t *) realloc ((void *) list,
					     max_size * sizeof (symstr_t));

	    }
	    if ((symstr_t *) NULL == list) {
		report ("out of memory making symbol list\n");
		exit (-3);
	    }
	    left = NMSTEP;
	}
	/* strip all leading white space */
	in = lbuf;
	while (' ' == *in || '\t' == *in)
	    in++;
	if ('\n' == *in)
	    continue;		/* empty line - skip it */
	out = &list[pos][0];
	for (i = GST_SYMLEN; i > 0; --i) {
	    if ('\n' == *in || ' ' == *in || '\t' == *in) {
		while (i-- > 0) *out++ = '\0';
		break;
	    }
	    *out++ = *in++;
	}
	pos++;
	--left;
    }				/* while */
    if ((symstr_t *) NULL != list) {
	list[pos][0] = '\0';	/* terminate created list */
    }
    return (list);
}

/*
 * From a file handle fd to a file handle tfd copy up to 'sbytes' bytes
 * of a symbol table selecting only those symbols which have A_GLOBL
 * flag set. Table nmlist is not really used, but is here for a uniform
 * interface.  Returns a number of bytes copied.
 */
long
sel_globs (fd, tfd, sbytes, nmlist)
int fd, tfd;
long sbytes;
symstr_t * nmlist;
{
    long            lbytes = 0;
    struct asym     cur_sym;
    int		    cont = 0;

    while (sbytes) {
	if ((long)SIZEOF_ASYM != lread (fd, &cur_sym, 
						(long)SIZEOF_ASYM)) {
	    report ("error on reading symbol table\n");
	    break;
	}
	if (0 == cont) { /* if we are not dealing with the second part */
	    cont = (0 != (swap_short(cur_sym.a_type) & A_LNAM));
	    if (swap_short(cur_sym.a_type) & A_GLOBL) {
		cont = -cont;
		if ((long)SIZEOF_ASYM != lwrite (tfd, &cur_sym,
						    (long)SIZEOF_ASYM)) {
		    report ("error on writing symbol table\n");
		    break;
		}
		lbytes += SIZEOF_ASYM;
	    }
	}
	else { /* this was an extended symbol */
	    if (cont < 0) {  /* if global then write */
		if ((long)SIZEOF_ASYM != lwrite (tfd, &cur_sym,
						    (long)SIZEOF_ASYM)) {
		    report ("error on writing symbol table\n");
		    break;
		}
		lbytes += SIZEOF_ASYM;
	    }
	    cont = 0;
	}
	sbytes -= SIZEOF_ASYM;
    }
    return (lbytes);
}

/*
 * From a file handle fd to a file handle tfd copy up to 'sbytes' bytes
 * of a symbol table selecting only those symbols which are on nmlist.
 * Free nmlist if different from a default global one.
 * Returns a number of bytes copied.
 */
long
sel_listed (fd, tfd, sbytes, nmlist)
int fd, tfd;
long sbytes;
symstr_t * nmlist;
{
    long            lbytes = 0;
    symstr_t       *kname;
    struct asym     cur_sym, spare;

    if ((symstr_t *) NULL == nmlist)
	return (0L);

    while (sbytes) {
	if ((long)SIZEOF_ASYM != lread (fd, &cur_sym,
						(long) SIZEOF_ASYM)) {
	    report ("error on reading symbol table\n");
	    break;
	}
	for (kname = nmlist; '\0' != **kname; kname++) {
	    if (0 == strncmp (&(*kname)[0], &cur_sym.a_name[0], SYMLEN)) {
		if ((A_LNAM & swap_short(cur_sym.a_type)) == A_LNAM) { /* if extended */
		    if ((long)SIZEOF_ASYM != lread (fd, &spare,
						(long)SIZEOF_ASYM)) {
			report ("error on reading symbol table\n");
			goto leave;  /* skip two loop levels */
		    }
		    sbytes -= SIZEOF_ASYM;
		    if (strncmp (&(*kname)[SYMLEN], (char *)&spare,
				 GST_SYMLEN-SYMLEN))
		      continue;
		}
		if ((long)SIZEOF_ASYM != lwrite (tfd, &cur_sym,
						(long)SIZEOF_ASYM)) {
		    report ("error on writing symbol table\n");
		    goto leave;
		}
		if ((swap_short(cur_sym.a_type) & A_LNAM) == A_LNAM)
		  {
		    if (lwrite (tfd, &spare, (long)SIZEOF_ASYM) != SIZEOF_ASYM)
		      {
			report ("error on writing symbol table\n");
			goto leave;
		      }
		    lbytes += SIZEOF_ASYM;
		  }
		lbytes += SIZEOF_ASYM;
		break;
	    }
	}  /* for */
	sbytes -= SIZEOF_ASYM;
    }  /* while */
leave:
    if (nmlist != stklist) {
	free (nmlist);
	nmlist = (symstr_t *) NULL;
    }
    return (lbytes);
}

#ifdef WORD_ALIGNED
/*
 * read header -- return !0 on err
 */
#define ck_read(fd, addr, siz) \
  if((long)siz != lread(fd, addr, (long)siz)) return !0;

int read_head (fd, a)
int fd;
struct aexec *a;
{
    ck_read(fd, &a->a_magic,   sizeof(a->a_magic));
    ck_read(fd, &a->a_text,    sizeof(a->a_text));
    ck_read(fd, &a->a_data,    sizeof(a->a_data));
    ck_read(fd, &a->a_bss,     sizeof(a->a_bss));
    ck_read(fd, &a->a_syms,    sizeof(a->a_syms));
    ck_read(fd, &a->a_AZero1,  sizeof(a->a_AZero1));
    ck_read(fd, &a->a_AZero2,  sizeof(a->a_AZero2));
    ck_read(fd, &a->a_isreloc, sizeof(a->a_isreloc));

    return 0;
}
#endif


#ifdef CROSSHPUX
char *xmalloc(n)
unsigned n;
{
    extern char *malloc();
    char *ret = malloc(n);
    
    if(!ret)
    {
        fprintf(stderr,"Out of memory!\n");
        exit(1);
    }
    return ret;
}
#endif

#ifdef BYTE_SWAP

/* these routines work for big endian machines as well, but are not needed */

long swap_long(l)
long l;
{
  long l1=l;
  unsigned char *p=(unsigned char *)&l1;

  return (long)p[3]|((long)p[2]<<8)|((long)p[1]<<16)|((long)p[0]<<24);
}

short swap_short(s)
short s;
{
  short s1=s;
  unsigned char *p=(unsigned char *)&s1;

  return (short)p[1]|((short)p[0]<<8);
}

#endif
