/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * The segedit(1) program.  This program extracts and replaces sections from
 * an object file.  Only sections in segments that have been marked that they
 * have no relocation can be replaced (SG_NORELOC).  This program takes the
 * following options:
 *   -extract <segname> <sectname> <filename>
 *   -replace <segname> <sectname> <filename>
 *   -output <filename>
 */
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <libc.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <mach/mach.h>
#include "stuff/openstep_mach.h"
#include <mach/mach_error.h>
#include "stuff/allocate.h"
#include "stuff/errors.h"
#include "stuff/rnd.h"
#include "stuff/bytesex.h"

/* These variables are set from the command line arguments */
__private_extern__
char *progname = NULL;	/* name of the program for error messages (argv[0]) */

static char *input,	/* object file to extract/replace sections from */
     	    *output;	/* new object file with replaced sections, if any
			   -replace options */

/* structure for holding -extract's arguments */
struct extract {
    char *segname;		/* segment name */
    char *sectname;		/* section name */
    char *filename;		/* file to put the section contents in */
    int32_t found;		/* set when the section is found */
    struct extract *next;	/* next extract structure, NULL if last */
} *extracts;			/* first extract structure, NULL if none */

/* structure for holding -replace's arguments */
struct replace {
    char *segname;		/* segment name */
    char *sectname;		/* section name */
    char *filename;		/* file to get the section contents from */
    int32_t found;		/* set when the section is found */
    uint32_t size;		/* size of new section contents */
    struct replace *next;	/* next replace structure, NULL if last */
} *replaces;			/* first replace structure, NULL if none */

/*
 * Structures used in replace_sections in replaceing sections in the segments
 * of the input file.  There is one such structure for each segment and section.
 */
struct rep_seg {
    int32_t modified;		/* this segment has a replaced section */
    uint32_t fileoff;		/* original file offset */
    uint32_t filesize;		/* original file size */
    uint64_t vmsize;		/* original vm size */
    uint32_t padsize;		/* new pad size */
    struct segment_command *sgp;/* pointer to the segment_command */
    struct segment_command_64 *sgp64;/* pointer to the segment_command_64 */
} *segs;

struct rep_sect {
    struct replace *rp;		/* pointer to the replace structure */
    uint32_t offset;		/* original file offset */
    struct section *sp;		/* pointer to the section structure */
    struct section_64 *sp64;	/* pointer to the section_64 structure */
} *sects;

/* These variables are set in the routine map_input() */
static void *input_addr;	/* address of where the input file is mapped */
static uint32_t input_size;	/* size of the input file */
static uint32_t input_mode;	/* mode of the input file */
static struct mach_header *mhp;	/* pointer to the input file's mach header */
static struct mach_header_64
			*mhp64;	/* pointer to the input file's mach header for
				   64-bit files */
static uint32_t mh_ncmds;	/* number of load commands */
static struct load_command
		*load_commands;	/* pointer to the input file's load commands */
static uint32_t pagesize = 8192;/* target pagesize */
static enum bool swapped;	/* TRUE if the input is to be swapped */
static enum byte_sex host_byte_sex = UNKNOWN_BYTE_SEX;
static enum byte_sex target_byte_sex = UNKNOWN_BYTE_SEX;

/* Internal routines */
static void map_input(
    void);
static void extract_sections(
    void);
static void extract_section(
    char *segname,
    char *sectname,
    uint32_t flags,
    uint32_t offset,
    uint32_t size);
static void replace_sections(
    void);
static void search_for_replace_section(
    char *segname,
    char *sectname,
    uint32_t seg_flags,
    uint32_t sect_flags,
    uint32_t offset,
    uint32_t size);
static int cmp_qsort(
    const struct rep_seg *seg1,
    const struct rep_seg *seg2);
static void usage(
    void);

int
main(
int argc,
char *argv[],
char *envp[])
{
    int i;
    struct extract *ep;
    struct replace *rp;

	progname = argv[0];
	host_byte_sex = get_host_byte_sex();

	for (i = 1; i < argc; i++) {
	    if(argv[i][0] == '-'){
		switch(argv[i][1]){
		case 'e':
		    if(i + 4 > argc){
			error("missing arguments to %s option", argv[i]);
			usage();
		    }
		    ep = allocate(sizeof(struct extract));
		    ep->segname =  argv[i + 1];
		    ep->sectname = argv[i + 2];
		    ep->filename = argv[i + 3];
		    ep->found = 0;
		    ep->next = extracts;
		    extracts = ep;
		    i += 3;
		    break;
		case 'r':
		    if(i + 4 > argc){
			error("missing arguments to %s option", argv[i]);
			usage();
		    }
		    rp = allocate(sizeof(struct replace));
		    rp->segname =  argv[i + 1];
		    rp->sectname = argv[i + 2];
		    rp->filename = argv[i + 3];
		    rp->next = replaces;
		    replaces = rp;
		    i += 3;
		    break;
		case 'o':
		    if(output != NULL)
			fatal("more than one %s option", argv[i]);
		    output = argv[i + 1];
		    i += 1;
		    break;
		default:
		    error("unrecognized option: %s", argv[i]);
		    usage();
		}
	    }
	    else{
		if(input != NULL){
		    fatal("only one input file can be specified");
		    usage();
		}
		input = argv[i];
	    }
	}

	if(input == NULL){
	    error("no input file specified");
	    usage();
	}
	if(replaces != NULL && output == NULL)
	    fatal("output file must be specified via -o <filename> when "
		  "replacing a section");

	if(extracts == NULL && replaces == NULL){
	    error("no -extract or -replace options specified");
	    usage();
	}

	map_input();

	if(extracts != NULL)
	    extract_sections();

	if(replaces != NULL)
	    replace_sections();

	return(0);
}

/*
 * map_input maps the input file into memory.  The address it is mapped at is
 * left in input_addr and the size is left in input_size.  The input file is
 * checked to be an object file and that the headers are checked to be correct
 * enough to loop through them.  The pointer to the mach header is left in mhp
 * and the pointer to the load commands is left in load_commands.
 */
static
void
map_input(void)
{
    int fd;
    uint32_t i, magic, mh_sizeofcmds;
    struct stat stat_buf;
    struct load_command l, *lcp;
    struct segment_command *sgp;
    struct segment_command_64 *sgp64;
    struct section *sp;
    struct section_64 *sp64;
    struct symtab_command *stp;
    struct symseg_command *ssp;

	/* Open the input file and map it in */
	if((fd = open(input, O_RDONLY)) == -1)
	    system_fatal("can't open input file: %s", input);
	if(fstat(fd, &stat_buf) == -1)
	    system_fatal("Can't stat input file: %s", input);
	input_size = stat_buf.st_size;
	input_mode = stat_buf.st_mode;
	input_addr = mmap(0, input_size, PROT_READ|PROT_WRITE,
			  MAP_FILE|MAP_PRIVATE, fd, 0);
	if((intptr_t)input_addr == -1)
	    system_error("Can't map input file: %s", input);
	close(fd);

	if(sizeof(uint32_t) > input_size)
	    fatal("truncated or malformed object (mach header would extend "
		  "past the end of the file) in: %s", input);
	magic = *(uint32_t *)input_addr;
#ifdef __BIG_ENDIAN__
	if(magic == FAT_MAGIC)
#endif /* __BIG_ENDIAN__ */
#ifdef __LITTLE_ENDIAN__
	if(magic == SWAP_INT(FAT_MAGIC))
#endif /* __LITTLE_ENDIAN__ */
	    fatal("file: %s is a fat file (%s only operates on Mach-O files, "
		  "use lipo(1) on it to get a Mach-O file)", input, progname);

	mh_ncmds = 0;
	mh_sizeofcmds = 0;
	host_byte_sex = get_host_byte_sex();
	if(magic == SWAP_INT(MH_MAGIC) || magic == MH_MAGIC){
	    if(sizeof(struct mach_header) > input_size)
		fatal("truncated or malformed object (mach header would extend "
		      "past the end of the file) in: %s", input);
	    mhp = (struct mach_header *)input_addr;
	    if(magic == SWAP_INT(MH_MAGIC)){
		swapped = TRUE;
		target_byte_sex = host_byte_sex == BIG_ENDIAN_BYTE_SEX ?
				  LITTLE_ENDIAN_BYTE_SEX : BIG_ENDIAN_BYTE_SEX;
		swap_mach_header(mhp, host_byte_sex);
	    }
	    else{
		swapped = FALSE;
		target_byte_sex = host_byte_sex;
	    }
	    if(mhp->sizeofcmds + sizeof(struct mach_header) > input_size)
		fatal("truncated or malformed object (load commands would "
		      "extend past the end of the file) in: %s", input);
	    load_commands = (struct load_command *)((char *)input_addr +
				sizeof(struct mach_header));
	    mh_ncmds = mhp->ncmds;
	    mh_sizeofcmds = mhp->sizeofcmds;
	}
	else if(magic == SWAP_INT(MH_MAGIC_64) || magic == MH_MAGIC_64){
	    if(sizeof(struct mach_header_64) > input_size)
		fatal("truncated or malformed object (mach header would extend "
		      "past the end of the file) in: %s", input);
	    mhp64 = (struct mach_header_64 *)input_addr;
	    if(magic == SWAP_INT(MH_MAGIC_64)){
		swapped = TRUE;
		target_byte_sex = host_byte_sex == BIG_ENDIAN_BYTE_SEX ?
				  LITTLE_ENDIAN_BYTE_SEX : BIG_ENDIAN_BYTE_SEX;
		swap_mach_header_64(mhp64, host_byte_sex);
	    }
	    else{
		swapped = FALSE;
		target_byte_sex = host_byte_sex;
	    }
	    if(mhp64->sizeofcmds + sizeof(struct mach_header_64) > input_size)
		fatal("truncated or malformed object (load commands would "
		      "extend past the end of the file) in: %s", input);
	    load_commands = (struct load_command *)((char *)input_addr +
				sizeof(struct mach_header_64));
	    mh_ncmds = mhp64->ncmds;
	    mh_sizeofcmds = mhp64->sizeofcmds;
	}
	else
	    fatal("bad magic number (file is not a Mach-O file) in: %s", input);

	lcp = load_commands;
	for(i = 0; i < mh_ncmds; i++){
	    l = *lcp;
	    if(swapped)
		swap_load_command(&l, host_byte_sex);
	    if(l.cmdsize % sizeof(uint32_t) != 0)
		error("load command %u size not a multiple of "
		      "sizeof(uint32_t) in: %s", i, input);
	    if(l.cmdsize <= 0)
		fatal("load command %u size is less than or equal to zero "
		      "in: %s", i, input);
	    if((char *)lcp + l.cmdsize >
	       (char *)load_commands + mh_sizeofcmds)
		fatal("load command %u extends past end of all load commands "
		      "in: %s", i, input);
	    switch(l.cmd){
	    case LC_SEGMENT:
		sgp = (struct segment_command *)lcp;
		sp = (struct section *)((char *)sgp +
					sizeof(struct segment_command));
		if(swapped)
		    swap_segment_command(sgp, host_byte_sex);
		if(swapped)
		    swap_section(sp, sgp->nsects, host_byte_sex);
		break;
	    case LC_SEGMENT_64:
		sgp64 = (struct segment_command_64 *)lcp;
		sp64 = (struct section_64 *)((char *)sgp64 +
					sizeof(struct segment_command_64));
		if(swapped)
		    swap_segment_command_64(sgp64, host_byte_sex);
		if(swapped)
		    swap_section_64(sp64, sgp64->nsects, host_byte_sex);
		break;
	    case LC_SYMTAB:
		stp = (struct symtab_command *)lcp;
		if(swapped)
		    swap_symtab_command(stp, host_byte_sex);
		break;
	    case LC_SYMSEG:
		ssp = (struct symseg_command *)lcp;
		if(swapped)
		    swap_symseg_command(ssp, host_byte_sex);
		break;
	    default:
		*lcp = l;
		break;
	    }
	    lcp = (struct load_command *)((char *)lcp + l.cmdsize);
	}
}

/*
 * This routine extracts the sections in the extracts list from the input file
 * and writes then to the file specified in the list.
 */
static
void
extract_sections(void)
{
    uint32_t i, j, errors;
    struct load_command *lcp;
    struct segment_command *sgp;
    struct segment_command_64 *sgp64;
    struct section *sp;
    struct section_64 *sp64;
    struct extract *ep;

	lcp = load_commands;
	for(i = 0; i < mh_ncmds; i++){
	    if(lcp->cmd == LC_SEGMENT){
		sgp = (struct segment_command *)lcp;
		sp = (struct section *)((char *)sgp +
					sizeof(struct segment_command));
		for(j = 0; j < sgp->nsects; j++){
		    extract_section(sp->segname, sp->sectname, sp->flags,
				    sp->offset, sp->size);
		    sp++;
		}
	    }
	    else if(lcp->cmd == LC_SEGMENT_64){
		sgp64 = (struct segment_command_64 *)lcp;
		sp64 = (struct section_64 *)((char *)sgp64 +
					sizeof(struct segment_command_64));
		for(j = 0; j < sgp64->nsects; j++){
		    extract_section(sp64->segname, sp64->sectname, sp64->flags,
				    sp64->offset, sp64->size);
		    sp64++;
		}
	    }
	    lcp = (struct load_command *)((char *)lcp + lcp->cmdsize);
	}

	errors = 0;
	ep = extracts;
	while(ep != NULL){
	    if(ep->found == 0){
		error("section (%s,%s) not found in: %s", ep->segname,
		      ep->sectname, input);
		errors = 1;
	    }
	    ep = ep->next;
	}
	if(errors != 0)
	    exit(1);
}

static
void
extract_section(
char *segname,
char *sectname,
uint32_t flags,
uint32_t offset,
uint32_t size)
{
    struct extract *ep;
    int fd;

	ep = extracts;
	while(ep != NULL){
	    if(ep->found == 0 &&
	       strncmp(ep->segname, segname, 16) == 0 &&
	       strncmp(ep->sectname, sectname, 16) == 0){
		if(flags == S_ZEROFILL || flags == S_THREAD_LOCAL_ZEROFILL)
		    fatal("meaningless to extract zero fill "
			  "section (%s,%s) in: %s", segname,
			  sectname, input);
		if(offset + size > input_size)
		    fatal("truncated or malformed object (section "
			  "contents of (%s,%s) extends past the "
			  "end of the file) in: %s", segname,
			  sectname, input);
		 if((fd = open(ep->filename, O_WRONLY | O_CREAT |
			       O_TRUNC, 0666)) == -1)
		    system_fatal("can't create: %s", ep->filename);
		 if(write(fd, (char *)input_addr + offset,
			 size) != (int)size)
		    system_fatal("can't write: %s", ep->filename);
		 if(close(fd) == -1)
		    system_fatal("can't close: %s", ep->filename);
		 ep->found = 1;
	    }
	    ep = ep->next;
	}
}

static
void
replace_sections(void)
{
    uint32_t i, j, k, l, errors, nsegs, nsects, high_reloc_seg;
    uint32_t low_noreloc_seg, high_noreloc_seg, low_linkedit;
    uint32_t oldoffset, newoffset, oldsectsize, newsectsize;
    uint64_t oldvmaddr, newvmaddr;
    struct load_command lc, *lcp;
    struct segment_command *sgp, *linkedit_sgp;
    struct segment_command_64 *sgp64, *linkedit_sgp64;
    struct section *sp;
    struct section_64 *sp64;
    struct symtab_command *stp;
    struct symseg_command *ssp;
    struct replace *rp;
    struct stat stat_buf;
    int outfd, sectfd;
    char *sect_addr;
    vm_address_t pad_addr;
    uint32_t size;
    kern_return_t r;

	errors = 0;

	high_reloc_seg = 0;
	low_noreloc_seg = input_size;
	high_noreloc_seg = 0;
	low_linkedit = input_size;

	nsegs = 0;
	segs = allocate(mh_ncmds * sizeof(struct rep_seg));
	bzero(segs, mh_ncmds * sizeof(struct rep_seg));
	nsects = 0;

	stp = NULL;
	ssp = NULL;
	linkedit_sgp = NULL;
	linkedit_sgp64 = NULL;

	/*
	 * First pass over the load commands and determine if the file is laided
	 * out in an order that the specified sections can be replaced.  Also
	 * determine if the specified sections exist in the input file and if
	 * it is marked with no relocation so it can be replaced. 
	 */
	lcp = load_commands;
	for(i = 0; i < mh_ncmds; i++){
	    switch(lcp->cmd){
	    case LC_SEGMENT:
		sgp = (struct segment_command *)lcp;
		sp = (struct section *)((char *)sgp +
					sizeof(struct segment_command));
		segs[nsegs++].sgp = sgp;
		nsects += sgp->nsects;
		if(strcmp(sgp->segname, SEG_LINKEDIT) != 0){
		    if(sgp->flags & SG_NORELOC){
			if(sgp->filesize != 0){
			    if(sgp->fileoff + sgp->filesize > high_noreloc_seg)
				high_noreloc_seg = sgp->fileoff + sgp->filesize;
			    if(sgp->fileoff < low_noreloc_seg)
				low_noreloc_seg = sgp->fileoff;
			}
		    }
		    else{
			if(sgp->filesize != 0 &&
			   sgp->fileoff + sgp->filesize > high_reloc_seg)
			    high_reloc_seg = sgp->fileoff + sgp->filesize;
		    }
		}
		else{
		    if(linkedit_sgp != NULL)
			fatal("more than one " SEG_LINKEDIT " segment found "
			      "in: %s", input);
		    linkedit_sgp = sgp;
		}
		for(j = 0; j < sgp->nsects; j++){
		    if(sp->nreloc != 0 && sp->reloff < low_linkedit)
			low_linkedit = sp->reloff;
		    search_for_replace_section(sp->segname, sp->sectname,
    			sgp->flags, sp->flags, sp->offset, sp->size);
		    sp++;
		}
		break;
	    case LC_SEGMENT_64:
		sgp64 = (struct segment_command_64 *)lcp;
		sp64 = (struct section_64 *)((char *)sgp64 +
					sizeof(struct segment_command_64));
		segs[nsegs++].sgp64 = sgp64;
		nsects += sgp64->nsects;
		if(strcmp(sgp64->segname, SEG_LINKEDIT) != 0){
		    if(sgp64->flags & SG_NORELOC){
			if(sgp64->filesize != 0){
			    if(sgp64->fileoff + sgp64->filesize >
			       high_noreloc_seg)
				high_noreloc_seg = sgp64->fileoff +
						   sgp64->filesize;
			    if(sgp64->fileoff < low_noreloc_seg)
				low_noreloc_seg = sgp64->fileoff;
			}
		    }
		    else{
			if(sgp64->filesize != 0 &&
			   sgp64->fileoff + sgp64->filesize > high_reloc_seg)
			    high_reloc_seg = sgp64->fileoff + sgp64->filesize;
		    }
		}
		else{
		    if(linkedit_sgp64 != NULL)
			fatal("more than one " SEG_LINKEDIT " segment found "
			      "in: %s", input);
		    linkedit_sgp64 = sgp64;
		}
		for(j = 0; j < sgp64->nsects; j++){
		    if(sp64->nreloc != 0 && sp64->reloff < low_linkedit)
			low_linkedit = sp64->reloff;
		    search_for_replace_section(sp64->segname, sp64->sectname,
    			sgp64->flags, sp64->flags, sp64->offset, sp64->size);
		    sp64++;
		}
		break;
	    case LC_SYMTAB:
		if(stp != NULL)
		    fatal("more than one symtab_command found in: %s", input);
		stp = (struct symtab_command *)lcp;
		if(stp->nsyms != 0 && stp->symoff < low_linkedit)
		    low_linkedit = stp->symoff;
		if(stp->strsize != 0 && stp->stroff < low_linkedit)
		    low_linkedit = stp->stroff;
		break;
	    case LC_DYSYMTAB:
		fatal("current limitation, can't process files with "
		      "LC_DYSYMTAB load command as in: %s", input);
		break;
	    case LC_SYMSEG:
		if(ssp != NULL)
		    fatal("more than one symseg_command found in: %s", input);
		ssp = (struct symseg_command *)lcp;
		if(ssp->size != 0 && ssp->offset < low_linkedit)
		    low_linkedit = ssp->offset;
		break;
	    case LC_THREAD:
	    case LC_UNIXTHREAD:
	    case LC_LOADFVMLIB:
	    case LC_IDFVMLIB:
	    case LC_IDENT:
	    case LC_FVMFILE:
	    case LC_PREPAGE:
	    case LC_LOAD_DYLIB:
	    case LC_LOAD_WEAK_DYLIB:
	    case LC_REEXPORT_DYLIB:
	    case LC_LOAD_UPWARD_DYLIB:
	    case LC_ID_DYLIB:
	    case LC_LOAD_DYLINKER:
	    case LC_ID_DYLINKER:
	    case LC_DYLD_ENVIRONMENT:
		break;
	    default:
		error("unknown load command %u (result maybe bad)", i);
		break;
	    }
	    lcp = (struct load_command *)((char *)lcp + lcp->cmdsize);
	}
	rp = replaces;
	while(rp != NULL){
	    if(rp->found == 0){
		error("section (%s,%s) not found in: %s", rp->segname,
		      rp->sectname, input);
		errors = 1;
	    }
	    else{
		if(stat(rp->filename, &stat_buf) == -1){
		    system_error("Can't stat file: %s to replace section "
				 "(%s,%s) with", rp->filename, rp->segname,
				 rp->sectname);
		    errors = 1;
		}
		rp->size = stat_buf.st_size;
	    }
	    rp = rp->next;
	}
	if(errors != 0)
	    exit(1);

	if(high_reloc_seg > low_noreloc_seg ||
	   high_reloc_seg > low_linkedit ||
	   high_noreloc_seg > low_linkedit)
	    fatal("contents of input file: %s not in an order that the "
		  "specified sections can be replaced by this program", input);

	qsort(segs, nsegs, sizeof(struct rep_seg),
	      (int (*)(const void *, const void *))cmp_qsort);

	sects = allocate(nsects * sizeof(struct rep_sect));
	bzero(sects, nsects * sizeof(struct rep_sect));

	/*
	 * First go through the segments and adjust the segment offsets, sizes
	 * and addresses without adjusting the offset to the relocation entries.
	 * This program can only handle object files that have contigious
	 * address spaces starting at zero and that the offsets in the file for
	 * the contents of the segments also being contiguious and in the same
	 * order as the vmaddresses.
	 */
	oldvmaddr = 0;
	newvmaddr = 0;
	if(nsegs > 1){
	    if(segs[0].sgp != NULL)
		oldoffset = segs[0].sgp->fileoff;
	    else
		oldoffset = segs[0].sgp64->fileoff;
	}
	else
	    oldoffset = 0;
	newoffset = 0;
	k = 0;
	for(i = 0; i < nsegs; i++){
	    if(segs[i].sgp != NULL){
		if(segs[i].sgp->vmaddr != oldvmaddr)
		    fatal("addresses of input file: %s not in an order that "
			  "the specified sections can be replaced by this "
			  "program", input);
		segs[i].filesize = segs[i].sgp->filesize;
		segs[i].vmsize = segs[i].sgp->vmsize;
		segs[i].sgp->vmaddr = newvmaddr;
		if(segs[i].sgp->filesize != 0){
		    if(segs[i].sgp->fileoff != oldoffset)
			fatal("segment offsets of input file: %s not in an "
			      "order that the specified sections can be "
			      "replaced by this program", input);
		    segs[i].fileoff = segs[i].sgp->fileoff;
		    if(strcmp(segs[i].sgp->segname, SEG_LINKEDIT) != 0 ||
		       i != nsegs - 1)
			segs[i].sgp->fileoff = newoffset;
		    sp = (struct section *)((char *)(segs[i].sgp) +
					    sizeof(struct segment_command));
		    oldsectsize = 0;
		    newsectsize = 0;
		    if(segs[i].sgp->flags & SG_NORELOC){
			for(j = 0; j < segs[i].sgp->nsects; j++){
			    sects[k + j].sp = sp;
			    sects[k + j].offset = sp->offset;
			    oldsectsize += sp->size;
			    rp = replaces;
			    while(rp != NULL){
				if(strncmp(rp->segname, sp->segname,
					   sizeof(sp->segname)) == 0 &&
				   strncmp(rp->sectname, sp->sectname,
					   sizeof(sp->sectname)) == 0){
				    sects[k + j].rp = rp;
				    segs[i].modified = 1;
				    sp->size = rnd(rp->size, 1 << sp->align);
				    break;
				}
				rp = rp->next;
			    }
			    sp->offset = newoffset + newsectsize;
			    sp->addr   = newvmaddr + newsectsize;
			    newsectsize += sp->size;
			    sp++;
			}
			if(strcmp(segs[i].sgp->segname, SEG_LINKEDIT) != 0 ||
			   i != nsegs - 1){
			    if(segs[i].sgp->filesize != rnd(oldsectsize,
							      pagesize))
				fatal("contents of input file: %s not in a "
				      "format that the specified sections can "
				      "be replaced by this program", input);
			    segs[i].sgp->filesize =
				rnd(newsectsize, pagesize);
			    segs[i].sgp->vmsize = rnd(newsectsize, pagesize);
			    segs[i].padsize =
				segs[i].sgp->filesize  - newsectsize;
			}
		    }
		    if(strcmp(segs[i].sgp->segname, SEG_LINKEDIT) != 0 ||
		       i != nsegs - 1){
			oldoffset += segs[i].filesize;
			newoffset += segs[i].sgp->filesize;
		    }
		}
		oldvmaddr += segs[i].vmsize;
		newvmaddr += segs[i].sgp->vmsize;
		k += segs[i].sgp->nsects;
	    }
	    else{
		if(segs[i].sgp64->vmaddr != oldvmaddr)
		    fatal("addresses of input file: %s not in an order that "
			  "the specified sections can be replaced by this "
			  "program", input);
		segs[i].filesize = segs[i].sgp64->filesize;
		segs[i].vmsize = segs[i].sgp64->vmsize;
		segs[i].sgp64->vmaddr = newvmaddr;
		if(segs[i].sgp64->filesize != 0){
		    if(segs[i].sgp64->fileoff != oldoffset)
			fatal("segment offsets of input file: %s not in an "
			      "order that the specified sections can be "
			      "replaced by this program", input);
		    segs[i].fileoff = segs[i].sgp64->fileoff;
		    if(strcmp(segs[i].sgp64->segname, SEG_LINKEDIT) != 0 ||
		       i != nsegs - 1)
			segs[i].sgp64->fileoff = newoffset;
		    sp = (struct section *)((char *)(segs[i].sgp) +
					    sizeof(struct segment_command));
		    oldsectsize = 0;
		    newsectsize = 0;
		    if(segs[i].sgp64->flags & SG_NORELOC){
			for(j = 0; j < segs[i].sgp64->nsects; j++){
			    sects[k + j].sp = sp;
			    sects[k + j].offset = sp->offset;
			    oldsectsize += sp->size;
			    rp = replaces;
			    while(rp != NULL){
				if(strncmp(rp->segname, sp->segname,
					   sizeof(sp->segname)) == 0 &&
				   strncmp(rp->sectname, sp->sectname,
					   sizeof(sp->sectname)) == 0){
				    sects[k + j].rp = rp;
				    segs[i].modified = 1;
				    sp->size = rnd(rp->size, 1 << sp->align);
				    break;
				}
				rp = rp->next;
			    }
			    sp->offset = newoffset + newsectsize;
			    sp->addr   = newvmaddr + newsectsize;
			    newsectsize += sp->size;
			    sp++;
			}
			if(strcmp(segs[i].sgp64->segname, SEG_LINKEDIT) != 0 ||
			   i != nsegs - 1){
			    if(segs[i].sgp64->filesize != rnd(oldsectsize,
							      pagesize))
				fatal("contents of input file: %s not in a "
				      "format that the specified sections can "
				      "be replaced by this program", input);
			    segs[i].sgp64->filesize =
				rnd(newsectsize, pagesize);
			    segs[i].sgp64->vmsize =
				rnd(newsectsize, pagesize);
			    segs[i].padsize =
				segs[i].sgp64->filesize  - newsectsize;
			}
		    }
		    if(strcmp(segs[i].sgp64->segname, SEG_LINKEDIT) != 0 ||
		       i != nsegs - 1){
			oldoffset += segs[i].filesize;
			newoffset += segs[i].sgp64->filesize;
		    }
		}
		oldvmaddr += segs[i].vmsize;
		newvmaddr += segs[i].sgp64->vmsize;
		k += segs[i].sgp64->nsects;
	    }
	}

	/*
	 * Now update the offsets to the linkedit information.
	 */
	if(oldoffset != low_linkedit)
	    fatal("contents of input file: %s not in an order that the "
		  "specified sections can be replaced by this program", input);
	for(i = 0; i < nsegs; i++){
	    if(segs[i].sgp != NULL){
		sp = (struct section *)((char *)(segs[i].sgp) +
					sizeof(struct segment_command));
		for(j = 0; j < segs[i].sgp->nsects; j++){
		    if(sp->nreloc != 0)
			sp->reloff += newoffset - oldoffset;
		    sp++;
		}
	    }
	    else{
		sp64 = (struct section_64 *)((char *)(segs[i].sgp64) +
					sizeof(struct segment_command_64));
		for(j = 0; j < segs[i].sgp64->nsects; j++){
		    if(sp64->nreloc != 0)
			sp64->reloff += newoffset - oldoffset;
		    sp64++;
		}
	    }
	}
	if(stp != NULL){
	    if(stp->nsyms != 0)
		stp->symoff += newoffset - oldoffset;
	    if(stp->strsize != 0)
		stp->stroff += newoffset - oldoffset;
	}
	if(ssp != NULL){
	    if(ssp->size != 0)
		ssp->offset += newoffset - oldoffset;
	}
	if(linkedit_sgp != NULL){
	    linkedit_sgp->fileoff += newoffset - oldoffset;
	}
	if(linkedit_sgp64 != NULL){
	    linkedit_sgp64->fileoff += newoffset - oldoffset;
	}

	/*
	 * Now write the new file by writing the header and modified load
	 * commands, then the segments with any new sections and finally
	 * the link edit info.
	 */
	if((outfd = open(output, O_CREAT | O_WRONLY | O_TRUNC ,input_mode)) 
	   == -1)
	    system_fatal("can't create output file: %s", output);

	if((r = vm_allocate(mach_task_self(), &pad_addr, pagesize, 1)) !=
	   KERN_SUCCESS)
	    mach_fatal(r, "vm_allocate() failed");

	k = 0;
	for(i = 0; i < nsegs; i++){
	    if(segs[i].modified){
		if(segs[i].sgp != NULL){
		    for(j = 0; j < segs[i].sgp->nsects; j++){
			/* if the section is replaced write the replaced
			   section */
			sp = sects[k + j].sp;
			rp = sects[k + j].rp;
			if(rp != NULL){
			    if((sectfd = open(rp->filename, O_RDONLY)) == -1)
				system_fatal("can't open file: %s to replace "
					 "section (%s,%s) with", rp->filename,
					 rp->segname, rp->sectname);
			    sect_addr = mmap(0, rp->size, PROT_READ|PROT_WRITE,
					      MAP_FILE|MAP_PRIVATE, sectfd, 0);
			    if((intptr_t)sect_addr == -1)
				system_error("Can't map file: %s",rp->filename);
			    for(l = rp->size + 1; l < sp->size; l++)
				*((char *)sect_addr + l) = '\0';
			    if(write(outfd, (char *)sect_addr,sp->size) !=
			       sp->size)
				system_fatal("can't write new section contents "
				    "for section (%s,%s) to output file: %s", 
				     rp->segname, rp->sectname, output);
			    if(close(sectfd) == -1)
				system_error("can't close file: %s to replace "
					 "section (%s,%s) with", rp->filename,
					 rp->segname, rp->sectname);
			    if((r = vm_deallocate(mach_task_self(),
						  (vm_address_t)sect_addr,
						  rp->size)) != KERN_SUCCESS)
				mach_fatal(r, "Can't deallocate memory for "
				   "mapped file: %s", rp->filename);
			}
			else{
			    /* write the original section */
			    if(sects[k + j].offset + sp->size > input_size)
				fatal("truncated or malformed object file: %s "
				      "(section (%.16s,%.16s) extends past the "
				      "end of the file)",input, sp->segname,
				      sp->sectname);
			    if(write(outfd,(char *)input_addr +
					   sects[k + j].offset,
			             sp->size) != sp->size)
				system_fatal("can't write section contents for "
					 "section (%s,%s) to output file: %s", 
					 rp->segname, rp->sectname, output);
			}
			sp++;
		    }
		    /* write the segment padding */
		    if(write(outfd, (char *)pad_addr, segs[i].padsize) !=
		       segs[i].padsize)
			system_fatal("can't write segment padding for segment "
			    "%s to output file: %s", segs[i].sgp->segname,
			     output);
		}
		else{
		    for(j = 0; j < segs[i].sgp64->nsects; j++){
			/* if the section is replaced write the replaced
			   section */
			sp64 = sects[k + j].sp64;
			rp = sects[k + j].rp;
			if(rp != NULL){
			    if((sectfd = open(rp->filename, O_RDONLY)) == -1)
				system_fatal("can't open file: %s to replace "
					 "section (%s,%s) with", rp->filename,
					 rp->segname, rp->sectname);
			    sect_addr = mmap(0, rp->size, PROT_READ|PROT_WRITE,
					      MAP_FILE|MAP_PRIVATE, sectfd, 0);
			    if((intptr_t)sect_addr == -1)
				system_error("Can't map file: %s",rp->filename);
			    for(l = rp->size + 1; l < sp64->size; l++)
				*((char *)sect_addr + l) = '\0';
			    if(write(outfd, (char *)sect_addr,sp64->size) !=
			       sp64->size)
				system_fatal("can't write new section contents "
				    "for section (%s,%s) to output file: %s", 
				     rp->segname, rp->sectname, output);
			    if(close(sectfd) == -1)
				system_error("can't close file: %s to replace "
					 "section (%s,%s) with", rp->filename,
					 rp->segname, rp->sectname);
			    if((r = vm_deallocate(mach_task_self(),
						  (vm_address_t)sect_addr,
						  rp->size)) != KERN_SUCCESS)
				mach_fatal(r, "Can't deallocate memory for "
				   "mapped file: %s", rp->filename);
			}
			else{
			    /* write the original section */
			    if(sects[k + j].offset + sp64->size > input_size)
				fatal("truncated or malformed object file: %s "
				      "(section (%.16s,%.16s) extends past the "
				      "end of the file)",input, sp64->segname,
				      sp64->sectname);
			    if(write(outfd,(char *)input_addr +
					   sects[k + j].offset,
			             sp64->size) != sp64->size)
				system_fatal("can't write section contents for "
					 "section (%s,%s) to output file: %s", 
					 rp->segname, rp->sectname, output);
			}
			sp64++;
		    }
		    /* write the segment padding */
		    if(write(outfd, (char *)pad_addr, segs[i].padsize) !=
		       segs[i].padsize)
			system_fatal("can't write segment padding for segment "
			    "%s to output file: %s", segs[i].sgp64->segname,
			     output);
		}
	    }
	    else{
		/* write the original segment */
		if(segs[i].sgp != NULL){
		    if(strcmp(segs[i].sgp->segname, SEG_LINKEDIT) != 0 ||
		       i != nsegs - 1){
			if(segs[i].fileoff + segs[i].sgp->filesize > input_size)
			    fatal("truncated or malformed object file: %s "
				  "(segment: %s extends past the end of "
				  "the file)", input, segs[i].sgp->segname);
			if(write(outfd, (char *)input_addr + segs[i].fileoff,
			   segs[i].sgp->filesize) != segs[i].sgp->filesize)
			    system_fatal("can't write segment contents for "
					 "segment: %s to output file: %s", 
					 segs[i].sgp->segname, output);
		    }
		}
		else{
		    if(strcmp(segs[i].sgp64->segname, SEG_LINKEDIT) != 0 ||
		       i != nsegs - 1){
			if(segs[i].fileoff + segs[i].sgp64->filesize >
			   input_size)
			    fatal("truncated or malformed object file: %s "
				  "(segment: %s extends past the end of "
				  "the file)", input, segs[i].sgp64->segname);
			if(write(outfd, (char *)input_addr + segs[i].fileoff,
			   segs[i].sgp64->filesize) != segs[i].sgp64->filesize)
			    system_fatal("can't write segment contents for "
					 "segment: %s to output file: %s", 
					 segs[i].sgp64->segname, output);
		    }
		}
	    }
	    if(segs[i].sgp != NULL)
		k += segs[i].sgp->nsects;
	    else
		k += segs[i].sgp64->nsects;
	}
	/* write the linkedit info */
	size = input_size - low_linkedit;
	if(write(outfd, (char *)input_addr + low_linkedit, size) != size)
	    system_fatal("can't write link edit information to output file: %s",
			 output);
	lseek(outfd, 0, L_SET);
	if(mhp != NULL)
	    size = sizeof(struct mach_header) + mhp->sizeofcmds;
	else
	    size = sizeof(struct mach_header_64) + mhp64->sizeofcmds;
	if(swapped){
	    lcp = load_commands;
	    for(i = 0; i < mh_ncmds; i++){
		lc = *lcp;
		switch(lcp->cmd){
		case LC_SEGMENT:
		    sgp = (struct segment_command *)lcp;
		    sp = (struct section *)((char *)sgp +
					    sizeof(struct segment_command));
		    swap_section(sp, sgp->nsects, host_byte_sex);
		    swap_segment_command(sgp, host_byte_sex);
		    break;
		case LC_SEGMENT_64:
		    sgp64 = (struct segment_command_64 *)lcp;
		    sp64 = (struct section_64 *)((char *)sgp64 +
					    sizeof(struct segment_command_64));
		    swap_section_64(sp64, sgp64->nsects, host_byte_sex);
		    swap_segment_command_64(sgp64, host_byte_sex);
		    break;
		case LC_SYMTAB:
		    stp = (struct symtab_command *)lcp;
		    swap_symtab_command(stp, host_byte_sex);
		    break;
		case LC_SYMSEG:
		    ssp = (struct symseg_command *)lcp;
		    swap_symseg_command(ssp, host_byte_sex);
		    break;
		default:
		    swap_load_command(lcp, host_byte_sex);
		    break;
		}
		lcp = (struct load_command *)((char *)lcp + lc.cmdsize);
	    }
	    if(mhp != NULL)
		swap_mach_header(mhp, host_byte_sex);
	    else
		swap_mach_header_64(mhp64, host_byte_sex);
	}
	if(write(outfd, input_addr, size) != size)
	    system_fatal("can't write headers to output file: %s", output);

	if(close(outfd) == -1)
	    system_fatal("can't close output file: %s", output);
}

static
void
search_for_replace_section(
char *segname,
char *sectname,
uint32_t seg_flags,
uint32_t sect_flags,
uint32_t offset,
uint32_t size)
{
    struct replace *rp;

	rp = replaces;
	while(rp != NULL){
	    if(rp->found == 0 &&
	       strncmp(rp->segname, segname, 16) == 0 &&
	       strncmp(rp->sectname, sectname, 16) == 0){
		if(sect_flags == S_ZEROFILL ||
		   sect_flags == S_THREAD_LOCAL_ZEROFILL){
		    error("can't replace zero fill section (%.16s,"
			  "%.16s) in: %s", segname,
			  sectname, input);
		    errors = 1;
		}
		if((seg_flags & SG_NORELOC) == 0){
		    error("can't replace section (%.16s,%.16s) "
			  "in: %s because it requires relocation",
			  segname, sectname, input);
		    errors = 1;
		}
		if(offset + size > input_size)
		    fatal("truncated or malformed object (section "
			  "contents of (%.16s,%.16s) extends "
			  "past the end of the file) in: %s",
			  segname, sectname, input);
		rp->found = 1;
	    }
	    rp = rp->next;
	}
}

/*
 * Function for qsort for comparing segment's vmaddrs
 */
static
int
cmp_qsort(
const struct rep_seg *seg1,
const struct rep_seg *seg2)
{
	if(seg1->sgp != NULL){
	    if(seg1->sgp->vmaddr > seg2->sgp->vmaddr)
		return(1);
	    if(seg1->sgp->vmaddr < seg2->sgp->vmaddr)
		return(-1);
	    /* seg1->sgp->vmaddr == seg2->sgp->vmaddr */
		return(0);
	}
	else{
	    if(seg1->sgp64->vmaddr > seg2->sgp64->vmaddr)
		return(1);
	    if(seg1->sgp64->vmaddr < seg2->sgp64->vmaddr)
		return(-1);
	    /* seg1->sgp64->vmaddr == seg2->sgp64->vmaddr */
		return(0);
	}
}

/*
 * Print the usage message and exit non-zero.
 */
static
void
usage(void)
{
	fprintf(stderr, "Usage: %s <input file> [-extract <segname> <sectname> "
			"<filename>] ...\n\t[[-replace <segname> <sectname> "
			"<filename>] ... -output <filename>]\n", progname);
	exit(1);
}
