[comp.sources.misc] v15i089: compact - Fast Compression of Large Files

gene@zeno.mn.org (Gene H. Olson) (12/17/90)

Posting-number: Volume 15, Issue 89
Submitted-by: gene@zeno.mn.org (Gene H. Olson)
Archive-name: compact_sv/part01

[This utility collides with a standard BSD utility, although it may be a port
of that utility since it's intended for System V.  ++bsa]

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  README Makefile compact.1 compact.c share.c smtest.c
# Wrapped by gene@zeno on Sat Nov  3 13:00:59 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f README -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"README\"
else
echo shar: Extracting \"README\" \(984 characters\)
sed "s/^X//" >README <<'END_OF_README'
X@(#)README	1.4 11/3/90
X
XCompact is a high speed compression/decompression program that works
Xbest on large files (>1 meg compressed).  On such files, it runs
X2-3 times faster than the public domain compress program, and compresses
Xbetter.
X
XCompact is PUBLIC DOMAIN, but due to potential patent problems,
Xthis program may only be used for research into data compression
Xtechniques.  See WARNING section in compact(1) manual page for
Xfurther discussion.
X
XThe program currently compiles w/o any special options on all
Xsystem V UNIX releases known to the author which provide at least
X1 Meg of uniformly addressable memory.  It will not run on machines
X(eg 286) with 16 bit integers or 64K segments.
X
XPlease forward comments, suggestions and bug reports to me.
X_________________________________________________________________________
X   __ 
X  /  )                 Gene H. Olson             gene@zeno.mn.org
X / __  _ __  _ 
X(__/ _(/_//_(/_        Minneapolis, MN           (612) 824-9108
END_OF_README
if test 984 -ne `wc -c <README`; then
    echo shar: \"README\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f Makefile -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"Makefile\"
else
echo shar: Extracting \"Makefile\" \(921 characters\)
sed "s/^X//" >Makefile <<'END_OF_Makefile'
X#	Makefile for the compact program.
X#
X#	@(#)Makefile	1.7 10/27/90
X#
X#	This is FREE software in the PUBLIC DOMAIN.
X#
X#	This program may be used only for research into data
X#	compression techniques.  See WARNING section in
X#	compact(1) manual page for further discussion.
X
XBIN	= /usr/local/bin
XMAN	= /usr/man/mann
XMANSUF	= n
X
XSHAR	= README Makefile compact.1 compact.c share.c smtest.c 
X
XCFLAGS = -O
X
Xcompact: compact.o share.o
X	cc -o compact compact.o share.o
X
Xsmtest: smtest.o share.o
X	cc -o smtest smtest.o share.o
X
Xinstall: compact compact.1
X	rm -f $(BIN)/compact $(BIN)/uncompact
X	cp compact $(BIN)/compact
X	ln $(BIN)/compact $(BIN)/uncompact
X	cp compact.1 $(MAN)/compact.$(MANSUF)
X
Xshar:	$(SHAR)
X	shar $(SHAR) >compact.shar
X
Xtar:	$(SHAR)
X	tar cf compact.tar $(SHAR)
X
Xunget:	clean
X	@x=;\
X	for i in $(SHAR); do [ -r $$i -a \! -w $$i ] && x="$$x $$i"; done;\
X	echo rm -f $$x; rm -f $$x
X
Xclean:
X	rm -f compact smtest *.o
END_OF_Makefile
if test 921 -ne `wc -c <Makefile`; then
    echo shar: \"Makefile\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f compact.1 -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"compact.1\"
else
echo shar: Extracting \"compact.1\" \(11811 characters\)
sed "s/^X//" >compact.1 <<'END_OF_compact.1'
X.        @(#)compact.1	1.15 10/27/90
X.TH COMPACT 1 "10/27/90" "Public Domain"
X.SH NAME
Xcompact, uncompact \- compress/uncompress large files
X.SH SYNOPSIS
X.HP 5
X.B compact
X[\ \fB\-clqsSuvxX\ \fR]
X[\ \fB\-a\ \fIcmd\ \fR]
X[\ \fB\-b\ \fIcbs\ \fR]
X[\ \fB\-B\ \fIebs\ \fR]
X[\ \fB\-f\ \fIcfile\ \fR]
X[\ \fB\-F\ \fIefile\ \fR]
X[\ \fB\-m\ \fImemory\ \fR]
X[\ \fB\-n\ \fIcblks\ \fR]
X[\ \fB\-N\ \fIeblks\ \fR]
X[\ \fB\-p\ \fIoutpad\ \fR]
X[\ \fB\-r\ \fIrunsize\ \fR]
X[\ \fB\-t\ \fIcbuf\ \fR]
X[\ \fB\-T\ \fIebuf\ \fR]
X[\ \fB\-w\ \fIbitwidth\ \fR]
X[\ \fB\-z\ \fIcmd\ \fR]
X.HP 5
X.B\ uncompact
X[\ \fB\-clqsSuvxX\ \fR]
X[\ \fB\-a\ \fIcmd\ \fR]
X[\ \fB\-b\ \fIcbs\ \fR]
X[\ \fB\-B\ \fIebs\ \fR]
X[\ \fB\-f\ \fIcfile\ \fR]
X[\ \fB\-F\ \fIefile\ \fR]
X[\ \fB\-n\ \fIcblks\ \fR]
X[\ \fB\-N\ \fIeblks\ \fR]
X[\ \fB\-p\ \fIoutpad\ \fR]
X[\ \fB\-t\ \fIcbuf\ \fR]
X[\ \fB\-T\ \fIebuf\ \fR]
X[\ \fB\-z\ \fIcmd\ \fR]
X.SH DESCRIPTION
X.LP
X.B compact
Xis a modified Lempel-Ziv-Welsh data compression utility designed
Xto do high speed data compression/decompression of large files,
Xparticularly system backups.
XThe algorithm resembles the \fBV.42bis\fR compression engine,
Xbut was developed independently.
X.LP
XThis program is similar to the well known \fBcompress\fR
Xprogram but runs faster, compresses large
Xfiles better, and has a superior worst case expansion
Xof incompressable data.
XThe program supports multi-volume data sets,
Xand can run as multiple concurrent tasks to overlap compute
Xand I/O activities.
XThese characteristics make it particularly useful when
Xdoing periodic system backups, especially with streaming
Xtape drives.
X.LP
XThe magic number for big-endian compacted files is C5A3;
Xlittle endian files use the byte-swapped A3C5.
XThe recommended suffix for compacted files is (\fB.W\fR).
X.LP
XThis program may be used for research into data
Xcompression techniques only.
XBe sure to read
X.B WARNING
Xsection for further discussion.
X.SH OPTIONS
X.LP
XIn the options below, upper case letters refer to the expanded
Xinput or output stream,
Xwhile opposite lower case letters refer to the compressed
Xstream.
XAll numbers may be suffixed by \fBb\fR (512 byte blocks),
X\fBk\fR (kilobytes),  or \fBm\fR (megabytes).
X.TP 12
X.BI \-a \ cmd
XExecute \fIcmd\fR before accessing each volume
Xof a multi-volume set.
XThis is useful when the media must be retensioned, erased
Xor formatted before accessing.
X.TP
X.BI \-b \ cbs
XUse a block size of \fIcbs\fR to read/write the compressed
Xstream.
XDefault is 1K.
X.TP
X.BI \-B \ ebs
XUse a block size of \fIebs\fR to read/write the expanded
Xstream.
XDefault is 1K.
X.TP
X.B \-c
XCompress data.
XThis is default for \fBcompact\fR.
X.TP
X.BI \-f \ cfile
XRead/write compressed data using file/device \fIcfile\fR.
XThis option is required for multi-volume compressed data sets.
X.TP
X.BI \-F \ efile
XRead/write compressed data using file/device \fIefile\fR.
XThis option is required for multi-volume expanded data sets.
X.TP
X.B \-l
XLock the data compression program in memory to avoid page
Xcontention in systems where file I/O and page I/O use the
Xsame buffer pool.
XThis is a priveleged function since blithely locking
Xlarge sections of main memory can lead to system deadlocks.
X.TP
X.BI \-m \ memory
XLimit the maximum amount of memory the program can use for
Xdata compression to \fImemory\fR bytes.
XThis overrides \fB\-w\fR below.
XUseful on machines where even 1 meg of working space is too large.
X.TP
X.BI \-n \ cblks
XThe volume size of the compressed file is \fIcblks\fR
Xblocks of size \fIcbs\fR bytes.
XWhen this much data has been read/written, the program will
Xprompt for another volume.
XThis is useful with multiple-volume backups.
X.TP
X.BI \-N \ eblks
XThe volume size of the expanded file is \fIeblks\fR
Xblocks of size \fIebs\fR bytes.
X.TP
X.BI \-r \ runsize
XResynchronize data compression (and discard the compression table)
Xevery \fIrunsize\fR bytes.
XThis reduces compression efficiency,
Xbut allows resynchronization of
Xthe compression data stream after a corrupted section
Xof the input file.
XThe current default is 10 megabytes.
X.TP
X.BI \-p \ outpad
XPad the final output record to a multiple of \fIoutpad\fR
Xbytes.  Raw devices often require multiples of 512 byte
Xphysical records.  Default is 512 bytes in compress mode.
X.TP
X.B \-q
XRun in quiet mode.  Do not report compression statistics
Xor byte-swapping actions.
X.TP
X.B \-s
XSwap bytes on the compressed file.
XThis option need only be selected when writing a tape, since the
Xmagic number in the compress file automatically swaps input
Xbytes when reading a byte-swapped tape.
XThe most common use of this option is to produce a tape that
Xcan be more efficiently read by a machine with opposite byte
Xordering; the \fB-S\fR option should be selected at the same time.
X.TP
X.B \-S
XSwap bytes on the expanded file.
XThis option is automatically toggled when reading a compressed
Xfile written with opposite byte ordering.
X.TP
X.BI \-t \ cbuf
XSpawn a child task to do compressed file I/O,
Xand allocate \fIcbuf\fR shared memory buffers of size \fIcbs\fR for
Xbuffering.
X.TP
X.BI \-T \ ebuf
XSpawn a child task to do expanded file I/O,
Xand allocate \fIcbuf\fR shared memory buffers of size \fIcbs\fR for
Xbuffering.
X.TP
X.B \-u
XUncompact data. This is default for \fBuncompact\fR.
X.TP
X.B \-v
XPrint program version number.
X.TP
X.BI \-w \ width
XLimit the bit \fIwidth\fR of an output code.
XMust be 17-26 bits.  See text.
X.TP
X.B \-x
XDo read/write compressed file operations to a separately
Xallocated unshared buffer.  This avoids driver bugs on
Xsome systems which manifest themselves as hung devices or
Xsystem panics.
X.TP
X.B \-X
XDo read/write expanded file operations to a separately
Xallocated unshared buffer.
X.TP
X.BI \-z \ cmd
XExecute \fIcmd\fR after each complete
Xvolume has been read or written.
XThis is useful to position a tape or eject a floppy after each
Xvolume of a multi-volume media set.
X.SH THEORY OF OPERATION
XThe program replaces commonly
Xencountered strings of 16-bit input data codes as 17-bit
Xor larger output data codes.
XAs with Lempel-Ziv-Welsh, the program allocates compression
Xcode entries in a fixed size table until the table fills.
XUnlike LZW (but like \fBV.42bis\fR) less recently encountered
Xstrings are replaced with more recently encountered strings when
Xthe table becomes full.
XThis modification of the algorithm is more expensive in processor
Xtime, and requires more memory for the hash table, but
Xgenerally reduces the size of the output file by about 10%.
X.LP
XThe 16-bit input code size halves the number of operations
Xrequired by the 8-bit \fBcompress\fR algorithm, but uses more
Xmemory, and takes longer to fill the string table.
XThe larger input code size also improves the worst-case expansion
Xof incompressible data, since (with default 17-bit output)
Xthe worst-case expansion is only 1/16 or about 6%.
X.LP
XBecause the program compresses 16-bit codes,
Xit is susceptable to big-endian/little-endian byte
Xordering incompatibilities.
XIn practice this is handled automatically when reading
Xthe compressed file, because the magic number is
Xseen as byte-swapped; the program knows to byte-swap
Xboth input and output streams when reading the data back in.
X.SH PERFORMANCE
X.LP
XOn systems available to the author,
Xwith compressed file sizes of 1 megabyte or larger,
X\fBcompact\fR runs 2.5 to 3
Xtimes faster than \fBcompress\fR while producing an output
Xfile typically 10% smaller.
XThese characteristics are largely insensitive
Xto the maximum output code size.
XOn highly compressible data, larger output code width
Xfurther improves compression perhaps 10%;  however with
Xincompressable input data (eg \fB*.Z\fR files) compression
Xdeteriorates by about the same amount.
X.LP
XAn output code width of 17 bits requires about a megabyte
Xof memory.
XEach additional output bit about doubles this
Xrequirement.
XHence output code sizes of 20 bits or more
Xare impractical on most systems, but values up to
X26 bits or so could theoretically be used.
X.LP
XOn systems without previously compressed files,
X\fBcompact\fR generally compresses anything (including program
Xbinaries) by 50% or better.
XWith 35% compressed files (the author's system disk)
Xdata compression is still 42%;
Xthis compares to 31% feeding the same input
Xstream to \fBcompress\fR.
X.LP
XWith the \fB-t\fR and \fB-T\fR the program uses multiple
Xprocesses, pipes, and System V shared memory to overlap
Xcompute and I/O activities.
XWhen both options are selected,
Xone child does input directly to shared memory,
Xwhile another child does output directly from shared memory,
Xand the parent does data compression from shared memory to
Xshared memory without device I/O delays.
X.LP
XTo achieve maximum performance on a system with adequate
Xreal memory, it is usually good to specify both input and
Xoutput buffering.
XBuffering on the expanded side is useful to smooth out
Xthe inherent bursty performance of the filesystem;
X100K to a megabyte or so works well here.
XBuffering on the compressed side should be sufficient to
Xhold all data produced while the streaming tape stops,
Xreverses and restarts; generally this takes 1-3 seconds.
X.LP
XAt the time of this writing, peak performance is limited
Xon most systems by the host processor.
XIf input-output buffering is well tuned,
Xprocessor utilization of 98% or better is common.
XThe compression algorithm itself compresses 20-30 KB/second
Xper processor VAX MIP available.
XHowever some system time is required to do I/O,
Xso uniprocessor systems cannot achieve this speed in practice.
X.LP
XAs an example, a particular SparcStation 1 (12 VAX MIPS) with a
Xfast SCSI disk, 35% compressed files and a cartridge tape drive can
Xdo a 273 Megabyte tar backup to 159 MB of tape in 36 minutes
Xusing about 3 megabytes of working storage, 50% user and 13%
Xsystem time.
XThe remaining processor power is absorbed by the tar program to bring
Xtotal system processor utilization to 99%.
XThis runs the tape drive at 74% of rated speed.  A similar
Xuncompressed backup running the drive at full speed would
Xtake 46 minutes, except that the data wouldn't fit on the
Xtape.
XThe specific shell command used is:
X.LP
X	tar cf - /home | compact -T400 -B4k -t6 -b100k >/dev/rct0
X.SH WARNING
X.LP
XThis program may be used only for research into data compression,
Xas a case study in program optimization, or as an example of
XSystem V interprocess communication.
X.LP
XThe author originally intended the program
Xfor practical day-to-day usage.
XHowever, in final preparation for program release,
Xit was learned the program probably infringes one
Xor more widely licensed patents in data compression.
XThere is no choice but to release the program for
Xresearch purposes only.
X.LP
X\fBCompact\fR was derived exclusively from the \fBcompress\fR
Xprogram and from the paper quoted in the references.
XIt appears the author duplicated much of the research
Xleading to \fBV.42bis\fR,
Xand the discussion given there is
Xuseful in understanding this program.
XThere remain significant differences the author has not
Xanalyzed in detail.
XCareful study of those differences might yield something new.
X.SH "SEE ALSO"
X.BR compress (1)
X.LP
X.IR "A Technique for High Performance Data Compression" ,
XTerry A. Welch,
X.IR "computer" ,
Xvol. 17, no. 6 (June 1984), pp. 8-19.
X.SH BUGS
X.LP
XThe program attempts to always read full input records.
XIf for example a 10KB read returns only 4KB, the program tries
Xagain with 6KB.  If less than 6KB is received, it will try
Xagain with the remaining buffer space.
XSome tape drivers may consider this antisocial behavior.
X.LP
XThe program won't run on machines with 16 bit integers and 64K addressing.
XIntel 80286 users, eat your hearts out.
X.LP
XAn alarmingly large number of systems seem unable to do floppy
Xor tape I/O to shared memory, presumably because the DMA lock-down
Xmechanism is confused by shared pages.
XOn these systems, the \fBx\fR or \fBX\fR options must be used
Xto prevent device hangs or system crashes.
END_OF_compact.1
if test 11811 -ne `wc -c <compact.1`; then
    echo shar: \"compact.1\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f compact.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"compact.c\"
else
echo shar: Extracting \"compact.c\" \(40644 characters\)
sed "s/^X//" >compact.c <<'END_OF_compact.c'
X/******************************************************************
X *	ex:se ts=4 sw=4 ai:
X *
X *	Data compression program.
X *
X *	Author: Gene H. Olson
X *	        Smartware Consulting
X *
X *	This is FREE software in the PUBLIC DOMAIN.
X *
X *	This program may be used only for research into data
X *	compression techniques. See WARNING in compact(1)
X *	manual page for further discussion.
X *
X *	"Everything FREE contains no guarantee."
X ********************************************************************/
X
X#define VERSION "compact version 1.0 PL0"
X
X#ifndef lint
Xstatic char sccs[] = "@(#)compact.c	1.25 10/28/90" ;
X#endif
X
X#include <sys/lock.h>
X
X#include <fcntl.h>
X#include <stdio.h>
X#include <ctype.h>
X#include <assert.h>
X#include <signal.h>
X
X#if MARK
X# include <prof.h>
X#else
X# define MARK(x)
X#endif
X
Xextern void exit() ;
Xextern char *memset() ;
Xextern char *memcpy() ;
X
Xextern char *share_create() ;
Xextern char *share_open() ;
X
X#ifndef DEBUG
X#	ifdef lint
X#		define DEBUG 99
X#	else
X#		define DEBUG 0
X#	endif
X#endif
X
X#ifndef CHECK
X#	if DEBUG >= 2
X#		define CHECK 100
X#	else
X#		define CHECK 0
X#	endif
X#endif
X
X/*
X *	Pet typedefs.
X */
X
X#define SWAP(x) (((ushort)(x) >> 8) | ((ushort)(x) << 8))
X
X#define uchar	unsigned char
X#define	ushort	unsigned short
X#define ulong	unsigned long
X
X#if lint
X#define malloc(x)		0
X#define realloc(x,y)	0
X#endif
X
X/*
X *	Fundamental constants.
X */
X
X#define MAGIC		0xc5a3		/* Magic number */
X#define ODDMAGIC	0xc3a4		/* EOF magic for odd files */
X#define EVENMAGIC	0xc3a5		/* EOF magic for even files */
X
X#define MAXCHAR ((1 << 16) + 1)	/* Size of character set */
X
X/*
X *	Tuning parameter defaults.
X */
X
X#define	MEMORY	0x40000000		/* Default bytes memory */
X#define INRUN	10000000		/* Default bytes input run */
X
X#ifndef HASHRATIO
X#define	HASHRATIO	1.2			/* Ratio of hash entries to items */
X#endif
X
X/*
X *	Compression item table definition.
X */
X
X#define	HASHFUN(prefix, suffix) \
X	((((((prefix) << 3) + (suffix)) << 4) - (prefix) + (suffix)) & (hashmask))
X
Xtypedef struct h_struct ITEM ;
X
Xstruct h_struct
X{
X	ITEM*	h_next ;			/* Next item on hash list */
X	ITEM**	h_last ;			/* Link ptr to this item */
X	ulong	h_code ;			/* Previous code */
X	ushort	h_char ;			/* Current input character */
X	ushort	h_ref ;				/* Reference flag */
X} ;
X
Xint hashsize ;					/* Hash table size (power 2) */
X
Xint maxitem ;					/* Maximum item number */
X
X/*
X *	Decompression item table definition.
X */
X
Xtypedef struct d_struct DEC ;
X
Xstruct d_struct
X{
X	ulong	d_code ;			/* Decompression code */
X	ushort	d_char ;			/* Decompression character */
X	ushort	d_ref ;				/* Reference flag */
X} ;
X
X/*
X *	Caught signal list.
X */
X
Xint sig[] = { SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGTERM, 0 } ;
X
X/*
X *	Debugging stuff.
X */
X
X#if DEBUG >= 1
Xint debug ;						/* Debug level */
X#endif
X
X/*
X *	Efficiency checks.
X */
X
X#if DEBUG >= 1
Xlong collision ;
X#endif
X
X/*
X *	User parameters.
X */
X
Xint action ;					/* c==compact or e==expand */
X
Xchar *infname ;					/* Input file name */
Xchar *outfname ;				/* Output file name */
X
Xint infd ;						/* Input file descriptor */
Xint outfd ;						/* Output file descriptor */
X
Xlong inrun ;					/* Maximum input run length */
X
Xlong memory ;					/* Memory to use */
Xlong maxwidth ;					/* Max width of output code */
X
Xint quiet ;						/* Quiet mode */
X
Xint inbs ;						/* Input block size */
Xint outbs ;						/* Output block size */
X
Xint phys ;						/* Output physical record size */
X
Xint insize ;					/* Input volume size (records) */
Xint outsize ;					/* Output volume size (records) */
X
Xulong inrec ;					/* Input record number */
Xulong outrec ;					/* Output record number */
X
Xint incopy ;					/* Read input to non-shared buffer */
Xint outcopy ;					/* Write output from non-shared buffer */
X
Xint inswap ;					/* Swap input bytes */
Xint outswap ;					/* Swap output bytes */
X
Xint memlock ;					/* Lock process into memory */
X
Xint intank ;					/* Number of input buffers */
Xint outtank ;					/* Number of output buffers */
X
Xint inpid ;						/* Input tank process PID */
Xint outpid ;					/* Output tank process PID */
X
Xint (*getin)() ;				/* Get input routine */
Xint (*putout)() ;				/* Put output routine */
X
Xchar *acommand ;				/* Medium setup command */
Xchar *zcommand ;				/* Medium teardown command */
X
Xchar *rsh ;						/* Read share structure (opaque) */
Xchar *wsh ;						/* Write share structure (opaque) */
X
X/*
X *	Miscellaneous declarations.
X */
X
Xint invol ;						/* Input volume number */
Xint outvol ;					/* Output volume number */
X
Xushort *inbuf ;					/* Input buffer */
Xushort *outbuf ;				/* Output buffer */
X
Xushort *lastbuf ;				/* Last buffer read by inprocess */
Xint lastcount ;					/* Corresponding buffer count */
X
Xulong intotal ;					/* Input character count */
Xulong outtotal ;				/* Output character count */
X
Xushort eofmagic ;				/* EOF magic number */
X
X
X
X/**************************************************************
X *	quit - Exit gracefully.
X **************************************************************/
X
Xvoid
Xquit(s)
Xint s ;
X{
X	register int i ;
X	register int pid ;
X	int rs ;
X
X	for (i = 0 ; sig[i] ; i++) (void) signal(sig[i], SIG_IGN) ;
X
X	/*
X	 *	Close shared memory.
X	 */
X
X	if (rsh) share_close(rsh) ;
X	if (wsh) share_close(wsh) ;
X
X	/*
X	 *	Wait for inprocess and outprocess to complete.
X	 */
X
X	while (inpid | outpid)
X	{
X		pid = wait(&rs) ;
X		if (pid == -1) break ;
X		if (inpid == pid)
X		{
X			inpid = 0 ;
X			if (s == 0) s = rs ;
X		}
X		if (outpid == pid)
X		{
X			outpid = 0 ;
X			if (s > 0) s = rs ;
X		}
X	}
X	
X	/*
X	 *	Destroy allocated shared memory.
X	 */
X
X	if (rsh) share_destroy(rsh) ;
X	if (wsh) share_destroy(wsh) ;
X
X	/*
X	 *	Execute final teardown command.
X	 *
X	 *	Note that if we were in compress mode with
X	 *	multi-volume input, the zcommand has already
X	 *	been done by the input() routine when prompting
X	 *	for the last volume of the set.
X	 */
X
X	if (s == 0 && zcommand && !(action == 'c' && insize))
X		(void) system(zcommand) ;
X
X	exit(s) ;
X}
X
X
X
X/***********************************************************
X *	getval - Get value
X ***********************************************************/
X
Xlong
Xgetval(cp, units)
Xregister char *cp ;
Xint units ;
X{
X	register int val ;
X
X	units = 0 ;
X
X	if (!isdigit(*cp)) return(-1) ;
X
X	val = 0 ;
X	do
X	{
X		val *= 10 ;
X		val += *cp++ - '0' ;
X	} while (isdigit(*cp)) ;
X
X	if (*cp) units = *cp++ ;
X
X	switch (units)
X	{
X		case 'B':
X		case 'b':
X			val *= 512 ;
X			break ;
X
X		case 'K':
X		case 'k':
X			val *= 1024 ;
X			break ;
X
X		case 'M':
X		case 'm':
X			val *= 1000 * 1024 ;
X			break ;
X		
X		case 0:
X			break ;
X		
X		default:
X			return(-1) ;
X	}
X
X	return (*cp==0 ? val : -1) ;
X}
X
X
X
X/************************************************************
X *	confirm - Wait for volume load confirmation.
X ************************************************************/
X
Xconfirm()
X{
X	register int n ;
X	char ch ;
X	int answer ;
X	static int ttyfd = -1 ;
X
X	/*
X	 *	Get a file descriptor to reference the
X	 *	users tty terminal.
X	 */
X
X	if (ttyfd < 0)
X	{
X		if (isatty(0)) ttyfd = 0 ;
X		else if ((ttyfd = open("/dev/tty", O_RDWR)) == -1)
X		{
X			(void) fprintf(stderr, "Cannot open ") ;
X			perror("/dev/tty") ;
X			quit(1) ;
X		}
X	}
X
X	/*
X	 *	Read the tty for the user's answer.
X	 */
X
X	answer = 0 ;
X
X	for (;;)
X	{
X		n = read(ttyfd, &ch, 1) ;
X		if (n == 1)
X		{
X			if (ch == '\r' || ch == '\n') break ;
X			if (answer == 0) answer = ch ;
X		}
X		if (n < 0)
X		{
X			perror("TTY") ;
X			quit(1) ;
X		}
X	}
X	
X	return(answer) ;
X}
X
X
X
X/************************************************************
X *	input - Get an input block.
X ************************************************************/
X
Xinput()
X{
X	register int n ;
X	register int i ;
X	register ushort *wp ;
X	int ch ;
X	static int eoi ;
X
X	if (eoi) return(0) ;
X
X	/*
X	 *	Loop to read in a full input record.
X	 */
X
X	for (n = 0 ;;)
X	{
X		/*
X		 *	Handle multi-volume input.
X		 */
X
X		if (infname && inrec == 0)
X		{
X			/*
X			 *	If not the first volume of an n-volume set,
X			 *	ask for confirmation before opening the file.
X			 *
X			 *	If he responds with (q), assume the last
X			 *	volume has been read, and return end-of-file.
X			 */
X
X			if (++invol > 1)
X			{
X				if (zcommand) (void) system(zcommand) ;
X
X				for (;;)
X				{
X					(void) fprintf(stderr,
X						"Mount input volume #%d, hit <RETURN> (or 'q' to quit)? ",
X						invol) ;
X
X					ch = confirm() ;
X			
X					if (ch == 0) break ;
X
X					if (ch == 'q')
X					{
X						eoi = 1 ;
X						goto done ;
X					}
X				}
X			
X
X				if (acommand) (void) system(acommand) ;
X			}
X			
X			/*
X			 *	Open the file.
X			 */
X
X			if ((infd = open(infname, O_RDONLY)) == -1)
X			{
X				(void) fprintf(stderr, "Cannot open ") ;
X				perror(infname) ;
X				quit(1) ;
X			}
X		}
X
X		/*
X		 *	Read in records until we get a full record, an
X		 *	end-of-file, or an error.
X		 */
X
X		for (;;)
X		{
X			i = read(infd, (char*)inbuf + n, inbs - n) ;
X
X			/*
X			 *	Handle EOF.  If we are not doing multi-volume input
X			 *	consider this the EOI.  If we are doing multi-volume
X			 *	input, go back for the next volume.
X			 */
X
X			if (i == 0)
X			{
X				(void) close(infd) ;
X
X				if (insize)
X				{
X					inrec = 0 ;
X					break ;
X				}
X
X				eoi = 1 ;
X				goto done ;
X			}
X
X			/*
X			 *	Handle a file I/O error.
X			 */
X
X			if (i == -1)
X			{
X				n = -1 ;
X				perror("Input read error") ;
X				(void) close(infd) ;
X				return(-1) ;
X			}
X
X			/*
X			 *	A good read.  Add the bytes read this time to
X			 *	the running total.  When we get a full record,
X			 *	increment the volume record count.  When the
X			 *	full volume size is reached, reset the record
X			 *	position so we go back for another volume next
X			 *	time.
X			 */
X
X			n += i ;
X
X			if (n == inbs)
X			{
X				if (++inrec == insize)
X				{
X					(void) close(infd) ;
X					inrec = 0 ;
X				}
X				goto done ;
X			}
X		}
X	}
X
X	/*
X	 *	If byte swapping is in effect, swap all the
X	 *	bytes in the current input buffer.
X	 */
X
Xdone:
X	if (inswap)
X	{
X		wp = inbuf ;
X		i = (n + 1) / 2 ;
X		while (--i >= 0)
X		{
X			*wp = SWAP(*wp) ;
X			wp++ ;
X		}
X	}
X
X	return(n) ;
X}
X
X
X
X/******************************************************************
X *	fileinput - File input.
X ******************************************************************/
X
Xfileinput()
X{
X	register int n ;
X
X	n = input() ;
X
X	if (n > 0)
X	{
X		intotal += n ;
X		if (n & 1) eofmagic = ODDMAGIC ;
X	}
X
X	return((n + 1) / 2 - 1) ;
X}
X
X
X
X/******************************************************************
X *	inprocess - Input process
X *
X *	This procedure forks, then runs as a process reading from
X *	the input device or pipe.
X ******************************************************************/
X
Xinprocess()
X{
X	register char *sh ;
X	int rc ;
X	ushort *buf ;
X	ushort *ibuf ;
X	ushort *wp ;
X	int pfd[2] ;
X	int cfd[2] ;
X
X	rsh = share_create(pfd, cfd, inbs, intank) ;
X	if (rsh == 0)
X	{
X		(void) fprintf(stderr, "Shared memory allocated failed\n") ;
X		quit(1) ;
X	}
X	
X	/*
X	 *	Create the task.  Setup file descriptors so the
X	 *	inprocess will die on SIGPIPE if the primary process
X	 *	exits, but no SIGPIPE occurs to the promary process
X	 *	when inprocess exits.
X	 */
X
X	while ((inpid = fork()) == -1) sleep(1) ;
X
X	if (inpid == 0)
X	{
X		(void) close(1) ;
X		(void) close(cfd[0]) ;
X		(void) close(cfd[1]) ;
X
X		/*
X		 *	If input is done to a non-shared buffer,
X		 *	allocate the buffer here.
X		 */
X
X		if (incopy)
X		{
X			buf = (ushort *)malloc(inbs) ;
X			if (buf == 0)
X			{
X				(void) fprintf(stderr, "Not enough memory\n") ;
X				exit(1) ;
X			}
X		}
X		else buf = 0 ;
X
X		/*
X		 *	Loop reading input blocks until done.
X		 */
X		
X		sh = share_open(pfd) ;
X		assert(sh) ;
X
X		for (;;)
X		{
X			/*
X			 *	Get a block, read data into it, then pass it
X			 *	to the data compression process.
X			 */
X
X			if (share_get(sh, (char**)&inbuf, &rc) <= 0) break ;
X
X			if (buf)
X			{
X				ibuf = inbuf ;
X				inbuf = buf ;
X			}
X
X			if ((rc = input()) <= 0) break ;
X
X			if (buf)
X			{
X				inbuf = ibuf ;
X				(void) memcpy((char *)inbuf, (char *)buf, rc) ;
X			}
X
X			if (share_put(sh, (char*)inbuf, rc) <= 0) break ;
X
X			/*
X			 *	In data compression mode with multi-volume
X			 *	input, detect EOI here so as to avoid prompting
X			 *	the user unnecessarily for another volume.
X			 */
X
X			if (action == 'e' && insize)
X			{
X				wp = inbuf + rc / sizeof(ushort) ;
X
X				while (wp > inbuf && *--wp == 0) ;
X
X				if	(	wp >= inbuf
X					&&	(*wp == ODDMAGIC || *wp == EVENMAGIC)
X					&&	(wp == inbuf || *--wp == 0)
X					&&	(wp == inbuf || *--wp == 0)
X					&&	(wp == inbuf || *--wp == 0)
X					)
X					break ;
X			}
X		}
X
X#if DEBUG >= 2
X		if (debug >= 2)
X		{
X			(void) fprintf(stderr, "Input child terminating normally\n") ;
X		}
X#endif
X
X		/*
X		 *	Exit the child when complete.
X		 */
X
X		share_close(sh) ;
X
X		exit(0) ;
X	}
X
X	/*
X	 *	Initialize the shared memory interface in the
X	 *	parent process.
X	 */
X
X	(void) close(pfd[1]) ;
X
X	rsh = share_open(cfd) ;
X}
X
X
X
X/************************************************************
X *	shareinput - Do input from shared memory.
X ************************************************************/
X
Xshareinput()
X{
X	int n ;
X
X	if (inbuf) (void) share_put(rsh, (char*)inbuf, 0) ;
X
X	if (share_get(rsh, (char**)&inbuf, &n) <= 0) return(-1) ;
X
X	if (n > 0)
X	{
X		intotal += n ;
X		if (n & 1) eofmagic = ODDMAGIC ;
X	}
X
X	return((n + 1) / 2 - 1) ;
X}
X
X
X
X/***********************************************************
X *	output - Output a block of data.
X ***********************************************************/
X
Xoutput(nbyte)
Xint nbyte ;					/* Number of bytes to write */
X{
X	register ushort *wp ;
X	register int n ;
X	register int bsize ;
X
X	/*
X	 *	Open the output file if necessary.
X	 */
X
X	if (outfname && outrec == 0)
X	{
X		if (++outvol > 1)
X		{
X			if (zcommand) (void) system(zcommand) ;
X
X			(void) fprintf(stderr,
X				"Please mount output volume #%d, hit <RETURN> ",
X				outvol) ;
X
X			(void) confirm() ;
X
X			if (acommand) (void) system(acommand) ;
X		}
X
X		if ((outfd = open(outfname, O_WRONLY)) == -1)
X		{
X			(void) fprintf(stderr, "Cannot open ") ;
X			perror(outfname) ;
X			quit(1) ;
X		}
X	}
X	
X	/*
X	 *	Swap output bytes if that option is selected.
X	 */
X
X	if (outswap)
X	{
X		n = nbyte / 2 + 1 ;
X		wp = outbuf ;
X		while (--n >= 0)
X		{
X			*wp = SWAP(*wp) ;
X			wp++ ;
X		}
X	}
X
X	/*
X	 *	Pad to a multiple of the given physical size if
X	 *	that option is selected.
X	 */
X
X	if (nbyte < outbs && phys)
X	{
X		wp = &outbuf[nbyte/2] ;
X		bsize = (nbyte + phys - 1) / phys * phys ;
X		while (nbyte < bsize)
X		{
X			*wp++ = 0 ;
X			nbyte += 2 ;
X		}
X	}
X
X	/*
X	 *	Write the data and check for errors.
X	 */
X
X	n = write(outfd, (char*) outbuf, (int) nbyte) ;
X
X	if (n < 0)
X	{
X		perror("Write error") ;
X		return(0) ;
X	}
X	
X	if (n != nbyte)
X	{
X		(void) fprintf(stderr,
X			"Write of %d bytes returned %d\n", nbyte, n) ;
X	}
X
X	if (++outrec == outsize)
X	{
X		outrec = 0 ;
X		(void) close(outfd) ;
X	}
X
X	return(outbs) ;
X}
X
X
X
X/*****************************************************************
X *	fileoutput - File oriented output routine.
X *****************************************************************/
X
Xfileoutput(n)
X{
X	if (n < outbs && phys) outtotal += (n + phys - 1) / phys * phys ;
X	else outtotal += n ;
X
X	return(output(n) / 2) ;
X}
X
X
X
X/******************************************************************
X *	outprocess - Output process
X *
X *	This procedure forks, then runs as a process writing to
X *	the output device or pipe.
X ******************************************************************/
X
Xoutprocess()
X{
X	int pfd[2] ;
X	int cfd[2] ;
X	int wcount ;
X	int n ;
X	char *sh ;
X	ushort *obuf ;
X	ushort *buf ;
X
X	wsh = share_create(pfd, cfd, outbs, outtank) ;
X	if (wsh == 0)
X	{
X		(void) fprintf(stderr, "Shared memory allocated failed\n") ;
X		quit(1) ;
X	}
X	
X	/*
X	 *	Create the task.  Setup file descriptors so the
X	 *	primary process will die on SIGPIPE if outprocess
X	 *	exits, but no SIGPIPE occurs to outprocess when
X	 *	the primary process exits.
X	 */
X
X	while ((outpid = fork()) == -1) sleep(1) ;
X
X	if (outpid == 0)
X	{
X		(void) close(0) ;
X		(void) close(pfd[1]) ;
X
X		sh = share_open(cfd) ;
X		assert(sh) ;
X
X		if (outcopy)
X		{
X			buf = (ushort *)malloc(outbs) ;
X			if (buf == 0)
X			{
X				(void) fprintf(stderr, "Not enough memory!\n") ;
X				exit(1) ;
X			}
X		}
X		else buf = 0 ;
X
X		for (;;)
X		{
X			if (share_get(sh, (char**)&outbuf, &wcount) <= 0) break ;
X
X			if (buf)
X			{
X				(void) memcpy((char *)buf, (char *)outbuf, wcount) ;
X				obuf = outbuf ;
X				outbuf = buf ;
X			}
X
X			if (output(wcount) <= 0) break ;
X
X			if (buf) outbuf = obuf ;
X
X			if (share_put(sh, (char*) outbuf, 0) <= 0) break ;
X		}
X
X		share_close(sh) ;
X
X		exit(0) ;
X	}
X	
X	(void) close(cfd[0]) ;
X	(void) close(cfd[1]) ;
X
X	wsh = share_open(pfd) ;
X
X	(void) share_get(wsh, (char**)&outbuf, &n) ;
X}
X
X
X
X/********************************************************
X *	shareoutput - Shared memory output routine.
X ********************************************************/
X
Xshareoutput(n)
Xint n ;
X{
X	if (n < outbs && phys) outtotal += (n + phys - 1) / phys * phys ;
X	else outtotal += n ;
X
X	if (share_put(wsh, (char*) outbuf, n) <= 0) return(0) ;
X
X	if (share_get(wsh, (char**)&outbuf, &n) <= 0) return(0) ;
X
X	return(outbs/2) ;
X}
X
X
X
X/*************************************************************
X *	check - keep track of the progress of the algorithm.
X *************************************************************/
X
X#if CHECK
Xcheck(incount, outptr)
Xint incount ;
Xushort *outptr ;
X{
X	ulong in ;
X	ulong out ;
X	float avg ;
X	float ratio ;
X
X	static ulong lastin ;
X	static ulong lastout ;
X
X	in = intotal - incount * sizeof(ushort) ;
X	out = outtotal + (long) outptr - (long) outbuf ;
X
X	avg = 100.0 * (float) out / (float) in ;
X	ratio = 100.0 * (float) (out - lastout) / (float) (in - lastin) ;
X
X	lastin = in ;
X	lastout = out ;
X
X#if DEBUG >= 1
X	if (debug >= 1) (void) fprintf(stderr,
X		"Read: %-8ld Wrote: %-8ld Col: %-8ld Ratio: %6.2f%%  %6.2f%%\n",
X		in, out, collision, ratio, avg
X		) ;
X#endif
X}
X#endif
X
X
X
X/**************************************************************
X *	compact - Compression algorithm.
X **************************************************************/
X
Xcompact()
X{
X	register ITEM *hp ;
X	register ITEM *sp ;
X	register ITEM **hpp ;
X	register ushort *inptr ;
X	register ushort *outptr ;
X
X	register ulong ch ;
X	register ulong code ;
X	register ulong out ;
X	register int obit ;
X	register int osize ;
X
X	register int incount ;
X	register int outcount ;
X	register ulong nitem ;
X	register ulong mitem ;
X	register ulong hashmask ;
X
X	ITEM *itemtable ;
X	ITEM **hashtable ;
X
X	ITEM *ep ;
X	long bitcount ;
X	long blockcount ;
X	long i ;
X
X	ushort header[10] ;
X
X#if CHECK
X	long chkcount = CHECK ;
X#endif
X
X	/*
X	 *	If max output width is given, set maxitem accordingly.
X	 */
X
X	maxitem = (1 << maxwidth) - MAXCHAR ;
X
X	/*
X	 *	Reduce maxitem if necessary to fit into the requested
X	 *	maximum memory.
X	 */
X
X	i = memory / (sizeof(ITEM) + sizeof(ITEM *)) ;
X	if (maxitem > i) maxitem = i ;
X
X	/*
X	 *	Set the hashtable to have the same approximate number
X	 *	of entries as in maxitem, rounded to the closest
X	 *	power of 2.
X	 */
X
X	hashsize = HASHRATIO * maxitem ;
X	while (i = hashsize & (hashsize - 1)) hashsize = i ;
X	hashmask = hashsize - 1 ;
X
X#if DEBUG >= 2
X	if (debug >= 2)
X	{
X		(void) fprintf(stderr,
X			"hashsize = %ld (%ldK), maxitem = %ld (%ldK)\n",
X			hashsize, sizeof(ITEM *) * hashsize / 1024,
X			maxitem, sizeof(ITEM) * maxitem / 1024) ;
X	}
X#endif
X
X	/*
X	 *	Allocate hash table and item table.
X	 */
X
X	itemtable = (ITEM *)malloc(maxitem * sizeof(ITEM)) ;
X	hashtable = (ITEM **)malloc(hashsize * sizeof(ITEM *)) ;
X
X	if (itemtable == 0 || hashtable == 0)
X	{
X		(void) fprintf(stderr,
X			"Cannot allocate enough memory!\n") ;
X		exit(2) ;
X	}
X
X	/*
X	 *	Initialize output.
X	 */
X
X	outcount = outbs / 2 + 1 ;
X	outptr = outbuf ;
X	eofmagic = EVENMAGIC ;
X
X	/*
X	 *	Each pass through the loop, compress one block
X	 *	of data.
X	 */
X
X	for (;;)
X	{
X
X#if DEBUG >= 1
X		if (debug >= 1) (void) fprintf(stderr, "\n") ;
X#endif
X
X		/*
X		 *	Initialize for a new scan.
X		 */
X
X		(void) memset((char*)hashtable, 0, (int)(hashsize * sizeof(ITEM *))) ;
X
X		nitem = 0 ;
X
X		sp = &itemtable[0] ;
X		ep = &itemtable[maxitem] ;
X
X		out = 0 ;
X		obit = 0 ;
X		code = 0 ;
X		osize = 17 ;
X		bitcount = (1 << 16) ;
X
X		blockcount = inrun / inbs + .5 ;
X
X		/*
X		 *	Prepare the Magic number, and the maximum number
X		 *	of output codes for transmission.
X		 */
X
X		mitem = maxitem ;
X
X		header[0] = MAGIC ;
X		header[1] = 17 ;
X		header[2] = maxitem & 0xffff ;
X		header[3] = maxitem >> 16 ;
X
X		for (i = 0 ; i < 4 ; i++)
X		{
X			if (--outcount <= 0)
X			{
X				if ((outcount = (*putout)(outbs)) == 0) return(1) ;
X				outptr = outbuf ;
X			}
X			*outptr++ = header[i] ;
X		}
X				
X		/*
X		 *	Read in the first input character of the run.
X		 */
X
X		if ((incount = (*getin)()) < 0) goto endrun ;
X
X		inptr = inbuf ;
X		ch = *inptr++ ;
X
X		/*
X		 *	Each pass through the loop, compress 1 or
X		 *	more input characters to output 1 compression
X		 *	code.
X		 */
X
X		for (;;)
X		{
X
X			MARK(cmatch) ;
X
X			code = ch + 1 ;
X
X#if DEBUG >= 3
X			if (debug >= 3) (void) fprintf(stderr,
X				"\nMatching char: %04x\n", ch) ;
X#endif
X
X			/*
X			 *	Each time through the loop, match the next
X			 *	input character if possible and continue.
X			 *	Exit when the match fails.
X			 */
X
X			for (;;)
X			{
X
X				/*
X				 *	Read in the next input character.
X				 */
X
X				if (--incount < 0)
X				{
X
X					if (--blockcount == 0)
X					{
X						incount = 0 ;
X						goto endrun ;
X					}
X#if CHECK
X					if (--chkcount <= 0)
X					{
X						check(0, outptr) ;
X						chkcount = CHECK ;
X					}
X#endif
X					if ((incount = (*getin)()) < 0) goto endrun ;
X
X					inptr = inbuf ;
X				}
X
X				ch = *inptr++ ;
X
X#if DEBUG >= 3
X				if (debug >= 3) (void) fprintf(stderr,
X					"Read input char: %04x", ch) ;
X#endif
X
X				/*
X				 *	Search the hash table for the required
X				 *	last code and current character.
X				 */
X
X				hpp = &hashtable[HASHFUN(code, ch)] ;
X
X				for (;;)
X				{
X					if ((hp = *hpp) == 0) goto miss ;
X
X					if (hp->h_code == code && hp->h_char == ch) break ;
X
X					hpp = &hp->h_next ;
X#if DEBUG >= 1
X					collision++ ;
X#endif
X				}
X
X				code = hp - itemtable + MAXCHAR ;
X
X#if DEBUG >= 3
X				if (debug >= 3) (void) fprintf(stderr,
X					"\tMatched code: %lx\n", (ulong) code) ;
X#endif
X			}
X
X			/*
X			 *	Output the next code.
X			 */
X
Xmiss:
X			MARK(cout) ;
X
X			out |= code << obit ;
X			obit += osize ;
X
X			if (--outcount <= 0)
X			{
X				if ((outcount = (*putout)(outbs)) == 0) return(1) ;
X				outptr = outbuf ;
X			}
X
X			*outptr++ = out ;
X
X			out >>= 16 ;
X			obit -= 16 ;
X
X			if (obit >= 16)
X			{
X				
X				if (--outcount <= 0)
X				{
X					if ((outcount = (*putout)(outbs)) == 0) return(1) ;
X					outptr = outbuf ;
X				}
X
X				*outptr++ = out ;
X
X				if (obit > 16) out |= code >> (osize - obit) ;
X
X				out >>= 16 ;
X				obit -= 16 ;
X			}
X
X#if DEBUG >= 3
X			if (debug >= 3) (void) fprintf(stderr,
X				"\nOutput code: %lx\n", (ulong) code) ;
X#endif
X
X			MARK(cbuild) ;
X
X			sp->h_next = 0 ;
X			sp->h_last = hpp ;
X			*hpp = sp ;
X
X			sp->h_code = code ;
X			sp->h_char = ch ;
X			sp->h_ref = 0 ;
X			sp++ ;
X
X			if (code >= MAXCHAR) itemtable[code - MAXCHAR].h_ref = 1 ;
X
X#if DEBUG >= 3
X			if (debug >= 3)
X			{
X				(void) fprintf(stderr,
X					"Created compression code: %05lx ( %05lx %04x )\n",
X					sp - itemtable + MAXCHAR - 1, code, ch) ;
X			}
X#endif
X			/*
X			 *	If we added a new entry to the table, expand
X			 *	the output bitsize if necessary.
X			 */
X
X			if (nitem < mitem)
X			{
X				MARK(calloc) ;
X
X				if (--bitcount == 0)
X				{
X					bitcount = 1 << osize ;
X					osize++ ;
X#if DEBUG >= 2
X					if (debug >= 2) (void) fprintf(stderr,
X						"nitem = %d, osize = %d, bitcount = %d\n",
X						nitem, osize, bitcount) ;
X#endif
X				}
X
X				if (++nitem != mitem) continue ;
X			}
X
X			/*
X			 *	When the compress item table has become full,
X			 *	find the least-recently-used leaf node and
X			 *	reallocate it.
X			 */
X
X			MARK(cscan) ;
X
X			for (;;)
X			{
X				if (sp == ep) sp = itemtable ;
X
X				if (sp->h_ref == 0) break ;
X
X				if (sp->h_code >= MAXCHAR)
X				{
X					itemtable[sp->h_code - MAXCHAR].h_ref = 1 ;
X				}
X
X				sp->h_ref = 0 ;
X				sp++ ;
X			}
X
X			*sp->h_last = sp->h_next ;
X			if (sp->h_next) sp->h_next->h_last = sp->h_last ;
X		}
X
X		/*
X		 *	Flush the output buffer, and add a few bytes
X		 *	of zero to guarantee synchronization.
X		 */
X
Xendrun:
X		MARK(cend) ;
X
X#if DEBUG >= 3
X		if (debug >= 3) (void) fprintf(stderr,
X			"\nOutput final code: %lx\n", (ulong) code) ;
X#endif
X
X		for (i = 0 ; i < 6 ; i++)
X		{
X			if (--outcount <= 0)
X			{
X				if ((outcount = (*putout)(outbs)) == 0) return(1) ;
X				outptr = outbuf ;
X			}
X
X			out |= code << obit ;
X
X			*outptr++ = out ;
X
X			out >>= 16 ;
X			code >>= 16 ;
X		}
X
X		if (incount < 0) break ;
X
X#if CHECK
X		check(incount, outptr) ;
X		chkcount = CHECK ;
X#endif
X	}
X
X	/*
X	 *	Add the EOF code at the end.
X	 */
X
X	if (--outcount <= 0)
X	{
X		if ((outcount = (*putout)(outbs)) == 0) return(1) ;
X		outptr = outbuf ;
X	}
X
X	*outptr++ = eofmagic ;
X
X	if	(	outptr != outbuf
X		&&	(*putout)((int) ((long)outptr-(long)outbuf)) == 0
X		)
X		return(1) ;
X	
X#if DEBUG >= 1
X	if (debug >= 1) check(0, outbuf) ;
X#endif
X
X	if (!quiet && intotal != 0)
X	{
X		(void) fprintf(stderr,
X			"Read %ld,  Wrote %ld,  Compression %6.2f%%\n",
X			intotal, outtotal, 100.0 * (long)(intotal - outtotal) / intotal
X			) ;
X	}
X
X#if DEBUG >= 1
X	if (!quiet && intotal > 1)
X	{
X		(void) fprintf(stderr,
X			"Collisions: %ld, %6.2f %%\n",
X			collision,
X			(100.0 * 17.0 / 16.0 * collision) / (intotal-1)
X			) ;
X	}
X#endif
X
X	return(0) ;
X}
X
X
X
X/***********************************************************
X *	expand - Expand previously compressed data.
X ***********************************************************/
X
Xexpand()
X{
X	register DEC *sp ;
X	register DEC *dp ;
X	register ushort *bp ;
X	register ushort *outptr ;
X	register ushort *inptr ;
X
X	register int ibit ;
X	register ulong in ;
X	register ulong ch ;
X	register ulong code ;
X	register ulong lastcode ;
X	register int incount ;
X	register int outcount ;
X
X	int bitcount ;
X	ulong maxcode ;
X	DEC *dectable ;
X	DEC *ep ;
X
X	int isize ;
X	ulong imask ;
X	ushort *ip ;
X	ushort *buffer ;
X	int ic ;
X	int i ;
X	ulong skip ;
X	ulong zskip ;
X	int state ;
X	ulong ncode ;
X	int err ;
X	int magic ;
X	int nbyte ;
X
X	ushort header[4] ;
X
X	/*
X	 *	Initialize I/O buffers.
X	 */
X
X	err = 0 ;
X	incount = 0 ;
X	magic = 0 ;
X	outptr = outbuf ;
X	outcount = outbs / 2 + 1 ;
X	maxcode = 0 ;
X	dectable = 0 ;
X	buffer = 0 ;
X
X	/*
X	 *	Each time through the loop, process a complete
X	 *	block of data.
X	 */
X
X	state = 4 ;
X
X	for (;;)
X	{
X
X		/*
X		 *	Synchronize to the beginning of a new block of data.
X		 *
X		 *	The file begins immediately with a magic number.
X		 *
X		 *	Thereafter, blocks of decompression data are delimited
X		 *	by at least two bytes of zero, followed by the two
X		 *	byte magic number.
X		 */
X
X		MARK(esync) ;
X
X		skip = 0 ;
X		zskip = 0 ;
X
X		for (;;)
X		{
X			if (--incount < 0)
X			{
X				if ((incount = (*getin)()) < 0) break ;
X				inptr = inbuf ;
X			}
X			in = *inptr++ ;
X
X			if (in == 0)
X			{
X				if (state <= 4) state++ ;
X				else zskip += sizeof(ushort) ;
X			}
X			else
X			{
X				if (state >= 2)
X				{
X					if (in == MAGIC)
X					{
X						magic = 1 ;
X						break ;
X					}
X					if (in == (SWAP(MAGIC) & 0xffff))
X					{
X						if (!quiet) (void) fprintf(stderr,
X							"Automatically reversing byte order!\n") ;
X
X						inswap = !inswap ;
X						outswap = !outswap ;
X
X						ip = inptr ;
X						ic = incount ;
X						while (--ic >= 0)
X						{
X							*ip = SWAP(*ip) ;
X							ip++ ;
X						}
X						magic = 1 ;
X						break ;
X					}
X					if ((in == ODDMAGIC || in == EVENMAGIC) && magic)
X					{
X						eofmagic = in ;
X						magic = 2 ;
X						incount = -1 ;
X						break ;
X					}
X				}
X				skip += sizeof(ushort) ;
X				state = 0 ;
X			}
X		}
X
X		/*
X		 *	Mention that data was skipped.
X		 */
X
X		if (skip != 0 || zskip != 0 && incount >= 0)
X		{
X			(void) fprintf(stderr,
X				"Warning: %d bytes of unintelligible data skipped!\n",
X				skip + zskip) ;
X		}
X
X		if (incount < 0)
X		{
X			if (magic == 0) (void) fprintf(stderr,
X				"Warning: Magic number not found!\n") ;
X			if (magic == 1) (void) fprintf(stderr,
X				"Warning: Old style EOF encountered!\n") ;
X			goto eof ;
X		}
X			
X		/*
X		 *	Get the maximum code present in the data.
X		 */
X
X		for (i = 1 ; i < 4 ; i++)
X		{
X			if (--incount < 0)
X			{
X				if ((incount = (*getin)()) < 0)
X				{
X					(void) fprintf(stderr, "EOF in header!\n") ;
X					err = 1 ;
X					goto eof ;
X				}
X				inptr = inbuf ;
X			}
X			header[i] = *inptr++ ;
X		}
X
X		code = header[3] ;
X		code <<= 16 ;
X		code |= header[2] ;
X
X#if DEBUG >= 2
X		if (debug >= 2) (void) fprintf(stderr,
X			"Read header, nbits=%d, maxitem=%d\n", header[1], code) ;
X#endif
X
X		/*
X		 *	Allocate the required memory.
X		 */
X
X		if (maxcode != code)
X		{
X
X			dectable = dectable
X				?	(DEC *) realloc((char*) dectable, code * sizeof(DEC))
X				:	(DEC *) malloc(code * sizeof(DEC))
X				;
X
X			buffer = buffer
X				?	(ushort *) realloc((char*)buffer, code * sizeof(ushort))
X				:	(ushort *) malloc(code * sizeof(ushort))
X				;
X
X			if (dectable == 0 || buffer == 0)
X			{
X				(void) fprintf(stderr,
X					"Cannot get enough memory to decode %d items\n", code) ;
X				exit(1) ;
X			}
X
X			maxcode = code ;
X		}
X
X		sp = &dectable[0] ;
X		ep = &dectable[maxcode] ;
X
X		/*
X		 *	Initialize to decompress this block of data.
X		 */
X
X		in = 0 ;
X		ibit = 0 ;
X		ncode = 0 ;
X		bp = buffer ;
X
X		isize = 17 ;
X		bitcount = (1 << 16) ;
X		imask = 0x1ffff ;
X
X		/*
X		 *	Read in the first item, which must be a
X		 *	single character.
X		 */
X
X		do
X		{
X			if (--incount < 0)
X			{
X				if ((incount = (*getin)()) < 0)
X				{
X					(void) fprintf(stderr, "Premature eof!\n") ;
X					err = 1 ;
X					goto eof ;
X				}
X				inptr = inbuf ;
X			}
X			in |= *inptr++ << ibit ;
X			ibit += 16 ;
X		} while (ibit < 17) ;
X
X		code = in & imask ;
X
X		if (code == 0)
X		{
X			state = 0 ;
X			continue ;
X		}
X
X		in >>= 17 ;
X		ibit -= 17 ;
X
X		if (code == 0)
X		{
X			(void) fprintf(stderr, "Null compression block!\n") ;
X			break ;
X		}
X
X		ch = code - 1 ;
X
X#if DEBUG >= 3
X		if (debug >= 3) (void) fprintf(stderr,
X			"First input code: %05lx output as < %04x >\n", code, ch) ;
X#endif
X
X		if (--outcount <= 0)
X		{
X			if ((outcount = (*putout)(outbs)) == 0) return(1) ;
X			outptr = outbuf ;
X		}
X		
X		*outptr++ = ch ;
X
X		/*
X		 *	Each time through the loop, read and process one
X		 *	compressed item code.
X		 */
X
X		MARK(eloop) ;
X
X		for (;;)
X		{
X			lastcode = code ;
X
X			/*
X			 *	Expand the input bitsize if necessary.
X			 */
X
X			if (ncode < maxcode)
X			{
X				MARK(ealloc) ;
X
X				if (--bitcount == 0)
X				{
X					bitcount = 1 << isize ;
X					isize++ ;
X					imask += imask + 1 ;
X#if DEBUG >= 2
X					if (debug >= 2) (void) fprintf(stderr,
X						"ncode = %d, isize = %d, bitcount = %d\n",
X						ncode, isize, bitcount) ;
X#endif
X				}
X				ncode++ ;
X			}
X
X			/*
X			 *	If the table is full, find the oldest leaf node
X			 *	and reallocate it.
X			 */
X
X			else
X			{
X				MARK(escan) ;
X
X				for (;;)
X				{
X					if (sp == ep) sp = dectable ;
X					if (sp->d_ref == 0) break ;
X					if (sp->d_code >= MAXCHAR)
X					{
X						dectable[sp->d_code - MAXCHAR].d_ref = 1 ;
X					}
X					sp->d_ref = 0 ;
X					sp++ ;
X				}
X			}
X
X			/*
X			 *	Read in the next compression code.
X			 */
X
X			MARK(eread) ;
X
X			if (--incount < 0)
X			{
X				if ((incount = (*getin)()) < 0)
X				{
X					(void) fprintf(stderr, "Premature eof\n") ;
X					err = 1 ;
X					goto eof ;
X				}
X				inptr = inbuf ;
X			}
X
X			in |= *inptr++ << ibit ;
X			ibit += 16 ;
X
X			if (ibit >= isize)
X			{
X				code = in & imask ;
X
X				in >>= isize ;
X				ibit -= isize ;
X			}
X			else
X			{
X				if (--incount < 0)
X				{
X					if ((incount = (*getin)()) < 0)
X					{
X						(void) fprintf(stderr, "Premature eof\n") ;
X						err = 1 ;
X						goto eof ;
X					}
X					inptr = inbuf ;
X				}
X
X				if (ibit <= 16)
X				{
X					in |= (ulong) *inptr++ << ibit ;
X
X					code = in & imask ;
X
X					in >>= isize ;
X					ibit -= isize - 16 ;
X				}
X				else
X				{
X					in |= (ulong) *inptr << ibit ;
X
X					code = in & imask ;
X
X					in >>= isize ;
X					in |= (ulong) *inptr++ >> (isize - ibit) ;
X
X					ibit -= isize - 16 ;
X				}
X			}
X
X#if DEBUG >= 4
X			if (debug >= 4) (void) fprintf(stderr,
X				"Read input code: %05lx\n", code) ;
X#endif
X			/*
X			 *	If a zero code is detected, start a new
X			 *	decompression pass.
X			 *
X			 *	If a character code is detected, output it.
X			 */
X
X			MARK(eexpand) ;
X
X			if (code < MAXCHAR)
X			{
X				if (code == 0) break ;
X				ch = code ;
X			}
X
X			/*
X			 *	Expand a compression code.
X			 *
X			 *	If the code is the next one to be allocated, 
X			 *	then it was allocated by the compression program
X			 *	already to be the lastcode + the first character
X			 *	of the last code.
X			 *
X			 *	Also check for an out-of-range decompression
X			 *	code.  Signal an error in this case.
X			 */
X
X			else
X			{
X				if (code - MAXCHAR >= ncode)
X				{
X					(void) fprintf(stderr,
X						"Decompress error - attempting resync\n") ;
X					break ;
X				}
X
X				dp = &dectable[code - MAXCHAR] ;
X
X				if (dp == sp)
X				{
X
X					if (sp - dectable == lastcode - MAXCHAR)
X					{
X						(void) fprintf(stderr,
X							"Nasty decompress error - attempting resync\n") ;
X						break ;
X					}
X
X					*bp++ = ch ;
X
X					ch = lastcode ;
X
X					if (ch >= MAXCHAR)
X					{
X
X						dp = &dectable[ch - MAXCHAR] ;
X
X						for (;;)
X						{
X							*bp++ = dp->d_char ;
X							ch = dp->d_code ;
X							if (ch < MAXCHAR) break ;
X							dp = &dectable[ch - MAXCHAR] ;
X						}
X					}
X				}
X
X				else
X				{
X					for (;;)
X					{
X						*bp++ = dp->d_char ;
X						ch = dp->d_code ;
X						if (ch < MAXCHAR) break ;
X						dp = &dectable[ch - MAXCHAR] ;
X					}
X				}
X			}
X			
X			ch -= 1 ;
X
X			/*
X			 *	Output the compression code data.
X			 */
X
X			MARK(eout) ;
X
X#if DEBUG >= 3
X			if (debug >= 3)
X			{
X				ushort *wp ;
X				(void) fprintf(stderr, "Expanded code %05lx to <", code) ;
X				wp = bp ;
X				*wp++ = ch ;
X				do
X				{
X					(void) fprintf(stderr, " %04x", *--wp) ;
X				} while (wp != buffer) ;
X				(void) fprintf(stderr, " >\n") ;
X			}
X#endif
X
X			if (--outcount <= 0)
X			{
X				if ((outcount = (*putout)(outbs)) == 0) return(1) ;
X				outptr = outbuf ;
X			}
X			
X			*outptr++ = ch ;
X
X			while (bp != buffer)
X			{
X				if (--outcount <= 0)
X				{
X					if ((outcount = (*putout)(outbs)) == 0) return(1) ;
X					outptr = outbuf ;
X				}
X
X				*outptr++ = *--bp ;
X			}
X
X			/*
X			 *	Create a new code from the input data.
X			 */
X
X			MARK(ebuild) ;
X
X			sp->d_code = lastcode ;
X			sp->d_char = ch ;
X			sp->d_ref = 0 ;
X			sp++ ;
X
X			if (lastcode >= MAXCHAR) dectable[lastcode - MAXCHAR].d_ref = 1 ;
X
X#if DEBUG >= 3
X			if (debug >= 3)
X			{
X				(void) fprintf(stderr,
X					"Created compression code: %05lx ( %05lx %04x )\n",
X					sp - dectable + MAXCHAR - 1, lastcode, ch) ;
X			}
X#endif
X		}
X
X		state = 0 ;
X	}
X
Xeof:
X	nbyte = (long)outptr - (long)outbuf ;
X	if (eofmagic == ODDMAGIC) nbyte-- ;
X
X	if (nbyte && (*putout)(nbyte) == 0) return(1) ;
X
X	if (!quiet && intotal != 0)
X	{
X		(void) fprintf(stderr,
X		"Read %ld,  Wrote %ld,  Expansion %6.2f%%\n",
X		intotal, outtotal,
X		100.0 * (long)(outtotal - intotal) / intotal) ;
X	}
X
X	return(err) ;
X}
X
X
X
X/**************************************************************
X *	main - main program.
X **************************************************************/
X
Xmain(argc, argv)
Xint argc ;
Xchar **argv ;
X{
X	register int i ;
X	register int c ;
X	register int err ;
X	register int rs ;
X	register char *p ;
X	extern char *optarg ;
X	extern int optind ;
X
X	/*
X	 *	Set default parameters.
X	 */
X
X	infd = 0 ;
X	outfd = 1 ;
X
X	inbs = BUFSIZ ;
X	outbs = BUFSIZ ;
X
X	memory = MEMORY ;
X	maxwidth = 17 ;
X
X	inrun = INRUN ;
X
X	/*
X	 *	If command begins with 'e' or 'u', expand the
X	 *	input file.  Otherwise compress it.
X	 */
X
X	p = argv[0] ;
X	action = *p ;
X	while (*p) if (*p++ == '/') action = *p ;
X
X	if (action == 'u') action = 'e' ;
X	else if (action != 'e') action = 'c' ;
X
X#if DEBUG >= 1
X	debug = DEBUG ;
X#endif
X
X	/*
X	 *	Unpack parameters.
X	 */
X
X	err = 0 ;
X
X	for (;;)
X	{
X		c = getopt(argc, argv, "a:b:B:cd:ef:F:lm:n:N:p:qr:R:sSt:T:uvw:xXz:") ;
X
X		if (c == EOF) break ;
X
X		switch (c)
X		{
X
X			/*
X			 *	Media setup command to be performed before
X			 *	first open.
X			 */
X			
X			case 'a':
X				acommand = optarg ;
X				break ;
X			
X			/*
X			 *	Input/output block size.
X			 */
X
X			case 'b':
X				inbs = getval(optarg, 'k') ;
X				if (inbs <= 2 || (inbs & 1)) err++ ;
X				break ;
X
X			case 'B':
X				outbs = getval(optarg, 'k') ;
X				if (outbs <= 2 || (outbs & 1)) err++ ;
X				break  ;
X
X			/*
X			 *	Compress or expand action.
X			 */
X
X			case 'c':
X			case 'e':
X				action = c ;
X				break ;
X
X			/*
X			 *	Debug level.
X			 */
X
X#if DEBUG >= 1
X			case 'd':
X				debug = atoi(optarg) ;
X				if (debug > DEBUG) err++ ;
X				break ;
X#endif
X
X			/*
X			 *	Input/Output file name.
X			 */
X
X			case 'f':
X				infname = optarg ;
X				break ;
X			
X			case 'F':
X				outfname = optarg ;
X				break ;
X			
X			/*
X			 *	Lock process into memory.
X			 */
X			
X			case 'l':
X				memlock = 1 ;
X				break ;
X
X			/*
X			 *	Amount of memory to use.
X			 */
X
X			case 'm':
X				memory = getval(optarg, 'k') ;
X				if (memory < 1) err++ ;
X				break ;
X
X			/*
X			 *	Physical record size.  Causes the last output
X			 *	record to be padded with zeros to the next
X			 *	multiple of this size if necessary.
X			 */
X
X			case 'p':
X				phys = getval(optarg, 'k') ;
X				if (phys <= 2 || (phys & 1)) err++ ;
X				break ;
X			
X			/*
X			 *	Quiet mode.
X			 */
X			
X			case 'q':
X				quiet = 1 ;
X				break ;
X			
X			/*
X			 *	Number of input/output records in volume.
X			 */
X
X			case 'n':
X				insize = getval(optarg, 'k') ;
X				if (insize < 1) err++ ;
X				break ;
X				
X			case 'N':
X				outsize = getval(optarg, 'k') ;
X				if (outsize < 1) err++ ;
X				break  ;
X
X			/*
X			 *	Maximum run length.
X			 */
X
X			case 'r':
X				inrun = getval(optarg, 0) ;
X				if (inrun < 5) err++ ;
X				break ;
X
X			/*
X			 *	Swap input or output bytes.
X			 */
X
X			case 's':
X				inswap++ ;
X				break ;
X			
X			case 'S':
X				outswap++ ;
X				break ;
X			
X			/*
X			 *	Tank size.
X			 */
X			
X			case 't':
X				intank = getval(optarg, 0) ;
X				if (intank < 2 || intank > 500) err++ ;
X				break ;
X
X			case 'T':
X				outtank = getval(optarg, 0) ;
X				if (outtank < 2 || outtank > 500) err++ ;
X				break ;
X			
X			/*
X			 *	Uncompress.
X			 */
X			
X			case 'u':
X				action = 'e' ;
X				break ;
X
X			/*
X			 *	Version.
X			 */
X			
X			case 'v':
X#if DEBUG >= 1
X				(void) fprintf(stderr, "%s (debug %d)\n", VERSION, DEBUG) ;
X#else
X				(void) fprintf(stderr, "%s\n", VERSION) ;
X#endif
X				return(0) ;
X
X			/*
X			 *	Maximum output bit width.
X			 */
X			
X			case 'w':
X				maxwidth = getval(optarg, 0) ;
X				if (maxwidth < 17 || maxwidth > 24) err++ ;
X				break ;
X
X			/*
X			 *	Read/Write data to/from non-shared memory buffers
X			 *	to avoid driver problems.
X			 */
X
X			case 'x':
X				incopy = 1 ;
X				break ;
X
X			case 'X':
X				outcopy = 1 ;
X				break ;
X
X			/*
X			 *	Command to be performed after last close.
X			 */
X
X			case 'z':
X				zcommand = optarg ;
X				break ;
X
X			default:
X				err++ ;
X		}
X	}
X
X	/*
X	 *	Check parameter consistency.
X	 */
X
X	if (insize && !infname)
X	{
X		(void) fprintf(stderr, "-n requires -f\n") ;
X		err++ ;
X	}
X	
X	if (outsize && !outfname)
X	{
X		(void) fprintf(stderr, "-N requires -F\n") ;
X		err++ ;
X	}
X			
X	/*
X	 *	Print the USAGE message.
X	 */
X
X	if (optind != argc || err)
X	{
X		(void) fprintf(stderr, "Usage: %s %s%s%s", argv[0],
X			"[-celqsSvxX] [-a cmd] [-b cbs] [-B ebs] [-f cfile] [-F efile]\n",
X			"\t[-m memory] [-n cblks] [-N eblks] [-p outpad] [-r runsize]\n",
X			"\t[-t cbuf] [-T ebuf] [-w width] [-z cmd]\n") ;
X		exit(2) ;
X	}
X	
X	/*
X	 *	Adjust compression parameters.
X	 */
X
X	if (action == 'c')
X	{
X		long i ;
X		char *p ;
X
X		/*
X		 *	Switch parameters around if necessary so the lower
X		 *	case options refer to the compressed file, and the
X		 *	upper case letters refer to the expanded file.
X		 */
X
X		if (phys == 0) phys = 512 ;
X
X		i = insize ; insize = outsize ; outsize = i ;
X		i = inbs ; inbs = outbs ; outbs = i ;
X		i = intank ; intank = outtank ; outtank = i ;
X		i = inswap ; inswap = outswap ; outswap = i ;
X		i = incopy ; incopy = outcopy ; outcopy = i ;
X		p = infname ; infname = outfname ; outfname = p ;
X	}
X	
X	/*
X	 *	Adjust output block size to correspond with
X	 *	the requested output padding.
X	 */
X
X	if (phys)
X	{
X		if (outbs < phys) outbs = phys ;
X		else outbs = (outbs + phys - 1) / phys * phys ;
X	}
X	
X	/*
X	 *	Execute setup command.
X	 */
X	
X	if (acommand) (void) system(acommand) ;
X
X	/*
X	 *	Setup input operation through shared memory.
X	 */
X	
X	if (intank)
X	{
X		inprocess() ;
X		getin = shareinput ;
X	}
X	else
X	{
X		inbuf = (ushort *) malloc((unsigned) inbs+1) ;
X		getin = fileinput ;
X	}
X	
X	/*
X	 *	Setup output operation through shared memory.
X	 */
X	
X	if (outtank)
X	{
X		outprocess() ;
X		putout = shareoutput ;
X	}
X	else
X	{
X		outbuf = (ushort *) malloc((unsigned) outbs) ;
X		putout = fileoutput ;
X	}
X
X	/*
X	 *	Lock process into memory.
X	 */
X	
X	if (memlock && plock(PROCLOCK) == -1)
X	{
X		(void) perror("Cannot lock process in memory") ;
X		return(2) ;
X	}
X		
X	/*
X	 *	Setup to gracefully exit.
X	 */
X
X	for (i = 0 ; sig[i] ; i++)
X	{
X		if (signal(sig[i], SIG_IGN) != SIG_IGN)
X			(void) signal(sig[i], quit) ;
X	}
X
X	/*
X	 *	Compress or expand input data.
X	 */
X
X	rs = action == 'c' ? compact() : expand() ;
X
X	quit(rs) ;
X
X	return(0) ;
X}
END_OF_compact.c
if test 40644 -ne `wc -c <compact.c`; then
    echo shar: \"compact.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f share.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"share.c\"
else
echo shar: Extracting \"share.c\" \(10813 characters\)
sed "s/^X//" >share.c <<'END_OF_share.c'
X/**********************************************************************
X *	ex:se ts=4 sw=4 ai:
X *
X *	Shared Memory Communication Library.
X *
X *	Author: Gene H. Olson
X *	        Smartware Consulting
X *
X *	This is FREE software in the PUBLIC DOMAIN.
X *
X *	"Everything FREE contains no guarantee."
X *
X ***********************************************************************
X *
X *	This library is designed to be a re-usable shared-library
X *	package for high-speed communication between cooperating
X *	tasks using System V shared memory.
X *
X *	Unfortunately the interface to system V shared libraries
X *	is sufficiently awkward that it is difficult to create
X *	a simple interface.  This was the best I could think of.
X *
X *	In this interface, a parent program creates a shared
X *	memory segment for use with its children using the
X *	share_create() function.  This function returns 4 pipe
X *	file descriptors to be selectively passed to its children.
X *
X *	Two of the file descriptors (pfd[2]) are passed to the
X *	producer process and two are passed to the consumer
X *	process.  The share_open() function is then called
X *	with the appropriate file descriptors, and shared
X *	memory is mapped into processor space.
X *
X *	In passing file descriptors, the producer task should
X *	always be passed pfd[0] and pfd[1], and cfd[1] must
X *	be closed.  If SIGPIPE interrupts are desired when
X *	sending a buffer to a terminated consumer process,
X *	cfd[0] should also be closed;  otherwise it should be
X *	left open and never accessed.  The consumer task
X *	should receive cfd[0], cfd[1] and close pfd[1].
X *	Usually pfd[0] is left open in the consumer task so
X *	it won't get SIGPIPE interrupts when returning buffers
X *	after the producer process terminates.
X *
X *	Both producer and consumer processes obtain a memory
X *	buffer to pass to the other by calling share_get().
X *	They pass the buffer to the opposite task by calling
X *	share_put().  share_get() blocks as necessary to wait
X *	for a buffer.   The only real difference between the
X *	producer and consumer processes is that the entire
X *	buffer list is initially queued to the producer.
X *
X *	When communication is complete, both producer and
X *	consumer can either terminate, or call share_close()
X *	to deallocate resources.  However at least one of
X *	them must always call share_destroy() to deallocate
X *	the shared memory segments.  Failure to do this will
X *	leave the segments hanging out there chewing up
X *	swap space.
X *
X *	Note that the SHARE object malloc()ed throughout these
X *	operations is not generally free()ed.  This wastes a
X *	bit of memory but simplifies caller programming.
X *
X *	In keeping with the current craze for object-oriented
X *	programming and information-hiding, the SHARE object
X *	which controls all of this is opaque to the calling
X *	routines, and should be declared as type (char *).
X *	How you reconcile this with lint is unresolved,
X *	but at least this module lints clean.
X *
X *	The module smtest.c provides a simple example of how
X *	to use the library.
X **********************************************************************/
X
X#ifndef lint
Xstatic char sccs[] = "@(#)share.c	1.10 11/3/90" ;
X#endif
X
X#include <sys/types.h>
X#include <sys/ipc.h>
X#include <sys/shm.h>
X
X#include <assert.h>
X#include <stdio.h>
X#include <errno.h>
X
Xextern char *shmat() ;
Xextern char *memset() ;
Xextern void exit() ;
Xextern int	errno ;
X
X/*
X *	Patch for Xenix which tests valid memory for reads & writes
X *	off by 1 byte.
X */
X
X#ifdef M_XENIX
X#	define MKLUDGE	2
X#else
X#	define MKLUDGE	0
X#endif
X
X/*
X *	Shared memory and pipe item descriptors.
X */
X
Xtypedef struct sh_struct SHARE ;
X
X#define SHARE_MAXID		16			/* Max shared memory ID's */
X
Xstruct sh_struct
X{
X	int*	sh_rbuf ;				/* Pipe receive buffer */
X	int		sh_fd[2] ;				/* Pipe read file descriptors */
X	int		sh_nid ;				/* Number of allocated IDs */
X	int		sh_nbuf ;				/* Number of buffers allocated */
X	int		sh_nmap ;				/* Number of segments mapped */
X	int		sh_in ;					/* Circular buffer in pointer */
X	int		sh_out ;				/* Circular buffer in pointer */
X	int		sh_bufsize ;			/* Buffer size */
X	int		sh_rin ;				/* Pipe buffer IN index */
X	int		sh_rout ;				/* Pipe buffer OUT index */
X	int		sh_id[SHARE_MAXID] ;	/* Shared memory ID */
X	char*	sh_base[SHARE_MAXID] ;	/* Shared memory base */
X	int		sh_len[SHARE_MAXID];	/* Shared segment length */
X} ;
X
X#if lint
X#	define	malloc(x)	0
X#endif
X
X
X/**************************************************************
X *	share_create - Create a shared memory entity in the parent
X *	               of two tasks to share memory.
X **************************************************************/
X
XSHARE *
Xshare_create(pfd, cfd, bufsize, nbuf)
Xint *pfd ;						/* Producer file descriptors */
Xint *cfd ;						/* Consumer file descriptors */
Xint bufsize ;					/* Buffer size */
Xint nbuf ;						/* Number of buffers */
X{
X	register int id ;
X	register int i ;
X	register int n ;
X	register int s ;
X	register SHARE* sh ;
X	int size ;
X	int fd[4] ;
X
X	if (nbuf > 500) return(0) ;
X
X	sh = (SHARE *) malloc(sizeof(SHARE)) ;
X	if (sh == 0) return(0) ;
X
X	(void) memset((char *)sh, 0, sizeof(SHARE)) ;
X
X	sh->sh_fd[0] = sh->sh_fd[1] = -1 ;
X
X	sh->sh_bufsize = bufsize ;
X
X	/*
X	 *	Allocate pipes for bi-directional communication
X	 *	between the producer and consumer processes.
X	 */
X
X	if (pipe(fd) == -1) goto fail ;
X
X	if (pipe(fd + 2) == -1)
X	{
X		(void) close(fd[0]) ;
X		(void) close(fd[1]) ;
X		goto fail ;
X	}
X
X	pfd[0] = fd[0] ;
X	pfd[1] = fd[3] ;
X
X	cfd[0] = fd[2] ;
X	cfd[1] = fd[1] ;
X
X	/*
X	 *	Allocate the required number of buffers in as many
X	 *	segments as needed.
X	 *
X	 *	When the system will not provide us with a single
X	 *	segment of the requested size, repeatedly request
X	 *	a segment of the next lower power-of-two size.
X	 */
X
X	for (n = nbuf ; n ; n -= s)
X	{
X		s = n ;
X
X		while (s > 0)
X		{
X			if ((id = shmget(IPC_PRIVATE, s * bufsize + MKLUDGE, 0600)) >= 0)
X			{
X				sh->sh_id[sh->sh_nid] = id ;
X				sh->sh_len[sh->sh_nid] = s ;
X				sh->sh_nid++ ;
X				break ;
X			}
X
X			i = s * bufsize + MKLUDGE - 1 ;
X			while (i & (i-1)) i = i & (i-1) ;
X			s = (i - MKLUDGE) / bufsize ;
X		}
X
X		if (s <= 0)
X		{
X			if (sh->sh_nid == 0) goto fail ;
X			break ;
X		}
X		
X		sh->sh_nbuf += s ;
X
X		if (sh->sh_nid == SHARE_MAXID) break ;
X	}
X
X	/*
X	 *	Write out the shared memory descriptor
X	 *	to both pipes.
X	 */
X
X	if (write(cfd[1], (char *)sh, sizeof(SHARE)) == -1)
X	{
X		errno = EIO ;
X		goto fail ;
X	}
X
X	if (write(pfd[1], (char *)sh, sizeof(SHARE)) == -1)
X	{
X		errno = EIO ;
X		goto fail ;
X	}
X
X	/*
X	 *	Allocate all buffers to the producer process.
X	 */
X
X	size = 0 ;
X
X	for (i = 0 ; i < sh->sh_nbuf ; i++)
X	{
X		if (write(cfd[1], (char *)&size, sizeof(int)) == -1) goto fail ;
X	}
X
X	return(sh) ;
X
Xfail:
X	share_destroy(sh) ;
X	free((char*)sh) ;
X
X	return(0) ;
X}
X
X
X
X/*******************************************************
X *	share_close - Close a SHARE descriptor.
X *******************************************************/
X
Xshare_close(sh)
Xregister SHARE *sh ;
X{
X	register int i ;
X
X	if (sh->sh_rbuf)
X	{
X		free((char *) sh->sh_rbuf) ;
X		sh->sh_rbuf = 0 ;
X	}
X
X	for (i = 0 ; i < sh->sh_nmap ; i++)
X	{
X		(void) shmdt(sh->sh_base[i]) ;
X	}
X
X	sh->sh_nmap = 0 ;
X
X	for (i = 0 ; i < 2 ; i++)
X	{
X		if (sh->sh_fd[i] != -1)
X		{
X			(void) close(sh->sh_fd[i]) ;
X			sh->sh_fd[i] = -1 ;
X		}
X	}
X}
X
X
X
X/*******************************************************
X *	share_destroy - Remove share descritors.
X *******************************************************/
X
Xshare_destroy(sh)
Xregister SHARE *sh ;				/* Shared memory descriptor */
X{
X	int i ;
X
X	share_close(sh) ;
X
X	for (i = 0 ; i < sh->sh_nid ; i++)
X	{
X		(void) shmctl(sh->sh_id[i], IPC_RMID, (struct shmid_ds *)0) ;
X	}
X
X	sh->sh_nid = 0 ;
X}
X
X
X
X/*****************************************************
X *	share_open - Open shared memory descriptor.
X *****************************************************/
X
XSHARE *
Xshare_open(fd)
Xint fd[2] ;
X{
X	register SHARE *sh ;
X	register char *p ;
X	register int i ;
X
X	/*
X	 *	Read in the Share structure from the read file
X	 *	descriptor.
X	 */
X
X	sh = (SHARE *)malloc(sizeof(SHARE)) ;
X
X	if (sh == 0) return(0) ;
X
X	if (read(fd[0], (char *)sh, sizeof(SHARE)) != sizeof(SHARE))
X	{
X		free((char *)sh) ;
X		return(0) ;
X	}
X
X	/*
X	 *	Initialize the structure.
X	 */
X
X	sh->sh_fd[0] = fd[0] ;
X	sh->sh_fd[1] = fd[1] ;
X
X	/*
X	 *	Map in the shared memory segment.
X	 */
X
X	for (i = 0 ; i < sh->sh_nid ; i++)
X	{
X		p = shmat(sh->sh_id[i], (char *)0, 0) ;
X
X		if (p == 0)
X		{
X			share_destroy(sh) ;
X			return(0) ;
X		}
X
X		sh->sh_nmap++ ;
X		sh->sh_base[i] = p ;
X
X#if defined(sun) && 0
X		(void) mlock(sh->sh_base[i], (unsigned)(sh->sh_bufsize*sh->sh_len[i])) ;
X#endif
X	}
X
X	return(sh) ;
X}
X
X
X
X/*******************************************************
X *	share_get - Fetch next block from shared memory.
X *******************************************************/
X
Xint
Xshare_get(sh, buf, n)
Xregister SHARE *sh ;
Xchar **buf ;
Xint *n ;
X{
X	register int i ;
X	register int r ;
X	register int index ;
X
X	/*
X	 *	For efficiency (probably misguided) allocate
X	 *	a buffer of 64 items.
X	 */
X
X	if (sh->sh_rbuf == 0)
X	{
X		sh->sh_rbuf = (int *)malloc(64 * sizeof(int)) ;
X		sh->sh_rin = 0 ;
X		sh->sh_rout = 0 ;
X	}
X
X	/*
X	 *	When the buffer is dry, read in another block
X	 *	of data sizes.
X	 */
X
X	if (sh->sh_rin == sh->sh_rout)
X	{
X		r = read(sh->sh_fd[0], (char *) sh->sh_rbuf, 64 * sizeof(int)) ;
X
X		if (r <= 0) return(r) ;
X
X		assert((r % sizeof(int)) == 0) ;
X
X		sh->sh_rin = 0 ;
X		sh->sh_rout = r / sizeof(int) ;
X	}
X
X	/*
X	 *	Get the next buffer pointer & count.
X	 */
X
X	*n = sh->sh_rbuf[sh->sh_rin++] ;
X
X#if DEBUG >= 2
X	if (1)
X	{
X		char b[100] ;
X		sprintf(b, "GET      %5d   %6d\n", sh->sh_in, *n) ;
X		write(2, b, strlen(b)) ;
X	}
X#endif
X
X	index = sh->sh_out ;
X
X	for (i = 0 ;; i++)
X	{
X		assert(i < sh->sh_nid) ;
X		
X		if (index < sh->sh_len[i])
X		{
X			*buf = sh->sh_base[i] + sh->sh_bufsize * index ;
X			break ;
X		}
X
X		index -= sh->sh_len[i] ;
X	}
X
X	if (++sh->sh_out == sh->sh_nbuf) sh->sh_out = 0 ;
X
X	return(1) ;
X}
X
X
X
X/***************************************************************
X *	share_put - Put a data buffer on the queue to the remote.
X ***************************************************************/
X
Xint
Xshare_put(sh, buf, n)
XSHARE *sh ;							/* Share pointer */
Xchar *buf ;							/* Buffer pointer */
Xint n ;								/* Number of bytes */
X{
X	register int i ;
X	register int r ;
X	register int index ;
X
X	index = sh->sh_in ;
X
X	for (i = 0 ;; i++)
X	{
X		assert(i < sh->sh_nid) ;
X		
X		if (index < sh->sh_len[i])
X		{
X			assert(buf == sh->sh_base[i] + sh->sh_bufsize * index) ;
X			break ;
X		}
X
X		index -= sh->sh_len[i] ;
X	}
X
X#if DEBUG >= 2
X	if (1)
X	{
X		char b[100] ;
X		sprintf(b, "PUT %5d        %6d\n", sh->sh_in, n) ;
X		write(2, b, strlen(b)) ;
X	}
X#endif
X
X	if (++sh->sh_in == sh->sh_nbuf) sh->sh_in = 0 ;
X
X	r = write(sh->sh_fd[1], (char *)&n, sizeof(n)) ;
X
X	return(r <= 0 ? r : 1) ;
X}
END_OF_share.c
if test 10813 -ne `wc -c <share.c`; then
    echo shar: \"share.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f smtest.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"smtest.c\"
else
echo shar: Extracting \"smtest.c\" \(2064 characters\)
sed "s/^X//" >smtest.c <<'END_OF_smtest.c'
X/**********************************************************************
X *	ex:se ts=4 sw=4 ai:
X *
X *	Shared Memory Test Program
X *
X *	Author: Gene H. Olson
X *		gene@zeno.mn.org
X *
X *	This is FREE software in the PUBLIC DOMAIN.
X *
X *	"Everything free contains no guarantee."
X *
X ***********************************************************************
X *
X *	This is a throwaway test program and example of how
X *	to use the "share.c" library module.  It is not intended
X *	to be a real program and certainly does not behave like one.
X *
X ***********************************************************************/
X
X#include <stdio.h>
X#include <assert.h>
X#include <signal.h>
X
X/* @(#)smtest.c	1.4 11/3/90 */
X
Xtypedef char *opaque ;
X
Xextern opaque share_create() ;
Xextern opaque share_open() ;
X
X#define BS 8096
X
Xopaque rsh, wsh ;
X
Xquit(sig)
X{
X	if (wsh) share_close(wsh) ;
X	while (wait((int *)0) != -1) ;
X	exit(sig) ;
X}
X
X
X
Xmain(argc, argv)
Xint argc ;
Xchar **argv ;
X{
X	int s ;
X	int rcount, wcount ;
X	char *rbuf, *wbuf ;
X	int rfd[2], wfd[2] ;
X
X	wsh = share_create(rfd, wfd, BS, 100) ;
X	assert(wsh) ;
X
X	if (fork() == 0)
X	{
X		close(1) ;
X		close(wfd[0]) ;
X		close(wfd[1]) ;
X
X		rsh = share_open(rfd) ;
X		assert(rsh) ;
X
X		while (1)
X		{
X			s = share_get(rsh, &rbuf, &rcount) ;
X			if (s <= 0) break ;
X
X			assert(rcount == 0) ;
X
X			rcount = read(0, rbuf, BS) ;
X			if (rcount == 0) break ;
X
X			s = share_put(rsh, rbuf, rcount) ;
X			if (s <= 0) break ;
X		}
X
X		share_close(rsh) ;
X
X		exit(s) ;
X	}
X	else
X	{
X		if (signal(SIGHUP, SIG_IGN) != SIG_IGN) signal(SIGHUP, quit) ;
X		if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, quit) ;
X		if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) signal(SIGQUIT, quit) ;
X		if (signal(SIGPIPE, SIG_IGN) != SIG_IGN) signal(SIGPIPE, quit) ;
X		if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, quit) ;
X
X		close(0) ;
X		close(rfd[1]) ;
X
X		wsh = share_open(wfd) ;
X		assert(wsh) ;
X
X		while (1)
X		{
X			s = share_get(wsh, &wbuf, &wcount) ;
X			if (s <= 0) break ;
X
X			(void) write(1, wbuf, wcount) ;
X
X			(void) share_put(wsh, wbuf, 0) ;
X		}
X
X		quit(s) ;
X	}
X}
END_OF_smtest.c
if test 2064 -ne `wc -c <smtest.c`; then
    echo shar: \"smtest.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of shell archive.
exit 0
_________________________________________________________________________
   __ 
  /  )                 Gene H. Olson             uunet!digibd!zeno!gene
 / __  _ __  _
(__/ _(/_//_(/_        Minneapolis, MN           (612) 824-9108