[comp.sources.amiga] iffcat

ain@j.cc.purdue.edu (Patrick White) (09/08/88)

Submitted by:	nuchat!sugar!karl@uunet.uu.net
Summary:	Uses IFF form(?) CAT to pack several IFF files into one
Poster Boy:	Patrick White	(ain@j.cc.purdue.edu)
Archive Name:	sources/amiga/volume5/iffcat.s.sh1.Z
Tested
 
NOTES:
    Re-shar'ed.
 
 
-- Pat White   (co-moderator comp.sources/binaries.amiga)
ARPA/UUCP: j.cc.purdue.edu!ain  BITNET: PATWHITE@PURCCVM  PHONE: (317) 743-8421
U.S.  Mail:  320 Brown St. apt. 406,    West Lafayette, IN 47906
[archives at: j.cc.purdue.edu.ARPA]
 
========================================
 
#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	Makefile
#	assert.h
#	cleanup.c
#	create.c
#	delete.c
#	extract.c
# This archive created: Tue Sep  6 13:38:14 1988
# By:	Patrick White (PUCC Land, USA)
cat << \SHAR_EOF > README
iffar - IFF CAT Archiver, Release 1.2, 5/9/88

Written by: 

	Karl Lehenbauer
	3918 Panorama
	Missouri City, TX  77459
	usenet:  ..uunet!nuchat!sugar!karl  or  ..bellcore!tness7!sugar!karl
	BIX: kelehen

All liability is disclaimed!  This code is free.  We do not have a contract.

This software, source and binary, is released to the public domain, 5/9/88.

I ask that you retain my name as the original author in the source and
documentation if you redistribute this and that I be credited in the user 
manual if iffar is redistributed with a commercial product.

Regards,  Karl Lehenbauer @ The Hacker's Haven,  Missouri City, Texas, 5/9/88

---------------------------------------------------------------------

NAME

	iffar - IFF CAT archiver

SYNOPSIS

	iffar option [posname] archive_file [IFF_file] ...

DESCRIPTION

	Iffar maintains archives of Interchange File Format (IFF) FORM, CAT 
	and LIST  files in a manner  that complies with the IFF CAT specification.

	IFF CAT archives should be portable to different machines.  No promises.

	The option string must start with 'd', 'q', 'r', 't' or 'x' and may
	additionally have modifiers of 'a', 'b', 'c', 'i' or 'v'.  Not all
	modifiers are valid with all options.

	The options are:

		'd'		Delete named IFF files from the CAT archive.

		'q'		Quickly append named IFF files to the CAT archive.

		'r'		Replace named IFF files in the CAT archive; append new ones.

		't'		Print a table of contents of the CAT archive.

		'x'		Extract named IFF files from the CAT archive.
				If no names are specified, all files are extracted.


	The modifiers are:
		'a'		"after", replace or append IFF files after entry in CAT
					archive named by posname 

		'b'		"before", replace or append IFF files before entry in CAT
					archive named by posname

		'c'		Do not print a message indicating archive is being created
					when it must be created.

		'i'		a synonym for "before"

		'v'		print verbose description of all activity
				For table of contents, prints IDs, and
				lengths of chunks within the IFF file chunks
				in the CAT archive.  It prints the contents of
				chunks that it knows to be textual and short.


	On all operations that modify the archive, except for "quick append",
	the archive will be renamed with a ".old" extension and a new archive
	will be created.

	The "quick append" option causes the named files to be added to the
	end of the archive without rewriting the archive or looking to see
	if the entry already exists.  This is to avoid quadratic behavior
	when building up an archive one or just a few entries at a time.

	Wildcards of the semi-Unix-type as supplied by Manx are provided.

	Only the basename of the specified IFF filenames will be used for
	element names within the archive.  In other words, pathnames are
	stripped from filenames to create the archive element name.
	This is also true for extracts.  
	
EXAMPLES

	iffar x foo ram:t/bar

		would extract element "bar" from archive "foo" into file 
		"ram:t/bar"
		
	iffar ra sounds Cabasa dh0:sounds/Snare
	
		would replace a FORM, CAT or LIST named Snare in the 
		archive with dh0:sounds/Snare, placing Snare directly 
		after Cabasa.  If Cabasa is not found in the archive,
		Snare is placed at the end.

BUGS

	The archive will be corrupted if a write error (including
	running out of disk space) occurs during "quick 
	append" mode and there won't be a ".old" backup file.

	Running out of disk space leaves corrupted archives.
	For all options but "q" the ".old" backup file will contain
	the archive in its state prior to the run that blew it up.
	The program should delete the corrupted archive and restore 
	the ".old" file, but it doesn't.

IMPLEMENTATION NOTES

	Iffar is written to run under Manx Aztec C 3.6a for the Amiga
	under AmigaDOS version 1.2.

	My IFF archiving routines were written to be well-behaved from an 
	IFF point of view; that is, they try to respect the virtual 
	end-of-file defined by the size field of a chunk's header when 
	diving through the chunk's subchunks.  It makes for more work to use
	the routines, but adds some certainty that the program is working
	properly.  I don't know.  The IFF spec requests that we do, so I do.

	Note that when converting your code to use a CAT file instead of
	reading several FORM files (it's about twice as fast for an
	application of mine involving about 25 files from two to ten
	kilobytes each, reading them in from floppy.), your code needs 
	to either be driven by the files  read from the CAT (you search 
	a list of names you're looking for when you see a FORM and load 
	what you want) or your code has to know what's coming from the 
	CAT specifically.  I'd prefer that you do the former, of course.
	The table of contents routines (toc.c) provide a reasonable template 
	for your CAT-reading application.

NOTES FOR THOSE WISHING TO PORT THE ARCHIVER

	Knock yourselves out.  The code is written using standard C library
	calls, with the exception of calls to "scdir", which Manx uses as
	a means of expanding wildcards.  If you get it to work on a different
	system, such as Unix System/V, please send me a copy, preferably
	with #ifdefs so the Manx stuff still works.  Ultimately, I think an
	alternative "_main" startup routine should be written for the Amiga
	that expands wildcards inline to provide an argc and argv as they
	would look on Unix after the shell had expanded all the wildcards.


REVISION HISTORY

4/25/88, version 1.1

	Initial version - never distributed, as far as I know

5/9/88, version 1.2

	Made 'r' (replace) option create the archive if it isn't there.

	Fixed bug that caused the IFF reader to get lost when doing
	  a verbose table of contents on certain archives.

	Flattened the source directory structure.

	Updated the documentation.

-----------------------------------------------------------------------------
SHAR_EOF
cat << \SHAR_EOF > Makefile
# iffar - IFF CAT archiver, makefile
#
#  By Karl Lehenbauer, version 1.2, release date 5/9/88.
#  This code is released to the public domain.
#  See the README file for more information.
#
#  This makefile is for Manx Aztec C 3.6a for the Commodore Amiga computer.
#  It probably works under 3.4, but I haven't tried it.
#
#CFLAGS=	-n +p -DDEBUG +Iincludes.pre
CFLAGS=	+p +Iincludes.pre

HFILES= assert.h iff.h

.c.o:
	cc $(CFLAGS) $*.c

all:	iffar
	say "ready"

clean:
	-delete #?.o
	-delete #?.bak
	-delete includes.pre

scratch:	clean	all

iffar:	includes.pre main.o cleanup.o create.o toc.o iff.o \
	extract.o delete.o replace.o quickappend.o misc.o
	ln +q -o iffar main.o cleanup.o create.o toc.o iff.o extract.o \
delete.o replace.o quickappend.o misc.o -lcl32

includes.pre:	$(HFILES)
	-delete #?.o
	cc +p +Hincludes.pre includes.c

SHAR_EOF
cat << \SHAR_EOF > assert.h
/* hackercorp - hackercore assert file for Amiga
 * kel 12/87 v1.0
 */

#ifndef NODEBUG
#ifndef stderr
#include <stdio.h>
#endif
#define assert(x) if (!(x)) {fprintf(stderr,"Assertion failed: x, file %s, line %d\n",__FILE__,__LINE__); cleanup(); exit(1);}
#else
#define assert(x)
#endif
SHAR_EOF
cat << \SHAR_EOF > cleanup.c
/* cleanup - Hackercorp Hackercore, standard cleanup management routines, v1.2
  
   This code is released to the public domain, 5/9/88, by Peter da Silva
   and Karl Lehenbauer
  
   Usage:
  
  	add_cleanup(cleanup_routine);
  	void cleanup_routine();

  		Add a routine to the cleanup list.  When cleanup  is called, routines 
		passed as arguments to add_cleanup will be executed  in the reverse 
		order in which they were received.  See the source to  the IFF CAT 
		archiver, iffar, for an example of how to use this.

	void cleanup()

		Execute all the routines passed as arguments to add_cleanup
  	
	panic(s)
	char *s;

		Abort the program by printing a panic message including string "s"
		on stderr, flushing stdout and stderr, calling cleanup and exiting.

	_abort()
		By defining _abort to call panic, entering control-C while using
		stdio will cause the program to abort, executing your cleanup
		routines.  Also note that Manx sdb calls _abort when the user
		requests an exit, so your cleanup routines will be executed then
		as well.
*/

#include <exec/memory.h>
#include <stdio.h>

struct _clean 
{
	int (*function)();
	struct _clean *next;
} *cleanlist = NULL;

/* add_cleanup
 * given a function, add it to a list of functions to call when cleanup
 * is executed
 */
add_cleanup(function)
int (*function)();
{
	struct _clean *ptr;

	ptr = AllocMem(sizeof(struct _clean), MEMF_PUBLIC);
	if(!ptr)
		return 0;
	ptr->function = function;
	ptr->next = cleanlist;
	cleanlist = ptr;
}

/* cleanup
 * call all the functions that were passed as arguments to add_cleanup
 * this run
 */
cleanup()
{
	struct _clean *ptr;
	int (*f)();

	while(cleanlist) 
	{
		/* locate the next cleanup function and get the function pointer */
		ptr = cleanlist;
		cleanlist = cleanlist->next;
		f = ptr->function;

		/* cleanup must clean up after itself */
		FreeMem(ptr, sizeof(struct _clean));

		/* execute the function */
		(*f)();
	}
}

/* panic - abort with an error message */

short panic_in_progress = 0;

panic(s)
char *s;
{
	fflush(stdout);
	fprintf(stderr,"panic: %s\n",s);
	fflush(stderr);
	if (!panic_in_progress)
	{
		cleanup();
		exit(10);
	}
	fprintf(stderr,"double panic!\n");
	exit(11);
}

_abort()
{
	panic("^C or other C library abort");
}
SHAR_EOF
cat << \SHAR_EOF > create.c
/* iffar - IFF CAT archiver output and file copy rouines

   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.

*/

/* culled from general purpose IFF file cracking routines for Karl's 
 * Audio Stuff by Karl Lehenbauer, based originally on public domain IFF 
 * code from Electronic Arts, 2/24/88
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include "assert.h"

#include "iff.h"

extern long lseek();

extern int verbose;

extern char *basename();

#define ID_MISC MakeID('M','I','S','C')

WriteCATheader(fd)
int fd;
{
	static ULONG dummy = ID_MISC;
	WriteChunk(fd,ID_CAT, &dummy, sizeof(dummy));
}

/* write a chunk header, that's the 4-byte chunk ID and a 4-byte 
 * chunk length 
 */
WriteChunkHeader(fd,chunktype,length)
int fd;
ULONG chunktype;
long length;
{
	ChunkHeader chunkheader;

	chunkheader.ckID = chunktype;
	chunkheader.ckSize = length;

	if (write(fd,&chunkheader,sizeof(chunkheader)) == -1)
	{
		perror("WriteChunkHeader");
		return(0);
	}
	return(1);
}

/* write a chunk that has a four character subtype, like FORM, CAT or LIST
*/
WriteSuperChunkHeader(fd,chunktype,subtype,length)
int fd;
ULONG chunktype, subtype;
long length;
{
	if (!WriteChunkHeader(fd,chunktype,length+sizeof(subtype)))
		return(0);
	return(write(fd,&subtype,sizeof(subtype)) != -1);
}

/* WriteCATentry
	This routine is given all of the info for a superchunk header and,
	in addition, a file name.  It writes the chunk header and a
	FNAM chunk containing the file name out to the fd provided.
*/
WriteCATentry(fd,fname,chunktype,subtype,length)
int fd;
char *fname;
ULONG chunktype, subtype;
long length;
{
	int fnamelen;
	int calc_chunk_length;

	/* Figure out the length of the file name.  Remember that
	 * it should be even.  (I should use a cool macro to force
	 * that, but I don't)
	 * Add the size of the FNAM chunk we're going to write to our
	 * calculated chunk length.
	 */
	fnamelen = strlen(fname);
	calc_chunk_length = fnamelen;
	if (fnamelen & 1)
		calc_chunk_length++;
	calc_chunk_length += length + sizeof(ChunkHeader);

	WriteSuperChunkHeader(fd,chunktype,subtype,calc_chunk_length);
	if (!WriteChunk(fd,ID_FNAM,fname,strlen(fname)))
		return(0);
	return(1);
}

/* write a chunk header and body, that's the 8-byte header and the data
 * to match the specified length
 */
WriteChunk(fd,chunktype,data,length)
int fd;
ULONG chunktype;
char *data;
long length;
{
	static char zero = '\0';

	if (!WriteChunkHeader(fd,chunktype,length))
		return(0);

	/* if there's a body, write it out */
	if (length != 0)
		if (write(fd,data,length) == -1)
		{
			perror("WriteChunk");
			return(0);
		}

	/* if the length is odd, write out an additional zero byte */
	if (length & 1)
		if (write(fd,&zero,1) == -1)
		{
			perror("WriteChunk");
			return(0);
		}
	return(1);
}

/* checknew - given fname, this routine prints "Creating IFF CAT archive "
 * and the fname if file fname does not exist
 */
checknew(fname)
char *fname;
{
	extern int suppress_creation_message;
	int fd;

	if (!suppress_creation_message)
	{
		if ((fd = open(fname,O_RDONLY)) < 0)
		{
			fprintf(stderr,"Creating IFF CAT archive '%s'\n",fname);
		}
		else
			close(fd);
	}
}

/* open_quick_append
 * open the archive for append, verifying that it is IFF, subtype CAT,
 * that the length in the header matches the length of the file and
 * such.  Note that this has been wrapped into a better OpenCAT routine
 * but I have not fixed open_quick_append to call it yet.
 */
int open_quick_append(fname)
char *fname;
{
	ChunkHeader mychunkheader;
	long filesize;
	int fd;

	/* If I can't open the archive read only, it doesn't exist, so
	 * create it
	 */
	if ((fd = open(fname,O_RDONLY)) < 0)
	{
		if ((fd = create_archive(fname,ID_MISC)) < 0)
		{
			perror(fname);
			return(-1);
		}
	}
	else
	{
		/* read the IFF header and validate we've got a CAT */
		if (read(fd,&mychunkheader,sizeof(mychunkheader)) != sizeof(mychunkheader))
		{
			perror(fname);
			fprintf(stderr,"couldn't read chunk header\n");
			return(-1);
		}

		if (mychunkheader.ckID != ID_CAT)
		{
			fprintf(stderr,"file '%s' is not an IFF CAT archive\n",fname);
			return(-1);
		}

		/* verify that the header's filesize matches the file's size */
		if ((filesize = lseek(fd,0,2)) == -1)
		{
			perror(fname);
			return(-1);
		}

		if ((filesize - sizeof(ChunkHeader)) != mychunkheader.ckSize)
		{
			fprintf(stderr,"archive %s's CAT chunk size does not equal the file's size.\n",fname);
			fprintf(stderr,"I'm assuming it's OK and using file size.\n");
		}

		/* ok, reopen the file for append and return the fd */
		close(fd);
		if ((fd = open(fname,O_RDWR|O_APPEND)) < 0)
		{
			perror(fname);
			return(-1);
		}
	}
	return(fd);
}

#define COPY_BUFFER_SIZE 32768

char *copy_buffer = 0;

/* append_file_to_archive
 *
 * this routine copies IFF file named by fname to the CAT archive known
 * by it's open-for-append fd.
 */
append_file_to_archive(fname,archive_fd)
char *fname;
int archive_fd;
{
	char *basename_ptr;
	int bytes, i;
	int infd, fnamelen, basenamelen;
	ULONG chunkid, subtype;
	long chunksize, new_chunk_address;
	ULONG subchunkid;
	long subchunksize, placeholder, calculated_chunk_size, inputfilesize;

	/* seek to the end of the archive */
	lseek(archive_fd,0,2);

	/* open the file to appended to the archive, read only */
	if ((infd = open(fname,O_RDONLY)) == -1)
	{
		perror(fname);
		return(0);
	}

	/* get the filesize of the input file and relocate back to the start
	 * of it */
	inputfilesize = lseek(infd,0,2);
	lseek(infd,0,0);

	/* get the ID and size of the next chunk */
	if ((chunkid = nextchunk(infd,&chunksize,&inputfilesize)) == 0)
	{
		fprintf(stderr,"couldn't get header chunk from file %s\n",fname);
		close(infd);
		return(0);
	}

	/* if the header isn't CAT, FORM or LIST, don't copy it */
	if (chunkid != ID_CAT && chunkid != ID_FORM && chunkid != ID_LIST)
	{
		fprintf(stderr,"file %s is not an IFF CAT, FORM or LIST, ignored\n",fname);
		close(infd);
		return(0);
	}

	/* kludgily get the subtype - for FORMs, LISTs & CATs it's the
	 * first 4 bytes of the chunk data.  These are included in chunksize
	 */
	if (read(infd,&subtype,4) != 4)
	{
		perror("copy subtype");
		return(0);
	}
	inputfilesize -= 4;

	/* record where we are in the archive so we can rewrite the header
	 * which we'll need to do if we add a FNAM chunk */
	 new_chunk_address = lseek(archive_fd,0L,1) + 4;

	/* write in the chunk ID and the subtype - the program will
	 * rewrite the length when we know for sure how long the
	 * chunk is */
	if (!WriteChunk(archive_fd,chunkid,&subtype,4))
	{
		perror("append WriteChunk");
		return(0);
	}

	/* keep track of the size of the FORM, CAT or LIST we're reading
	 * through.  We start with 4, the size of subtype written above */
	calculated_chunk_size = 4;

	fnamelen = strlen(fname);

	/* if the filename includes a path, use only the base portion */
	basename_ptr = basename(fname);
	basenamelen = strlen(basename);

	/* write a FNAM chunk, it's the basename determined above,
	 * and our handle for it */
	if (!WriteChunk(archive_fd,ID_FNAM,basename_ptr,basenamelen))
		return(0);

	/* add size of the chunk header and the length of the chunk
	 * body to the calculated chunk size.  If the number is odd,
	 * increment it by one as the IFF spec requires odd-sized
	 * chunks to be one-null-padded to make them even.  Note
	 * that WriteChunk took care of it for the actual data written
	 */
	calculated_chunk_size += sizeof(ChunkHeader) + basenamelen;
	if (basenamelen & 1)
		calculated_chunk_size++;

	/* for all remaining chunks inside the FORM, LIST or CAT that
	 * we're adding to the archive, */
	while ((subchunkid = nextchunk(infd,&subchunksize,&inputfilesize)) != 0)
	{
		/* if it's an FNAM chunk, discard it */
		if (subchunkid == ID_FNAM)
			skipchunk(infd,subchunksize,&inputfilesize);
		else
		{
			calculated_chunk_size += subchunksize + sizeof(ChunkHeader);
			if (subchunksize & 1)
				calculated_chunk_size++;

			/* write the chunk header for the embedded chunk we're copying */
			if (!WriteChunkHeader(archive_fd,subchunkid,subchunksize))
				return(0);

			/* now copy the embedded chunk's data */
			copychunkbytes(infd,archive_fd,subchunksize,&inputfilesize);
		}
	}
	/* get current position in the archive, seek back to the header of the
	 * FORM, CAT or LIST we just copied (into the archive) and write in the 
	 * correct chunk size, then seek back to where we were
	 */

	placeholder = lseek(archive_fd,0L,1);
	lseek(archive_fd,new_chunk_address,0);
	if (write(archive_fd,&calculated_chunk_size,4) != 4)
	{
		perror("archive subheader rewrite");
		fprintf(stderr,"archive is blown.\n");
		close(infd);
		return(0);
	}
	/* return to previous place in archive, close file we copied and
	 * return 'success' */
	lseek(archive_fd,placeholder,0);
	close(infd);
	return(1);
}

/* rewrite_archive_header - write (filesize - sizeof(ChunkHeader)) into
 * CAT archive's header, assumes file is open for write
 */
rewrite_archive_header(fd)
{
	long filesize;

	/* get filesize by seeking to EOF */
	if ((filesize = lseek(fd,0,2)) == -1)
	{
		perror("archive");
		return(0);
	}

	/* go back to the beginning of the file */
	if (lseek(fd,0,0) == -1)
	{
		perror("archive cleanup seek");
		return(0);
	}

	if (!WriteChunkHeader(fd,ID_CAT,(filesize - sizeof(ChunkHeader))))
	{
		perror("archive cleanup");
		return(0);
	}

	return(1);
}

/* the copy buffer cleanup routine, it frees the copy buffer memory if
 * the buffer has been allocated
 */
void cleanup_copy_buffer()
{
	if (copy_buffer)
		FreeMem(copy_buffer,COPY_BUFFER_SIZE);
}

/* copychunkbytes
 *
 * copy nbytes from infd to outfd, subtracting that amount from
 * the number of filebytes left within the virtual chunk, as given
 * by the address of that variable
 */
copychunkbytes(infd,outfd,nbytes,filebytes_left_ptr)
int infd, outfd;
long nbytes, *filebytes_left_ptr;
{
	int copysize, odd;

	/* if we haven't allocated copy_buffer, allocate it and add it's cleanup
	 * routine (cleanup_copy_buffer) to the cleanup list */
	if (!copy_buffer)
	{
		if ((copy_buffer = (char *)AllocMem(COPY_BUFFER_SIZE,MEMF_FAST)) == (char *)NULL)
			panic("couldn't allocate copy buffer");

		add_cleanup(cleanup_copy_buffer);
	}

	/* if nbytes of copying requested exceeds the virtual EOF (end of
	 * the chunk's metachunk), truncate the chunk
	 */
	if (nbytes > *filebytes_left_ptr)
	{
		fprintf(stderr,"copychunkbytes: chunk size exceeds size of superchunk - truncating\n");
		nbytes = *filebytes_left_ptr;
	}

	/* find out if the length of the chunk is odd or not - we'll need
	 * it later to see if we need to write a pad byte
	 */
	 odd = (nbytes & 1);

	/* do the copy, breaking it up into multiple COPY_BUFFER_SIZE sized
	 * portions, if neccessary */
	while (nbytes > 0)
	{
		copysize = (nbytes > COPY_BUFFER_SIZE) ? COPY_BUFFER_SIZE : nbytes;

		if (read(infd,copy_buffer,copysize) != copysize)
		{
			perror("copybytes input");
			fprintf(stderr,"archive is blown.\n");
			close(infd);
			return(0);
		}
		if (write(outfd,copy_buffer,copysize) != copysize)
		{
			perror("copybytes output");
			fprintf(stderr,"archive is blown.\n");
			close(infd);
			return(0);
		}
		/* update bytes left in chunk and in chunk's metachunk */
		nbytes -= copysize;
		*filebytes_left_ptr -= copysize;
	}

	/* done with copy - if number of bytes copied was odd, read and
	 * discard the pad byte, write out a pad byte and update the
	 * virtual EOF (end of chunk) */
	if (odd)
	{
		if (read(infd,copy_buffer,1) != 1)
			perror("copychunkbytes: failed to skip input byte");
		assert(*copy_buffer == '\0');
		write(outfd,copy_buffer,1);
		(*filebytes_left_ptr)--;
	}
}

/* create an archive by opening it, writing a CAT header, and returning the
 * file descriptor if it succeeded or -1 otherwise
 */
int create_archive(archive_name,subtype)
char *archive_name;
ULONG subtype;
{
	int archive_fd;

	if ((archive_fd = open(archive_name, O_RDWR|O_CREAT|O_TRUNC)) == -1)
	{
		perror(archive_name);
		return(-1);
	}

	if (!WriteCATheader(archive_fd))
	{
		fprintf("create_archive: couldn't write CAT chunkheader of new archive\n");
		return(-1);
	}

	/* success, return the archive fd */
	return(archive_fd);
}

/* end of create.c */

SHAR_EOF
cat << \SHAR_EOF > delete.c
/* iffar - IFF CAT archiver delete functions

   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.

*/

#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include "assert.h"
#include "iff.h"

extern ULONG nextchunk();

int delete_entries(archive_name,fnames,nfiles)
char *archive_name;
char *fnames[];
int nfiles;
{
	int old_archive_fd, new_archive_fd;
	ULONG cat_type, chunkid, innerchunkid, subtype;
	long chunksize, innerchunksize, filesize;
	char textbuf[128], old_archive_name[128];
	int i, delete_file, file_bytes;

	extern int verbose;

	/* rename the archive to its old name concatenated with ".old"
	 */
	sprintf(old_archive_name,"%s.old",archive_name);
	unlink(old_archive_name);
	rename(archive_name,old_archive_name);

	if ((old_archive_fd = OpenCAT(old_archive_name,&cat_type,&filesize)) == -1)
	{
		fprintf(stderr,"Can't open archive '%s'\n",old_archive_name);
		return(0);
	}

	if ((new_archive_fd = create_archive(archive_name,ID_MISC)) < 0)
		return(0);

	while ((chunkid = nextCATchunk(old_archive_fd,&subtype,&textbuf[0],&chunksize,&filesize)) != 0L)
	{
		/* if the chunk type isn't FORM, CAT or LIST, copy it across 
		 * without looking at it */
		 if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
		 {
		 	if (!WriteChunkHeader(new_archive_fd,chunkid,chunksize))
				return(0);
			copychunkbytes(old_archive_fd,new_archive_fd,chunksize,&filesize);
		}

		/* search to see if this chunk's name is one specified in fnames,
		 * an array of pointer to char strings */

		delete_file = 0;
		for (i = 0; i < nfiles; i++)
		{
			if (!strnicmp(fnames[i],textbuf, 128))
			{
				delete_file = 1;
				break;
			}
		}
		/* if we did decide to delete it, skip it */
		if (delete_file)
		{
			if (verbose)
				fprintf(stderr,"deleting %s\n",textbuf);

				if (!skipchunk(old_archive_fd,chunksize,&filesize))
				{
					fprintf(stderr,"delete: skipchunk failed\n");
					return(0);
				}
		}
		else	/* we want to copy it */
		{
			if (!WriteCATentry(new_archive_fd,textbuf,chunkid,subtype,chunksize))
				return(0);

			copychunkbytes(old_archive_fd,new_archive_fd,chunksize,&filesize);
		}
	}

	/* write the right length in for the header */
	rewrite_archive_header(new_archive_fd);

	/* close the old and new archive files and return success */
	close(old_archive_fd);
	close(new_archive_fd);
	return(1);
}

/* end of extract.c */

SHAR_EOF
cat << \SHAR_EOF > extract.c
/* iffar - IFF CAT archiver extractor functions

   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.

*/

#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include "assert.h"
#include "iff.h"

#define ID_NAME MakeID('N','A','M','E')
#define ID_AUTH MakeID('A','U','T','H')
#define ID_ANNO MakeID('A','N','N','O')
#define ID_Copyright MakeID('(','c',')',' ')

extern long lseek();

extern ULONG nextchunk();

int extract(archive_name,fnames,nfiles)
char *archive_name;
char *fnames[];
int nfiles;
{
	int archive_fd, outfd;
	ULONG cat_type, chunkid, innerchunkid, subtype;
	long chunksize, innerchunksize, filesize;
	char textbuf[256], *extract_name;
	long placeholder;
	int do_extract, i;

	extern int verbose;

	if ((archive_fd = OpenCAT(archive_name,&cat_type,&filesize)) == -1)
	{
		fprintf(stderr,"Can't open archive '%s'\n",archive_name);
		return(0);
	}

	while ((chunkid = nextCATchunk(archive_fd,&subtype,&textbuf[0],&chunksize,&filesize)) != 0L)
	{
		/* if the chunk type isn't FORM, CAT or LIST, skip it 'cuz it
		 * isn't anything we know how to extract */
		if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
		{
			if (!skipchunk(archive_fd,chunksize,&filesize))
			{
				perror(archive_fd);
				fprintf(stderr,"extract: skipchunk failed\n");
				return(0);
			}
			break;
		}

		/* if no extract names (nfiles == 0) were specified, extract all
		 * files, so extract this one, else search to see if this name
		 * is one specified in fnames, an array of pointers to char
		 * strings */
		do_extract = 0;
		if (nfiles == 0)
		{
			do_extract = 1;
			extract_name = textbuf;
		}
		else
		{
			for (i = 0; i < nfiles; i++)
			{
				if (!strnicmp(basename(fnames[i]),textbuf,128))
				{
					do_extract = 1;
					extract_name = fnames[i];
					break;
				}
			}
		}
		/* if we did decide to extract it, extract it */
		if (do_extract)
		{
			if (verbose)
				fprintf(stderr,"extracting %s\n",extract_name);

			/* create, open and truncate the extract file */
			if ((outfd = open(extract_name,O_RDWR|O_CREAT|O_TRUNC)) == -1)
			{
				perror(textbuf);
			}
			else
			{
				if (!WriteSuperChunkHeader(outfd,chunkid,subtype,chunksize))
					return(0);

				copychunkbytes(archive_fd,outfd,chunksize,&filesize);
				close(outfd);
			}

			/* if they specified files on the command line, null them
			 * out after we retrieve them, later we'll make a pass
			 * through and complain about any we don't find with
			 * their first bytes nulled out
			 */
			if (nfiles != 0)
				*fnames[i] = '\0';
		}
		/* else we don't want to extract it, so skip it */
		else if (!skipchunk(archive_fd,chunksize,&filesize))
		{
			perror(archive_fd);
			fprintf(stderr,"extract: skipchunk failed\n");
			return(0);
		}
	}

	/* complain about any files named that we didn't extract */
	for (i = 0; i < nfiles; i++)
	{
		if (*fnames[i] != '\0')
			fprintf(stderr,"%s: no such archive entry\n",fnames[i]);
	}

	/* close the archive file and return success */
	close(archive_fd);
	return(1);
}

/* end of extract.c */

SHAR_EOF
#	End of shell archive
exit 0

ain@j.cc.purdue.edu (Patrick White) (09/08/88)

Submitted by:	nuchat!sugar!karl@uunet.uu.net
Summary:	Uses IFF form(?) CAT to pack several IFF files into one
Poster Boy:	Patrick White	(ain@j.cc.purdue.edu)
Archive Name:	sources/amiga/volume5/iffcat.s.sh2.Z
Tested
 
NOTES:
    Re-shar'ed.
 
 
-- Pat White   (co-moderator comp.sources/binaries.amiga)
ARPA/UUCP: j.cc.purdue.edu!ain  BITNET: PATWHITE@PURCCVM  PHONE: (317) 743-8421
U.S.  Mail:  320 Brown St. apt. 406,    West Lafayette, IN 47906
[archives at: j.cc.purdue.edu.ARPA]
 
========================================
 
#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	iff.c
#	iff.h
#	includes.c
#	main.c
#	misc.c
#	quickappend.c
#	replace.c
#	toc.c
# This archive created: Tue Sep  6 13:38:53 1988
# By:	Patrick White (PUCC Land, USA)
cat << \SHAR_EOF > iff.c
/* iffar - IFF CAT archiver, IFF support functions

   By Karl Lehenbauer, version 1.2, release date 5/9/88
   This code is released to the public domain.
   See the README file for more information.

*/

/* culled general purpose IFF file cracking routines for Karl's 
 * IFF Stuff by Karl Lehenbauer, based originally on public domain IFF 
 * code from Electronic Arts, 2/24/88
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include "assert.h"

#include "iff.h"

extern long lseek();

extern ULONG nextchunk();

/* print a chunkID to stderr */
PutID(id)
ID id;
{
    fprintf(stderr,"%c%c%c%c",
	   (char)((id>>24L) & 0x7f),
	   (char)((id>>16L) & 0x7f),
	   (char)((id>>8)   & 0x7f),
	   (char)(id        & 0x7f) );
}

UBYTE *MyAllocMem(bytes, type)
ULONG bytes, type;
{
	UBYTE *tmp;
	UBYTE *AllocMem();
		tmp = AllocMem(bytes, type);
	return tmp;
}

/* return chunktype of next chunk */
/* every time nextchunk is executed and returns that it found a chunk,
 * either readchunk or skipchunk must be called and only one time!
 */
ULONG nextchunk(fd,chunksize,chunk_bytes_left)
int fd;
long *chunksize, *chunk_bytes_left;
{
	int sawsize, i, blown = 0;
	ChunkHeader mychunkheader;
	char checkchar;

	/* if chunk_bytes_left is zero, we obey it as a virtual EOF, so
	 * return 0 */
	 if (*chunk_bytes_left == 0)
	 	return(0);

	/* read the next chunk header */
	if ((sawsize = read(fd,&mychunkheader,sizeof(mychunkheader))) != 
		sizeof(mychunkheader))
	{
		if (sawsize != 0)
			fprintf(stderr,"Something's wrong with nextchunk! (sawsize %d)\n", sawsize);
		*chunksize = 0;
		return(0);
	}

#ifdef MAJORDEBUG
	fputs("nextchunk: next chunk '",stderr);
	PutID(mychunkheader.ckID);
	fprintf(stderr,"', size %d, parent bytes left %d\n",mychunkheader.ckSize,*chunk_bytes_left);
#endif

	*chunksize = mychunkheader.ckSize;

	/* see if chunk ID looks OK */
	for (i = 0; i < 4; i++)
	{
		checkchar = (mychunkheader.ckID >> (i * 8)) & 0xff;
		if (!isprint(checkchar))
		{
			if (!blown)
			{
				blown = 1;
				fprintf(stderr,"nextchunk: chunk ID contains an unprintable character (0x%x)\n",checkchar);
			}
			break;
		}
	}

	/* see if chunk length is reasonable */
	if ((mychunkheader.ckSize < 0) || (mychunkheader.ckSize > MAXCHUNKSIZE))
	{
		fprintf(stderr,"nextchunk: chunk length of %ld is unreasonable\n",mychunkheader.ckSize);
		blown = 1;
	}

	if (blown)
	{
		fprintf(stderr,"nextchunk: I either got lost or the archive is blown\n");
		return(0);
	}

	/* square up the bytes left in the chunk by the size of a chunk header,
	 * eight bytes.  We leave it to the caller to subtract the size of the
	 * body of the chunk by calling skipchunk or readchunk
	 */
	*chunk_bytes_left -= sizeof(mychunkheader);

	if (*chunk_bytes_left < 0)
	{
		fprintf("nextchunk: chunk overran its parent by %d bytes\n",(0-*chunk_bytes_left));
		*chunksize = 0;
		*chunk_bytes_left = 0;
		return(0);
	}

	return(mychunkheader.ckID);
}

/* read next chunk into buffer supplied, size must be value returned by
 * nextchunk
 * zero is returned on failure, one on success
 */
readchunk(fd,buf,size,chunk_bytes_left)
int fd;
char *buf;
LONG size, *chunk_bytes_left;
{
	*chunk_bytes_left -= size;

	if (*chunk_bytes_left < 0)
	{
		fprintf(stderr,"readchunk: chunk requested passed the end of its parent chunk\n");
		*chunk_bytes_left = 0;
		return(0);
	}

	if (read(fd,buf,size) != size) 
	{
		perror("smus file");
		fputs("LoadSMUS: read of IFF chunk failed\n",stderr);
		return(0);
	}

	/* odd-length chunks have a trailer byte - skip it */
	if (size & 1)
	{
		lseek(fd,1L,1);
		(*chunk_bytes_left)--;
	}
	return(1);
}

/* skip non-header portion of chunk, chunksize must have been returned
 * by nextchunk
 * returns 1 on success, 0 on failure
 */
skipchunk(fd,chunksize,chunk_bytes_left)
int fd;
LONG chunksize, *chunk_bytes_left;
{
	*chunk_bytes_left -= chunksize;
	if (chunksize & 1)
		(*chunk_bytes_left)--;
	if (*chunk_bytes_left < 0)
	{
		fprintf(stderr,"skipchunk: chunk size passes end of parent chunk's data by %d bytes\n",0 - *chunk_bytes_left);
		return(0);
	}
	/* skip over chunk data and skip an extra byte if length is odd */
	lseek(fd,(long)chunksize,1);
	if (chunksize & 1)
		lseek(fd,1L,1);
	return(1);
}

/* OpenIFF
 * given file name, open the IFF file.
 * read the header, return failure if it's not a FORM
 * (someday we'll handle the more complex types)
 * read the form type, return failure if it's not the type requested
 * success, return the file descriptor
 */

int OpenIFF(fname,expected_formtype,length_ptr)
char *fname;
LONG expected_formtype;
LONG *length_ptr;
{
	int iffile;
	ChunkHeader chunkhead;
	LONG formtype;

	/* open the file */
	if ((iffile = open(fname, O_RDONLY)) < 0)
	{
		fprintf(stderr,"OpenIFF: can't open IFF SMUS file %s\n",fname);
		perror(fname);
		return(-1);
	}

	/* get the length */
	*length_ptr = lseek(iffile,0,2);
	lseek(iffile,0,0);

	/* read the header chunk */
	if (read(iffile, &chunkhead, sizeof(chunkhead)) < 0)
	{
		fprintf(stderr,"OpenIFF: initial read from IFF file %s failed!\n",fname);
		return(-1);
	}

	/* return if the header chunk doesn't say it's IFF FORM */
	if (chunkhead.ckID != ID_FORM)
	{
		fprintf(stderr,"OpenIFF: File %s isn't IFF, is too complex, or doesn't start with FORM\n",fname);
		return(-1);
	}
	/* fprintf(stderr,"OpenIFF: FORM found, size is %d\n",chunkhead.ckSize); */

	/* read the form type */
	read(iffile, &formtype, sizeof(formtype));

	/* return if the form type isn't the type requested */
	if (formtype != expected_formtype)
	{
		fprintf(stderr,"OpenIFF: File %s is IFF ");
		PutID(formtype);
		fprintf(stderr," rather than the requested ");
		PutID(expected_formtype);
		fprintf(stderr,"\n");
		return(-1);
	}
	return(iffile);
}

/* read chunks until one of type chunktype is found or EOF
 * note that after a successful call to chunkuntil,
 * skipchunk or readchunk must be performed or the IFF reading
 * software will get lost on the next nextchunk
 * chunksize is returned on success, -1 otherwise
 * The caller should probably check the return explicitly for -1.
 * If checking only for less than zero, chunks larger than
 * two gigabytes will cause your code to break.
 */

LONG chunkuntil(fd,chunktype,file_bytes_left)
int fd;
ULONG chunktype;
long *file_bytes_left;
{
	ULONG currentchunk;
	LONG chunksize;

	while ((currentchunk = nextchunk(fd,&chunksize,file_bytes_left)) != NULL)
	{
		if (currentchunk == chunktype)
			return(chunksize);
		skipchunk(fd,chunksize,file_bytes_left);
	}
	return(0);
}

/* OpenCAT - Open an IFF CAT archive */

/* OpenCAT
 * Open an IFF CAT archive, insuring that the file starts with an
 * IFF CAT header and that the length in the header is valid.
 * Return the CAT subtype, file descriptor and length, leaving the
 * file pointed at the start of the first subchunk
 */

int OpenCAT(archive_name,subtype_ptr,length_ptr)
char *archive_name;
ULONG *subtype_ptr, *length_ptr;
{
	ChunkHeader mychunkheader;
	int archive_fd;
	long start_of_body, filesize;
	long placeholder;

	if ((archive_fd = open(archive_name,O_RDONLY)) == -1)
	{
		/* fprintf(stderr,"Can't open archive '%s'\n",archive_name); */
		return(-1);
	}

	if (read(archive_fd,&mychunkheader,sizeof(mychunkheader)) != sizeof(mychunkheader))
	{
		perror(archive_name);
		fprintf(stderr,"couldn't read chunk header\n");
		return(-1);
	}

	if (mychunkheader.ckID != ID_CAT)
	{
		fprintf(stderr,"file '%s' is not an IFF CAT archive\n",archive_name);
		return(-1);
	}

	if (read(archive_fd,subtype_ptr,sizeof(subtype_ptr)) != sizeof(subtype_ptr))
	{
		fprintf(stderr,"error reading archive header - subtype\n");
		return(-1);
	}

	/* save location of current start of body */
	if ((start_of_body = lseek(archive_fd,0,1)) == -1)
	{
		perror(archive_name);
		return(-1);
	}

	/* seek to the end to get the size */
	if ((filesize = lseek(archive_fd,0,2)) == -1)
	{
		perror(archive_name);
		return(-1);
	}

	/* see if the shoe fits */
	if ((filesize - sizeof(ChunkHeader)) != mychunkheader.ckSize)
	{
		fprintf(stderr,"archive %s's CAT chunk size does not equal the file's size.\n",archive_name);
		fprintf(stderr,"I'm assuming it's blown.\n");
		return(-1);
	}

	/* go back to the start of the IFF CAT archive's data */
	if (lseek(archive_fd,start_of_body,0) == -1)
	{
		perror(archive_name);
		return(-1);
	}

	/* it worked store filesize in location pointed to by 'length' 
	 * and return the archive file's file descriptor
	 */
	*length_ptr = filesize;
	return(archive_fd);
}

/* end of OpenCAT */

/* nextcat - read header info for the next entry in an IFF CAT */

/* nextCATchunk
 *
 * given fd, read into IFF file.
 * if we're not at a FORM, CAT or LIST, print the chunk type if verbose,
 *    then skip the chunk
 * if we are at a FORM, CAT or LIST, read the subtype and return it
 * via the argument subtype_ptr.
 * if the next chunk within the embedded FORM, CAT or LIST is FNAM,
 * read the text in the FNAM chunk (file name) and write it into space
 * pointed to by argument fname_ptr.
 * return the size of the chunk in argument chunk_length_ptr.
 * update the space left in the metachunk (usually the file) of argument
 * metachunk_length_ptr
 */

ULONG nextCATchunk(fd,subtype_ptr,fname_ptr,chunk_length_ptr,metachunk_length_ptr)
int fd;
ULONG *subtype_ptr;
char *fname_ptr;
LONG *chunk_length_ptr, *metachunk_length_ptr;
{
	ULONG cat_type, chunkid, innerchunkid;
	long chunksize, innerchunkposition, innerchunksize, filesize;
	int odd;

	/* null out the returned subtype and fnam */
	*subtype_ptr = 0L;
	*fname_ptr = '\0';

	if ((chunkid = nextchunk(fd,chunk_length_ptr,metachunk_length_ptr)) == 0L)
		return(0L);

	/* if the chunk type isn't FORM, CAT or LIST, return the chunkid
	 */
	if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
		return(chunkid);

	/* get the chunk subtype */
	if (read(fd,subtype_ptr,4) != 4)
	{
		perror("reading subtype");
		return(0);
	}

	/* reduce chunksize and metachunksize by the size of the subtype */
	*chunk_length_ptr -= sizeof(ULONG);
	*metachunk_length_ptr -= sizeof(ULONG);

	/* sneak a peek into the embedded FORM, CAT or LIST to see
	 * if the next chunk is an FNAM chunk */

	 assert(*chunk_length_ptr > 0);

	/* fetch the current location in the file - we'll restore it
	 * if we don't find this next chunk to be a FNAM one
	 */
	innerchunkposition = lseek(fd,0L,1);

	/* get the type and size of the inner chunk */
	chunksize = *chunk_length_ptr;
	innerchunkid = nextchunk(fd,&innerchunksize,&chunksize);

	/* if it's not an fname chunk, seek back to the start of the
	 * chunk and return the chunk id - master length should be OK
	 */
	if (innerchunkid != ID_FNAM)
	{
		lseek(fd,innerchunkposition,0);
		return(chunkid);
	}

	odd = innerchunksize & 1;

	/* read and zero-terminate the file name (contents of FNAM chunk) */
	if (!readchunk(fd,fname_ptr,innerchunksize,&chunksize))
	{
		fprintf(stderr,"nextCATchunk: got into trouble reading chunk text\n");
		return(0);
	}
	*(fname_ptr + innerchunksize) = '\0';

	/* update the length of the chunk and its parent &  return the chunk id
	 * (nextchunk normally handles updating the length but we used different
	 * variables to make restoring (in case we don't find an FNAM chunk)
	 * easier
	 */
	*chunk_length_ptr -= (sizeof(ChunkHeader) + innerchunksize);
	*metachunk_length_ptr -= (sizeof(ChunkHeader) + innerchunksize);
	if (odd)
	{
		(*chunk_length_ptr)--;
		(*metachunk_length_ptr)--;
	}
	return(chunkid);
}

/* end of nextCATchunk */

/* end of iff.c */
SHAR_EOF
cat << \SHAR_EOF > iff.h
#ifndef IFF_H
#define IFF_H
/*
   IFF.H  defs for IFF-85 Interchange Format Files.	        	1/22/86

   By Jerry Morrison and Steve Shaw, Electronic Arts.

   Mods and stripping of a lot of stuff by Karl Lehenbauer, 1987, 1988

   This software is in the public domain.
*/

#ifndef LIBRARIES_DOS_H
#include "libraries/dos.h"
#endif

typedef LONG ID;

/* Four-character IDentifier builder.*/
#define MakeID(a,b,c,d)  ( (LONG)(a)<<24L | (LONG)(b)<<16L | (c)<<8 | (d) )

/* Standard group IDs.  A chunk with one of these IDs contains a
   SubTypeID followed by zero or more chunks.*/
#define ID_FORM MakeID('F','O','R','M')
#define ID_PROP MakeID('P','R','O','P')
#define ID_LIST MakeID('L','I','S','T')
#define ID_CAT  MakeID('C','A','T',' ')
#define ID_FILLER MakeID(' ',' ',' ',' ')
#define ID_FNAM MakeID('F','N','A','M')
#define ID_MISC MakeID('M','I','S','C')

/* The IDs "FOR1".."FOR9", "LIS1".."LIS9", & "CAT1".."CAT9" are reserved
 * for future standardization.*/

/* ---------- Chunk ----------------------------------------------------*/

/* All chunks start with a type ID and a count of the data bytes that 
   follow--the chunk's "logicl size" or "data size". If that number is odd,
   a 0 pad byte is written, too. */

typedef struct {
    ID	  ckID;
    LONG  ckSize;
    } ChunkHeader;

typedef struct {
    ID	  ckID;
    LONG  ckSize;
    UBYTE ckData[ 1 /*REALLY: ckSize*/ ];
    } Chunk;

/* define the maximum reasonable chunk size - the real max is around 2^32,
 * but we need some reasonability limit to check for */
#define MAXCHUNKSIZE 900000

/* The Grouping chunks (LIST, FORM, PROP, & CAT) contain concatenations of
 * chunks after a subtype ID that identifies the content chunks.
 * "FORM type XXXX", "LIST of FORM type XXXX", "PROPerties associated
 * with FORM type XXXX", or "conCATenation of XXXX".*/
typedef struct {
    ID	  ckID;
    LONG  ckSize;	/* this ckSize includes "grpSubID".*/
    ID    grpSubID;
    } GroupHeader;

typedef struct {
    ID	  ckID;
    LONG  ckSize;
    ID    grpSubID;
    UBYTE grpData[ 1 /*REALLY: ckSize-sizeof(grpSubID)*/ ];
    } GroupChunk;


#endif IFF_H

SHAR_EOF
cat << \SHAR_EOF > includes.c
/* iffar - archiver includes.c - for Manx +H +I options to precompile includes */

/* iffar - IFF CAT archiver includes.c for include precompile

   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.

*/

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/nodes.h>
#include <exec/lists.h>
/* #include <devices/audio.h> */
#include <fcntl.h>
#include <stdio.h>
#include "assert.h"
#include "iff.h"

/* end of includes.c */
SHAR_EOF
cat << \SHAR_EOF > main.c
/* iffar - IFF CAT archiver, main()

   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.

*/

/* iffar - iff 'cat' archiver, 3/26/88 */

#include <stdio.h>
#include "assert.h"

/* options */
int verbose = 0;
int insert_after = 0;
int insert_before = 0;
int suppress_creation_message = 0;

/* if insert_after or insert_before is set, this global has the relevant
 * name */
char *location_modifier_name;

/* variables */
int number_of_arguments;	/* number of args to the program, a global */

/* this'll point to the command line's command char string */
char *cmdkeys;

/* this'll be the name of the archive file */
char *archive_name;

/* this'll contain the command */
char command_key;

/* if a command must have some names as arguments, must_have_names can be 
 * called to insure that arguments were supplied.
 * Note that what constitutes the presence of arguments varies whether
 * a positional modifier ('i', 'b' or 'a') was specified.
 */
must_have_names()
{
	if ((insert_before || insert_after) && (number_of_arguments < 5))
	{
		fprintf(stderr,"a positioning element and at least one other element must be specified for\n");
		fprintf(stderr,"the combination of options you have requested.\n");
		usage();
	}
	if (number_of_arguments < 4)
	{
		fprintf(stderr,"at least one archive element must be specified for this option\n");
		usage();
	}
}

header()
{
	fprintf(stderr,"iffar - public domain IFF CAT archiver, Version 1.1, by Karl Lehenbauer\n\n");
	fprintf(stderr,"This program maintains archives of IFF FORM, CAT and LIST files\n");
	fprintf(stderr,"in a manner that complies with the IFF CAT specification.\n");
}

/* usage  print usage text and exit */
usage()
{
	fprintf(stderr,"\nUsage: iffar key [posname] afile name ...\n\n");
	fprintf(stderr,"key can be one of the following:\n");
	fprintf(stderr,"\td\tdelete\n\tr\treplace\n\tq\tquick append\n");
	fprintf(stderr,"\tt\ttable of contents\n\tx\textract\n");
	fprintf(stderr,"and zero or more of the following options:\n");
	fprintf(stderr,"\tv\tverbose\n\ta\tafter\n\ti,b\tbefore\n");
	fprintf(stderr,"\tc\tsuppress creation message\n");
	cleanup();
	exit(1);
}

main(argc,argv)
int argc;
char *argv[];
{
	int i, j;
	int archive_fd, files;
	char *nameptr;
	char **entryname_pointers;
	int entrycount;

	number_of_arguments = argc;

	if (argc < 3)
	{
		header();
		usage();
	}

	/* assign nice variable names to various command line arguments */
	cmdkeys = argv[1];
	command_key = *cmdkeys;
	archive_name = argv[2];
	location_modifier_name = (char *)NULL;
	entryname_pointers = &argv[3];
	entrycount = argc - 3;

	/* figure out any modifiers specified to the main function requested */
	for (i = 1; i < strlen(cmdkeys); i++)
	{
		switch(cmdkeys[i])
		{
			case 'v':				/* verbose selected, set verbose flag */
				verbose = 1;
				break;

			/* 'after' option, make sure they've selected 'r' or 'm' as
			 * the main command.  Also, make sure they haven't already
			 * chosen the insert_before option.  After that, we're OK,
			 * so twiddle parameters and break
			 */
			case 'a':
				if (command_key != 'r' && command_key != 'm')
				{
					fprintf(stderr,"i, b and a modifiers are only good for r and m options\n");
					usage();
				}
				if (insert_before)
				{
					fprintf(stderr,"you can't select 'insert before' and 'insert after'\n");
					usage();
				}
				insert_after = 1;
				location_modifier_name = argv[3];
				entryname_pointers = &argv[4];
				entrycount = argc - 4;
				break;

			/* 'insert before' option, make sure they've selected 'r' or
			 * 'm' as the main command.  Also, make sure they haven't already
			 * chosen the insert_after option.  After that, we're OK,
			 * so twiddle parameters and break
			 */
			case 'i':				/* insert; before */
			case 'b':
				if (command_key != 'r' && command_key != 'm')
				{
					fprintf(stderr,"i, b and a modifiers are only good for r and m options\n");
					usage();
				}
				if (insert_after)
				{
					fprintf(stderr,"you can't select 'insert before' and 'insert after'\n");
					usage();
				}
				insert_before = 1;
				location_modifier_name = argv[3];
				entryname_pointers = &argv[4];
				entrycount = argc - 4;
				break;

			case 'c':				/* supress creation message */
				suppress_creation_message = 1;
				break;

			default:				/* unrecognized option */
				fprintf(stderr,"option '%c' unrecognized\n",cmdkeys[i]);
				usage();
		}
	}

	/* now execute the major command */

	switch(command_key)
	{
		case 'd':		/* delete */
			must_have_names();
			delete_entries(archive_name,entryname_pointers,entrycount);
			break;

		/* replace - if the archive doesn't exist, fall through to
		 * quick append */
		case 'r':
			must_have_names();
			if ((archive_fd = open(archive_name,O_RDONLY)) != -1)
			{
				close(archive_fd);
				replace_entries(archive_name,entryname_pointers,entrycount);
				break;
			}
		case 'q':		/* quick append */
			must_have_names();
			checknew(archive_name);
			if ((archive_fd = open_quick_append(archive_name)) == -1)
				break;
			quickappend_entries(archive_fd,entryname_pointers,entrycount);
			break;
		case 't':		/* table of contents */
			table_of_contents(archive_name);
			break;

		case 'm':		/* move */
			fprintf(stderr,"move option not implemented\n");
			must_have_names();
			break;
		case 'x':		/* extract */
			extract(archive_name,entryname_pointers,entrycount);
			break;

		default:
			fprintf(stderr,"requested command (%c) is invalid\n",command_key);
			usage();
	}
	cleanup();
	exit(0);
}

/* end of main.c */
SHAR_EOF
cat << \SHAR_EOF > misc.c
/* iffar - IFF CAT archiver miscellaneous functions

   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.

*/

#include <ctype.h>

/* strnicmp - case-insensitive strncmp, not provided by manx, this one
 * is like lattice's */
char *strnicmp(s1, s2, len)
char *s1, *s2;
int len;
{
	char c1, c2;

	c1 = *s1;
	c2 = *s2;
	while (len-->0 && *s1) 
	{
		if (isupper(*s1))
			c1 = tolower(*s1);
		else
			c1 = *s1;
		s1++;
		if (isupper(*s2))
			c2 = tolower(*s2);
		else
			c2 = *s2;
		s2++;
		if (c1 != c2)
			break;
	}
	return c1 - c2;
}

/* return the base portion of a file name - needs to be hacked in C style BWTF*/

char *basename(fname)
char *fname;
{
	char *basename_ptr;
	int i;
	int fnamelen = strlen(fname);

	basename_ptr = fname;
	for (i = fnamelen - 1; i > 0; i--)
	{
		if (fname[i] == '/' || fname[i] == ':')
		{
			basename_ptr = &fname[i+1];
			break;
		}
	}
	return(basename_ptr);
}

/* end of misc.c */
SHAR_EOF
cat << \SHAR_EOF > quickappend.c
/* iffar - IFF CAT archiver quick append functions

   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.

*/

#include <stdio.h>
#include "assert.h"

extern int verbose;

/* append a list of files specified by an array of pointers to
 * names, the number of entries in the array, and a file decscriptor
 * to write them to
 */
quickappend_entries(archive_fd,entryname_pointers,entrycount)
int archive_fd;
char *entryname_pointers[];
int entrycount;
{
	int files;
	char *nameptr;

	/* for all names specified on the command line */
	for (files = 0; files < entrycount; files++)
	{
		/* scdir will return nonnull values as long as the pattern
		 * matches files (it will return patternless names the first
		 * time and NULL the second
		 */
		while (nameptr = scdir(entryname_pointers[files]))
		{
			/* append the file */
			if (append_file_to_archive(nameptr,archive_fd))
			{
				if (verbose)
					fprintf(stderr,"appended %s\n",nameptr);
			}
		}
	}
	if (!rewrite_archive_header(archive_fd))
	{
		fprintf(stderr,"rewrite of archive header failed... archive is presumed blown\n");
	}
	close(archive_fd);
}

/* end of quickappend.c */
SHAR_EOF
cat << \SHAR_EOF > replace.c
/* iffar - IFF CAT archiver replace functions

   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.

*/

#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include "assert.h"
#include "iff.h"

/* if a "insert before" or "insert after" position modifier is
 * not specified, the program attepts to replace entries at the
 * same place as where they occurred in the original archive.
 * if the entry could not be found, the file is appended to the
 * end of the archive.  hence, when a position modifier is not
 * specified and a file being replaced did not exist in the archive,
 * the file needs to be appended onto the
 * end - we do this by nulling out the argument if and only if
 * neiter insert_before or insert_after is set, else a bug would
 * be introduced as when they are selected the delete and insert
 * portions are asynchronous and either could occur before the
 * other, hence one guy nulling it makes the other guy not see
 * it.  it's not a problem for the specified case, though, 'cuz
 * it's only looked at in this one place and at the end,
 * where, again only when neither insert_before and insert_after
 * are checked, we run through the arguments and if the first byte
 * of any entry is not null, this implies that entry was not in
 * the original archive, hence not replaced in-place, so we tack
 * it on to the end.
 * if insert_before or insert_after is selected, archive entries
 * are deleted on the fly as they are read from the original
 * archive by being skipped and, when the insert conditions are
 * met, all of the names in the list of names are appended, thus, 
 * we don't have to worry about it 
 *
 * note that it's kind of broken for insert_after and insert_before
 * when the after or before name isn't found the old entries will
 * be deleted but the new ones won't be inserted, heck, the program
 * may even blow up
 */

extern ULONG nextchunk();

extern int insert_before;
extern int insert_after;
extern char *location_modifier_name;

int replace_entries(archive_name,fnames,nfiles)
char *archive_name;
char *fnames[];
int nfiles;
{
	int old_archive_fd, new_archive_fd;
	ULONG cat_type, chunkid, innerchunkid, subtype;
	long chunksize, innerchunksize, filesize;
	char textbuf[128], old_archive_name[128];
	int i, replace_file, file_bytes;
	int entryindex, insert_next_time = 0;
	int modifier_matches, did_insert = 0;

	extern int verbose;

	/* rename the archive to its old name concatenated with ".old"
	 */
	sprintf(old_archive_name,"%s.old",archive_name);
	unlink(old_archive_name);
	rename(archive_name,old_archive_name);

	if ((old_archive_fd = OpenCAT(old_archive_name,&cat_type,&filesize)) == -1)
	{
		fprintf(stderr,"Can't open archive '%s'\n",old_archive_name);
		return(0);
	}

	if ((new_archive_fd = create_archive(archive_name,ID_MISC)) < 0)
		return(0);

	while ((chunkid = nextCATchunk(old_archive_fd,&subtype,&textbuf[0],&chunksize,&filesize)) != 0L)
	{
		/* if the chunk type isn't FORM, CAT or LIST, copy it across 
		 * without looking at it */
		if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
		{
		 	if (!WriteChunkHeader(new_archive_fd,chunkid,chunksize))
				return(0);
			copychunkbytes(old_archive_fd,new_archive_fd,chunksize,&filesize);
			break;
		}

		/* we shouldn't ever not get a filename back here at the top level 
		 * that is, we saw a CAT, LIST or FORM, we expect a FNAM chunk*/
		 if (textbuf[0] == '\0')
		 {
		 	fprintf(stderr,"FORM, CAT or LIST in archive doesn't have an FNAM chunk, abandoning\n");
			return(0);
		}

		if (insert_before || insert_after)
			modifier_matches = !strnicmp(location_modifier_name,textbuf,128);

		/* if insert_before option has been selected and the chunk ID
		 * we just read from the old archive matches global 
		 * location_modifier_name, append all the named files to the
		 * new archive.  also do this if it's insert_after and
		 * we matched the name last time */
		if ((modifier_matches && insert_before) || insert_next_time)
		{
			insert_next_time = 0;

			for (entryindex = 0; entryindex < nfiles; entryindex++)
			{
				if (verbose)
				{
					if (insert_before)
						fprintf(stderr,"inserting ");
					else
						fprintf(stderr,"appending ");
					fprintf(stderr,"%s\n",fnames[entryindex]);
				}
				append_file_to_archive(fnames[entryindex],new_archive_fd);
			}

			did_insert = 1;
		}

		/* if this is a match and insert_after is selected, set 
		 * insert_next_time so we'll know to do the appends after the
		 * next entry */
		if (modifier_matches && insert_after)
			insert_next_time = 1;

		/* search to see if this chunk's name is one specified in fnames,
		 * an array of pointer to char strings */
		replace_file = 0;
		for (i = 0; i < nfiles; i++)
		{
			if (!strnicmp(basename(fnames[i]),textbuf,128))
			{
				/* it is */
				replace_file = 1;
				break;
			}
		}

		/* if we want to replace it, */
		if (replace_file)
		{
			/* copy the file being replaced into the archive if
			 * neither insert_before or insert_after are set.
			 * if they are set, this will be done elsewhere */
			if (!insert_before && !insert_after)
			{
				if (verbose)
					fprintf(stderr,"replacing %s\n",textbuf);
					append_file_to_archive(fnames[i],new_archive_fd);
					/* null out the first byte of the name so we won't
					 * append it again at the end */
					*fnames[i] = '\0';
			}
			else if (verbose)
				fprintf(stderr,"removing old %s\n",textbuf);

			/* in either case (replace selected, we don't care if
			 * before or after are chosen), skip the chunk in the 
			 * old archive */
			if (!skipchunk(old_archive_fd,chunksize,&filesize))
			{
				fprintf(stderr,"replace: skipchunk failed\n");
				return(0);
			}
		}
		else	/* not on the replacement list, we copy it */
		{
			if (!WriteCATentry(new_archive_fd,textbuf,chunkid,subtype,chunksize))
				return(0);

			copychunkbytes(old_archive_fd,new_archive_fd,chunksize,&filesize);
		}
	}

	/* now if insert_before or insert_after were not set, for all entries 
	 * in the list that don't have a null byte for their first byte,
	 * append them to the archive */
	if ((!insert_before && !insert_after) || !did_insert)
	{
		/* if we didn't do the insert, report that we're planning
		 * to append.  note that if insert_after and !insert_next_time
		 * it means that they said append after the last one, so
		 * don't mention it
		 */
		if (insert_before || (insert_after && !insert_next_time))
			fprintf(stderr,"couldn't find entry %s that was specified as a position modifier\n, appending your entries\n",textbuf);

		for (i = 0; i < nfiles; i++)
		{
			if (*fnames[i] != '\0')
			{
				if (verbose)
					fprintf(stderr,"appending %s\n",fnames[i]);
				append_file_to_archive(fnames[i],new_archive_fd);
			}
		}
	}

	/* write the right length in for the header */
	rewrite_archive_header(new_archive_fd);

	/* close the old and new archive files and return success */
	close(old_archive_fd);
	close(new_archive_fd);
	return(1);
}

/* end of extract.c */

SHAR_EOF
cat << \SHAR_EOF > toc.c
/* iffar - IFF CAT archiver table of contents functions

   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.

*/

#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include "assert.h"
#include "iff.h"

#define ID_NAME MakeID('N','A','M','E')
#define ID_AUTH MakeID('A','U','T','H')
#define ID_ANNO MakeID('A','N','N','O')
#define ID_Copyright MakeID('(','c',')',' ')

extern long lseek();

extern ULONG nextchunk();

int table_of_contents(fname)
char *fname;
{
	int fd;
	ULONG cat_type, chunkid, innerchunkid, subtype;
	long chunksize, innerchunksize, filesize;
	char namebuf[256];

	extern int verbose;

    if ((fd = OpenCAT(fname,&cat_type,&filesize)) == -1)
	{
		fprintf(stderr,"Can't open archive '%s'\n",fname);
		return(0);
	}

	if (verbose)
	{
		fprintf(stderr,"CAT subtype is ");
		PutID(cat_type);
		fprintf(stderr,"\n");
	}

	while ((chunkid = nextCATchunk(fd,&subtype,&namebuf[0],&chunksize,&filesize)) != 0L)
	{
		/* if the chunk type isn't FORM, CAT or LIST, skip it */
		 if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
		 {
		 	if (verbose)
			{
				PutID(chunkid);
				fprintf(stderr," chunk is being skipped\n");
			}
			skipchunk(fd,chunksize,&filesize);
			goto bigchunkdone;
		}

		if (namebuf[0] == '\0')
		{
			fprintf(stderr,"CAT entry didn't contain a FNAM chunk - skipping\n");
			skipchunk(fd,chunksize,&filesize);
			goto bigchunkdone;
		}

		PutID(chunkid);
		fprintf(stderr," ");
		/* print out the chunk length */
		PutID(subtype);
		fprintf(stderr," %6ld  %s\n",chunksize, namebuf);

		/* if verbose isn't selected, skip the rest of the chunks in the
		 * embedded FORM, CAT or LIST
		 */
		if (!verbose)
		{
		 	skipchunk(fd,chunksize,&filesize);
			goto bigchunkdone;
		}

		/* else verbose is selected, dive down into the embedded FORM, 
		 * CAT or LIST and print chunks found there and their contents
		 * when they're known to be text.
		 */

		/* follow a good neighbor policy by reducing our (the parent's)
		 * chunk size by the size we've found - since we'll be moving
		 * through the embedded FORM effectively recursively; that is,
		 * we'll be using the same nextchunk, skipchunk, readchunk
		 * routines on the chunk nextchunk got for us, we'll
		 * decrease parent chunk size by the size we've found,
		 * as, when calling nextchunk, skipchunk and readchunk below,
		 * we'll be concerned with keeping track of the form's chunk length
		 * as a virtual EOF, rather than the whole CAT's as above
		 */
		filesize -= chunksize;
		if (chunksize & 1)
			filesize--;

		while (chunksize != 0)
		{
			/* get the type and size of the inner chunk */
			innerchunkid = nextchunk(fd,&innerchunksize,&chunksize);
			if (chunksize == 0)
				break;


			/* print some presumably useful information */
			fprintf(stderr,"   ");
			PutID(innerchunkid);

			switch(innerchunkid)
			{
				case ID_FNAM:
					panic("more than one FNAM chunk in an embedded FORM");

				case ID_NAME:
				case ID_AUTH:
				case ID_ANNO:
				case ID_Copyright:
						if (innerchunksize >= sizeof(namebuf))
						{
							fprintf(stderr,"[too long]\n");
							skipchunk(fd,innerchunksize,&chunksize);
						}
						else
						{
							if (!readchunk(fd,namebuf,innerchunksize,&chunksize))
							{
								fprintf(stderr,"got into trouble reading chunk text\n");
								return(0);
							}
							namebuf[innerchunksize] = '\0';
							fprintf(stderr,", %s\n",namebuf);
						}
					break;

				default:
					fprintf(stderr,", size %ld\n",innerchunksize);

					/* skip the body of the subchunk */
					skipchunk(fd,innerchunksize,&chunksize);
				}
		}
		/* we make it to here if the length of all the subchunks equalled
		 * the length of their superchunk
		 */
		 bigchunkdone: ;
	}
	close(fd);
}

/* end of toc.c */
SHAR_EOF
#	End of shell archive
exit 0