/*
 *  smoke16/libObj/src/Obj.c - Functions to be performed on Objs, and fcopy.
 *  $Revision: 1.24 $
 *  Copyright 1997, 2000, Benjamin Sittler
 *
 *  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 2 of the License, 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.
 *
 */

#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <smoke16/libObj.h>

#ifndef SEEK_SET
#define SEEK_SET 0
#endif

Obj *newObj(char *name)
{
  Obj *obj = (Obj *)malloc(sizeof(Obj));

  if (obj) {
    obj -> first = 0;
    obj -> magic = SMOKE16_OMAGIC;
    obj -> toolversion = SMOKE16_TOOLVERSION;
    obj -> mtime = 0;
    obj -> text_size =
      (obj -> data_size =
       (obj -> bss_size = 0));
    obj -> text_align =
      (obj -> data_align =
       (obj -> bss_align = 0));
    obj -> text_relocs =
      (obj -> data_relocs = 0);
    if ((obj -> text = newTmpFile())) {
      if ((obj -> data = newTmpFile())) {
	if ((obj -> tab = newSymTab())) {
	  if ((obj -> name = strdup(name)))
	    return obj;
	  else
	    perror("strdup");
	  freeSymTab(obj -> tab);
	}	
	freeTmpFile(obj -> data);
      }
      else
	fputs("cannot create temporary file for data section.",
	      stderr);
      freeTmpFile(obj -> text);
    }
    else
      fputs("cannot create temporary file for text section.",
	    stderr);
    free((void *)obj);
  }
  else
    perror("malloc");
  return 0;
}

void freeObj(Obj *obj)
{
  Reloc *reloc;
  Member *member;

  freeTmpFile(obj -> text);
  freeTmpFile(obj -> data);
  for (member = obj -> first; member; member = obj -> first) {
    obj -> first = member -> next;
    free((void *)member -> name);
    free((void *)member);
  }
  for (reloc = obj -> text_relocs; reloc; reloc = obj -> text_relocs) {
    obj -> text_relocs = reloc -> next;
    free((void *)reloc);
  }
  for (reloc = obj -> data_relocs; reloc; reloc = obj -> data_relocs) {
    obj -> data_relocs = reloc -> next;
    free((void *)reloc);
  }
  freeSymTab(obj -> tab);
  free((void *)obj -> name);
  free((void *)obj);
}

size_t fcopy(FILE *src, FILE *dest, size_t max_bytes) {
  int c;
  size_t bytes;
  
  for (bytes = 0; bytes < max_bytes; bytes++)
    if (((c = getc(src)) == EOF) ||
       (putc(c, dest) == EOF))
      return bytes;
  return bytes;
}

Obj *readObj(char *name, FILE *objfile)
{
  Obj *obj;
  struct a_out16_exec *exec;

  if ((obj = newObj(name))) {
    if ((exec = (struct a_out16_exec *)malloc(sizeof(struct a_out16_exec)))) {
      if (fget16exec(exec, 1, objfile)) {
	if (!SMOKE16_N_BADMAG(*exec)) {
	  if (obj -> toolversion == 0)
	    obj -> text_align = 1;
	  obj -> toolversion = SMOKE16_A_TOOLVERSION(*exec);
	  if (obj -> toolversion > SMOKE16_TOOLVERSION) {
	    fprintf(stderr,
		    "%s: v%" FMT_u16 " toolset can't read file written by v%" FMT_u16 " toolset\n",
		    name,
		    SMOKE16_TOOLVERSION,
		    obj -> toolversion);
	  } else if (SMOKE16_A_MACHTYPE(*exec) == M_SMOKE16) {
	    /* copy relevant fields to Obj structure */
	    obj -> magic = exec -> a_magic;
	    obj -> text_size = exec -> a_text;
	    obj -> data_size = exec -> a_data;
	    obj -> bss_size = exec -> a_bss;
	    obj -> entry = exec -> a_entry;
#ifdef DEBUG_OBJ
	    fprintf(stderr,
                    "%s: magic: %#" FMT_o16
                    " text: %" FMT_u32 " r%" FMT_u16
                    " data: %" FMT_u32 " r%" FMT_u16
                    " bss: %" FMT_u16 " entry: %#" FMT_x16 "\n",
		    obj -> name,
		    obj -> magic,
		    obj -> text_size,
		    exec -> a_trsize,
		    obj -> data_size,
		    exec -> a_drsize,
		    obj -> bss_size,
		    obj -> entry);
#endif
	    if (!(SMOKE16_A_DYNAMIC(*exec)))
	      if ((obj -> text_size ? 
		  (fcopy(objfile, obj -> text -> file, (size_t) obj -> text_size)
                   == (size_t) obj -> text_size)
		  : 1) &&
		 (obj -> data_size ?
		  (fcopy(objfile, obj -> data -> file, (size_t) obj -> data_size)
                   == (size_t) obj -> data_size)
		  : 1)) {
		/* read text relocations */
		struct a_out16_reloc *trelocs;
		size_t n_trelocs = exec -> a_trsize / fsz16reloc(obj -> toolversion);
		
#ifdef DEBUG_OBJ
		fprintf(stderr, "%s: %ld text relocation%s (%" FMT_u16 " bytes)\n",
			name, (long int) n_trelocs, ((n_trelocs == 1) ? "" : "s") ,
                        exec -> a_trsize);
#endif
		if ((trelocs =
		    (struct a_out16_reloc *)calloc(n_trelocs, sizeof(struct a_out16_reloc)))) {
		  if (n_trelocs ==
		      fget16reloc(trelocs, n_trelocs, objfile)) {
		    /* read data relocations */
		    struct a_out16_reloc *drelocs;
		    size_t n_drelocs = exec -> a_drsize / fsz16reloc(obj -> toolversion);
		    
#ifdef DEBUG_OBJ
		    fprintf(stderr, "%s: %ld data relocation%s (%" FMT_u16 " bytes)\n",
			    name, (long int) n_drelocs,
                            ((n_drelocs == 1) ? "" : "s") , exec -> a_drsize);
#endif
		    if ((drelocs =
			(struct a_out16_reloc *)calloc(n_drelocs, sizeof(struct a_out16_reloc)))) {
		      if (n_drelocs ==
			  fget16reloc(drelocs, n_drelocs, objfile)) {
			/* read symbol table */
			struct a_out16_nlist *symtab;
			size_t n_syms = exec -> a_syms / fsz16nlist(obj -> toolversion);
			
#ifdef DEBUG_OBJ
			fprintf(stderr, "%s: %ld sym%s (%" FMT_u16 " bytes)\n",
				name, (long int) n_syms,
                                ((n_syms == 1) ? "" : "s") , exec -> a_syms);
#endif
			if ((symtab =
			    (struct a_out16_nlist *)calloc(n_syms, sizeof(struct a_out16_nlist)))) {
			  if (n_syms ==
			      fget16nlist(symtab, n_syms, objfile)) {
			    u16 strsize;
			    
			    /* read string table... it's ok if this fails */
			    if (! fget_u16(&strsize, 1, objfile))
			      strsize = 2U;

			    {
			      char *strtab;
			      
#ifdef DEBUG_OBJ
			      fprintf(stderr, "%s: string table: %" FMT_u16 " bytes\n",
				      name, strsize);
#endif
			      if ((strtab = (char *)malloc((size_t)(strsize + 1U)))) {
				strtab[strsize] = '\0';
				if (fget_as(strtab + 2U, strsize - 2U, objfile) ==
				    (strsize - 2U)) {
				  size_t i, ok = 1;
				  
				  /* enter syms */
				  for (i = 0; ok && (i < n_syms); i++) {
				    Sym *sym;
				    
				    if (symtab[i] . n_strx >= strsize) {
				      ok = 0;
				      fprintf(stderr,
					      "%s: symbol's string-table index is out of range\n",
					      obj -> name);
				      break;
				    }

				    /* (nil) -> "" */
				    if (! symtab[i] . n_strx)
				      symtab[i] . n_strx = strsize;

				    /* convert undef with value to common */
				    if (((symtab[i] . n_type & SMOKE16_N_TYPE) == SMOKE16_N_UNDF) &&
					symtab[i] . n_value) {
				      symtab[i] . n_type =
					(symtab[i] . n_type & ~SMOKE16_N_TYPE)
					| SMOKE16__N_COMM;
				    }

				    /* alignment symbols never make it into obj -> tab */
				    if ((symtab[i] . n_type & SMOKE16_N_TYPE) ==
					SMOKE16_N_ALIGN) {
				      if (! strcmp(strtab + symtab[i] . n_strx, "@t"))
					obj -> text_align = symtab[i] . n_value;
				      else if (! strcmp(strtab + symtab[i] . n_strx, "@d"))
					obj -> data_align = symtab[i] . n_value;
				      else if (! strcmp(strtab + symtab[i] . n_strx, "@b"))
					obj -> bss_align = symtab[i] . n_value;
				      else {
					ok = 0;
					fprintf(stderr,
						"%s: unknown alignment symbol `%s'\n",
						obj -> name, strtab + symtab[i] . n_value);
					break;
				      }
				    } else if ((sym = newSym(strtab + symtab[i] . n_strx,
							     symtab[i] . n_type,
							     symtab[i] . n_other,
							     symtab[i] . n_desc,
							     symtab[i] . n_value))) {
				      sym -> obj = obj;
				      if (! (sym = enterinSymTab(obj -> tab, sym))) {
					ok = 0;
					fprintf(stderr, 
						"%s: couldn't enter `%s' in symbol table\n",
						obj -> name,
						strtab + symtab[i] . n_strx);
				      }
#ifdef DEBUG_SYMS
				      if (ok)
					fprintf(stderr,
						"entered sym `%s'\n", 
						sym -> string);
#endif
				    } else
				      ok = 0;
				  }
				  /* done entering syms */
				  
				  /* create text relocations */
				  for (i = 0; ok && (i < n_trelocs); i++) {
				    Reloc *reloc;

				    if (SMOKE16_R_EXTERN(trelocs[i])) {
				      Sym *sym;
				      
				      if (! (sym = newSym(&(strtab[symtab[trelocs[i] . r_index] . n_strx]),
							  SMOKE16_N_UNDF,
							  0, /* stabs other */
							  0, /* stabs desc */
							  0))) {
					ok = 0;
					break;
				      }		
				      reloc =
					newSymReloc(SMOKE16_R_TYPE(trelocs[i]),
						    SMOKE16_R_HIGH(trelocs[i]),
						    SMOKE16_R_LOW(trelocs[i]),
						    trelocs[i] . r_addr_high,
						    trelocs[i] . r_addr_low,
						    trelocs[i] . r_value,
						    enterinSymTab(obj -> tab, sym));
				    } else
				      reloc = 
					newSectionReloc(SMOKE16_R_TYPE(trelocs[i]),
							SMOKE16_R_HIGH(trelocs[i]),
							SMOKE16_R_LOW(trelocs[i]),
							trelocs[i] . r_addr_high,
							trelocs[i] . r_addr_low,
							trelocs[i] . r_value,
							trelocs[i] . r_index);
				    if (reloc) {
				      reloc -> next = obj -> text_relocs;
				      obj -> text_relocs = reloc;
				    } else
				      ok = 0;
				  }
				  /* done creating text relocations */
				  
				  /* create data relocations */
				  for (i = 0; ok && (i < n_drelocs); i++) {
				    Reloc *reloc;

				    if (SMOKE16_R_EXTERN(drelocs[i])) {
				      Sym *sym;
				      
				      if (! (sym = newSym(&(strtab[symtab[drelocs[i] . r_index] . n_strx]),
							  SMOKE16_N_UNDF,
							  0, /* stabs other */
							  0, /* stabs desc */
							  0))) {
					ok = 0;
					break;
				      }		
				      reloc =
					newSymReloc(SMOKE16_R_TYPE(drelocs[i]),
						    SMOKE16_R_HIGH(drelocs[i]),
						    SMOKE16_R_LOW(drelocs[i]),
						    trelocs[i] . r_addr_high,
						    trelocs[i] . r_addr_low,
						    trelocs[i] . r_value,
						    enterinSymTab(obj -> tab, sym));
				    } else
				      reloc = 
					newSectionReloc(SMOKE16_R_TYPE(drelocs[i]),
							SMOKE16_R_HIGH(drelocs[i]),
							SMOKE16_R_LOW(drelocs[i]),
							drelocs[i] . r_addr_high,
							drelocs[i] . r_addr_low,
							drelocs[i] . r_value,
							drelocs[i] . r_index);
				    if (reloc) {
				      reloc -> next = obj -> data_relocs;
				      obj -> data_relocs = reloc;
				    } else
				      ok = 0;
				  }
				  /* done creating data relocations */
				  
				  /* if it's an object archive, read the directory */
				  if (obj -> magic == SMOKE16_LMAGIC) {
				    u32 i, nmembers;
				    struct a_out16_dirent *members;

				    nmembers = obj -> text_size /
				      fsz16dirent(obj -> toolversion);
				    if (! (members = (struct a_out16_dirent *)
					   calloc((size_t)nmembers,
						  sizeof(struct a_out16_dirent))))
				      {
					perror("malloc");
					ok = 0;
				      }
				    if (ok)
				      rewind(obj -> text -> file);
				    if (ok)
				      {
					if (fget16dirent(members,
							 (size_t)nmembers,
							 obj -> text -> file) != (size_t)nmembers)
					  {
					    ok = 0;
					    if (feof (obj -> text -> file))
					      fprintf(stderr,
						      "%s: premature EOF in temporary file\n",
						      obj -> text -> name);
					    else
					      perror(obj -> text -> name);
					  }
				      }
				    for (i = nmembers; ok && i--; )
				      {
					Member *member;
					char *name;

					if (members[i] . d_strx > strsize) {
					  ok = 0;
					  fprintf(stderr,
						  "%s: archive directory is corrupt\n",
						  obj -> name);
					  break;
					}
					if (! (name = strdup (&(strtab[members[i] . d_strx])))) {
					  ok = 0;
					  perror("strdup");
					  break;
					}
					if (! (member = (Member *)
					       malloc(sizeof(Member)))) 
					  {
					    free((void *)name);
					    ok = 0;
					    perror("malloc");
					    break;
					  }
					member -> next = obj -> first;
					member -> name = name;
					member -> magic = members[i] . d_magic;
					member -> value = members[i] . d_value;
					member -> size = members[i] . d_size;
					member -> mtime = members[i] . d_mtime;
					obj -> first = member;
				      }
				    if (members)
				      free((void *)members);
				  }

				  if (ok) {
				    free((void *)strtab);
				    free((void *)symtab);
				    free((void *)drelocs);
				    free((void *)trelocs);
				    free((void *)exec);
				    if (simplifyObj(obj))
				      fprintf(stderr,
					      "%s: cannot simplify\n",
					      obj->name),
				      ok = 0;
				    else
				      return obj;
				  }
				} else if (feof(objfile))
				  fprintf(stderr,
					  "%s: premature EOF in string table\n",
					  name);
				else if (ferror(objfile))
				  perror(name);
				else
				  fprintf(stderr,
					  "%s: couldn't convert string table to native encoding\n",
					  name);
				free((void *)strtab);
			      } else
				perror("malloc");
			    }
			    /* done reading string table */
			  } else if (feof(objfile))
			    fprintf(stderr,
				    "%s: premature EOF in symbol table\n",
				    name);
			  else
			    perror(name);
			  free((void *)symtab);
			} else
			  perror("calloc");
			/* done reading symbol table */
		      } else if (feof(objfile))
			fprintf(stderr,
				"%s: premature EOF in data relocation\n",
				name);
		      else
			perror(name);
		      free((void *)drelocs);
		    } else
		      perror("calloc");
		    /* done reading data relocations */
		  } else if (feof(objfile))
		    fprintf(stderr,
			    "%s: premature EOF in text relocation\n",
			    name);
		  else
		    perror(name);
		  free((void *)trelocs);
		} else
		  perror("calloc");
		/* done reading text relocations */
	      } else
		if (ferror(objfile))
		  perror(name);
		else if (feof(objfile))
		  fprintf(stderr,
			  "%s: premature EOF in text or data\n",
			  name);
		else
		  perror("couldn't copy text or data section");
	    else
	      fprintf(stderr, "%s: unsupported a_dynamic flag\n", name);
	  } else
	    fprintf(stderr,
		    "%s: unsupported machtype %2.2" FMT_X16
                    " is not M_SMOKE16 (%2.2" FMT_X16 ")\n",
		    name,
		    SMOKE16_A_MACHTYPE(*exec),
		    M_SMOKE16);
	} else
	  fprintf(stderr,
		  "%s: File format not recognized\n",
		  name);
      } else {
	if (!feof(objfile))
	  perror(name);
	else
	  fprintf(stderr,
		  "%s: File format not recognized\n",
		  name);
      }
      free((void *)exec);
    }
    else
      perror("malloc");
    freeObj(obj);
  }
  return 0;
}

Obj *extractObj(Obj *archive, Member *member)
{
  Obj *mobj;

  if (fseek(archive -> data -> file, member -> value, SEEK_SET)) {
    fputs(archive -> data -> name, stderr);
    perror(": fseek in temporary file");
    return 0;
  }
  if ((mobj = readObj(member -> name, archive -> data -> file)))
    mobj -> mtime = member -> mtime;
  return mobj;
}

int writeObj(Obj *obj, FILE *objfile)
{
  int ok = 1;
  struct a_out16_exec exec;
  u16 n_syms = 0U;
  u16 strsize = 2U;
  SymSearch *search;
  Sym *sym;

  /* temporarily insert alignment symbols */
  if (ok && obj -> text_align) {
    if ((sym = newSym(SMOKE16__ALIGN_TEXT,
		      SMOKE16_N_ALIGN | SMOKE16_N_EXT,
		      0, /* stabs other */
		      0, /* stabs desc */
		      obj -> text_align))) {
      sym -> obj = obj;
      if (! enterinSymTab(obj -> tab, sym)) {
	ok = 0;
	fprintf(stderr, 
		"%s: couldn't enter `%s' in symbol table\n",
		obj -> name,
		sym -> string);
      }
    }
  }
  if (ok && obj -> data_align) {
    if ((sym = newSym(SMOKE16__ALIGN_DATA,
		      SMOKE16_N_ALIGN | SMOKE16_N_EXT,
		      0, /* stabs other */
		      0, /* stabs desc */
		      obj -> data_align))) {
      sym -> obj = obj;
      if (! enterinSymTab(obj -> tab, sym)) {
	ok = 0;
	fprintf(stderr, 
		"%s: couldn't enter `%s' in symbol table\n",
		obj -> name,
		sym -> string);
      }
    }
  }
  if (ok && obj -> bss_align) {
    if ((sym = newSym(SMOKE16__ALIGN_BSS,
		      SMOKE16_N_ALIGN | SMOKE16_N_EXT,
		      0, /* stabs other */
		      0, /* stabs desc */
		      obj -> bss_align))) {
      sym -> obj = obj;
      if (! enterinSymTab(obj -> tab, sym)) {
	ok = 0;
	fprintf(stderr, 
		"%s: couldn't enter `%s' in symbol table\n",
		obj -> name,
		sym -> string);
      }
    }
  }
  if (ok && (search = newSymSearch(obj -> tab, usedorexternSym))) {
    while ((sym = nextSym(search)))
      sym -> index = n_syms++;
    freeSymSearch(search);
  } else
    fprintf(stderr,
	    "%s: couldn't search symbol table\n",
	    obj->name),
    ok = 0;
  if (ok) {
    Reloc *reloc;
    
    if (obj -> toolversion == 0) {
      if (obj -> text_size != (obj -> text_size & 0xFFFF)) {
	fprintf(stderr,
		"%s: warning: text section won't fit in a \"v0\" file, promoting to v1\n",
		obj -> name);
	obj -> toolversion = 1;
      } else if (obj -> data_size != (obj -> data_size & 0xFFFF)) {
	fprintf(stderr,
		"%s: warning: data section won't fit in a \"v0\" file, promoting to v1\n",
		obj -> name);
	obj -> toolversion = 1;
      }
    }
    SMOKE16_SET_A_DYNAMIC(exec, 0);
    SMOKE16_SET_A_TOOLVERSION(exec, obj -> toolversion);
    SMOKE16_SET_A_MACHTYPE(exec, M_SMOKE16);
    if (obj -> magic == SMOKE16_LMAGIC) {
      Member *member;

      obj -> text_size = 0;
      obj -> data_size = 0;
      for (member = obj -> first; member; member = member -> next) {
	obj -> text_size += fsz16dirent(obj -> toolversion);
	obj -> data_size += member -> size;
      }
    }
    exec . a_magic = obj -> magic;
    exec . a_text = obj -> text_size;
    exec . a_data = obj -> data_size;
    exec . a_bss = obj -> bss_size;
    exec . a_syms = n_syms * fsz16nlist(obj -> toolversion);
    exec . a_entry = obj -> entry;
    exec . a_trsize = 0;
    for (reloc = obj -> text_relocs; reloc; reloc = reloc -> next)
      exec . a_trsize += fsz16reloc(obj -> toolversion);
    exec . a_drsize = 0;
    for (reloc = obj -> data_relocs; reloc; reloc = reloc -> next)
      exec . a_drsize += fsz16reloc(obj -> toolversion);
    if (ok && !fput16exec(&exec, 1, objfile)) {
      fputs(obj -> name, stderr);
      perror(": couldn't write exec header");
      ok = 0;
    }
    if (SMOKE16_N_BADMAG(exec))
      fprintf(stderr,
	      "%s: bad magic number (%#" FMT_o16 ")\n",
	      obj -> name,
	      exec . a_magic),
      ok = 0;
  }
  if (ok) {
    u32 bytes;

    if (obj -> magic == SMOKE16_LMAGIC) {
      Member *member;
      struct a_out16_dirent de;

      bytes = 0;
      for (member = obj -> first; ok && member; member = member -> next) {
	struct a_out16_exec exec;

	de . d_strx = strsize;
	strsize += strlen(member -> name) + 1U;
	de . d_magic = member -> magic;
	de . d_value = bytes;
	de . d_size = member -> size;
	de . d_mtime = member -> mtime;
	if (fseek(obj -> data -> file, member -> value, SEEK_SET))
	  fputs(obj -> data -> name, stderr),
	    perror(": fseek in temporary file"),
	    ok = 0;
	if (ok && !fget16exec(&exec, 1, obj -> data -> file)) {
	  if (feof (obj -> data -> file))
	    fprintf(stderr, "%s: premature EOF\n",
		    obj -> data -> name);
	  else
	    perror(obj -> data -> name);
	  ok = 0;
	}
	if (ok && (exec . a_magic != de . d_magic))
	  fprintf(stderr, "%s(%s): archive member is corrupt\n",
		  obj -> name, member -> name),
	    ok = 0;
	if (ok && !fput16dirent(&de, 1, objfile))
	  fputs(obj -> name, stderr),
	    perror(": couldn't write archive directory"),
	    ok = 0;
#ifdef DEBUG_OBJ
	{
	  time_t t;

	  t = (time_t) member -> mtime;
	  fprintf(stderr,
		  "%s(%s): magic: %#" FMT_o16
                  " offset: %" FMT_u32 " size: %" FMT_u32 " mtime: %s",
		  obj -> name,
		  member -> name,
		  member -> magic,
		  bytes,
		  member -> size,
		  ctime(&t));
	}
#endif
	bytes += member -> size;
      }
    } else {
      rewind(obj -> text -> file);
      if ((bytes = (u32) fcopy(obj -> text -> file, objfile, (size_t) obj -> text_size)
           != obj -> text_size)) {
	fputs(obj -> name, stderr);
	fprintf(stderr,
		": wrote %" FMT_u32 " of %" FMT_u32 " bytes",
		bytes, obj -> text_size);
	perror(": couldn't write text section");
	ok = 0;
      }
    }
  }
  if (ok) {
    u32 bytes;

    if (obj -> magic == SMOKE16_LMAGIC) {
      Member *member;

      bytes = 0;
      for (member = obj -> first; ok && member; member = member -> next) {
	u32 pad = 0;
	Obj *mobj;

	if (! (mobj = extractObj(obj, member)))
	  ok = 0;
	if (mobj && writeObj(mobj, objfile))
	  ok = 0;
	if (! fput_u32(&pad, 1, objfile)) {
	  perror(obj -> name);
	  ok = 0;
	}
	if (mobj)
	  freeObj(mobj);
      }
    } else {
      rewind(obj -> data -> file);
      if ((bytes = (u32) fcopy(obj -> data -> file, objfile, (size_t) obj -> data_size)
           != obj -> data_size)) {
	fputs(obj -> name, stderr);
	fprintf(stderr,
		": wrote %" FMT_u32 " of %" FMT_u32 " bytes",
		bytes, obj -> data_size);
	perror(": couldn't write data section");
	ok = 0;
      }
    }
  }
  if (ok) {
    Reloc *found;

    for (found = obj -> text_relocs; ok && found; found = found -> next) {
      struct a_out16_reloc reloc;

      reloc . r_addr_high = found -> addr_high;
      reloc . r_addr_low = found -> addr_low;
      if (found -> r_extern)
	reloc . r_index = found -> rel . sym -> index;
      else
	reloc . r_index = found -> rel . n_type;
      SMOKE16_SET_R_EXTERN(reloc, found -> r_extern);
      SMOKE16_SET_R_HIGH(reloc, found -> high);
      SMOKE16_SET_R_LOW(reloc, found -> low);
      SMOKE16_SET_R_TYPE(reloc, found -> type);
      reloc . r_value = found -> value;
      if (!fput16reloc(&reloc, 1, objfile))
	fputs(obj -> name, stderr),
	perror(": couldn't write text relocation"),
	ok = 0;
    }
  }
  if (ok) {
    Reloc *found;

    for (found = obj -> data_relocs; ok && found; found = found -> next) {
      struct a_out16_reloc reloc;

      reloc . r_addr_high = found -> addr_high;
      reloc . r_addr_low = found -> addr_low;
      if (found -> r_extern)
	reloc . r_index = found -> rel . sym -> index;
      else
	reloc . r_index = found -> rel . n_type;
      SMOKE16_SET_R_EXTERN(reloc, found -> r_extern);
      SMOKE16_SET_R_HIGH(reloc, found -> high);
      SMOKE16_SET_R_LOW(reloc, found -> low);
      SMOKE16_SET_R_TYPE(reloc, found -> type);
      reloc . r_value = found -> value;
      if (!fput16reloc(&reloc, 1, objfile))
	fputs(obj -> name, stderr),
	perror(": couldn't write data relocation"),
	ok = 0;
    }
  }
  if (ok) {
    if ((search = newSymSearch(obj -> tab, usedorexternSym)))
    {
      while ((sym = nextSym(search)) && ok) {
	struct a_out16_nlist nlist;

	nlist . n_strx = strsize;
	strsize += strlen(sym -> string) + 1U;
	nlist . n_type = sym -> type;
	nlist . n_other = sym -> other;
	nlist . n_desc = sym -> desc;
	nlist . n_value = sym -> value;

	/* convert common to extern+undef */
	if ((nlist . n_type & SMOKE16_N_TYPE) ==
	    SMOKE16__N_COMM) {
	  nlist . n_type =
	    (nlist . n_type & ~SMOKE16_N_TYPE)
	    | SMOKE16_N_UNDF | SMOKE16_N_EXT;
	}

	if (!fput16nlist(&nlist, 1, objfile))
	  fputs(obj -> name, stderr),
	    perror(": couldn't write symbol table entry"),
	    ok = 0;
      }
      freeSymSearch(search);
      if (ok) {
#ifdef DEBUG_OBJ
	fprintf(stderr,
		"%s: %" FMT_u16 " bytes in string table\n",
		obj -> name,
		strsize);
#endif
	/* we don't write a string table if it would be empty */
	if (strsize == 2U)
	  ;
	else if (fput_u16(&strsize, 1, objfile)) {
	  if (obj -> magic == SMOKE16_LMAGIC) {
	    Member *member;
	    
	    for (member = obj -> first; ok && member; member = member -> next)
	      if (fput_as(member -> name,
			  strlen(member -> name) + 1,
			  objfile) < (strlen(member -> name) + 1)) {
		if (ferror(objfile))
		  perror(obj -> name);
		else
		  fprintf(stderr, "%s: couldn't convert member name to portable encoding: `%s'\n",
			  obj -> name, member -> name);
		ok = 0;
	      }
	  }
	  if (ok) {
	    if ((search = newSymSearch(obj -> tab, usedorexternSym))) {
	      while (ok && (sym = nextSym(search)))
		if (fput_as(sym -> string,
			    strlen(sym -> string) + 1,
			    objfile) < (strlen(sym -> string) + 1)) {
		  if (ferror(objfile))
		    perror(obj -> name);
		  else
		    fprintf(stderr, "%s: couldn't convert symbol name to portable encoding: `%s'\n",
			    obj -> name, sym -> string);
		  ok = 0;
		}
	      freeSymSearch(search);
	    } else
	      fprintf(stderr,
		      "%s: couldn't search symbol table\n",
		      obj->name),
		ok = 0;
	  }
	} else
	  fputs(obj -> name, stderr),
	    perror(": couldn't write string table size"),
	    ok = 0;
      }
    } else
      fprintf(stderr,
	      "%s: couldn't search symbol table\n",
	      obj->name),
      ok = 0;
  }
  /* remove temporary alignment symbols */
  if ((search = newSymSearch(obj -> tab, alignmentSym))) {
    while ((sym = nextSym(search))) {
      backupSymSearch(search);
      if (removefromSymTab(obj -> tab, sym))
	fprintf(stderr,
		"%s: cannot remove alignment symbol `%s' from symbol table\n",
		obj -> name,
		sym -> string),
	  ok = 0;
    }
    freeSymSearch(search);
  } else if (ok) {
    fprintf(stderr,
	    "%s: couldn't search symbol table\n",
	    obj->name),
    ok = 0;
  }
  return !ok;
}

int linkObj(Obj *obj, Obj *linkin)
{
  u16 ltxtoff, ldatoff, lbssoff;

  switch (obj -> magic) {
  case SMOKE16_LMAGIC:
    {
      Member *member;
      char *s, *name;
      u32 value, size;
      u32 pad = 0;
      u16 index = 0;
      SymSearch *search;
      Sym *sym;

      /*
       * FIXME: this should be off_t, and we should use fseeko/ftello
       *        instead of fseek/ftell
       */

      long offset;

      if (fseek(obj -> data -> file, obj -> data_size, SEEK_SET)) {
	fputs(obj -> data -> name, stderr);
	if (feof(obj -> data -> file))
	  fprintf(stderr, ": premature EOF in temporary file\n");
	else
	  perror(": fseek in temporary file");
	break;
      }
      value = obj -> data_size;
      if (writeObj(linkin, obj -> data -> file)) {
	break;
      }
      if (! fput_u32(&pad, 1, obj -> data -> file)) {
	fputs(obj -> data -> name, stderr);
	perror(": write in temporary file");
      }
      obj -> data_size = offset = ftell(obj -> data -> file);
      if (offset == -1) {
	obj -> data_size = value;
	fputs(obj -> data -> name, stderr);
	perror(": ftell in temporary file");
	break;
      }
      size = obj -> data_size - value;
      name = linkin -> name;
      for (s = name; *s; s++)
	if ((*s == '/') &&
	    (s[1] != '\0') &&
	    (s[1] != '/'))
	  name = s+1;
      if (! (name = strdup(name))) {
	obj -> data_size = value;
	perror("strdup");
	break;
      }
      if (! (member = (Member *)malloc(sizeof(*member)))) {
	obj -> data_size = value;
	free(name);
	perror("malloc");
	break;
      }
      member -> name = name;
      member -> magic = linkin -> magic;
      member -> value = value;
      member -> size = size;
      member -> mtime = linkin -> mtime;
      if (! (obj -> first))
	obj -> first = member;
      else {
	index = 1;
	for (member -> next = obj -> first;
	     member -> next -> next;
	     member -> next = member -> next -> next)
	  index ++;
	member -> next -> next = member;
      }
      member -> next = 0;
      search = newSymSearch(linkin -> tab, externSym);
      if (! search) {
	fprintf(stderr,
		"%s: couldn't search symbol table\n",
		linkin -> name);
	break;
      }
      while ((sym = nextSym(search))) {
	Sym *s;
	
	if (! (s = cpSym(sym)))
	  break;
	s -> value = index;
	addtoSymTab(obj -> tab, s);
      }
      if (sym)
	break;
      freeSymSearch(search);
      return 0;
    }
  default:
    switch (linkin -> magic) {
    case SMOKE16_LMAGIC:
      {
	struct SymSearch *search;
	struct Sym *xsym, *sym;

	do {
	  if (! (search = newSymSearch(linkin -> tab, anySym)))
	    return 1;
	  while ((xsym = nextSym(search)))
	    if ((sym = lookinSymTab(obj -> tab, xsym -> string)))
	      if (((sym -> type & SMOKE16_N_TYPE) == SMOKE16_N_UNDF) ||
		  (((sym -> type & SMOKE16_N_TYPE) == SMOKE16__N_COMM) &&
		   ((xsym -> type & SMOKE16_N_TYPE) != SMOKE16__N_COMM)))
		break;
	  freeSymSearch(search);
	  if (xsym) {
	    Member *member;
	    Obj *mobj;
	    u16 index;

	    member = linkin -> first;
	    for (index = 0; index < xsym -> value; index ++)
	      if (member)
		member = member -> next;
	    if (! member) {
	      fprintf(stderr,
		      "%s: archive index is corrupt\n",
		      linkin -> name);
	      return 1;
	    }
#ifdef DEBUG_OBJ
	    fprintf(stderr,
		    "linking %s(%s) into %s to resolve `%s'\n",
		    linkin -> name, member -> name, obj -> name, xsym -> string);
#endif
	    if (! (mobj = extractObj(linkin, member))) {
	      fprintf(stderr,
		      "%s(%s): failed to read archive member\n",
		      linkin -> name, member -> name);
	      return 1;
	    }
	    if (linkObj(obj, mobj)) {
	      fprintf (stderr,
		       "%s: failed to link in %s(%s)\n",
		       obj -> name, linkin -> name, member -> name);
	      freeObj(mobj);
	      return 1;
	    }
	    freeObj(mobj);
	  }
	} while (xsym);
	return 0;
      }
      break;
    default:
      if ((obj -> text_size) &&
	  fseek(obj -> text -> file, obj -> text_size, SEEK_SET)) {
	fputs(obj -> name, stderr);
	perror(": fseek in text section");
	break;
      }
      if ((obj -> data_size) &&
	  fseek(obj -> data -> file, obj -> data_size, SEEK_SET)) {
	fputs(obj -> name, stderr);
	perror(": fseek in data section");
	break;
      }

      /* fix section alignment for .text */
      while(obj -> text_size !=
	    ((obj -> text_size >> linkin -> text_align)
	     << linkin -> text_align)) {
	u8 pad = 0;

	if (! fput_u8(&pad, 1, obj -> text -> file)) {
	  fputs(obj -> name, stderr);
	  perror(": warning: padding text section failed");
	  break;
	}
	obj -> text_size ++;
      }
      if (linkin -> text_align > obj -> text_align)
	obj -> text_align = linkin -> text_align;

      /* fix section alignment for .data */
      while(obj -> data_size !=
	    ((obj -> data_size >> linkin -> data_align)
	     << linkin -> data_align)) {
	u8 pad = 0;

	if (! fput_u8(&pad, 1, obj -> data -> file)) {
	  fputs(obj -> name, stderr);
	  perror(": warning: padding data section failed");
	  break;
	}
	obj -> data_size ++;
      }

      /* fix section alignment for .bss */
      if (linkin -> data_align > obj -> data_align)
	obj -> data_align = linkin -> data_align;
      while(obj -> bss_size !=
	    ((obj -> bss_size >> linkin -> bss_align)
	     << linkin -> bss_align)) {
	obj -> bss_size ++;
      }
      if (linkin -> bss_align > obj -> bss_align)
	obj -> bss_align = linkin -> bss_align;

      rewind(linkin -> text -> file);
      rewind(linkin -> data -> file);
      ltxtoff = obj -> text_size;
      ldatoff = obj -> data_size;
      lbssoff = obj -> bss_size;
      obj -> bss_size += linkin -> bss_size;
      if ((obj -> text_size +=
	   (u32) fcopy(linkin -> text -> file, obj -> text -> file, (size_t) linkin -> text_size))
          == ltxtoff + linkin -> text_size)
	if ((obj -> data_size +=
	     (u32) fcopy(linkin -> data -> file, obj -> data -> file, (size_t) linkin -> data_size))
            == ldatoff + linkin -> data_size) {
	  int ok = 1;
	  SymSearch *search;
	  
	  if ((search = newSymSearch(linkin -> tab, anySym))) {
	    Sym *found;
	    
	    while (ok && (found = nextSym(search))) {
	      Sym *sym = cpSym(found);
	      
	      if (sym) {
		switch (sym -> type & SMOKE16_N_TYPE) {
		case SMOKE16__N_COMM:
		case SMOKE16_N_UNDF:
		case SMOKE16_N_ABS:
		  break;
		case SMOKE16_N_TEXT:
		  sym -> value += ltxtoff;
		  break;
		case SMOKE16_N_DATA:
		  sym -> value += ldatoff;
		  break;
		case SMOKE16_N_BSS:
		  sym -> value += lbssoff;
		  break;
		default:
		  fprintf(stderr, "%s: unsupported symbol type %#2.2" FMT_X8 "\n",
			  obj -> name,
			  sym -> type & SMOKE16_N_TYPE);
		  ok = 0;
		  freeSym(sym);
		}
		if (ok) {
		  sym -> obj = obj;
		  if (!enterinSymTab(obj -> tab, sym))
		    fprintf(stderr, 
			    "%s: couldn't enter `%s' in symbol table\n",
			    obj -> name,
			    sym -> string),
		      ok = 0;
		}
	      } else
		ok = 0;
	    }
	    freeSymSearch(search);
	  } else
	    fprintf(stderr,
		    "%s: couldn't search symbol table from %s\n",
		    obj -> name,
		    linkin -> name),
	      ok = 0;
	  if (ok) {
	    Reloc *found;
	    
	    for (found = linkin -> text_relocs; found && ok; found = found -> next) {
	      Reloc *reloc = 0;
	      
	      if (found -> r_extern) {
		Sym *sym = lookinSymTab(obj -> tab, found -> rel . sym -> string);
		
		if (!(sym &&
		      (reloc = newSymReloc(found -> type,
					   found -> high,
					   found -> low,
					   found -> addr_high,
					   found -> addr_low,
					   found -> value,
					   sym))))
		  ok = 0;
	      } else
		if ((reloc = newSectionReloc(found -> type,
					     found -> high,
					     found -> low,
					     found -> addr_high,
					     found -> addr_low,
					     found -> value,
					     found -> rel . n_type)))
		  switch ((reloc -> rel . n_type) & SMOKE16_N_TYPE) {
		  case SMOKE16_N_ABS:
		    break;
		  case SMOKE16_N_TEXT:
		    reloc -> value += ltxtoff;
		    break;
		  case SMOKE16_N_DATA:
		    reloc -> value += ldatoff;
		    break;
		  case SMOKE16_N_BSS:
		    reloc -> value += lbssoff;
		    break;
		  default:
		    fprintf(stderr, "%s: unsupported section-relative text relocation type %#2.2" FMT_X8 "\n",
			    linkin -> name,
			    reloc -> rel . n_type & SMOKE16_N_TYPE);
		    ok = 0;
		    freeReloc(reloc);
		    break;
		  }
		else
		  ok = 0;
	      if (ok) {
		if (reloc -> high)
		  reloc -> addr_high += ltxtoff;
		if (reloc -> low)
		  reloc -> addr_low += ltxtoff;
		reloc -> next = obj -> text_relocs;
		obj -> text_relocs = reloc;
	      }
	    }
	    for (found = linkin -> data_relocs; found && ok; found = found -> next) {
	      Reloc *reloc = 0;
	      
	      if (found -> r_extern) {
		Sym *sym = lookinSymTab(obj -> tab, found -> rel . sym -> string);
		
		if (!(sym &&
		      (reloc = newSymReloc(found -> type,
					   found -> high,
					   found -> low,
					   found -> addr_high,
					   found -> addr_low,
					   found -> value,
					   sym))))
		  ok = 0;
	      } else
		if ((reloc = newSectionReloc(found -> type,
					     found -> high,
					     found -> low,
					     found -> addr_high,
					     found -> addr_low,
					     found -> value,
					     found -> rel . n_type)))
		  switch ((reloc -> rel . n_type) & SMOKE16_N_TYPE) {
		  case SMOKE16_N_ABS:
		    break;
		  case SMOKE16_N_TEXT:
		    reloc -> value += ltxtoff;
		    break;
		  case SMOKE16_N_DATA:
		    reloc -> value += ldatoff;
		    break;
		  case SMOKE16_N_BSS:
		    reloc -> value += lbssoff;
		    break;
		  default:
		    fprintf(stderr, "%s: unsupported section-relative data relocation type %#2.2" FMT_X8 "\n",
			    linkin -> name,
			    reloc -> rel . n_type & SMOKE16_N_TYPE);
		    ok = 0;
		    freeReloc(reloc);
		    break;
		  }
		else
		  ok = 0;
	      if (ok) {
		if (reloc -> high)
		  reloc -> addr_high += ldatoff;
		if (reloc -> low)
		  reloc -> addr_low += ldatoff;
		reloc -> next = obj -> data_relocs;
		obj -> data_relocs = reloc;
	      }
	    }
	    if (!ok)
	      fprintf(stderr, "%s: couldn't copy relocations into %s\n",
		      linkin -> name,
		      obj -> name);
	  }
	  if (ok) {
	    if(simplifyObj(obj))
	      fprintf(stderr,
		      "%s: cannot simplify\n",
		      obj->name),
		ok = 0;
	    else
	      return 0;
	  }
	} else
	  fputs(linkin -> name, stderr),
	    perror(": couldn't copy data section");
      else
	fputs(linkin -> name, stderr),
	  perror(": couldn't copy text section");
    }
  }
  return 1;
}

int simplifyObj(Obj *obj) {
  Reloc *reloc;
  SymSearch *search;
  int ok = 1;

  for (reloc = obj -> text_relocs; reloc; reloc = reloc -> next)
    if (reloc -> r_extern &&
	((reloc -> rel . sym -> type & SMOKE16_N_TYPE) != SMOKE16_N_UNDF) &&
	((reloc -> rel . sym -> type & SMOKE16_N_TYPE) != SMOKE16__N_COMM)) {
#ifdef DEBUG_RELOCS
      fprintf(stderr,
	      "making text relocation for `%s'+%#" FMT_x16 " section-relative\n",
	      reloc -> rel . sym -> string, reloc -> value);
#endif
      reloc -> value += reloc -> rel . sym -> value;
      reloc -> r_extern = 0;
      reloc -> rel . sym -> refcnt--;
      reloc -> rel . n_type = reloc -> rel . sym -> type;
    }
  for (reloc = obj -> data_relocs; reloc; reloc = reloc -> next)
    if (reloc -> r_extern &&
	((reloc -> rel . sym -> type & SMOKE16_N_TYPE) != SMOKE16_N_UNDF) &&
	((reloc -> rel . sym -> type & SMOKE16_N_TYPE) != SMOKE16__N_COMM)) {
#ifdef DEBUG_RELOCS
      fprintf(stderr,
	      "making data relocation for `%s'+%#" FMT_x16 " section-relative\n",
	      reloc -> rel . sym -> string, reloc -> value);
#endif
      reloc -> value += reloc -> rel . sym -> value;
      reloc -> r_extern = 0;
      reloc -> rel . sym -> refcnt--;
      reloc -> rel . n_type = reloc -> rel . sym -> type;
    }
  if ((search = newSymSearch(obj -> tab, unusedinternSym))) {
    Sym *sym;

    while ((sym = nextSym(search)) && ok) {
      backupSymSearch(search);
      if (removefromSymTab(obj -> tab, sym))
	fprintf(stderr,
		"%s: cannot remove `%s' from symbol table\n",
		obj -> name,
		sym -> string),
	ok = 0;
    }
    freeSymSearch(search);
  } else
    fprintf(stderr,
	    "%s: couldn't search symbol table\n",
	    obj->name),
    ok = 0;
  return !ok;
}

/* relocate an object */
int loadObj(Obj *obj,
	    int Entry_p, u16 Entry,
	    int Ttext_p, u16 Ttext,
	    int Tdata_p, u16 Tdata,
	    int Tbss_p, u16 Tbss,
	    char *entry) {
  u16 entry_value;
  Sym *entry_sym = 0;
  SymSearch *search;
  int ok = 1;
  Reloc *reloc;
  u8 pad = 0;

  /* allocate space for common symbols */ 
  if (ok && (search = newSymSearch(obj -> tab, commonSym))) {
    Sym *sym;

    while ((sym = nextSym(search))) {
      u16 bytes = sym -> value;

      sym -> type =
	(sym -> type & ~SMOKE16_N_TYPE)
	| SMOKE16_N_BSS;
      sym -> value = obj -> bss_size;
      obj -> bss_size += bytes;
    }
    freeSymSearch(search);
  } else
    fprintf(stderr,
	    "%s: couldn't search symbol table\n",
	    obj->name),
    ok = 0;

  /* determine entry point */
  if (!entry)
    entry = ENTRY_SYM;
  if (Entry_p) {
    entry = "(unknown)";
    entry_value = Entry;
  } else if ((entry_sym = lookinSymTab(obj -> tab, entry)))
    switch (entry_sym -> type & SMOKE16_N_TYPE) {
    case SMOKE16_N_UNDF:
      fprintf(stderr,
	      "%s: entry symbol `%s' is undefined\n",
	      obj -> name, entry_sym -> string);
      return 1;
    case SMOKE16_N_ABS:
    case SMOKE16_N_TEXT:
    case SMOKE16_N_DATA:
    case SMOKE16_N_BSS:
      entry_value = entry_sym -> value;
      break;
    default:
      fprintf(stderr,
	      "%s: unsupported symbol type %#2.2" FMT_X8 " for entry symbol `%s'\n",
	      obj -> name,
	      entry_sym -> type & SMOKE16_N_TYPE,
	      entry_sym -> string);
      return 1;
      break;
    }
  else {
    fprintf(stderr,
	    "%s: no entry symbol `%s' found\n",
	    obj -> name, entry);
    return 1;
  }

  /* establish load addresses and fix aligment/padding */
  if (!Ttext_p)
    Ttext = obj -> toolversion ? 0x0400 : 0x0000;
  if (((Ttext >> obj -> text_align) << obj -> text_align) != Ttext) {
    fprintf (stderr,
	     "%s: text section load address %#" FMT_x16 " isn't aligned on a %d-byte boundary\n",
	     obj -> name,
	     Ttext,
	     1 << obj -> text_align);
    return 1;
  }
  if (!Tdata_p) {
    Tdata = Ttext + obj -> text_size;
    if ((obj -> text_size) &&
	fseek(obj -> text -> file, obj -> text_size, SEEK_SET)) {
      fputs(obj -> name, stderr);
      perror(": fseek in text section");
      return 1;
    }
    while (((Tdata >> obj -> data_align) << obj -> data_align) != Tdata) {
      if (! fput_u8(&pad, 1, obj -> text -> file)) {
	fputs(obj -> name, stderr);
	perror(": padding text section failed");
	return 1;
      }
      obj -> text_size ++;
      Tdata ++;
    }
  }
  if (((Tdata >> obj -> data_align) << obj -> data_align) != Tdata) {
    fprintf (stderr,
	     "%s: data section load address %#" FMT_x16 " isn't aligned on a %d-byte boundary\n",
	     obj -> name,
	     Tdata,
	     1 << obj -> data_align);
    return 1;
  }
  if (!Tbss_p) {
    Tbss = Tdata + obj -> data_size;
    if ((obj -> data_size) &&
	fseek(obj -> data -> file, obj -> data_size, SEEK_SET)) {
      fputs(obj -> name, stderr);
      perror(": fseek in data section");
      return 1;
    }
    while (((Tbss >> obj -> bss_align) << obj -> bss_align) != Tbss) {
      if (! fput_u8(&pad, 1, obj -> data -> file)) {
	fputs(obj -> name, stderr);
	perror(": padding data section failed");
	return 1;
      }
      obj -> text_size ++;
      Tbss ++;
    }
  }
  if (((Tbss >> obj -> bss_align) << obj -> bss_align) != Tbss) {
    fprintf (stderr,
	     "%s: bss load address %#" FMT_x16 " isn't aligned on a %d-byte boundary\n",
	     obj -> name,
	     Tbss,
	     1 << obj -> bss_align);
    return 1;
  }

  /* alignment is applied now, no need to keep it */
  obj -> text_align = 0;
  obj -> data_align = 0;
  obj -> bss_align = 0;

  if (entry_sym)
    switch (entry_sym -> type & SMOKE16_N_TYPE) {
    case SMOKE16_N_TEXT:
      entry_value += Ttext;
      break;
    case SMOKE16_N_DATA:
      entry_value += Tdata;
      break;
    case SMOKE16_N_BSS:
      entry_value += Tbss;
      break;
    default:
      break;
    }
  for (reloc = obj -> text_relocs; reloc; reloc = reloc -> next) {
    s16 value = reloc -> value;
    
    if (reloc -> r_extern)
      switch (reloc -> rel . sym -> type & SMOKE16_N_TYPE) {
      case SMOKE16_N_UNDF:
	ok = 0;
	fprintf(stderr,
		"%s: undefined reference to `%s'\n",
		obj -> name,
		reloc -> rel . sym -> string);
	break;
      case SMOKE16_N_ABS:
	value += reloc -> rel . sym -> value;
	break;
      case SMOKE16_N_TEXT:
	value += reloc -> rel . sym -> value + Ttext;
	break;
      case SMOKE16_N_DATA:
	value += reloc -> rel . sym -> value + Tdata;
	break;
      case SMOKE16_N_BSS:
	value += reloc -> rel . sym -> value + Tbss;
	break;
      default:
	fprintf(stderr,
		"%s: unsupported symbol type %#2.2" FMT_X8 " for `%s'\n",
		obj -> name,
		reloc -> rel . sym -> type & SMOKE16_N_TYPE,
		reloc -> rel . sym -> string);
	ok = 0;
	break;
      }
    else
      switch (reloc -> rel . n_type & SMOKE16_N_TYPE) {
      case SMOKE16_N_ABS:
	break;
      case SMOKE16_N_TEXT:
	value += Ttext;
	break;
      case SMOKE16_N_DATA:
	value += Tdata;
	break;
      case SMOKE16_N_BSS:
	value += Tbss;
	break;
      default:
	fprintf(stderr,
		"%s: unsupported section-relative relocation type %#2.2" FMT_X8 "\n",
		obj -> name,
		reloc -> rel . n_type & SMOKE16_N_TYPE);
	ok = 0;
	break;
      }
    switch (reloc -> type) {
    case SMOKE16_RELOC_ABSOLUTE:
      break;
    case SMOKE16_RELOC_DISP8:
      if (reloc -> high)
	fprintf(stderr,
		"%s: branch displacement has the high-byte address enabled\n",
		obj -> name),
	  ok = 0;
      if (ok && reloc -> low) {
	value -= reloc -> addr_low + Ttext;
	value--;
	if (value & 1) {
	  fprintf(stderr,
		  "%s: branch displacement of %" FMT_s16 " byte%s "
                  "isn't even (.text+%#" FMT_x16 ")\n",
		  obj -> name,
		  value,
		  (value == 1) ? "" : "s",
		  reloc -> addr_low);
	  ok = 0;
	}
	value /= 2;
	if (ok && (((s16)value < (s16)-128) || ((s16)value > (s16)127))) {
	  fprintf(stderr,
		  "%s: branch displacement of %" FMT_s16 " instruction%s "
                  "doesn't fit in a signed 8-bit number (.text+%#" FMT_x16 ")\n",
		  obj -> name,
		  value,
		  (value == 1) ? "" : "s",
		  reloc -> addr_low);
	  ok = 0;
	}
      } else if (ok)
	fprintf(stderr,
		"%s: branch displacement is missing operand address\n",
		obj -> name),
	  ok = 0;
      break;
    default:
      fprintf(stderr,
	      "%s: unsupported text relocation type %#" FMT_x16 "\n",
	      obj -> name,
	      reloc -> type);
      ok = 0;
      break;
    }
    if (ok) {
      if (reloc -> high)
	if (fseek(obj -> text -> file, (long)reloc -> addr_high, SEEK_SET) ||
	    (fputc((value >> 8) & 0xff, obj -> text -> file) == EOF))
	  fputs(obj -> name, stderr),
	    perror(": couldn't patch text section"),
	    ok = 0;
      if (reloc -> low && ok)
	if (fseek(obj -> text -> file, (long)reloc -> addr_low, SEEK_SET) ||
	    (fputc(value & 0xff, obj -> text -> file) == EOF))
	  fputs(obj -> name, stderr),
	    perror(": couldn't patch text section"),
	    ok = 0;
    }
  }
  if (ok)
    while ((reloc = obj -> text_relocs)) {
      obj -> text_relocs = reloc -> next;
      freeReloc(reloc);
    }
  for (reloc = obj -> data_relocs; reloc; reloc = reloc -> next) {
    s16 value = reloc -> value;
    
    if (reloc -> r_extern)
      switch (reloc -> rel . sym -> type & SMOKE16_N_TYPE) {
      case SMOKE16_N_UNDF:
	ok = 0;
	fprintf(stderr,
		"%s: undefined reference to `%s'\n",
		obj -> name,
		reloc -> rel . sym -> string);
	break;
      case SMOKE16_N_ABS:
	value += reloc -> rel . sym -> value;
	break;
      case SMOKE16_N_TEXT:
	value += reloc -> rel . sym -> value + Ttext;
	break;
      case SMOKE16_N_DATA:
	value += reloc -> rel . sym -> value + Tdata;
	break;
      case SMOKE16_N_BSS:
	value += reloc -> rel . sym -> value + Tbss;
	break;
      default:
	fprintf(stderr,
		"%s: unsupported symbol type %#2.2" FMT_X8 " for `%s'\n",
		obj -> name,
		reloc -> rel . sym -> type & SMOKE16_N_TYPE,
		reloc -> rel . sym -> string);
	ok = 0;
	break;
      }
    else
      switch (reloc -> rel . n_type & SMOKE16_N_TYPE) {
      case SMOKE16_N_ABS:
	break;
      case SMOKE16_N_TEXT:
	value += Ttext;
	break;
      case SMOKE16_N_DATA:
	value += Tdata;
	break;
      case SMOKE16_N_BSS:
	value += Tbss;
	break;
      default:
	fprintf(stderr,
		"%s: unsupported section-relative relocation type %#2.2" FMT_X8 "\n",
		obj -> name,
		reloc -> rel . n_type & SMOKE16_N_TYPE);
	ok = 0;
	break;
      }
    switch (reloc -> type) {
    case SMOKE16_RELOC_ABSOLUTE:
      break;
    case SMOKE16_RELOC_DISP8:
      if (reloc -> high) {
	fprintf(stderr,
		"%s: branch displacement has the high-byte address enabled\n",
		obj -> name);
	ok = 0;
      }
      if (ok && reloc -> low) {
	value -= reloc -> addr_low + Tdata;
	value--;
	value /= 2;
	if ((value & 0xff80) != 0xff80)
	  if (value & 0xff00)
	    fprintf(stderr,
		    "%s: branch displacement (%" FMT_s16 ") is too large\n",
		    obj -> name,
		    value),
	      ok = 0;
      } else if (ok)
	fprintf(stderr,
		"%s: branch displacement is missing operand address\n",
		obj -> name),
	  ok = 0;
      break;
    default:
      fprintf(stderr,
	      "%s: unsupported data relocation type %#" FMT_x16 "\n",
	      obj -> name,
	      reloc -> type);
      ok = 0;
      break;
    }
    if (ok) {
      if (reloc -> high)
	if (fseek(obj -> data -> file, (long)reloc -> addr_high, SEEK_SET) ||
	    (fputc((value >> 8) & 0xff, obj -> data -> file) == EOF))
	  fputs(obj -> name, stderr),
	    perror(": couldn't patch data section"),
	    ok = 0;
      if (reloc -> low && ok)
	if (fseek(obj -> data -> file, (long)reloc -> addr_low, SEEK_SET) ||
	    (fputc(value & 0xff, obj -> data -> file) == EOF))
	  fputs(obj -> name, stderr),
	    perror(": couldn't patch data section"),
	    ok = 0;
    }
  }
  if (ok)
    while ((reloc = obj -> data_relocs)) {
      obj -> data_relocs = reloc -> next;
      freeReloc(reloc);
    }
  if (ok) {
    obj -> entry = entry_value;
#ifdef DEBUG_OBJ
    fprintf(stderr,
	    "%s: text: %" FMT_u32 " at %#" FMT_x16
            ", data: %" FMT_u32 " at %#" FMT_x16
            ", bss: %" FMT_u16 " at %#" FMT_x16
            ", entry at `%s' (%#" FMT_x16 ")\n",
	    obj -> name,
	    obj -> text_size,
	    Ttext,
	    obj -> data_size,
	    Tdata,
	    obj -> bss_size,
	    Tbss,
	    entry,
	    obj -> entry);
#endif
    return 0;
  }
  return 1;
}

int stripObj(Obj *obj, int stripall) {
  SymSearch *search = newSymSearch(obj -> tab, (stripall ? anySym : debugSym));
  int ok = 1;

#ifdef DEBUG_SYMS
  fprintf(stderr,
	  "stripping %s symbols from %s\n",
	  (stripall ? "all" : "debugging"),
	  obj -> name);
#endif
  if (search) {
    Sym *sym;

    while ((sym = nextSym(search))) {
      backupSymSearch(search);
#ifdef DEBUG_SYMS
      fprintf(stderr,
	      "%s: stripping `%s'\n",
	      obj -> name,
	      sym -> string);
#endif
      if (removefromSymTab(obj -> tab, sym))
	fprintf(stderr,
		"%s: cannot remove `%s' from symbol table\n",
		obj -> name,
		sym -> string),
	ok = 0;
    }
    freeSymSearch(search);
  } else
    fprintf(stderr,
	    "%s: cannot search symbol table for strippable symbols\n",
	    obj -> name),
    ok = 0;
  return !ok;
}
