ftp.nice.ch/pub/next/unix/admin/qlipo.1.1.s.tar.gz#/qlipo-1.1/qlipo.c

This is qlipo.c in view mode; [Download] [Up]

/* 
 * QLIPO -- NeXT compatible, portable LIPO. 
 *
 * You may not remove this comment header, though you may add to it.
 * You may not remove attributions to the author or contributors, though
 * you may add to them.
 * 
 * Darcy Brockbank <samurai@hasc.ca>
 * Hutchison Ave. Software
 */
#define VERSION "Version 1.1 BETA."
/* 
 * Version 1.0 <BETA>
 * Dec 22, 1994
 * 
 * This program is loosely based on plipo, by Christian Scheider, which
 * served mainly as example code and inspiration that this could be done.
 * The intent of this program is to furnish an upgrade path for people
 * who have broken versions of 'lipo' as under NEXTSTEP 3.2 and previous
 * versions. QLIPO is bug-for-bug compatible with lipo... well, not exactly
 * bug-for-bug, but it does a good job of handling all the arguments you
 * can pass it, while giving some more useful messages than our old
 * buggy friend lipo.
 * 
 * For the most part, this program is portable to any UNIX, though it works
 * only on MACH-O files. At the moment, I'm being very conservative by
 * keeping the byte alignments of the fat files at 8192 byte boundaries. 
 * While this will result in a bit of wasted space, it makes sure that
 * all executables are happy. Future versions will allow for less
 * conservative alignments.
 * 
 * This program is being provided with the hope it will be interesting,
 * and perhaps useful, but no guarantees are made as to it's functionality
 * or usefulness, nor do I or Hutchison Ave. Software warrant it in any
 * other way.
 * 
 * Please keep me informed as to incompatabilities or problems, and I
 * will do my best to fix things. Lots of this code is gross... I'm 
 * sorry about that, but I'm just trying to get it to work. Typical
 * C programmer's excuse. I did write this over the course of 24 hours
 * though, so please be gentle with me.
 *
 * TODO
 *
 * 1) support segalign
 * 2) support multiple -remove flags... I had no idea this could be
 *    specified.
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cpu.h"
#include <math.h>
#include <errno.h>
#include <sys/stat.h>

extern int chmod(const char *path, unsigned mode);

#define PROGRAM_NAME "qlipo"

#define FAT_MAGIC  0xcafebabe
#define NON_FAT_BIG_ENDIAN     0xfeedface
#define NON_FAT_LITTLE_ENDIAN  0xcefaedfe

/* sizeof() will pad the struct */
#define HEADER_BYTE_COUNT 8
typedef struct fat_header 
{
    char magic[4];      /* FAT_MAGIC */
    char nfat_arch[4];  /* number of structs that follow */
} fat_header_t;

/* sizeof() will pad the struct */
#define ARCH_BYTE_COUNT 20
typedef struct fat_arch 
{
    char cputype[4];      /* cpu specifier (int) */
    char cpusubtype[4];   /* machine specifier (int) */
    char offset[4];       /* file offset to this object file */
    char size[4];         /* size of this object file */
    char align[4];        /* alignment as a power of 2 */
} fat_arch_t;

typedef struct non_fat_header 
{
    char magic[4];      /* FAT_MAGIC */
    char cputype[4];    /* cpu specifier (int) */
} non_fat_header_t;

/* types of files */
#define MH_OBJECT 	0x1
#define MH_EXECUTE	0x2
#define	MH_FVMLIB	0x3
#define MH_CORE		0x4
#define MH_PRELOAD	0x5
#define MH_DYLIB	0x6
#define MH_DYLINKER	0x7
#define MH_BUNDLE	0x8

/* flags */
#define MH_NOUNDEFS	0x1
#define MH_INCRLINK	0x2
#define MH_DYLDLINK	0x4

#define LOADER_BYTE_COUNT 28
typedef struct loader
{
    char magic[4];   /* magic number */
    char cputype[4];   /* machine specifier (int) */
    char cpusubtype[4];   /* machine specifier (int) */
    char filetype[4];       /* the kind of file */
    char ncmds[4];         /* size of all the load commands */
    char sizeofcmds[4];         /* size of all the load commands */
    char flags[4];        /* flags */
} loader_t;

#define GET32(x) ((unsigned long)((((unsigned char *)(x))[0] << 24) | \
				  (((unsigned char *)(x))[1] << 16) | \
				  (((unsigned char *)(x))[2] <<  8) | \
				  (((unsigned char *)(x))[3])))

#define GET32L(x) ((unsigned long)((((unsigned char *)(x))[3] << 24) | \
				   (((unsigned char *)(x))[2] << 16) | \
				   (((unsigned char *)(x))[1] <<  8) | \
				   (((unsigned char *)(x))[0])))

#define SET32(a, x) ((((unsigned char *)(a))[0] = (unsigned char )(x >> 24)), \
		     (((unsigned char *)(a))[1] = (unsigned char )(x >> 16)), \
		     (((unsigned char *)(a))[2] = (unsigned char )(x >>  8)), \
		     (((unsigned char *)(a))[3]) = (unsigned char )x)

#define COPY32(d, s) (((char *)(d))[0] = ((char *)(s))[0]), \
    (((char *)(d))[1] = ((char *)(s))[1]), \
    (((char *)(d))[2] = ((char *)(s))[2]), \
    (((char *)(d))[3] = ((char *)(s))[3]);


#define IS_FAT(hdr) (GET32(((hdr)->magic)) == FAT_MAGIC)

#define IS_MAGIC(hdr) (GET32(((hdr)->magic)) == FAT_MAGIC || \
    GET32(((hdr)->magic))==NON_FAT_BIG_ENDIAN || \
    GET32(((hdr)->magic))==NON_FAT_LITTLE_ENDIAN)

    /* 2^13 == 8192 */
#define DEFAULT_ALIGN 13
    
/* 
 * one of the operations we can perform...
 */

typedef enum {
    _info=0,
    _detailed_info,
    _create,
    _thin,
    _replace,
    _remove,
    _extract,
    _arch,	/* non unique */
    _output,	/* non unique */
    _segalign	/* non unique ... and ignored under this version */
} operation_type_t;

typedef struct _operation_t {
    operation_type_t op;
    char * argv[3];
    int argc;
} operation_t;

/* 
 * The switches we're looking for, and their matching arguments.
 */

typedef struct _arg_t {
    const char * name;
    int numargs;
    operation_type_t op;
} arg_t;

/* 
 * Argument parsing aid .
 */
const arg_t argument_set[] = {
    {"-info",0,_info},
    {"-detailed_info",0,_detailed_info},
    {"-arch",2,_arch},	
    {"-output",1,_output},
    {"-o",1,_output},
    {"-create",0,_create},
    {"-thin",1,_thin},
    {"-replace",2,_replace},
    {"-remove",1,_remove},
    {"-extract",1,_extract},
    {"-segalign",2,_segalign},
    {0}
};

/* 
 * Filename and association.
 */
typedef struct _filelist_t {
    char * filename;	/* our name */
    int cputype;	/* our cpu */
    int cpusubtype;	/* and subtype */
    off_t size;		/* our size */
    long start;		/* which fat arch we own in the list */
    int isfat;		/* are we fat? */
    int skip;		/* an arch to skip */
    int suck;		/* the only arch to keep */
    int narch;		/* the number of architectures */
    unsigned long mode; /* the permissions */
} filelist_t;

/* 
 * A list of named input files. Null terminated, and counted for your
 * convenience.
 */
filelist_t ** input_files=0;
int input_file_count=0;

/*
 * An output file.
 */
filelist_t output_file={0,CPU_TYPE_ANY,0};

/*
 * A buffer for everyone to play in.
 */
#define BUFFER_SIZE 8192
static char buffer[BUFFER_SIZE];

#define DEBUG
#ifdef DEBUG
static int ArgC=0;
static char ** ArgV=0;
static void
quit(int flag)
{
    if (flag){
	int i;
	fprintf(stderr,"ArgV: qlipo ");
	for(i=1;i<ArgC;i++){
		fprintf(stderr,"%s ",ArgV[i]);
	}
	fprintf(stderr,"\n");
    }
    exit(flag);
}
#else
#  define quit(flag) exit(flag)
#endif


		
static void
usage(void)
{
    fprintf(stderr,
	    "qlipo: NeXT compatible lipo, by Darcy Brockbank <samurai@hasc.ca>\n"
	    "       based on 'plipo' by Christian Schneider. " VERSION "\n"
	    "Note:  ONLY one of -create, -thin <arch_type>, -extract <arch_type>,\n"
	    "       -remove <arch_type>, -replace <arch_type> <file_name>, -info \n"
	    "       or -detailed_info must be specified.\n"
	    "Usage: qlipo [input_file] ... [-arch <arch_type> input_file] ...\n"
	    "       [-info] [-detailed_info] [-output output_file] [-create]\n"
	    "       [-thin <arch_type>] [-extract <arch_type] ... [-remove <arch_type>]\n"
	    "       ... [-replace <arch_type> <file_name>] ...\n");
    quit(1);
}

static void 
stat_file(int which)
{
    const char * filename = input_files[which]->filename;
    struct stat sb;
    stat(filename,&sb);
    input_files[which]->size = sb.st_size;
    input_files[which]->mode = sb.st_mode;
}

/*
 * Returns a structure representing the switch in the arglist, and what
 * it is. If it's nothing (ie. a filename) then returns NULL.
 */
const arg_t *
switch_at(char *argv[],int i)
{
    const arg_t *op;
    for(op=argument_set;op->name;op++){
	if (strcmp(op->name,argv[i])==0){
	    return op;
	}
    }
    return 0;
}

/* 
 * Could expand this to check for syntax, but not right now
 */

int 
switch_argc(const arg_t *op)
{
    if (!op) return 0;
    return op->numargs;
}

static void
grok_fileinfo(void)
{
    FILE * file;
    long i;
    loader_t header;
    if (!input_file_count){
	fprintf(stderr,"%s: No input files specified.\n", PROGRAM_NAME);
	usage();
    }
    for(i=0;i<input_file_count;i++){
	file = fopen(input_files[i]->filename,"r");
	if (!file){
	    fprintf(stderr,"%s: Can't open input file \"%s\", (%s)\n",
		    PROGRAM_NAME, input_files[i]->filename,strerror(errno));
	    quit(1);
	}
	if (fread(&header,1,LOADER_BYTE_COUNT,file)==LOADER_BYTE_COUNT){
	    switch(GET32(header.magic)){
	    case NON_FAT_BIG_ENDIAN:
		input_files[i]->cputype=GET32(header.cputype);
		input_files[i]->cpusubtype=GET32(header.cpusubtype);
		input_files[i]->narch = 1;
		break;
	    case NON_FAT_LITTLE_ENDIAN:
		input_files[i]->cputype=GET32L(header.cputype);
		input_files[i]->cpusubtype=GET32L(header.cpusubtype);
		input_files[i]->narch = 1;
		break;
	    case FAT_MAGIC:
		input_files[i]->isfat=1;
		input_files[i]->narch = GET32(((fat_header_t *)&header)->nfat_arch);
		break;
	    default:
		/* ignore it */
		break;
	    }
	}
	stat_file(i);
	fclose(file);
    }
    output_file.mode = input_files[0]->mode;
}

/* 
 * Find the operation. If we've already found it, then we should quit.
 */
static operation_t *
grok_operation(int argc, char *argv[])
{
    int i;
    const arg_t *found=0;
    const arg_t *current=0;
    int spot=0;
    int numargs;
    operation_t * perform = calloc(sizeof(operation_t),1);
    for(i=1;i<argc;i++){
	if ((current = switch_at(argv,i))){
	    switch (current->op){
	    case _segalign:
	    case _output:
	    case _arch:
		/* ignore */
		break;
	    default:
		if (found){
		    usage();
		} else {
		    found=current;
		    spot=i;
		}
	    }
	}
    }
    if (!found) usage();
    perform->op = found->op;
    numargs = switch_argc(found);
    for(i=0;i<numargs;i++){
	if (spot+i+1>=argc){
	    usage();
	}
	perform->argv[i]=argv[spot+i+1];
    }
    perform->argv[i]=0;
    perform->argc=i;
    return perform;
}
	
static void 
add_file(char *filename, long type)
{
    filelist_t * new = calloc(sizeof(filelist_t),1);
    new->filename = filename;
    new->cputype = type;
    input_files[input_file_count++]=new;
}

/*
 * Input filenames stand alone, or are preceded with -arch
 */
static void
grok_infiles(int argc, char *argv[])
{
    int i;
    const arg_t *arg;
    /* we may need to add one under a -replace operation */
    input_files = calloc(sizeof(filelist_t *),argc+1); 
    
    for(i=1;i<argc;i+=switch_argc(arg)+1){
	arg = switch_at(argv,i);
	if (!arg || arg->op == _arch){
	    if ((arg && i>=argc-2) || i>=argc) usage();
	    add_file(argv[(i)+((arg)?2:0)],cpu_type((arg)?argv[i+1]:0));
	}
    }
}
	
static void
grok_outfile(int argc, char *argv[])
{
    int i;
    for(i=1;i<argc;i++){
	const arg_t *arg = switch_at(argv,i);
	if (arg && arg->op == _output){
		if (output_file.filename || i>=argc-1) usage();
		output_file.filename=argv[i+1];
	}
    }
}

static void
grok_myargs(int argc, char *argv[])
{
    int i;
    for(i=1;i<argc;i++){
	if (strcmp(argv[i],"-help")==0 ||
	    strcmp(argv[i],"--help")==0 ||
	    strcmp(argv[i],"-version")==0 ||
	    strcmp(argv[i],"--version")==0 ||
	    strcmp(argv[i],"-v")==0 ||
	    strcmp(argv[i],"-h")==0)
	{
	    usage();
	}
    }
}
	
    /* 
     * We want to find a unique operation, build an input filelist
     * and figure out the output file. I could do it efficiently
     * by building a hashtable, etc, but this is OK because the
     * data set is so tiny.
     */
static operation_t *
grok_args(int argc, char *argv[])
{
    operation_t *ret;
    if (argc<=1) {
	usage();
    }
    grok_myargs(argc,argv);
    grok_infiles(argc,argv);
    grok_outfile(argc,argv);
    ret = grok_operation(argc,argv);
    grok_fileinfo();
    return ret;
}

static fat_arch_t *
fatgetarch(char *mem, int cpu)
{
    fat_header_t *fh = (fat_header_t *)mem;
    fat_arch_t *fa;

    if (fh && (GET32(fh->magic) == FAT_MAGIC))
    {
	unsigned long num;
	fa = (fat_arch_t *)(fh + 1);
    
	for (num = GET32(fh->nfat_arch); num > 0; num--, fa++)
	{
	    if ((cpu == CPU_TYPE_ANY) || (GET32(fa->cputype) == cpu))
		return fa;
	}
    }
    return (fat_arch_t *)0;
}

static unsigned long 
fatgetoffset(char *mem, int cpu)
{
    fat_arch_t *fa, *fa0;

    if ((fa0 = fatgetarch(mem, CPU_TYPE_ANY)) && (fa = fatgetarch(mem, cpu)))
	return GET32(fa->offset);
    else
	return 0;
}

static unsigned long 
fatgetsize(char *mem, int cpu)
{
    fat_arch_t *fa;
  
    if ((fa = fatgetarch(mem, cpu)))
	return GET32(fa->size);
    else
	return 0;
}

static void 
fatsave(char *file, char *mem, int cpu)
{
    FILE *out;
    fat_arch_t *fa, *fa0;
  
    if (file && ((out = fopen(file, "w")) != NULL))
    {
	if ((fa0 = fatgetarch(mem, CPU_TYPE_ANY)) && (fa = fatgetarch(mem, cpu)))
	{
	    char num[4], msg[] = "\nExtracted with qlipo\n";
	    unsigned long t;
      
	    fwrite(mem, 4, 1, out);
	    SET32(num, 1);
	    fwrite(num, sizeof(num), 1, out);
	    t = GET32(fa->offset);
	    COPY32(fa->offset, fa0->offset);
	    fwrite(fa, sizeof(fat_arch_t), (size_t)1, out);
	    SET32(fa->offset, t);
	    fwrite(msg, sizeof(msg), 1, out);
	    fwrite(mem, (size_t)(GET32(fa0->offset) - sizeof(msg) -
				 sizeof(fat_arch_t) - 4 - 4), 1, out);
	    fwrite(mem + fatgetoffset(mem, cpu),
		   (size_t)fatgetsize(mem, cpu), 1, out);
	}
    
	fclose(out);
    }
}

static char *
fatload(char *filename)
{
    FILE *file = stdin;
    char *mem = NULL;
    long old, size = 16384;
  
    if (filename && (!strcmp(filename, "-") || (file = fopen(filename, "r"))))
    {
	for (mem = (char *)malloc((size_t)size), old = 0;
	     mem = (char *)realloc(mem, (size_t)size);
	     old += (size - old), size *= 2)
	{
	    if (fread(mem + old, 1, (size_t)(size - old), file) < (size - old))
	    {
		if (ferror(file))
		{
		    free(mem);
		    mem = NULL;
		}
		break;
	    }
	}
	fclose(file);
    }
    return mem;
}

static long
type_from_header(non_fat_header_t *nfh)
{
    long ct = CPU_TYPE_ANY;
    if (GET32(nfh->magic) == NON_FAT_BIG_ENDIAN)
    {
	ct = (int)GET32(nfh->cputype);
    } else if (GET32(nfh->magic) == NON_FAT_LITTLE_ENDIAN)
    {
	ct = (int)GET32L(nfh->cputype);
    }
    return ct;
}      

static long
type_from_fat_arch(fat_arch_t *fa)
{
    return GET32(fa->cputype);
}

static long
byte_alignment(fat_arch_t *fa)
{
    return pow(2,GET32(fa->align));
}

static void 
fatanalyse(char *mem, const char *filename)
{
    fat_header_t *fh = (fat_header_t *)mem;
    non_fat_header_t *nfh = (non_fat_header_t *)mem;
    int ct = -1;

    if (fh)
    {
	if (GET32(fh->magic) == FAT_MAGIC)
	{
	    long num = GET32(fh->nfat_arch);
	    fat_arch_t *fa = (fat_arch_t *)(fh + 1);
    
	    printf("Architectures (%ld) in the fat file: \"%s\" are:", num, filename);
	    for ( ; num > 0; fa++, num--)
	    {
		int ct = (int)GET32(fa->cputype);
      
		if ((ct < -1) || (ct >= (sizeof(cpus) / sizeof(char *))))
		    ct = 1;
		printf(" %s", cpus[ct + 1]);
	    }
	    printf("\n");
	} else {
	    ct = type_from_header(nfh);
	    if (ct >= 0) {
		printf("Non-fat file: \"%s\" is architecture (%d)", filename,ct);
		if ((ct < -1) || (ct >= (sizeof(cpus) / sizeof(char *))))
		    ct = 1;
		printf(": %s\n", cpus[ct + 1]);
	    } else {
		printf("unknown file type (magic: %08x)\n", (unsigned)GET32(fh->magic)); 
	    }
	}
    }
}

static void 
info(void)
{
    char * mem;
    int i;
    for(i=0;input_files[i];i++){
	if ((mem = fatload(input_files[i]->filename)))
	{
	    fatanalyse(mem,input_files[i]->filename);
	    free(mem);
	}
    }
}

static void
fatprint_header(fat_header_t *header, const char *filename)
{
    if (IS_FAT(header)){
	printf("%s: Fat header in: \"%s\"\n"
	       "fat_magic 0x%lx\n"
	       "nfat_arch %lx\n",
	       PROGRAM_NAME,
	       filename,
	       GET32(header->magic),
	       GET32(header->nfat_arch));
    } else {
	printf("Input file \"%s\" is not a fat file.\n",filename);
    }
}

static void 
fatprint_arch(fat_arch_t *arch)
{
    long type = GET32(arch->cputype);
    long subtype = GET32(arch->cpusubtype);
    const char * architecture = arch_from_type(type);
    const char * cputype = get_cputype(type);
    const char * cpusubtype = get_cpusubtype(type,subtype);
    long offset = GET32(arch->offset);
    long size = GET32(arch->size);
    long align = GET32(arch->align);
    printf("architecture %s\n"
	   "    cputype %s\n"
	   "    cpusubtype %s\n"
	   "    offset %ld\n"
	   "    size %ld\n"
	   "    align 2^%ld (%.0f)\n",
	   architecture,cputype,cpusubtype,offset,size,align,pow(2.0,(float)align));
}
    
static void
fatprint(const char *filename)
{
    FILE *file = fopen(filename,"r");
    fat_header_t header;
    fat_arch_t arch;
    if (!file) {
	fprintf(stderr,"%s: Can't open \"%s\", (%s)\n",
		PROGRAM_NAME,
		filename,strerror(errno));
	quit(1);
    }
    fread(&header,1,HEADER_BYTE_COUNT,file);
    fatprint_header(&header,filename);
    if (IS_FAT(&header)){
	long i,count=GET32(header.nfat_arch);
	for(i=0;i<count;i++){
	    fread(&arch,1,ARCH_BYTE_COUNT,file);
	    fatprint_arch(&arch);
	}
    } else {
	/* nfat_arch is filled with the cpu type if the file is not fat */
	long type = type_from_header((non_fat_header_t *)&header);
	printf("Non-fat file: \"%s\" is architecture: %s\n",
	       filename,
	       arch_from_type(type));
    }
    fclose(file);
}

    
static void 
detailed_info(void)
{
    int i;
    for(i=0;input_files[i];i++){
	fatprint(input_files[i]->filename);
    }
}

static void 
write_fat_header(FILE *file, long archs)
{
    fat_header_t header;
    SET32(header.magic,FAT_MAGIC);
    SET32(header.nfat_arch,archs);
    if (fwrite(&header,1,HEADER_BYTE_COUNT,file)!=HEADER_BYTE_COUNT){
	fprintf(stderr,"Error writing to file... (%s)\n",strerror(errno));
	quit(1);
    }
}

static void 
set_fat_arch(fat_arch_t *a,long ct, long cst, long o, long s, long algn)
{
    SET32(a->cputype,ct);
    SET32(a->cpusubtype,cst);
    SET32(a->offset,o);
    SET32(a->size,s);
    SET32(a->align,algn);
}

static fat_arch_t *
make_fat_arch_at(long which)
{
    long size = input_files[which]->size;
    long offset = 0; /* we consider it pure data... */
    long cputype = input_files[which]->cputype;
    long cpusubtype = input_files[which]->cpusubtype;
    long align = DEFAULT_ALIGN;	
    fat_arch_t * a;
    if (cputype == CPU_TYPE_ANY){
	fprintf(stderr,"%s: Can't figure out the arch type for \"%s\".\n",
		PROGRAM_NAME,input_files[which]->filename);
	usage();
    }
    a = calloc(sizeof(fat_arch_t),1);
    set_fat_arch(a,cputype,cpusubtype,offset,size,align);
    return a;
}

	
static long 
add_arch(fat_arch_t ***fa, fat_arch_t *add, long at)
{
    if (!*fa) {
	*fa = calloc(sizeof(fat_arch_t *),1);
    } else {
	*fa = realloc(*fa, sizeof(fat_arch_t *)*(at+1));
    }
    (*fa)[at]=add;
    return 1;
}

static long
load_fat_arch(long which, fat_arch_t ***fa, long total)
{
    FILE * file = fopen(input_files[which]->filename,"r");
    fat_header_t header;
    fat_arch_t *a;
    if (!file) { /* Can't assume it's still here. */
	fprintf(stderr,"%s: Can't open \"%s\", (%s)\n",
		PROGRAM_NAME,
		input_files[which]->filename,strerror(errno));
	quit(1);
    }
    fread(&header,1,HEADER_BYTE_COUNT,file);
    if (input_files[which]->isfat){
	long i,count=GET32(header.nfat_arch);
	for(i=0;i<count;i++){
	    a = calloc(sizeof(fat_arch_t),1);
	    fread(a,1,ARCH_BYTE_COUNT,file);
	    total += add_arch(fa,a,total);
	}
    } else {
	a = make_fat_arch_at(which);
	total += add_arch(fa,a,total);
    }
    fclose(file);
    return total;
}
	
static fat_arch_t **
load_fat_archs(long *total)
{
    long i;
    long count = 0;
    fat_arch_t **fa = 0;
    for(i=0;i<input_file_count;i++){
	input_files[i]->start = count;
	count = load_fat_arch(i,&fa,count);
    }
    *total = count;
    return fa;
}

static long
file_owning_fa(long chunk)
{
    int i;
    for(i=input_file_count-1;i>0;i--){
	if (input_files[i]->start <= chunk){
	    return i;
	}
    }
    return 0;
}

static void 
consistency_check(fat_arch_t **fa, long count)
{
    long archs[CPU_TYPE_MAX];
    long i;
    for(i=0;i<CPU_TYPE_MAX;i++) archs[i]=-1;
    for(i=0;i<count;i++){
	long current = type_from_fat_arch(fa[i]);
	long which, suck, skip;
	if (current<0 || current>=CPU_TYPE_MAX){
	    fprintf(stderr,"%s: File \"%s\" has unknown architecture.\n",
		    PROGRAM_NAME,input_files[file_owning_fa(i)]->filename);
	    quit(1);
	}
	which = file_owning_fa(i);
	suck = input_files[which]->suck;
	skip = input_files[which]->skip;
	/* 
	 * See if this file is losing this architecture, or if it is the
	 * only one being preserved.
	 */
	if (skip != current && (!suck || suck==current)){
	    if (archs[current]!=-1) {
		fprintf(stderr,
			"%s: \"%s\" and \"%s\" share common architecture (%s)\n" 
			"       and can't exist in the same fat output file.\n",
			PROGRAM_NAME,
			input_files[archs[current]]->filename,
			input_files[file_owning_fa(i)]->filename,
			get_cputype(current));
		quit(1);
	    }
	    archs[current] = which;
	}
    }
}
	
static FILE *
grok_output(void)
{
    FILE * file;
    if (!output_file.filename) 
    {
	    fprintf(stderr,"%s: No output file specified.\n",PROGRAM_NAME);
	    usage();
    }
    if (!(file = fopen(output_file.filename,"w"))){
	fprintf(stderr,"%s: Error opening \"%s\", (%s)\n",
		PROGRAM_NAME,output_file.filename,strerror(errno));
	quit(1);
    }
    chmod(output_file.filename,output_file.mode);
    return file;
}

    
static void
write_fat_archlist(FILE *output, fat_arch_t **fa, long total)
{
    long offset=0;
    long align,j,i,stop;
    long old_offset;
    long skip;
    long suck;
    for(i=0;i<input_file_count;i++){
	if (i<input_file_count-1){
	    stop = input_files[i+1]->start;
	} else {
	    stop = total;
	}
	skip = input_files[i]->skip;
	suck = input_files[i]->suck;
	for(j=input_files[i]->start;j<stop;j++){
	    long current = GET32(fa[j]->cputype);
	    if (current != skip){
		if (current == suck || !suck){
		    align = byte_alignment(fa[j]);
		    offset = ((offset / align) + 1) * align;
		    old_offset = GET32(fa[j]->offset);
		    SET32(fa[j]->offset,offset);
		    fwrite(fa[j],1,ARCH_BYTE_COUNT,output);
		    offset+=GET32(fa[j]->size);
		    /* we set it back so we know were to get it later */
		    SET32(fa[j]->offset,old_offset);
		}
	    }
	}
    }
}

static void
write_pad(FILE *output, long start, long align)
{
    long count = (align) ? align - start % align : 0;
    long i;
    char null='\0';
    for(i=0;i<count;i++){
	fwrite(&null,1,1,output);
    }
}

static void 
write_binary(FILE *output, long align, long which, long where, long size)
{
    long tell = ftell(output);
    /* assume it's still ok */
    FILE * source = fopen(input_files[which]->filename,"r"); 
    long bufsiz;
    long current;
    write_pad(output,tell,align);
    fseek(source,where,SEEK_SET);
    bufsiz = (BUFFER_SIZE > input_files[which]->size) ?
	input_files[which]->size : BUFFER_SIZE;
    
    for(current=bufsiz;(size>0);){
	if (fread(buffer,1,current,source)!=current){
	    fprintf(stderr,"%s: Error reading from \"%s\", (%s)\n",
		    PROGRAM_NAME,input_files[which]->filename,strerror(errno));
	}
	if (fwrite(buffer,1,current,output)!=current){
	    fprintf(stderr,"%s: Error writing to \"%s\", (%s)\n",
		    PROGRAM_NAME,output_file.filename,strerror(errno));
	}
	size -= current;
	if (size<bufsiz){
	    current = size;
	}
    }
    fclose(source);
}

static void
write_fat_binaries(FILE *output, fat_arch_t **fa, long total)
{
    long offset=0;
    long align,j,i,stop;
    long where,size;
    long skip, suck;
    for(i=0;i<input_file_count;i++){
	if (i<input_file_count-1){
	    stop = input_files[i+1]->start;
	} else {
	    stop = total;
	}
	skip = input_files[i]->skip;
	suck = input_files[i]->suck;
	for(j=input_files[i]->start;j<stop;j++){
	    long current = GET32(fa[j]->cputype);
	    if (current != skip){
		if (current == suck || !suck){
		    align = byte_alignment(fa[j]);
		    offset = ((offset / align) + 1) * align;
		    where = GET32(fa[j]->offset);
		    size = GET32(fa[j]->size);
		    write_binary(output,align,i,where,size);
		    offset+=size;
		}
	    }
	}
    }
}    
    
/* take the infiles, and meld them into the outfile */
static void
create(const operation_t *perform)
{
    FILE * output;
    long total;
    fat_arch_t **fa;
    fa = load_fat_archs(&total);
    consistency_check(fa,total);
    output = grok_output();
    write_fat_header(output,total);
    write_fat_archlist(output,fa,total);
    write_fat_binaries(output,fa,total);
    fclose(output);
}

static fat_arch_t *
find_fat_arch(fat_arch_t **fa, long count, int type)
{
    long i;
    for(i=0;i<count;i++){
	if (GET32(fa[i]->cputype)==type){
	    return fa[i];
	}
    }
    return 0;
}

static void
assert_one_input(const char *tag)
{
    if (input_file_count!=1){
	fprintf(stderr,"%s: %s requires that you specify a single "
		"input file.\n", PROGRAM_NAME, tag);
	usage();
    }
}

static void
assert_arch_arg(const operation_t *perform, const char *tag)
{
    if (perform->argc<1 || cpu_type(perform->argv[0])<=CPU_TYPE_ANY){
	fprintf(stderr,	"%s: %s requires an architecture argument, "
		"like \"m68k\"\n", PROGRAM_NAME,tag);
	usage();
    }
}

static void
assert_fat(int which, const char *tag)
{
    if (!input_files[which]->isfat){
	fprintf(stderr,"%s: \"%s\" is not fat, and %s only operates "
		"on fat files.\n",PROGRAM_NAME,input_files[which]->filename,tag);
	quit(1);
    }
}

static void
assert_arch(fat_arch_t *single, long type, const char *tag)
{
    if (!single){
	fprintf(stderr,
		"%s: \"%s\" doesn't contain the architecture \"%s\" passed "
		"to %s.\n",
		PROGRAM_NAME,input_files[0]->filename,get_cputype(type),tag);
	quit(1);
    }
}    

/* 
 * Take the infiles, and meld them into a fat outfile with one arch 
 */
static void
extract(const operation_t *perform)
{
    int type;
    FILE * output;
    long total;
    fat_arch_t **fa;
    fat_arch_t *single;
    
    assert_arch_arg(perform,"-extract");
    type = cpu_type(perform->argv[0]);
    assert_one_input("-extract");
    fa = load_fat_archs(&total);
    assert_fat(0,"-extract");
    consistency_check(fa,total);
    single = find_fat_arch(fa,total,type);
    assert_arch(single,type,"-extract");
    output = grok_output();
    write_fat_header(output,1);
    write_fat_archlist(output,&single,1);
    write_fat_binaries(output,&single,1);
    fclose(output);
}

/* 
 * Take the infile, and meld it into a thin outfile
 */
static void
thin(const operation_t *perform)
{
    FILE * output;
    long total;
    fat_arch_t **fa, *single;
    int type;
    
    assert_arch_arg(perform,"-thin");
    type = cpu_type(perform->argv[0]);
    assert_one_input("-thin");
    fa = load_fat_archs(&total);
    assert_fat(0,"-thin");
    consistency_check(fa,total);
    single = find_fat_arch(fa,total,type);
    assert_arch(single,type,"-thin");
    output = grok_output();
    write_binary(output,0,0,GET32(single->offset),GET32(single->size));
    fclose(output);
}

static void
assert_file_arg(const operation_t *perform, int index, const char *tag)
{
    if (perform->argc<=index){
	fprintf(stderr,"%s: %s requires more arguments than was given (%d)\n",
		PROGRAM_NAME,tag,index);
	usage();
    }
}

/* 
 * Take the infile, and replace the named arch with the named arch
 * from the argument file.
 */
static void
replace(const operation_t *perform)
{
    FILE * output;
    long total;
    fat_arch_t **fa, *single;
    int type;
    
    assert_one_input("-replace");
    assert_fat(0,"-replace");
    assert_arch_arg(perform,"-replace");
    type = cpu_type(perform->argv[0]);
    assert_file_arg(perform,1,"-replace");
    add_file(perform->argv[1],CPU_TYPE_ANY);
    /* 
     * Rerun this to assert the new file we just added.
     */
    grok_fileinfo();
    /*
     * file 0 is the source file, file 1 is the suck file
     */
    input_files[0]->skip = type;
    input_files[1]->suck = type;
    fa = load_fat_archs(&total);
    consistency_check(fa,total);
    single = find_fat_arch(fa,total,type);
    /* this is wrong... we have to check both files */
    assert_arch(single,type,"-replace");
    output = grok_output();
    write_fat_header(output,input_files[0]->narch);
    write_fat_archlist(output,fa,total);
    write_fat_binaries(output,fa,total);
    fclose(output);
}

static void
assert_total(int archcount, const char *tag)
{
    if (!archcount){
	fprintf(stderr,	"%s: %s operation would result in an empty fat file\n",
		 PROGRAM_NAME,tag);
	quit(1);
    }
}

/* 
 * Remove the named architecture.
 */
static void
remove_arch(const operation_t *perform)
{
    FILE * output;
    long total;
    fat_arch_t **fa, *single;
    int type;
    
    assert_arch_arg(perform,"-remove");
    type = cpu_type(perform->argv[0]);
    
    assert_one_input("-remove");
    fa = load_fat_archs(&total);
    assert_fat(0,"-remove");
    consistency_check(fa,total);
    single = find_fat_arch(fa,total,type);
    assert_arch(single,type,"-remove");
    output = grok_output();
    assert_total(total-1,"-remove");
    write_fat_header(output,total-1);
    input_files[0]->skip = type;
    write_fat_archlist(output,fa,total);
    write_fat_binaries(output,fa,total);
    fclose(output);
}

int 
main(int argc, char **argv)
{
    int i, xcpu = -1;
    char *mem;
    extern int optind;
    operation_t * perform;
#ifdef DEBUG
	ArgC = argc;
	ArgV = argv;
#endif
    perform = grok_args(argc,argv);
    switch(perform->op){
    case _info:
	info();
	break;
    case _detailed_info:
	detailed_info();
	break;
    case _create:
	create(perform);
	break;
    case _extract:
	extract(perform);
	break;
    case _thin:
	thin(perform);
	break;
    case _replace:
	replace(perform);
	break;
    case _remove:
	remove_arch(perform);
	break;
    default:	/* arch, output, segalign */
	usage();
	break;
    }

    quit(0);


    if (xcpu > 0)
	printf("Extracting cpu type '%s'\n", cpus[xcpu + 1]);

    if (optind == argc)
    {
    }

    for (i = optind; i < argc; i++)
    {
	if ((mem = fatload(argv[i])))
	{
	    char buf[1024];

	    printf("File '%s': ", argv[i]);
	    fatanalyse(mem,0);

	    if (xcpu > 0)
	    {
		if (fatgetarch(mem, xcpu))
		{
		    sprintf(buf, "%s.%s", argv[i], cpus[xcpu + 1]);
		    fatsave(buf, mem, xcpu);
		}
		else
		    printf("specified architecture not found.\n");
	    }

	    free(mem);
	}
    }

    return 0;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.