$! LZMAKE.COM
$!
$! Rewritten from the more intricate routines built for use with the DECUS
$! archiving software.  All the original work was done by Martin Minow, and
$! my only modification has been to reduce the number of steps in creating
$! the source code and add steps to create the VMS executables.
$!
$! Pete Kaiser
$! 3 February 1992
$!------------------------------------------------------------------------------
$ write sys$output "Creating source files"
$ create /log readme.txt
$ deck
This is a rewrite of the Unix compress utility.  It is *not*
switch-compatible with Unix compress, however it is (almost)
file-compatible (when compiled on Unix, or when "export" mode
is selected on VMS Version 4).

The advantages of this version are as follows:

1. Compress and decompress are separate programs, simplifying the
   problems of the small system implementor.  Both run on an
   unmapped PDP-11 (with a maximum of 12 bits).

   The command interface is just

	lzcomp input compressed_output
	lzdcmp compressed_input output

   Input files are not deleted.

2. The compression algorithm and I/O design is intended to simplify
   embedding the programs (as subroutines) in some other task.
   (for example, in a database package.)

3. On non-Unix systems, the I/O design should be significantly
   faster.  It should be slightly faster on Unix.

The only disadvantage is that, as noted, it is not command (option)
compatible with Unix compress.  Also, some peripheral functionality
(such as the deletion of input files and the output file naming
conventions) have not been implemented.

On Unix (i.e., in "export" mode), the compressed data file is
identical to the Unix file, *except* that lzcomp writes two
CLEAR codes in a row to signal end-of-file (and lzdcmp treats
two CLEAR codes in a row as signalling end-of-file).  This
means that, if you compress a file using lzcomp and decompress
it using Unix compress, there will be a couple of extra bytes
added at the end of the decompressed output file.

lzcomp and lzdcmp have been added to the Decus C distribution.

Martin Minow
minow@ranger.enet.dec.com
$ eod
$ create /log makefile.txt
$ deck
# Unix makefile for lzcomp, lzdcmp
#
# The redefinition of strchr() and strrchr() are needed for
# Ultrix-32, Unix 4.2 bsd (and maybe some other Unices).
#
BSDDEFINE = -Dstrchr=index -Dstrrchr=rindex
#
# On certain systems, such as Unix System III, you may need to define
# $(LINTFLAGS) in the make command line to set system-specific lint flags.
#

CFLAGS = -O $(BSDDEFINES)

all	: lzcomp lzdcmp

#
# ** compile lzcomp
#
LZCOMP_SRCS = lzcmp1.c lzcmp2.c lzcmp3.c lzio.c
LZCOMP_OBJS = lzcmp1.o lzcmp2.o lzcmp3.o lzio.o
lzcomp: $(LZCOMP_OBJS)
	$(CC) $(CFLAGS) $(LZCOMP_OBJS) -o lzcomp

#
# ** compile lzdcmp
#
LZDCMP_SRCS = lzdcm1.c lzdcm2.c lzdcm3.c lzio.c
LZDCMP_OBJS = lzdcm1.o lzdcm2.o lzdcm3.o lzio.o
lzdcmp: $(LZDCMP_OBJS)
	$(CC) $(CFLAGS) $(LZDCMP_OBJS) -o lzdcmp

#
# ** Lint the code
#
lint:	$(LZCOMP_SRCS) $(LZDCMP_SRCS)
	lint $(LINTFLAGS) $(DEFINES) $(LZCOMP_SRCS)
	lint $(LINTFLAGS) $(DEFINES) $(LZDCMP_SRCS)

#
# ** Remove unneeded files
#
clean:
	rm -f $(OBJS) lzcomp lzdcmp

#
# ** Rebuild the archive files
# ** Uses the Decus C archive utility.
#
archive:
lzarch_arc	= 1streadme.txt archx.c archc.c
lz1a_arc	= readme.txt descrip.mms makefile.txt lz.com fixmms.com
lz1b_arc	= lzcomp.mem lzdcmp.mem
lz2_arc		= lzcmp1.c lzcmp2.c lzcmp3.c
lz3_arc		= lzdcm1.c lzdcm2.c lzdcm3.c lz.hlp
lz4_arc		= lz.h lzcomp.cld lzdcmp.cld lzdcl.c lzio.c lzvio.c

makefile.txt	: Makefile
	cp Makefile makefile.txt

lzarch.arc	: $(lzarch_arc) archc
	archc $(lzarch_arc) >lzarch.arc

lz1.arc		: $(lz1a_arc) $(lz1b_arc) archc
	archc $(lz1a_arc) >lz1.arc
	archc $(lz1b_arc) >>lz1.arc

lz2.arc		: $(lz2_arc) archc
	archc $(lz2_arc) >lz2.arc

lz3.arc		: $(lz3_arc) archc
	archc $(lz3_arc) >lz3.arc

lz4.arc		: $(lz4_arc) archc
	archc $(lz4_arc) >lz4.arc

archc		: archc.c
	cc archc.c
	mv a.out archc

#
# Object module dependencies
#

lzcmp1.o	:	lzcmp1.c lz.h

lzcmp2.o	:	lzcmp2.c lz.h

lzcmp3.o	:	lzcmp3.c lz.h

lzio.o		:	lzio.c lz.h

lzdcm1.o	:	lzdcm1.c lz.h

lzdcm2.o	:	lzdcm2.c lz.h

lzdcm3.o	:	lzdcm3.c lz.h
$ eod
$ create /log lz.com
$ deck
$!----------------------------------------------------------------------
$! Creating common object files
$!----------------------------------------------------------------------
$	WRITE SYS$OUTPUT "Compiling common routines"
$	CC /NOLIST /OBJECT=LZIO		LZIO.C
$	CC /NOLIST /OBJECT=LZVIO	LZVIO.C
$	CC /NOLIST /OBJECT=LZDCL	LZDCL.C
$!----------------------------------------------------------------------
$! Creating LZCOMP.EXE
$!----------------------------------------------------------------------
$	WRITE SYS$OUTPUT "Compiling LZCOMP routines"
$	CC /NOLIST /OBJECT=LZCMP1	LZCMP1.C
$	CC /NOLIST /OBJECT=LZCMP2	LZCMP2.C
$	CC /NOLIST /OBJECT=LZCMP3	LZCMP3.C
$	SET COMMAND /OBJECT=LZCOMP	LZCOMP.CLD
$	WRITE SYS$OUTPUT "Linking LZCOMP"
$	LINK /NOMAP /EXE=LZCOMP SYS$INPUT:/OPT
LZCMP1.OBJ
LZCMP2.OBJ
LZCMP3.OBJ
LZCOMP.OBJ
LZIO.OBJ
LZVIO.OBJ
LZDCL.OBJ
SYS$LIBRARY:VAXCRTL /SHARE
$!----------------------------------------------------------------------
$! Creating LZDCMP.EXE
$!----------------------------------------------------------------------
$	WRITE SYS$OUTPUT "Compiling LZDCMP routines"
$	CC /NOLIST /OBJECT=LZDCM1	LZDCM1.C
$	CC /NOLIST /OBJECT=LZDCM2	LZDCM2.C
$	CC /NOLIST /OBJECT=LZDCM3	LZDCM3.C
$	SET COMMAND /OBJECT=LZDCMP	LZDCMP.CLD
$	WRITE SYS$OUTPUT "Linking LZDCMP"
$	LINK /NOMAP /EXE=LZDCMP SYS$INPUT:/OPT
LZDCM1.OBJ
LZDCM2.OBJ
LZDCM3.OBJ
LZDCMP.OBJ
LZIO.OBJ
LZVIO.OBJ
LZDCL.OBJ
SYS$LIBRARY:VAXCRTL /SHARE
$!----------------------------------------------------------------------
$! Creating the HELP library.
$!----------------------------------------------------------------------
$	WRITE SYS$OUTPUT "Creating (or replacing) the HELP library"
$	IF F$SEARCH("LZ.HLB") .EQS. "" THEN LIBRARY /CREATE /HELP LZ.HLB
$	LIBRARY /REPLACE LZ.HLB LZ.HLP
$ eod
$ create /log lzcomp.mem
$ deck

           ____ ___________        1  File Compression

                                   **********
                                   * lzcomp *
                                   **********

        NAME:   lzcomp -- File Compression

        SYNOPSIS:

                lzcomp [-options] [infile [outfile]]

        DESCRIPTION:

                lzcomp  implements  the  Lempel-Ziv   file   compression
                algorithm.  (Files compressed by lzcomp are uncompressed
                by lzdcmp.) It operates by finding common substrings and
                replaces  them  with  a  variable-size  code.   This  is
                deterministic, and can be done with a single  pass  over
                the  file.   Thus,  the decompression procedure needs no
                input table, but can track the way the table was built.

                Options may be given in either case.

                -B      Input file  is  "binary",  not  "human  readable
                        text".   This  is  necessary  on  Dec  operating
                        systems, such as VMS  and  RSX-11M,  that  treat
                        these  files  differently.   (Note  that  binary
                        support is rudamentary and probably insufficient
                        as  yet.)  (On  VMS  version  4, this is ignored
                        unless the -x option is specified or  the  input
                        file is record-oriented.)

                -M bits Write using the specified number of bits in  the
                        code  -- necessary for big machines making files
                        for   little   machines.    For   example,    if
                        compressing a file on VMS which is to be read on
                        a PDP-11, you should select -M 12.

                -V [n]  Verbose if specified.  If a value is  specified,
                        it  will enable debugging code (if compiled in).
                        The values are bit-encoded as follows:
                          -V  0 Don't print anything
                          -V  1 Print a summary of the operation
                          -V  2 Print a running status on occassion
                          -V  4 Print some debug information.
                          -V  8 Print too much debug information
                          -V 16 Dump the compressed files
                                                                          Page 2
        lzcomp  File Compression

                -S      Perform  a  transformation  on  the  input  file
                        whereby   each   byte  is  subtracted  from  its
                        predecessor.  I.e., the difference between bytes
                        is  compressed,  rather than their actual value.
                        This improves the performance of  the  algorithm
                        on  certain  kinds  of  data files.  However, it
                        will worsen the compression of most other  kinds
                        of data.  Note that files compressed with the -S
                        option cannot be  decompressed  on  versions  of
                        Unix  compress  (or  lzdcmp) that do not support
                        this option.

                        The -S option  is  useful  for  digitized  video
                        images,  where each pixel element is represented
                        as a single byte.  It may also  be  useful  when
                        compressing digitized encoding of similar analog
                        data, where each sample is contained in a single
                        byte.

                -X [n]  "Export" -- write a file format that can be read
                        by  other  operating systems.  Only the bytes in
                        the file are copied;  file  attributes  are  not
                        preserved.   If  specified, the value determines
                        the level of compatiblity.  If not specified, or
                        specified  with  an  explicit value of zero, and
                        lzcomp is running on  Vax/VMS  version  4  under
                        VaxC  and  the  input  file is a disk or magtape
                        file  (block-oriented),  a  VMS-private   output
                        format  is  used  which is incompatible with the
                        Unix compress utility, but which  preserves  VMS
                        file  attributes.   -X may take on the following
                        values:

                         0  Choose VMS private format.  See restrictions
                            below.
                         1  Compatible with Unix compress  version  3.0,
                            except  that an end code is written that may
                            not be compatible with Unix compress.   This
                            is  the  default  if  -x  is given without a
                            value.  This is the correct mode to use when
                            transferring  files between VMS and RSX-11M,
                            RSTS/E, or RT11, or when transferring  files
                            to LZDCMP running on Unix.
                         2  As above, but do not  write  the  extra  end
                            code.    This   will   cause  problems  when
                            decompressing on RSX-11M and RT11 systems.
                         3  As  in  (2)  above,  but   suppress   "block
                            compression".
                         4  As  in  (3)  above,  but  do  not  output  a
                            compress   header   block.    This   is  for
                            compatiblity with a quite early  version  of
                            Unix       compress       (and      requires
                            conditional-compilation to use).
                                                                          Page 3
        lzcomp  File Compression

                        Note that the  -B  (binary)  option  is  ignored
                        unless the input file is "record-oriented", such
                        as a terminal or mailbox.

                The  other  two  arguments  are  the  input  and  output
                filenames   respectively.    Redirection  is  supported,
                however, the output must be a disk/tape file.

                The file format is almost identical to the current  Unix
                implementation  of  compress  (V4.0).   Files written by
                Unix compress  should  be  readable  by  lzdcmp.   Files
                written by lzcomp in export (-x) format will be readable
                by Unix compress (except that lzcomp outputs two "clear"
                codes  to  mark  EOF.   A  patch  to  Unix  compress  is
                available.)

        VMS COMMAND LANGUAGE INTERFACE:

                In addition  to  the  above  (Unix-style)  command  line
                interface, lzcomp supports a VMS command line interface.
                The following options are available:

                /BITS=<value>

                /EXPORT=(VMS, UNIX, BLOCK, HEADER, ENDMARKER)

                /MODE=(DELTA)

                /SHOW=(ALL, PROGRESS, STATISTICS, FDL,
                                DEBUG, DEBUG_SERIOUS, DEBUG_IO)

        VMS RESTRICTIONS:

                VMS Private mode stores the true name and attributes  of
                the  input  file  into  the  compressed  file and lzdcmp
                restores the attributes  (and  filename  if  requested).
                The  following  restrictions apply -- they may be lifted
                in the future as they are primarily due to the  author's
                lack of understanding of the intricacies of of VMS I/O:

                    All files must be stored on disk.
                    The lzcomp output file must be specified directly.

                Also, for all usage on VMS, the compressed file must  be
                written to, and read from disk.

                The following  file  attributes  are  not  preserved  by
                lzcomp:

                    File ownership and protection codes.
                    Date of creation, access, and backup.
                    Access control lists.

        RSX-11M RESTRICTIONS:
                                                                          Page 4
        lzcomp  File Compression

                lzcomp cannot determine the file attributes and may  not
                correctly read certain specialized file formats, such as
                "print image".  If a binary  file  is  compressed,  note
                that  it will be decompressed as "fixed-block, 512 byte"
                records.

        COMPATIBILITY WITH UNIX COMPRESS:

                LZCOMP and LZDCMP are compatible with  Unix  compress  v
                3.0.   Additionally, they have the capability of writing
                a special end-marker to signify the end of  file.   This
                is needed for record-oriented file systems, but confuses
                some version of Unix compress.  For  best  compatibility
                with   Unix   compress,   use   the   following  command
                specificiation:

                    $ lzcomp/export=(unix,noendmarker) ...

        LZW COMPRESSION ALGORITHM:

                This section is abstracted from  Terry  Welch's  article
                referenced   below.    The  algorithm  builds  a  string
                translation table that maps substrings in the input into
                fixed-length  codes.   The  compress  algorithm  may  be
                described as follows:

                  1. Initialize table to contain single-character
                     strings.
                  2. Read the first character.  Set <w> (the prefix
                     string) to that character.
                  3. (step): Read next input character, K.
                  4. If at end of file, output code(<w>); exit.
                  5. If <w>K is in the string table:
                        Set <w> to <w>K; goto step 3.
                  6. Else <w>K is not in the string table.
                        Output code(<w>);
                        Put <w>K into the string table;
                        Set <w> to K; Goto step 3.

                "At each execution of the basic step an acceptable input
                string <w> has been parsed off.  The next character K is
                read and the extended string <w>K is tested to see if it
                exists  in  the  string table.  If it is there, then the
                extended string becomes the parsed string  <w>  and  the
                step  is  repeated.  If <w>K is not in the string table,
                then it is entered, the code for the successfully parsed
                string  <w> is put out as compressed data, the character
                K becomes the beginning of the next string, and the step
                is repeated."

                The decompression  algorithm  translates  each  received
                code   into  a  prefix  string  and  extension  [suffix]
                character.  The extension  character  is  stored  (in  a
                                                                          Page 5
        lzcomp  File Compression

                push-down stack), and the prefix translated again, until
                the  prefix  is  a  single  character,  which  completes
                decompression  of  this  code.   The entire code is then
                output by popping the stack.

                "An update to the string table is  made  for  each  code
                received  (except  the first one).  When a code has been
                translated, its final character is used as the extension
                character,  combined with the prior string, to add a new
                string to the string table.  This new string is assigned
                a  unique  code  value,  which is the same code that the
                compressor assigned to that string.  In  this  way,  the
                decompressor  incrementally reconstructs the same string
                table that the decompressor used....  Unfortunately  ...
                [the algorithm] does not work for an abnormal case.

                The abnormal case occurs  whenever  an  input  character
                string  contains  the  sequence  K<w>K<w>K,  where  K<w>
                already appears in the compressor string table."

                The decompression algorithm,  augmented  to  handle  the
                abnormal case, is as follows:

                  1. Read first input code;
                     Store in CODE and OLDcode;
                     With CODE = code(K), output(K);  FINchar = K;
                  2. Read next code to CODE; INcode = CODE;
                     If at end of file, exit;
                  3. If CODE not in string table (special case) then
                        Output(FINchar);
                        CODE = OLDcode;
                        INcode = code(OLDcode, FINchar);

                  4. If CODE == code(<w>K) then
                        Push K onto the stack;
                        CODE == code(<w>);
                        Goto 4.

                  5. If CODE == code(K) then
                        Output K;
                        FINchar = K;

                  6. While stack not empty
                        Output top of stack;
                        Pop stack;

                  7. Put OLDcode,K into the string table.
                     OLDcode = INcode;
                     Goto 2.

                The  algorithm  as  implemented  here   introduces   two
                additional complications.

                The actual codes are transmitted using a variable-length
                                                                          Page 6
        lzcomp  File Compression

                encoding.  The lowest-level routines increase the number
                of bits in the code when the largest  possible  code  is
                transmitted.

                Periodically, the algorithm checks that  compression  is
                still increasing.  If the ratio of input bytes to output
                bytes decreases, the entire process is reset.  This  can
                happen if the characteristics of the input file change.

        VMS PRIVATE FILE STRUCTURE:

                In VMS Private mode, the compressed data file contains a
                variable-length  (but  compressed)  file header with the
                file "attributes" needed  by  the  operating  system  to
                construct  the  file.   This  allows  the  decompression
                program to recreate the file  in  its  original  format,
                which is essential if ISAM databases are compressed.

                The overall file format is as follows:

                LZ_SOH  "start of  header"  signal  (this  value  cannot
                        appear in user data).

                        A  variable-length  data  record  (maximum   256
                        bytes)  containing  the header name, followed by
                        whitespace,    followed    by    header-specific
                        information.  In this case, the name record will
                        contain the string "vms$attributes" followed  by
                        the number of bytes in the attribute data block.
                        (I assume that the name record will consist of a
                        facility  name,  such  as  "vms",  followed by a
                        dollar  sign,  followed  by  a   facility-unique
                        word.)

                LZ_EOR  Signals "end of record".

                        This is followed by a VMS file attributes record
                        (generated by a VMS system library
                              routine).

                LZ_EOR  Signals "end of record" (optional).

                        This is followed by another "header record" with
                        additional information.  Currently, this is only
                        used to transmit  the  "longest  record  length"
                        field from the input file.

                        Additional header records may be defined in  the
                        future.

                LZ_ETX  Signals "end of segment".
                                                                          Page 7
        lzcomp  File Compression

                ST_STX  Signals "start of text"  (i.e.,  start  of  data
                        file).

                        This is followed by the user data file.

                LZ_ETX  Signals "end of segment"

                LZ_ETX  Two in a row signals "end of file".

                Note that this format can easily be extended to  include
                trailer  records (with file counts and checksums) and/or
                multiple data files in one compressed file.

                Note also that the LZ_CLEAR code may appear  in  headers
                or  data  files  to  cause  the decompression program to
                "readapt" to the  characteristics  of  the  input  data.
                LZ_STX  and  LZ_SOH  reset  the  compression  algorithm.
                LZ_EOR does not.

        AUTHORS:

                The algorithm is from "A Technique for High  Performance
                Data  Compression."  Terry A.  Welch.  IEEE Computer Vol
                17, No.  6 (June 1984), pp 8-19.

                This revision is by Martin Minow.

                Unix Compress authors are as follows:

                Spencer W. Thomas
                       (decvax!harpo!utah-cs!utah-gr!thomas)
                Jim McKie               (decvax!mcvax!jim)
                Steve Davies            (decvax!vax135!petsd!peora!srd)
                Ken Turkowski           (decvax!decwrl!turtlevax!ken)
                James A. Woods          (decvax!ihnp4!ames!jaw)
                Joe Orost               (decvax!vax135!petsd!joe)
$ eod
$ create /log lzdcmp.mem
$ deck

           ____ _____________        1  File Decompression

                                   **********
                                   * lzdcmp *
                                   **********

        NAME:   lzdcmp -- File Decompression

        SYNOPSIS:

                lzdcmp [-options] [infile [outfile]]

        DESCRIPTION:

                lzdcmp decompresses files  compressed  by  lzcomp.   The
                documentation   for  lzcomp  describes  the  process  in
                greater detail.

                Options may be given in either case.

                -B      Output file is "binary", not text.  (Ignored  in
                        VMS  private  mode.)  On  VMS,  this generates a
                        stream file without carriage-control attributes.
                        On Decus C systems, it generates a "fixed block,
                        512 byte record" file.  It is unneeded on Unix.

                -F      Output file is "fixed block, 512 bytes." This is
                        used  only  by  VMS for reading files created by
                        Unix  compress  or  by  "export"  mode.   It  is
                        identical to binary on Decus C.

                -X 3    To read files compressed by an old Unix  version
                        that doesn't generate header records.

                -V val  Verbose (print  status  messages  and  debugging
                        information).   The  value selects the amount of
                        verbosity.  See  the  lzcomp  documentation  for
                        details.

                VMS COMMAND LANGUAGE INTERFACE:

                        In addition to the  above  (Unix-style)  command
                        line  interface,  lzcomp  supports a VMS command
                        line  interface.   The  following  options   are
                        available:

                        /EXPORT=(VMS, UNIX, HEADER, ENDMARKER)
                                                                          Page 2
        lzdcmp  File Decompression

                        /MODE=(TEXT, BINARY, FIXED)

                        /SHOW=(ALL, PROGRESS, STATISTICS, FDL,
                                        DEBUG, DEBUG_SERIOUS, DEBUG_IO)

                VMS PRIVATE MODE:

                        If the file was compressed in VMS private  mode,
                        all  information  needed to reconstruct the file
                        is stored in the compressed file, using the  VMS
                        run-time  library FDL routines.  This means that
                        the expanded file will have the  same  name  and
                        directory  location  it  had originally.  If the
                        directory structure does not exist (for example,
                        because  you  have  moved the compressed file to
                        another machine), you must  specify  the  second
                        argument to lzdcmp to specify the file name.

                UNIX COMPATIBILITY:

                        lzdcmp is compatable with Unix compress.  It can
                        read  and  write files in Unix compress "header"
                        and "non-header" modes.  Normally, it  writes  a
                        special  end-marker  to  the file and expects to
                        read an end-marker  from  the  compressed  file.
                        Specifying /NOENDMARKER supresses this check.

                        Thus, to decompress a  file  written  with  Unix
                        compress, specify
                          /EXPORT=(UNIX,NOENDMARKER)
                          /MODE=(whatever)

                AUTHOR:

                        This version by Martin Minow.   See  lzcomp  for
                        more details.
$ eod
$ create /log lzcmp1.c
$ deck
/*
 *		lzcomp [-options] infile outfile
 */
/*
 * Edit History
 *  4-Sep-1985 MM	First (Beta test) release
 * 24-Oct-1985 MM	Save and restore xab longest record length
 *			(Note: files written with this version can't
 *			be read with previous versions.)
 * 07-Nov-1985 MM	Can now compress VMS ISAM files.  Added -s option
 *			courtesy of Arthur Olsen (elsie!ado).
 * 18-May-1994 Terry Lemons Digital Equipment Corporation
 *         Fixed a divide by zero problem that occurs when a very
 *         small file is compressed on a fast processor, with
 *         /SHOW=STATISTICS enabled.  Also, fixed an OpenVMS
 *         compile-time problem.
 */

#ifdef	DOCUMENTATION

title	lzcomp	File Compression
index		File compression

synopsis
	.s.nf
	lzcomp [-options] [infile [outfile]]
	.s.f
description

	lzcomp implements the Lempel-Ziv file compression algorithm.
	(Files compressed by lzcomp are uncompressed by lzdcmp.)
	It operates by finding common substrings and replaces them
	with a variable-size code.  This is deterministic, and
	can be done with a single pass over the file.  Thus,
	the decompression procedure needs no input table, but
	can track the way the table was built.

	Options may be given in either case.
	.lm +8
	.p -8
	-B	Input file is "binary", not "human readable text".
	This is necessary on Dec operating systems, such as VMS and
	RSX-11M, that treat these files differently.  (Note that binary
	support is rudamentary and probably insufficient as yet.)
	(On VMS version 4, this is ignored unless the -x option is
	specified or the input file is record-oriented.)
	.p -8
	-M bits	Write using the specified number of bits in the
	code -- necessary for big machines making files for little
	machines.  For example, if compressing a file on VMS
	which is to be read on a PDP-11, you should select -M 12.
	.p -8
	-V [n]	Verbose if specified.  If a value is specified,
	it will enable debugging code (if compiled in).  The values
	are bit-encoded as follows:
	  -V  0	Don't print anything
	  -V  1	Print a summary of the operation
	  -V  2	Print a running status on occassion
	  -V  4	Print some debug information.
	  -V  8	Print too much debug information
	  -V 16 Dump the compressed files
	.p -8
	-S	Perform a transformation on the input file whereby
	each byte is subtracted from its predecessor.  I.e., the
	difference between bytes is compressed, rather than their
	actual value.  This improves the performance of the algorithm
	on certain kinds of data files.  However, it will worsen
	the compression of most other kinds of data.  Note that files
	compressed with the -S option cannot be decompressed on versions
	of Unix compress (or lzdcmp) that do not support this option.
	.s
	The -S option is useful for digitized video images, where each
	pixel element is represented as a single byte.  It may also be
	useful when compressing digitized encoding of similar analog
	data, where each sample is contained in a single byte.
	.p -8
	-X [n]	"Export" -- write a file format that can be read by
	other operating systems.  Only the bytes in the file are copied;
	file attributes are not preserved.  If specified, the value
	determines the level of compatiblity.  If not specified,
	or specified with an explicit value of zero, and lzcomp is
	running on Vax/VMS version 4 under VaxC and the input file
	is a disk or magtape file (block-oriented), a VMS-private output
	format is used which is incompatible with the Unix compress
	utility, but which preserves VMS file attributes.  -X may
	take on the following values:
	.lm +4.s
	.i -4;#0##Choose VMS private format.  See restrictions below.
	.i -4;#1##Compatible with Unix compress version 3.0, except
	that an end code is written that may not be compatible with
	Unix compress. This is the default if -x is given without a value.
	This is the correct mode to use when transferring files between
	VMS and RSX-11M, RSTS/E, or RT11, or when transferring files
	to LZDCMP running on Unix.
	.i -4;#2##As above, but do not write the extra end code.
	This will cause problems when decompressing on RSX-11M and
	RT11 systems.
	.i -4;#3##As in (2) above, but suppress "block compression".
	.i -4;#4##As in (3) above, but do not output
	a compress header block.  This is for compatiblity
	with a quite early version of Unix compress (and requires
	conditional-compilation to use).
	.lm -4.s
	Note that the -B (binary) option is ignored unless
	the input file is "record-oriented", such as a terminal
	or mailbox.
	.lm -8.s
	The other two arguments are the input and output
	filenames respectively.  Redirection is supported,
	however, the output must be a disk/tape file.

	The file format is almost identical to the current
	Unix implementation of compress (V4.0).  Files written
	by Unix compress should be readable by lzdcmp.  Files
	written by lzcomp in export (-x) format will be
	readable by Unix compress (except that lzcomp outputs
	two "clear" codes to mark EOF.  A patch to Unix
	compress is available.)

VMS Command Language Interface

	In addition to the above (Unix-style) command line
	interface, lzcomp supports a VMS command line interface.
	The following options are available:
	.lm +8
	.p -8
	/BITS=<value>
	.p -8
	/EXPORT=(VMS, UNIX, BLOCK, HEADER, ENDMARKER)
	.p -8
	/MODE=(DELTA)
	.p -8
	/SHOW=(ALL, PROGRESS, STATISTICS, FDL,
		DEBUG, DEBUG_SERIOUS, DEBUG_IO)
	.lm -8

VMS Restrictions

	VMS Private mode stores the true name and attributes
	of the input file into the compressed file and lzdcmp
	restores the attributes (and filename if requested).
	The following restrictions apply -- they may be lifted
	in the future as they are primarily due to the author's
	lack of understanding of the intricacies of of VMS I/O:

	    All files must be stored on disk.
	    The lzcomp output file must be specified directly.

	Also, for all usage on VMS, the compressed file must
	be written to, and read from disk.

	The following file attributes are not preserved by
	lzcomp:

	    File ownership and protection codes.
	    Date of creation, access, and backup.
	    Access control lists.

RSX-11M restrictions

	lzcomp cannot determine the file attributes and may
	not correctly read certain specialized file formats,
	such as "print image".  If a binary file is compressed,
	note that it will be decompressed as "fixed-block,
	512 byte" records.

Compatibility with Unix compress

	LZCOMP and LZDCMP are compatible with Unix compress v 3.0.
	Additionally, they have the capability of writing a special
	end-marker to signify the end of file.  This is needed for
	record-oriented file systems, but confuses some version
	of Unix compress.  For best compatibility with Unix compress,
	use the following command specificiation:

	    $ lzcomp/export=(unix,noendmarker) ...

LZW compression algorithm

	This section is abstracted from Terry Welch's article
	referenced below.  The algorithm builds a string
	translation table that maps substrings in the input
	into fixed-length codes.  The compress algorithm may
	be described as follows:

	  1. Initialize table to contain single-character
	     strings.
	  2. Read the first character.  Set <w> (the prefix
	     string) to that character.
	  3. (step): Read next input character, K.
 	  4. If at end of file, output code(<w>); exit.
	  5. If <w>K is in the string table:
		Set <w> to <w>K; goto step 3.
	  6. Else <w>K is not in the string table.
		Output code(<w>);
		Put <w>K into the string table;
		Set <w> to K; Goto step 3.

	"At each execution of the basic step an acceptable input
	string <w> has been parsed off.  The next character K is
	read and the extended string <w>K is tested to see if it
	exists in the string table.  If it is there, then the
	extended string becomes the parsed string <w> and the
	step is repeated.  If <w>K is not in the string table,
	then it is entered, the code for the successfully
	parsed string <w> is put out as compressed data, the
	character K becomes the beginning of the next string,
	and the step is repeated."

	The decompression algorithm translates each received
	code into a prefix string and extension [suffix] character.
	The extension character is stored (in a push-down stack),
	and the prefix translated again, until the prefix is a
	single character, which completes decompression of this
	code.  The entire code is then output by popping the
	stack.

	"An update to the string table is made for each code received
	(except the first one).  When a code has been translated,
	its final character is used as the extension character,
	combined with the prior string, to add a new string to
	the string table.  This new string is assigned a unique
	code value, which is the same code that the compressor
	assigned to that string.  In this way, the decompressor
	incrementally reconstructs the same string table that
	the decompressor used.... Unfortunately ... [the algorithm]
	does not work for an abnormal case.

	The abnormal case occurs whenever an input character string
	contains the sequence K<w>K<w>K, where K<w> already
	appears in the compressor string table."

	The decompression algorithm, augmented to handle
	the abnormal case, is as follows:

	  1. Read first input code;
	     Store in CODE and OLDcode;
	     With CODE = code(K), output(K);  FINchar = K;
	  2. Read next code to CODE; INcode = CODE;
	     If at end of file, exit;
	  3. If CODE not in string table (special case) then
		Output(FINchar);
		CODE = OLDcode;
		INcode = code(OLDcode, FINchar);

	  4. If CODE == code(<w>K) then
		Push K onto the stack;
		CODE == code(<w>);
		Goto 4.

	  5. If CODE == code(K) then
		Output K;
		FINchar = K;

	  6. While stack not empty
		Output top of stack;
		Pop stack;

	  7. Put OLDcode,K into the string table.
	     OLDcode = INcode;
	     Goto 2.

	The algorithm as implemented here introduces two additional
	complications.

	The actual codes are transmitted using a variable-length
	encoding.  The lowest-level routines increase the number
	of bits in the code when the largest possible code is
	transmitted.

	Periodically, the algorithm checks that compression is
	still increasing.  If the ratio of input bytes to output
	bytes decreases, the entire process is reset.  This can
	happen if the characteristics of the input file change.

VMS Private File Structure

	In VMS Private mode, the compressed data file contains
	a variable-length (but compressed) file header with the
	file "attributes" needed by the operating system to
 	construct the file.  This allows the decompression
	program to recreate the file in its original format,
	which is essential if ISAM databases are compressed.

	The overall file format is as follows:
	.lm +8
	.p -8
	LZ_SOH	"start of header" signal (this value cannot appear
	in user data).

	A variable-length data record (maximum 256 bytes)
	containing the header name, followed by whitespace, followed
	by header-specific information.  In this case, the name
	record will contain the string "vms$attributes" followed
	by the number of bytes in the attribute data block.
	(I assume that the name record will consist of a facility
	name, such as "vms", followed by a dollar sign, followed
	by a facility-unique word.)
	.p -8
	LZ_EOR	Signals "end of record".

	This is followed by a VMS file attributes record (generated
	by a VMS system library	routine).
	.p -8
	LZ_EOR	Signals "end of record" (optional).

	This is followed by another "header record" with additional
	information.  Currently, this is only used to transmit
	the "longest record length" field from the input file.

	Additional header records may be defined in the future.
	.p -8
	LZ_ETX	Signals "end of segment".
	.p -8
	ST_STX	Signals "start of text" (i.e., start of data file).

	This is followed by the user data file.
	.p -8
	LZ_ETX	Signals "end of segment"
	.p -8
	LZ_ETX	Two in a row signals "end of file".
	.s.lm -8
	Note that this format can easily be extended to include
	trailer records (with file counts and checksums) and/or
	multiple data files in one compressed file.

	Note also that the LZ_CLEAR code may appear in headers
	or data files to cause the decompression program to
	"readapt" to the characteristics of the input data.
	LZ_STX and LZ_SOH reset the compression algorithm.
	LZ_EOR does not.

Authors

	The algorithm is from "A Technique for High Performance
	Data Compression."  Terry A. Welch. IEEE Computer Vol 17,
	No. 6 (June 1984), pp 8-19.

	This revision is by Martin Minow.

	Unix Compress authors are as follows:
	.s.nf
	Spencer W. Thomas	(decvax!harpo!utah-cs!utah-gr!thomas)
	Jim McKie		(decvax!mcvax!jim)
	Steve Davies		(decvax!vax135!petsd!peora!srd)
	Ken Turkowski		(decvax!decwrl!turtlevax!ken)
	James A. Woods		(decvax!ihnp4!ames!jaw)
	Joe Orost		(decvax!vax135!petsd!joe)
	.s.f

#endif

/*
 * Compatible with compress.c, v3.0 84/11/27
 */

/*)BUILD
		$(PROGRAM) = lzcomp
		$(INCLUDE) = lz.h
		$(CPP) = 1
		$(RMS) = 1
		$(FILES) = { lzcmp1.c lzcmp2.c lzcmp3.c lzio.c lzvio.c }
*/

#include	"lz.h"

#ifdef unix
#include <sys/types.h>
#include <sys/stat.h>
#endif

/*
 * These global parameters are written to the compressed file.
 * The decompressor needs them.  The initialized values are defaults
 * and are modified by command line arguments.
 */
short		maxbits = BITS;		/* settable max # bits/code	*/
code_int maxmaxcode = 1 << BITS; 	/* Totally largest code	*/
code_int	hsize = HSIZE;		/* Actual hash table size	*/

/*
 * Flags (command line arguments) to control compression.
 */
#if VMS_V4
flag		export = EXPORT_VMS	/* Assume vms "private" mode	*/
			| EXPORT_HEADER
			| EXPORT_BLOCK
			| EXPORT_ENDMARKER;
#else
flag		export = EXPORT_UNIX	/* Assume Unix compatible mode	*/
			| EXPORT_HEADER
			| EXPORT_BLOCK
			| EXPORT_ENDMARKER;
#endif
flag		method = METHOD_LZ;	/* Of course			*/
flag		mode = MODE_TEXT;
flag		show = 0;
flag		background = FALSE;	/* TRUE (Unix) if detached	*/
readonly flag	is_compress = TRUE;	/* for lzdcl and lzvio		*/
long		fsize;			/* Input file size in bytes	*/
char		*infilename = NULL;	/* For error printouts		*/
char		*outfilename = NULL;	/* For openoutput and errors	*/
int		firstcode;		/* First code after internals	*/
count_int	tot_incount = 0;	/* Total number of input bytes	*/
count_int	tot_outcount = 0;	/* Total number of output codes	*/
extern count_int in_count;
extern count_int out_count;
static long	start_time;		/* Time we started (in msec)	*/
extern long	cputime();		/* Returns process time in msec	*/
STREAM		instream;
STREAM		outstream;
char_type	inbuffer[MAXIO];
char_type	outbuffer[MAXIO];
#if VMS_V4
static STREAM	mem_stream;
#endif
jmp_buf		failure;
#if VMS_V4
#include types
#include stat
/* 18-May-1994 Terry Lemons - disabled next three lines
#ifndef FDLSTUFF
#define FDLSTUFF char
#endif
*/
FDLSTUFF	*fdl_input;
FDLSTUFF	*fdl_output;
static struct dsc$descriptor fdl_descriptor;
#endif

main(argc, argv)
int		argc;
char		*argv[];
/*
 * Compress mainline
 */
{
	int		status;

#ifndef	decus
	/*
	 * background is TRUE if running detached from the command terminal.
	 */
	background = (signal(SIGINT, SIG_IGN) == SIG_IGN) ? TRUE : FALSE;
	if (!background)
	    background = !isatty(fileno(stderr));
	if (!background) {
	    if ((show & (SHOW_DEBUG | SHOW_SERIOUS_DEBUG | SHOW_IO_DEBUG)) != 0)
		signal(SIGINT, abort);
	    else {
		signal(SIGINT, interrupt);
		signal(SIGSEGV, address_error);
	    }
	}
#endif
	if ((status = setjmp(failure)) == 0) {
	    setup(argc, argv);		/* Command line parameters	*/
#if DEBUG
	    if (show & SHOW_DEBUG)
		dumpoptions();
#endif
	    openinput();		/* Open input, set instream	*/
	    getfilesize();		/* Get input file size		*/
	    gethashsize();		/* Get actual hash table size	*/
	    initialize();		/* Set maxbits and the like	*/
	    openoutput();		/* Open output file		*/
	    if ((show & SHOW_STATISTICS) != 0)
		start_time = cputime();
	    put_magic_header();
	    init_compress(TRUE);
	    compress(&instream);
#if VMS_V4
	    if ((export & EXPORT_VMS) != 0) {
		outputcode((code_int) LZ_ETX);
		outputcode((code_int) LZ_ETX);
		fdl_close(fdl_input);
	    }
	    else
#endif
	    if ((export & EXPORT_ENDMARKER) != 0) {
		outputcode((code_int) LZ_CLEAR);
		outputcode((code_int) LZ_CLEAR);
	    }
	    outputcode((code_int) -1);		/* Flush output buffers	*/
#if VMS_V4
	    if ((export & EXPORT_VMS) != 0)
		fdl_close(fdl_output);
	    else {
		fclose(stdout);
	    }
#else
	    fclose(stdout);
#endif
	    if ((show & SHOW_STATISTICS) != 0)
		report();
	    exit(EXIT_SUCCESS);
	}
	else {
	    fprintf(stderr, "Error when compressing \"%s\" to \"%s\"\n",
		(infilename  == NULL) ?
		    "<input file unknown>" : infilename,
		(outfilename == NULL) ?
		    "<output file unknown>" : outfilename);
	    exit(status);
	}
}

report()
/*
 * Print verbose report.
 */
{
	start_time = cputime() - start_time;
	tot_incount += in_count;
	tot_outcount += out_count;
	fprintf(stderr, "%8ld chars, %5ld blocks processed.",
	    tot_incount, (tot_incount + 511L) / 512L);
	if (tot_outcount > 0) {
	    divout("  Compression ratio: ",
		(long) tot_incount, (long) tot_outcount, "");
	    divout(" (",
		((long) tot_incount - (long) tot_outcount) * 100,
		(long) tot_incount, "%),");
	}
	fprintf(stderr, "\n%8ld chars, %5ld blocks output\n",
	    tot_outcount, (tot_outcount + 511L) / 512L);
	fprintf(stderr,
	    "%ld.%02ld seconds (process time) for compression.",
	    start_time / 1000L, (start_time % 1000L) / 10L);
	if (start_time > 0) {
/* Replaced 18-May-1994 Terry Lemons                                */
/* The '(start_time + 50L) / 100' produced zero on small files,     */
/* resulting in a divide by zero in divout.  And multiplying        */
/* tot_incount by 1000L to produce the right division result in     */
/* divout produced a value that exceeded a long.  So, made the      */
/* decision to use a float, feeling most compilers should handle it.*/
/*	divout("  ", (long) tot_incount * 10L,             */
/*	(start_time + 50L) / 100L,                         */
/*	" input bytes/sec.\n");                            */
	fprintf(stderr, "  %.2f input bytes/sec.\n",
	    ((float) tot_incount / ((float) start_time / 1000.0f)));
	}
}

divout(leader, numer, denom, trailer)
char		*leader;
long		numer;
long		denom;
char		*trailer;
/*
 * Print numer/denom without floating point on small machines.
 */
{
	register int 	negative;

	negative = FALSE;
	if (numer < 0) {
	    numer = -numer;
	    negative = TRUE;
	}
	if (denom < 0) {
	    denom = -denom;
	    negative = !negative;
	}
	fprintf(stderr, "%s%s%ld.%02ld%s",
	    leader, (negative) ? "-" : "",
	    numer / denom, ((numer % denom) * 100L) / denom, trailer);
}

static
initialize()
/*
 * Mung some global values.
 */
{
	if (maxbits < INIT_BITS)	/* maxbits is set by the -M 	*/
	    maxbits = INIT_BITS;	/* option.  Make sure it's	*/
	if (maxbits > BITS)		/* within a reasonable range	*/
	    maxbits = BITS;
	maxmaxcode = 1 << maxbits;	/* Truly biggest code		*/
	if ((export & EXPORT_VMS) != 0)
	    firstcode = LZ_FIRST;	/* VMS private			*/
	else if ((export & EXPORT_BLOCK) != 0)
	    firstcode = LZ_CLEAR + 1;	/* Default			*/
	else
	    firstcode = 256;		/* Backwards compatible		*/
}

put_magic_header()
/*
 * Write the magic header bits.
 */
{
	if ((export & (EXPORT_VMS | EXPORT_HEADER)) == EXPORT_HEADER) {
	    PUT(HEAD1_MAGIC, &outstream);
	    PUT(HEAD2_MAGIC, &outstream);
	    PUT(maxbits
		| (((export & EXPORT_HEADER) != 0) ? BLOCK_MASK : 0)
		| (((mode   & MODE_DELTA)    != 0) ? DIFF_MASK  : 0),
		&outstream);
	}
#if VMS_V4
	else if ((export & EXPORT_VMS) != 0) {
	    char		text[256];
	    /*
	     * VMS private mode (with attribute block)
	     */
	    PUT(HEAD1_MAGIC, &outstream);
	    PUT(VMS_HEAD2_MAGIC, &outstream);
	    PUT(maxbits
		| BLOCK_MASK
		| (((mode & MODE_DELTA) != 0) ? DIFF_MASK  : 0),
		&outstream);
	    PUT(firstcode - 0x100, &outstream);
	    init_compress();
	    outputcode(LZ_SOH);
#if DEBUG
	    if (strlen(ATT_NAME) != ATT_SIZE) {
		fprintf("\"%s\", expected %d, got %d\n",
		    ATT_NAME, ATT_SIZE, strlen(ATT_NAME));
	    }
#endif
	    sprintf(text, "%s%d;", ATT_NAME, fdl_descriptor.dsc$w_length);
	    mem_compress(text, strlen(text));
	    outputcode(LZ_EOR);
	    mem_compress(fdl_descriptor.dsc$a_pointer,
			 fdl_descriptor.dsc$w_length);
	    fdl_free(&fdl_descriptor);
	    if (fdl_input->xabfhc.xab$w_lrl != 0) {
		outputcode(LZ_EOR);
		sprintf(text, "xabfhc$w_lrl %d", fdl_input->xabfhc.xab$w_lrl);
		if ((show & SHOW_FDL) != 0)
		    fprintf(stderr, "Additional header: %s\n", text);
		mem_compress(text, strlen(text));
	    }
	    outputcode(LZ_ETX);
	    outputcode(LZ_STX);
	}
#endif
}

#if VMS_V4
mem_compress(datum, length)
char_type	*datum;
int		length;
/*
 * Compress from memory
 */
{
	mem_stream.bp = mem_stream.bstart = datum;
	mem_stream.bsize = length;
	mem_stream.bend = datum + length;
	mem_stream.func = lz_eof;
	compress(&mem_stream);
}
#endif

/*
 * This routine is used to tune the hash table size according to
 * the file size.  If the filesize is unknown, fsize should be
 * set to zero.
 */

typedef struct TUNETAB {
    long	fsize;
    code_int	hsize;
} TUNETAB;

static readonly TUNETAB tunetab[] = {
#if HSIZE > 5003
    {	1 << 12,	 5003	},
#endif
#if HSIZE > 9001
    {	1 << 13,	 9001	},
#endif
#if HSIZE > 18013
    {	1 << 14,	18013	},
#endif
#if HSIZE > 35023
    {	1 << 15,	35023	},
    {	  47000,	50021	},
#endif
    {	      0,	    0	},
};

static
gethashsize()
/*
 * Tune the hash table parameters for small files.
 * We don't have a good way to find the file size on vms V3.
 * fsize is set to zero if we can't find it.
 */
{
	register TUNETAB	*tunep;

	hsize = HSIZE;
	if (fsize > 0) {
	    for (tunep = tunetab; tunep->fsize != 0; tunep++) {
		if (fsize < tunep->fsize) {
		    hsize = tunep->hsize;
		    break;
		}
	    }
	}
}

static
getfilesize()
/*
 * Set fsize to the input filesize (in bytes) if possible.
 * Magic for all operating systems.
 */
{
#ifdef	rsx
	extern char	f_efbk;	/* F.EFBK -- highest block in file	*/
#define	fdb(p,offset)	(stdin->io_fdb[((int) &p + offset)] & 0xFF)
#define efbk(offset)	fdb(f_efbk, offset)
	extern char	f_rtyp;	/* F.RTYP -- Record type		*/
	extern char	f_ratt;	/* F.RATT -- Record attributes		*/
	/*
	 * Note: Block number is stored high-order word first.
	 */
	fsize = efbk(2)
	    + (efbk(3) << 8)
	    + (efbk(0) << 16)
	    + (efbk(1) << 24);
	fsize *= 512;
#endif
#ifdef	rt11
	fsize = stdin->io_size;		/* Set by Decus C		*/
	fsize *= 512;
#endif
#ifdef	vms
#if VMS_V4
	struct stat	statbuf;

	fsize = 0;
	if ((export & EXPORT_VMS) == 0) {
	    if (fstat(fileno(stdin), &statbuf) == 0)
		fsize = (long) statbuf.st_size;
	}
	else {
	    fsize = (long) fdl_fsize(fdl_input);
	}
#else
	fsize = 0;				/* Can't find filesize	*/
#endif
#endif
#ifdef	unix
	struct stat	statbuf;

	fsize = 0;
	if (fstat(fileno(stdin), &statbuf) == 0)
	    fsize = (long) statbuf.st_size;
#endif
}

static readonly char *helptext[] = {
	"The following options are valid:",
	"-B\tBinary file (important on VMS/RSX, ignored on Unix)",
	"-M val\tExplicitly set the maximum number of code bits",
	"-S\tCompress difference between bytes",
	"-V val\tPrint status information (or debugging) to stderr",
	"-X val\tSet export (compatiblity) mode:",
#if VMS_V4
	"  -X 0\tExplicitly choose VMS Private mode",
#endif
	"  -X 1\t(default if -X specified, output format is compatible",
	      "\twith Unix compress V3.0, except that two endcodes are written",
	"  -X 2\t(default if -X specified, output format is compatible",
	      "\twith Unix compress V3.0",
	"  -X 3\tCompatible with Unix compress 3.0, block compression",
	      "\tsuppressed.",
	"  -X 4No header (file is readable by old compress)",
	NULL,
};

static
setup(argc, argv)
int		argc;
char		*argv[];
/*
 * Get parameters and open files.  Exit fatally on errors.
 */
{
	register char	*ap;
	register int	c;
	char		**hp;
	auto int	i;
	int		j;
	int		temp;

#ifdef	vms
	argc = getredirection(argc, argv);
	/*
	 * Prescan to see whether we must do a DCL parse.
	 */
	for (j = FALSE, i = 1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		j = TRUE;
		break;
	    }
	}
	if (j == FALSE) {
	    if ((i = lzdcl(argc, argv)) != SS$_NORMAL)
		exit(i);
	    return;
	}
#endif
	for (i = j = 1; i < argc; i++) {
	    ap = argv[i];
	    if (*ap++ != '-' || *ap == EOS)	/* Filename?		*/
		argv[j++] = argv[i];		/* Just copy it		*/
	    else {
		while ((c = *ap++) != EOS) {
		    if (islower(c))
			c = toupper(c);
		    switch (c) {
		    case 'B':
			mode |= MODE_BINARY;
			break;

		    case 'M':
			maxbits = getvalue(ap, &i, argv);
			if (maxbits < MIN_BITS) {
			    fprintf(stderr, "Illegal -M value\n");
			    goto usage;
			}
			break;

		    case 'S':
			mode |= MODE_DELTA;
			break;

		    case 'V':
			show = getvalue(ap, &i, argv);
			break;

		    case 'X':
			switch ((temp = getvalue(ap, &i, argv))) {
			case 0:	export = EXPORT_VMS
					| EXPORT_BLOCK
					| EXPORT_HEADER
					| EXPORT_ENDMARKER;
				break;
			case 1:	export = EXPORT_UNIX
					| EXPORT_BLOCK
					| EXPORT_HEADER
					| EXPORT_ENDMARKER;
				break;

			case 2: export = EXPORT_UNIX
					| EXPORT_BLOCK
					| EXPORT_HEADER;
				break;
			case 3:	export = EXPORT_UNIX
					| EXPORT_HEADER;
				break;
			case 4:	export = EXPORT_UNIX;
				break;
			default:
			    fprintf(stderr, "Illegal -X value: %d\n", temp);
			    goto usage;
			}
			break;

		    default:
			fprintf(stderr, "Unknown option '%c' in \"%s\"\n",
				*ap, argv[i]);
usage:			for (hp = helptext; *hp != NULL; hp++)
			    fprintf(stderr, "%s\n", *hp);
			FAIL("usage", EXIT_FAILURE);
		    }				/* Switch on options	*/
		}				/* Everything for -xxx	*/
	    }					/* If -option		*/
	}					/* For all argc's	*/
	/*  infilename = NULL; */		/* Set "stdin"  signal	*/
	/* outfilename = NULL; */		/* Set "stdout" signal	*/
	switch (j) {				/* Any file arguments?	*/
	case 3:					/* both files given	*/
	    if (!streq(argv[2], "-"))		/* But - means stdout	*/
		outfilename = argv[2];
	case 2:					/* Input file given	*/
	    if (!streq(argv[1], "-"))
		infilename = argv[1];
	    break;

	case 0:					/* None!		*/
	case 1:					/* No file arguments	*/
	    break;

	default:
	    fprintf(stderr, "Too many file arguments\n");
	    FAIL("too many files", EXIT_FAILURE);
	}
}

static int
getvalue(ap, ip, argv)
register char		*ap;
int			*ip;
char			*argv[];
/*
 * Compile a "value".  We are currently scanning *ap, part of argv[*ip].
 * The following are possible:
 *	-x123		return (123) and set *ap to EOS so the caller
 *	ap^		cycles to the next argument.
 *
 *	-x 123		*ap == EOS and argv[*ip + 1][0] is a digit.
 *			return (123) and increment *i to skip over the
 *			next argument.
 *
 *	-xy or -x y	return(1), don't touch *ap or *ip.
 *
 * Note that the default for "flag option without value" is 1.  This
 * can only cause a problem for the -M option where the value is
 * mandatory.  However, the result of 1 is illegal as it is less
 * than INIT_BITS.
 */
{
	register int	result;
	register int	i;

	i = *ip + 1;
	if (isdigit(*ap)) {
	    result = atoi(ap);
	    *ap = EOS;
	}
	else if (*ap == EOS
	      && argv[i] != NULL
	      && isdigit(argv[i][0])) {
	    result = atoi(argv[i]);
	    *ip = i;
	}
	else {
	    result = 1;
	}
	return (result);
}

openinput()
{
#ifdef decus
	if (infilename == NULL) {
	    infilename = malloc(256 + 1);
	    fgetname(stdin, infilename);
	    infilename = realloc(infilename, strlen(infilename) + 1);
	}
	else {
	    if (freopen(infilename,
			((mode & MODE_BINARY) != 0) ? "rb" : "r",
			stdin) == NULL) {
		perror(infilename);
		FAIL("can't reopen input", ERROR_EXIT);
	    }
	}
#else
#ifdef vms
#if VMS_V4
	if ((export & EXPORT_VMS) != 0) {
	    char		*fname;
	    char		filename[256];

	    if ((fname = infilename) == NULL) {
		fgetname(stdin, filename);
		fname = filename;
	    }
	    if ((fdl_input = fdl_open(fname, &fdl_descriptor)) == NULL) {
		if (!$VMS_STATUS_SUCCESS(fdl_status)) {
		    FAIL("can't fdl_open", fdl_status);
		}
		fprintf(stderr,
		    "Cannot open \"%s\" in vms private format,", fname);
		fprintf(stderr, " trying export format.\n");
		export = TRUE;
		goto try_export;
	    }
	    fclose(stdin);
	    stdin = NULL;
	    infilename = malloc(strlen(fname) + 1);
	    strcpy(infilename, fname);
	    if ((show & SHOW_FDL) != 0) {
		fprintf(stderr, "FDL information for \"%s\"\n", infilename);
		fdl_dump(&fdl_descriptor, stderr);
	    }
	    goto opened;
	}
try_export:
#endif
	if (infilename == NULL) {
	    infilename = malloc(256 + 1);
	    fgetname(stdin, infilename);
	    infilename = realloc(infilename, strlen(infilename) + 1);
	}
	else {
#if VMS_V4
	    if ((stdin = freopen(infilename,
		((mode & MODE_BINARY) != 0) ? "rb" : "r", stdin)) == NULL) {
#else
	    if (freopen(infilename, "r", stdin) == NULL) {
#endif
		perror(infilename);
		exit(ERROR_EXIT);
	    }
	}
#else
	if (infilename == NULL)
	    infilename = "stdin";
	else {
	    if (freopen(infilename, "r", stdin) == NULL) {
		perror(infilename);
		exit(ERROR_EXIT);
	    }
	}
#endif
#endif
#if VMS_V4
opened:	;				/* Exit from VMS-private open	*/
#endif
	instream.bp = instream.bend = NULL;
	instream.bstart = inbuffer;
	instream.bsize = sizeof inbuffer;
	instream.func = lz_fill;
}

openoutput()
/*
 * Open the output file (after the input file has been opened).
 * if outfilename == NULL, it's already open on stdout.
 */
{
	int			ttytest;

	ttytest = TRUE;			/* Need to test output file	*/
	if (outfilename == NULL) {
#if VMS_V4
	    fprintf(stderr,
		"Restriction: The output file must be specified.\n");
	    FAIL("can't redirect on VMS V4", EXIT_FAILURE);
#else
#ifdef	vms
	    outfilename = malloc(256 + 1);
	    fgetname(stdout, outfilename);
	    outfilename = realloc(outfilename, strlen(outfilename) + 1);
#else
#ifdef decus
	    outfilename = malloc(256 + 1);
	    fgetname(stdout, outfilename);
	    outfilename = realloc(outfilename, strlen(outfilename) + 1);
#else
	    outfilename = "<stdout>";
#endif
#endif
#endif
	}
	else {
#if VMS_V4
	    if ((export & EXPORT_VMS) != 0) {
		fclose(stdout);
		ttytest = FALSE;	/* Can't do terminal test below	*/
		if ((fdl_output = fdl_create(NULL, outfilename, 0)) == NULL) {
		    fprintf(stderr,
			"Can't create \"%s\" (VMS private)\n", outfilename);
		    FAIL("can't fdl_create", fdl_status);
		}
	    }
	    else {
		if (freopen(outfilename, "w", stdout) == NULL) {
		    perror(outfilename);
		    FAIL("can't create", ERROR_EXIT);
		}
	    }
#else
#ifdef decus
	    if (freopen(outfilename, "wb", stdout) == NULL) {
		perror(outfilename);
		FAIL("can't create", ERROR_EXIT);
	    }
#else
	    if (freopen(outfilename, "w", stdout) == NULL) {
		perror(outfilename);
		FAIL("can't create", ERROR_EXIT);
	    }
#endif
#endif
	}
	if (ttytest && isatty(fileno(stdout))) {
	    fprintf(stderr, "%s: is a terminal.  We object.\n",
		outfilename);
	    FAIL("can't create", EXIT_FAILURE);
	}
	outstream.bp = outstream.bstart = outbuffer;
	outstream.bend = outbuffer + sizeof outbuffer;
	outstream.bsize = sizeof outbuffer;
	outstream.func = lz_flush;
}
$ eod
$ create /log lzcmp2.c
$ deck
/*
 *		l z c m p 2 . c
 *
 * Actually do compression.  Terminology (and algorithm):
 *
 * Assume the input string is "abcd", we have just processed "ab" and
 * read 'c'.  At this point, a "prefix code" will be assigned to "ab".
 * Search in the prefix:character memory (either the "fast memory" or
 * the hash-code table) for the code followed by this character.  If
 * found, assign the code found to the "prefix code" and read the
 * next character.  If not found, output the current prefix code,
 * generate a new prefix code and store "old_prefix:char" in the
 * table with "new_prefix" as its definition.
 *
 * Naming conventions:
 *   code	a variable containing a prefix code
 *   c or char	a variable containing a character
 *
 * There are three tables that are searched (dependent on compile-time
 * and execution time considerations):
 *   fast	Direct table-lookup -- requires a huge amount of physical
 *		(non-paged) memory, but is very fast.
 *   hash	Hash-coded table-lookup.
 *   cache	A "look-ahead" cache for the hash table that optimizes
 *		searching for the most frequent character.  This considerably
 *		speeds up processing for raster-images (for example) at
 *		a modest amount of memory.
 * Structures are used to hold the actual tables to simplify organization
 * of the program.
 *
 * Subroutines:
 *    compress()	performs data compression on an input datastream.
 *    init_compress()	called by the output routine to clear tables.
 */

#include	"lz.h"

/*
 * General variables
 * Cleared by init_compress on a "hard initialization"
 * outputcode() in lzcmp3.c refers to next_code.
 */

long int	in_count;		/* Length of input		*/
long int	out_count;		/* Bytes written to output file	*/
static flag	first_clear = TRUE;	/* Don't zero first time	*/
code_int	next_code;		/* Next output code		*/
static count_int checkpoint = CHECK_GAP; /* When to test ratio again	*/
static long	ratio = 0;		/* Ratio for last segment	*/

/*
 * These global parameters are set by mainline code.  Unchanged here.
 */
extern code_int	maxmaxcode;		/* Actual maximum output code	*/
extern long	tot_incount;		/* Total input count		*/
extern long	tot_outcount;		/* Total output count		*/
extern code_int	hsize;			/* Actual hash table size	*/

#ifdef XENIX_16
static count_int htab0[8192];
static count_int htab1[8192];
static count_int htab2[8192];
static count_int htab3[8192];
static count_int htab4[8192];
static count_int htab5[8192];
static count_int htab6[8192];
static count_int htab7[8192];
static count_int htab8[HSIZE - 65536];
static count_int *hashtab[9] = {
    htab0, htab1, htab2, htab3, htab4, htab5, htab6, htab7, htab8
};

static U_short code0[16384];
static U_short code1[16384];
static U_short code2[16384];
static U_short code3[16384];
static U_short code4[HSIZE - 65536];
static U_short *codetab[5] = {
    code0, code1, code3, code3, code4
}

#define HASH(i)		(hashtab[((unsigned) (i)) >> 13][(i) & 0x1FFF])
#define CODE(i)		(codetab[((unsigned) (i)) >> 14][(i) & 0x3FFF])

#else
count_int	hashtab[HSIZE];
U_short		codetab[HSIZE];

#define HASH(i)		hashtab[i]
#define CODE(i)		codetab[i]
#endif

/*
 * compress a datastream
 *
 * Algorithm:  on large machines, for maxbits <= FBITS, use fast direct table
 * lookup on the prefix code / next character combination.  For smaller code
 * size, use open addressing modular division double hashing (no chaining), ala
 * Knuth vol. 3, sec. 6.4 Algorithm D, along with G. Knott's relatively-prime
 * secondary probe.  Do block compression with an adaptive reset, whereby the
 * code table is cleared when the compression ratio decreases, but after the
 * table fills.  The variable-length output codes are re-sized at this point,
 * and a special LZ_CLEAR code is generated for the decompressor.  For the
 * megamemory version, the sparse array is cleared indirectly through a
 * "shadow" output code history.  Late additions: for the hashing code,
 * construct the table according to file size for noticeable speed improvement
 * on small files.  Also detect and cache codes associated with the most
 * common character to bypass hash calculation on these codes (a characteristic
 * of highly-compressable raster images).  Please direct questions about this
 * implementation to ames!jaw.
 */

compress(in)
STREAM		*in;		/* Input stream structure		*/
/*
 * Compress driver.  Global fsize is the size of the entire datastream
 * (from LZ_STX or LZ_SOH to the terminating LZ_ETX).  You must
 * force a reinitialization -- by calling outputcode() with a new header --
 * if size is changed.  If the "newer" output format is chosen (with
 * data streams delimited by LZ_SOH/LZ_STX, init_compress will be
 * called automatically.  Otherwise, you must call init_compress(TRUE)
 * before calling compress() for the first time.
 */
{
	register long		hash_code;	/* What we look for	*/
	register code_int	i;		/* Index into vectors	*/
	register unsigned int	c;		/* Current input char	*/
	register code_int	code;		/* Substring code	*/
	register int		displacement;	/* For secondary hash	*/
	register code_int	hsize_reg;	/* Size of hash table	*/
	register int		hshift;		/* For xor hasher	*/
	register unsigned int	previous;	/* Prev. char for diffs	*/
	register unsigned int	difference;	/* Temp for difference	*/

	if ((code = GET(in)) == (unsigned) EOF)
	    return;
	previous = code;
	in_count++;
	hsize_reg = hsize;
	/*
	 * Set hash code range bound
	 */
	hshift = 0;
	for (hash_code = (long) hsize; hash_code < 65536L; hash_code <<= 1)
	    hshift++;
	hshift = 8 - hshift;
	while ((c = GET(in)) != (unsigned) EOF) {
	    in_count++;
	    if ((mode & MODE_DELTA) != 0) {
#if UCHAR
		difference = (unsigned char) (c - previous);
#else
		difference = (c - previous) & 0xFF;
#endif
		previous = c;
		c = difference;
	    }
	    hash_code = (long) (((long) c << maxbits) + code);
	    i = (c << hshift) ^ code;		/* XOR hashing		*/
	    if (HASH(i) == hash_code) {		/* Found at first slot?	*/
		code = CODE(i);
		continue;
	    }
	    else if ((long) HASH(i) < 0)	/* empty slot		*/
		goto nomatch;
	    displacement = hsize_reg - i;	/* secondary hash	*/
	    if (i == 0)
		displacement = 1;
probe:
	    if ((i -= displacement) < 0)	/* Wrap around?		*/
		i += hsize_reg;
	    if (HASH(i) == hash_code) {		/* Found in hash table?	*/
		code = CODE(i);			/* Set new prefix code	*/
		continue;			/* Read next input char	*/
	    }
	    else if ((long) HASH(i) > 0)	/* If slot is occupied	*/
		goto probe;			/* Look somewhere else	*/
nomatch:
	    /*
	     * Output the current prefix and designate a new prefix.
	     * If the input character was the "hog", save it in the
	     * look-ahead cache table.  Then, save in the hash table.
	     */
	    outputcode((code_int) code);	/* No match, put prefix	*/
#if SIGNED_COMPARE_SLOW
	    if ((unsigned) next_code < (unsigned) maxmaxcode) {
#else
	    if (next_code < maxmaxcode) {
#endif
		CODE(i) = next_code++;		/* code -> hashtable	*/
		HASH(i) = hash_code;
	    }
	    else if ((export & EXPORT_BLOCK) != 0
		  && (count_int) in_count >= checkpoint) {
		clear();
	    }
	    code = c;				/* Start new substring	*/
	}
	/*
	 * At EOF, put out the final code.
	 */
	outputcode((code_int) code);
}

clear()
/*
 * Check the compression ratio to see whether it is going up
 * or staying the same.  If it is going down, the internal
 * statistics of the file have changed, so clear out our
 * tables and start over.  Inform the decompressor of the
 * change by sending out a LZ_CLEAR code.
 */
{
	register long int	rat;

	checkpoint = in_count + CHECK_GAP;
	if ((show & (SHOW_PROGRESS | SHOW_DEBUG)) != 0) {
	    divout("at compression ratio test ",  in_count, out_count, "\n");
	}
	if (in_count > 0x007FFFFL) {		/* Shift will overflow	*/
	    rat = out_count >> 8;
	    if (rat == 0)
		rat = 0x7FFFFFFFL;
	    else {
		rat = in_count / rat;
	    }
	}
	else {
	    rat = (in_count << 8) / out_count;
	}
	if (rat > ratio)
	    ratio = rat;
	else {
	    if ((show & (SHOW_PROGRESS | SHOW_DEBUG)) != 0) {
		fprintf(stderr, "Resetting compression, in %ld, out %ld\n",
		    in_count, out_count);
		fprintf(stderr, "Old ratio: %ld.%02ld",
		    ratio / 256L, ((ratio & 255L) * 100L) / 256L);
		fprintf(stderr, ", test ratio: %ld.%02ld, gap %d\n",
		    rat / 256L, ((rat & 255L) * 100L) / 256L, CHECK_GAP);
	    }
	    outputcode((code_int) LZ_CLEAR);	/* Calls init_compress	*/
	}
}

init_compress(full_init)
flag		full_init;	/* TRUE for full initialization		*/
/*
 * Clear the tables.  Called by outputcode() on LZ_SOH, LZ_STX
 * (full_init TRUE) or on LZ_CLEAR (full_init FALSE).
 * init_compress() is not called on LZ_EOR.
 */
{
#ifdef XENIX_16
	register count_int	*hp;
	register int		n;
	register int		j;
	register code_int	k;

	k = hsize;
	for (j = 0; k > 0; k -= 8192) {
	    i = (k < 8192) ? k : 8192;
	    hp = hashtab[j++];
	    n = i >> 4;
	    switch (i & 15) {
	    case 15:	*hp++ = -1;
	    case 14:	*hp++ = -1;
	    case 13:	*hp++ = -1;
	    case 12:	*hp++ = -1;
	    case 11:	*hp++ = -1;
	    case 10:	*hp++ = -1;
	    case  9:	*hp++ = -1;
	    case  8:	*hp++ = -1;
	    case  7:	*hp++ = -1;
	    case  6:	*hp++ = -1;
	    case  5:	*hp++ = -1;
	    case  4:	*hp++ = -1;
	    case  3:	*hp++ = -1;
	    case  2:	*hp++ = -1;
	    case  1:	*hp++ = -1;
	    }
	    while (--n >= 0) {
		*hp++ = -1; *hp++ = -1; *hp++ = -1; *hp++ = -1;
		*hp++ = -1; *hp++ = -1; *hp++ = -1; *hp++ = -1;
		*hp++ = -1; *hp++ = -1; *hp++ = -1; *hp++ = -1;
		*hp++ = -1; *hp++ = -1; *hp++ = -1; *hp++ = -1;
	   }
	}
#else
	register count_int	*hp;
	register code_int	n;

	hp = &hashtab[0];
	n = hsize >> 4;			/* divide by 16			*/
	switch (hsize & 15) {
	case 15:	*hp++ = -1;
	case 14:	*hp++ = -1;
	case 13:	*hp++ = -1;
	case 12:	*hp++ = -1;
	case 11:	*hp++ = -1;
	case 10:	*hp++ = -1;
	case  9:	*hp++ = -1;
	case  8:	*hp++ = -1;
	case  7:	*hp++ = -1;
	case  6:	*hp++ = -1;
	case  5:	*hp++ = -1;
	case  4:	*hp++ = -1;
	case  3:	*hp++ = -1;
	case  2:	*hp++ = -1;
	case  1:	*hp++ = -1;
	}
	while (--n >= 0) {
	    *hp++ = -1; *hp++ = -1; *hp++ = -1; *hp++ = -1;
	    *hp++ = -1; *hp++ = -1; *hp++ = -1; *hp++ = -1;
	    *hp++ = -1; *hp++ = -1; *hp++ = -1; *hp++ = -1;
	    *hp++ = -1; *hp++ = -1; *hp++ = -1; *hp++ = -1;
	}
#endif
	if (full_init) {
	    tot_incount += in_count;
	    tot_outcount += out_count;
	    in_count = 0;
	    out_count = 0;
	    ratio = 0;
	}
	first_clear = FALSE;
	next_code = firstcode;
}
$ eod
$ create /log lzcmp3.c
$ deck
/*
 * 			l z c m p 3 . c
 * Output a given code.
 */
#include "lz.h"

extern STREAM	outstream;
extern code_int	next_code;
extern code_int	maxmaxcode;		/* Actual maximum output code	*/
extern short	maxbits;
extern count_int out_count;

static char_type buf[BITS];
static int	offset;
static short	n_bits = INIT_BITS;	/* # of bits in compressed file	*/
static short	n_8bits = INIT_BITS << 3;
static code_int	maxcode = MAXCODE(INIT_BITS);

#if !vax_asm && !vms_asm
static readonly char_type lmask[9] = {
    0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, 0x00
};
static readonly char_type rmask[9] = {
    0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF
};
#endif

#if DEBUG
extern int	col;			/* Printout column		*/
static int	todump;			/* Dump start of the code range	*/
#endif

outputcode(code)
code_int  code;
/*
 * Output the given code.
 * Inputs:
 * 	code:	A n_bits-bit integer.  If == -1, then EOF.  This assumes
 *		that n_bits <= (long)wordsize - 1.
 * Note: if not in "export" mode, the following values are special:
 *	LZ_CLEAR	(also in export mode if block_compress TRUE)
 *			(soft) clear out compress tables and reset the
 *			number of bits per code to the minimum.
 *	LZ_SOH, LZ_STX	(hard) clear out compress tables and reset as above.
 *	LZ_ETX, LZ_EOR	force out the current output segment, analogous
 *			to fflush.
 *
 * Outputs:
 * 	Outputs code to the file.  If the codespace has filled
 *	(next_code >= (1 << n_bits), increase n_bits.
 *	If LZ_CLEAR, LZ_SOH, or LZ_STX is seen, reset n_bits
 *	to the initial value and call init_compress to reset
 *	the lookup and cache tables.
 *
 * Assumptions:
 *	Output chars are 8 bits long.  This is deeply hardwired
 *	into the algorithm.  It is independent, however, of the
 *	size of the input data.
 *
 * Algorithm:
 * 	Maintain a BITS character long buffer (so that 8 codes will
 *	fit in it exactly).  Use the VAX insv instruction to insert each
 *	code in turn.  When the buffer fills up empty it and start over.
 */
{
	/*
	 * On the VAX (Unix), it is important to have the register declarations
	 * in exactly the order given, or the asm will break.
	 */
	register int	r_off;			/* R11 -- offset	*/
	register int	bits;			/* R10 -- bits/code	*/
	register char_type	*bp;		/* R09 -- buffer	*/
#if !vax_asm
	register code_int	r_code;
#endif

	r_off = offset;
	bits = n_bits;
	bp = buf;
	if (code >= 0) {
	    /*
	     * Not at EOF, add the code
	     */
#if DEBUG
	    if ((show & SHOW_SERIOUS_DEBUG) != 0) {
		fprintf(stderr, "%c%5d %5d",
		    ((col += 12) >= 72) ? (col = 0, '\n') : ' ',
		    code, next_code);
		if (code >= LZ_CLEAR && code < firstcode) {
		    fprintf(stderr, " = %s", lz_names[code - LZ_CLEAR]);
		    col = 74;
		}
	    }
#endif
#if vax_asm
	    /*
	     * VAX (4.x bsd) DEPENDENT!! Implementation on other
	     * machines or operating systems may be difficult.
	     *
	     * Translation: Insert BITS bits from the argument starting at
	     * offset bits from the beginning of buf.
	     */
	    0;					/* C compiler bug ??	*/
	    asm("insv	4(ap),r11,r10,(r9)");
#else
#if vms_asm
	    lib$insv(&code, &offset, &bits, bp);
#else
	    /*
	     * WARNING: byte/bit numbering on the vax is simulated
	     * by the following code
	     */
	    bp += (r_off >> 3);			/* -> first output slot	*/
	    r_off &= 7;
	    /*
	     * Since code is always >= 8 bits, only need to mask the first
	     * hunk on the left.
	     */
	    r_code = code;
	    *bp = (*bp & rmask[r_off]) | (r_code << r_off) & lmask[r_off];
	    bp++;
	    bits -= (8 - r_off);
	    r_code >>= 8 - r_off;
	    /*
	     * Get any 8 bit parts in the middle ( <= 1 for up to 16 bits).
	     */
	    if (bits >= 8) {
		*bp++ = r_code;
		r_code >>= 8;
		bits -= 8;
	    }
	    if (bits != 0)				/* Last bits. */
		*bp = r_code;
#endif
#endif
	    offset += n_bits;
	    if (offset == n_8bits) {
		out_count += n_bits;
		lz_putbuf(buf, n_bits, &outstream);
#if DEBUG
		if (todump > 0) {
		    dumphex(buf, n_bits, stderr);
		    todump -= n_bits;
		}
#endif
		offset = 0;
	    }
	    /*
	     * If the next entry is going to be too big for the code size,
	     * then increase it, if possible.  Note:
	     *     !export			firstcode == LZ_FIRST
	     *	   export && block_compress	firstcode == LZ_CLEAR + 1
	     *	   export && !block_compress	firstcode == LZ_CLEAR
	     */
	    if (next_code > maxcode) {
		if (offset > 0) {
		    lz_putbuf(buf, n_bits, &outstream);
		    out_count += n_bits;
		    offset = 0;
#if DEBUG
		    if (todump > 0) {
			dumphex(buf, n_bits, stderr);
			todump -= n_bits;
		    }
#endif
		}
		n_bits++;			/* Need more bits	*/
		n_8bits += (1 << 3);
		if (n_bits == maxbits)
		    maxcode = maxmaxcode;
		else
		    maxcode = MAXCODE(n_bits);
#if DEBUG
		if ((show & SHOW_DEBUG) != 0) {
		    fprintf(stderr,
			"%snext_code is %d, changing to %d bits (max %d)",
			(col > 0) ? "\n" : "", next_code,
			n_bits, maxcode);
		    col = 74;
		}
#endif
	    }
	    if (code >= LZ_CLEAR && code < firstcode) {
		switch (code) {
		case LZ_SOH:
		case LZ_STX:
		case LZ_CLEAR:
		    if (offset > 0) {
			lz_putbuf(buf, n_bits, &outstream);
			out_count += n_bits;
			offset = 0;
#if DEBUG
			if (todump > 0) {
			    dumphex(buf, n_bits, stderr);
			    todump -= n_bits;
			}
#endif
		    }
		    n_bits = INIT_BITS;		/* Reset codes		*/
		    n_8bits = INIT_BITS << 3;
		    maxcode = MAXCODE(INIT_BITS);
		    init_compress(code != LZ_CLEAR);
#if DEBUG
		    if ((show & SHOW_DEBUG) != 0) {
			fprintf(stderr,
		    "\n(%s) Change to %d bits, maxcode %d, next_code = %d",
			    lz_names[code - LZ_CLEAR],
			    n_bits, maxcode, next_code);
			col = 74;
			todump = 32;
		    }
#endif
		    break;

		case LZ_EOR:
		case LZ_ETX:			/* Just written out	*/
		    break;

		default:
		    abort();			/* Can't happen		*/
		}
	    }
	}
	else {
	    /*
	     * At EOF, write the rest of the buffer.
	     */
	    if ((r_off = offset) > 0) {
		r_off += 7;
		r_off >>= 3;
		lz_putbuf(buf, r_off, &outstream);
		out_count += r_off;
#if DEBUG
		if (todump > 0) {
		    dumphex(buf, r_off, stderr);
		    todump -= r_off;
		}
#endif
	    }
	    offset = 0;
	    lz_flush(&outstream);		/* Flush output buffer	*/
#if DEBUG
	    if ((show & SHOW_SERIOUS_DEBUG) != 0
	     || todump > 0) {
		fprintf(stderr, "\n*EOF*\n");
		col = 0;
	    }
#endif
	}
}
$ eod
$ create /log lzdcm1.c
$ deck
/*
 *		lzdcmp [-options] [infile [outfile]]
 */
#ifdef	DOCUMENTATION

title	lzdcmp	File Decompression
index		File decompression

synopsis
	.s.nf
	lzdcmp [-options] [infile [outfile]]
	.s.f
description

	lzdcmp decompresses files compressed by lzcomp.  The
	documentation for lzcomp describes the process in
	greater detail.

	Options may be given in either case.
	.lm +8
	.p -8
	-B	Output file is "binary", not text.  (Ignored
	in VMS private mode.)  On VMS, this generates a stream
	file without carriage-control attributes.  On Decus C
	systems, it generates a "fixed block, 512 byte record"
	file.  It is unneeded on Unix.
	.p -8
	-F	Output file is "fixed block, 512 bytes."  This is
	used only by VMS for reading files created by Unix compress
	or by "export" mode.  It is identical to binary on Decus C.
	.p -8
	-X 3	To read files compressed by an old Unix version
	that doesn't generate header records.
	.p -8
	-V val	Verbose (print status messages and debugging
	information).  The value selects the amount of verbosity.
	See the lzcomp documentation for details.

VMS Command Language Interface

	In addition to the above (Unix-style) command line
	interface, lzcomp supports a VMS command line interface.
	The following options are available:
	.lm +8
	.p -8
	/EXPORT=(VMS, UNIX, HEADER, ENDMARKER)
	.p -8
	/MODE=(TEXT, BINARY, FIXED)
	.p -8
	/SHOW=(ALL, PROGRESS, STATISTICS, FDL,
		DEBUG, DEBUG_SERIOUS, DEBUG_IO)
	.lm -8

VMS private mode

	If the file was compressed in VMS private mode, all information
	needed to reconstruct the file is stored in the compressed
	file, using the VMS run-time library FDL routines.  This means
	that the expanded file will have the same name and directory
	location it had originally.  If the directory structure does
	not exist (for example, because you have moved the compressed
	file to another machine), you must specify the second argument
	to lzdcmp to specify the file name.

Unix compatibility

	lzdcmp is compatable with Unix compress.  It can read and write
	files in Unix compress "header" and "non-header" modes.  Normally,
	it writes a special end-marker to the file and expects to read
	an end-marker from the compressed file.  Specifying /NOENDMARKER
	supresses this check.

	Thus, to decompress a file written with Unix compress, specify
	  /EXPORT=(UNIX,NOENDMARKER)
	  /MODE=(whatever)

Author

	This version by Martin Minow.  See lzcomp for more
	details.

#endif

/*
 * Compatible with compress.c, v3.0 84/11/27
 */

/*)BUILD
		$(PROGRAM) = lzdcmp
		$(INCLUDE) = lz.h
		$(CPP) = 1
		$(RMS) = 1
		$(FILES) = { lzdcm1.c lzdcm2.c lzdcm3.c lzio.c lzvio.c }
*/

#include	"lz.h"

/*
 * These global parameters are read from the compressed file.
 * The decompressor needs them.
 */
short		maxbits = BITS;		/* settable max # bits/code	*/
code_int	maxmaxcode = 1 << BITS;

/*
 * Note, if export is zero or 1, the "true" value will be set from
 * the file header.  If it is 2, no header will be read.
 */
#if VMS_V4
flag		export = EXPORT_VMS	/* Assume VMS private		*/
			| EXPORT_HEADER
			| EXPORT_BLOCK
			| EXPORT_ENDMARKER;
#else
flag		export = EXPORT_UNIX	/* Assume standard Unix		*/
			| EXPORT_HEADER
			| EXPORT_BLOCK
			| EXPORT_ENDMARKER;
#endif
flag		method = METHOD_LZ;	/* Of course			*/
flag		mode = MODE_TEXT;
flag		show = 0;		/* No statistics		*/
flag		background = FALSE;	/* TRUE (Unix) if detached	*/
flag		is_compress = FALSE;	/* For lzio.c and lzdcl.c	*/
char		*infilename = NULL;	/* For error printouts		*/
char		*outfilename = NULL;	/* For openoutput and errors	*/
int		firstcode;		/* First code after internals	*/
static long	start_time;		/* Time we started (in msec)	*/
extern long	cputime();		/* Returns process time in msec	*/
jmp_buf		failure;
STREAM		instream;
STREAM		outstream;
char_type	inbuffer[MAXIO];
char_type	outbuffer[MAXIO];
#if VMS_V4
FDLSTUFF	*fdl_input;
FDLSTUFF	*fdl_output;
static struct dsc$descriptor fdl_descriptor;
int		xab_lrl = 0;
static STREAM	mem_stream;

typedef struct more_attributes {
    char	*name;
    int		type;
    char	*result;	/* Actually a union (int *, char *)	*/
} MORE_ATTRIBUTES;

#define	STRING	0
#define INTEGER	1

/*
 * Additional attributes (put into the compressed file by lzcomp)
 * are extracted using this extensible table.
 */

static MORE_ATTRIBUTES more_attributes[] = {
    { "xabfhc$w_lrl", INTEGER, &xab_lrl },
    NULL
};

#endif

main(argc, argv)
int		argc;
char		*argv[];
/*
 * Decompress mainline
 */
{
	int		result;
	int		status;
	extern code_int	getcode();

#ifndef	decus
	/*
	 * background is TRUE if running detached from the command terminal.
	 */
	background = (signal(SIGINT, SIG_IGN) == SIG_IGN) ? TRUE : FALSE;
	if (!background)
	    background = !isatty(fileno(stderr));
	if (!background) {
	    if ((show & (SHOW_DEBUG | SHOW_SERIOUS_DEBUG | SHOW_IO_DEBUG)) != 0)
		signal(SIGINT, abort);
	    else {
		signal(SIGINT, interrupt);
		signal(SIGSEGV, address_error);
	    }
	}
#endif
	if ((status = setjmp(failure)) == 0) {
	    setup(argc, argv);
#if DEBUG
	    if (show & SHOW_DEBUG)
		dumpoptions();
#endif
	    do {
	        openinput();
	    } while (get_magic_header());	/* Sets export, etc.	*/
	    openoutput();
	    if ((show & SHOW_STATISTICS) != 0)
		start_time = cputime();
	    init_decompress();
	    result = decompress(&outstream);
	    if ((export & EXPORT_ENDMARKER) != 0
	     && result != LZ_ETX
	     && getcode() != (code_int) LZ_ETX) {
		fprintf(stderr, "Decompress didn't finish correctly.\n");
		goto fail;
	    }
	    lz_flush(&outstream);
#if VMS_V4
	    if ((export & EXPORT_VMS) != 0)
		fdl_close(fdl_output);
	    else {
		fclose(stdout);
	    }
#else
	    fclose(stdout);
#endif
#if DEBUG
	    if ((show & SHOW_SERIOUS_DEBUG) != 0)
		dump_tab(stderr);
#endif
	    if ((show & SHOW_STATISTICS) != 0) {
		start_time = cputime() - start_time;
		fprintf(stderr,
		    "%ld.%02ld seconds (process time) for decompression.\n",
		    start_time / 1000L, (start_time % 1000L) / 10L);
	    }
	    exit(EXIT_SUCCESS);
	}
	else {
fail:	    fprintf(stderr, "Error when decompressing \"%s\" to \"%s\"\n",
		(infilename  == NULL) ?
		    "<input file unknown>" : infilename,
		(outfilename == NULL) ?
		    "<no output file>" : outfilename);
	    exit(status);
	}
}

static int
get_magic_header()
/*
 * Read the compressed file header.  Note: if we switch from
 * Unix to VMS (or vice-versa), we must re-open the file in
 * the new mode.
 */
{
	int		head1;
	int		head2;
	int		head3;
#if VMS_V4
	int		old_export = export & (EXPORT_VMS | EXPORT_UNIX);
#endif

	head2 = 0;
	if ((export & EXPORT_HEADER) != 0) {
	    if ((head1 = GET(&instream)) != HEAD1_MAGIC) {
		fprintf(stderr, "Incorrect first header byte 0x%x\n",
		    head1);
		FAIL("can't get header", EXIT_FAILURE);
	    }
	    head2 = GET(&instream);
	    head3 = GET(&instream);
	    switch (head2) {
	    case HEAD2_MAGIC:
		export &= ~(EXPORT_VMS | EXPORT_BLOCK);
		export |= EXPORT_UNIX;
		mode &= ~MODE_DELTA;
		break;

	    case VMS_HEAD2_MAGIC:
		export &= ~(EXPORT_UNIX | EXPORT_BLOCK);
		export |= EXPORT_VMS;
		mode &= ~MODE_DELTA;
		break;

	    default:
		fprintf(stderr, "Incorrect second header byte 0x%x\n",
		    head2);
		FAIL("can't get header", EXIT_FAILURE);
	    }
#if VMS_V4
	    /*
	     * If export status changes, we must re-open the input file.
	     */
	    if ((export & (EXPORT_VMS | EXPORT_UNIX)) != old_export)
		return (TRUE);
#endif
	    maxbits = head3 & BIT_MASK;
	    if ((head3 & BLOCK_MASK) != 0)
		export |= EXPORT_BLOCK;
	    if ((head3 & DIFF_MASK) != 0)
		mode |= MODE_DELTA;
#if DEBUG
	    if ((show & SHOW_DEBUG) != 0) {
		fprintf(stderr, "%s: compressed with %d bits,",
		   infilename, maxbits);
		fprintf(stderr, " block compression %s.\n",
		    (export & EXPORT_BLOCK) ? "enabled" : "disabled");
		fprintf(stderr, " differential compression %s.\n",
		    (mode & MODE_DELTA) ? "enabled" : "disabled");
	    }
#endif
	}
	if (maxbits > BITS) {
	    fprintf(stderr, "%s: compressed with %d bits,",
		infilename, maxbits);
	    fprintf(stderr, " lzdcmp can only handle %d bits\n", BITS);
	    FAIL("too many bits", EXIT_FAILURE);
	}
	maxmaxcode = 1 << maxbits;
	if ((export & EXPORT_VMS) != 0)
	    firstcode = GET(&instream) + 0x100;	/* From compressed file	*/
	else if ((export & EXPORT_BLOCK) != 0)
	    firstcode = LZ_CLEAR + 1;		/* Default		*/
	else
	    firstcode = 256;			/* Backwards compatible	*/
#if VMS_V4
	if ((export & EXPORT_VMS) != 0) {
	    register code_int	code;
	    char		text[256];
	    extern code_int	getcode();
	    auto int		termin;
	    /*
	     * Get the attribute record.
	     */
	    if ((code = getcode()) != LZ_SOH) {
		fprintf(stderr, "Expected header, read 0x%x\n", code);
		FAIL("can't get header (private)", EXIT_FAILURE);
	    }
	    init_decompress();
	    code = mem_decompress(text, sizeof text, &termin);
	    text[code] = EOS;
	    if (strncmp(text, ATT_NAME, ATT_SIZE) != 0) {
		fprintf(stderr, "Expected \"%s\", read \"%.*s\"\n",
		    ATT_NAME, code, text);
		FAIL("can't get attribute block header", EXIT_FAILURE);
	    }
	    code = atoi(text + ATT_SIZE);
	    fdl_descriptor.dsc$b_class = DSC$K_CLASS_S;
	    fdl_descriptor.dsc$b_dtype = DSC$K_DTYPE_T;
	    /*
	     * Note: malloc should probably be lib$getvm
	     */
	    fdl_descriptor.dsc$a_pointer = malloc(code);
	    fdl_descriptor.dsc$w_length = code;
	    code = mem_decompress(fdl_descriptor.dsc$a_pointer, code, &termin);
	    if (code != fdl_descriptor.dsc$w_length) {
		fprintf(stderr, "\nError reading fdl attributes block,");
		fprintf(stderr, " expected %d bytes, read %d bytes\n",
		    fdl_descriptor.dsc$w_length, code);
		FAIL("can't get attribute block data", EXIT_FAILURE);
	    }
	    if ((show & SHOW_FDL) != 0) {
		fprintf(stderr, "\nFDL information read from \"%s\"\n",
		    infilename);
		fdl_dump(&fdl_descriptor, stderr);
	    }
	    while (termin == LZ_EOR) {
		MORE_ATTRIBUTES		*p;
		int			len;

		code = mem_decompress(text, sizeof text - 1, &termin);
		text[code] = EOS;
		if ((show & SHOW_FDL) != 0)
		    fprintf(stderr, "Additional header: \"%s\"\n", text);
		for (p = more_attributes;; p++) {
		    if (p->name == NULL) {
		        fprintf(stderr,
			    "\nIgnoring unknown descriptor \"%s\"\n", text);
			break;
		    }
		    len = strlen(p->name);
		    if (strncmp(p->name, text, len) == 0) {
			switch (p->type) {
			case INTEGER:
			    *((int *)p->result) = atoi(&text[len]);
			    break;

			case STRING:
			    strcpy(p->result, &text[len]);
			    break;
			}
			break;
		    }
		}
	    }
	    if ((code = getcode()) != LZ_STX) {
		fprintf(stderr, "\nExpecting start of text, got 0x%x\n", code);
		FAIL("no start of text", EXIT_FAILURE);
	    }
	}
#else
	if ((export & EXPORT_VMS) != 0)
	    FAIL("VMS private mode not supported", EXIT_FAILURE);
#endif
	return (FALSE);
}

#if VMS_V4
int
mem_decompress(buffer, size, termin)
char_type	*buffer;
int		size;
int		*termin;
/*
 * Decompress up to size bytes to buffer.  Return actual size.
 */
{
	mem_stream.bp = mem_stream.bstart = buffer;
	mem_stream.bend = buffer + size;
	mem_stream.bsize = size;
	mem_stream.func = lz_fail;
	if ((*termin = decompress(&mem_stream)) == LZ_EOR
	 || *termin == LZ_ETX)
	    return (mem_stream.bp - buffer);
	else {
	    fprintf(stderr, "Decompress to memory failed.\n");
	    FAIL("can't decompress to memory", EXIT_FAILURE);
	}
	return (-1);				/* Can't happen		*/
}
#endif

static readonly char *helptext[] = {
	"The following options are valid:",
	"-B\tBinary file (important on VMS/RSX, ignored on Unix)",
	"-F\tOutput fixed-block 512 byte records (VMS export only",
	"-M val\tSet the maximum number of code bits (unless header present)",
	"-V val\tPrint status information or debugging data.",
	"-X val\tSet export (compatibility) mode:",
	"-X 0\tVMS private mode",
	"-X 1\tCompatibility with Unix compress",
	"-X 2\tDo not read a header, disable \"block-compress\" mode",
	"\t(If a header is present, lzdcmp will properly configure itself,",
	"\toverriding the -X, -B and -M flag values.",
	NULL,
};

static
setup(argc, argv)
int		argc;
char		*argv[];
/*
 * Get parameters and open files.  Exit fatally on errors.
 */
{
	register char	*ap;
	register int	c;
	char		**hp;
	auto int	i;
	int		j;
	int		temp;

#ifdef	vms
	argc = getredirection(argc, argv);
	/*
	 * Prescan to see whether we must do a DCL parse.
	 */
	for (j = FALSE, i = 1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		j = TRUE;
		break;
	    }
	}
	if (j == FALSE) {
	    if ((i = lzdcl(argc, argv)) != SS$_NORMAL)
		exit(i);
	    return;
	}
#endif
	for (i = j = 1; i < argc; i++) {
	    ap = argv[i];
	    if (*ap++ != '-' || *ap == EOS)	/* Filename?		*/
		argv[j++] = argv[i];		/* Just copy it		*/
	    else {
		while ((c = *ap++) != EOS) {
		    if (islower(c))
			c = toupper(c);
		    switch (c) {
		    case 'B':
			mode |= MODE_BINARY;
			break;

		    case 'F':
			mode |= MODE_FIXED;
			break;

		    case 'M':
			maxbits = getvalue(ap, &i, argv);
			if (maxbits < MIN_BITS) {
			    fprintf(stderr, "Illegal -M value\n");
			    goto usage;
			}
			break;

		    case 'V':
			show = getvalue(ap, &i, argv);
			break;

		    case 'X':
			switch ((temp = getvalue(ap, &i, argv))) {
			case 0:	export = EXPORT_VMS
					| EXPORT_BLOCK
					| EXPORT_HEADER
					| EXPORT_ENDMARKER;
				break;
			case 1:	export = EXPORT_UNIX
					| EXPORT_BLOCK
					| EXPORT_HEADER
					| EXPORT_ENDMARKER;
				break;

			case 2: export = EXPORT_UNIX
					| EXPORT_BLOCK
					| EXPORT_HEADER;
				break;
			case 3:	export = EXPORT_UNIX
					| EXPORT_HEADER;
				break;
			case 4:	export = EXPORT_UNIX;
				break;
			default:
			    fprintf(stderr, "Illegal -X value: %d\n", temp);
			    goto usage;
			}
			break;

		    default:
			fprintf(stderr, "Unknown option '%c' in \"%s\"\n",
				*ap, argv[i]);
usage:			for (hp = helptext; *hp != NULL; hp++)
			    fprintf(stderr, "%s\n", *hp);
			FAIL("unknown option", EXIT_FAILURE);
		    }				/* Switch on options	*/
		}				/* Everything for -xxx	*/
	    }					/* If -option		*/
	}					/* For all argc's	*/
	/*  infilename = NULL; */		/* Set "stdin"  signal	*/
	/* outfilename = NULL; */		/* Set "stdout" signal	*/
	switch (j) {				/* Any file arguments?	*/
	case 3:					/* both files given	*/
	    if (!streq(argv[2], "-"))		/* But - means stdout	*/
		outfilename = argv[2];
	case 2:					/* Input file given	*/
	    if (!streq(argv[1], "-"))
		infilename = argv[1];
	    break;

	case 0:					/* None!		*/
	case 1:					/* No file arguments	*/
	    break;

	default:
	    fprintf(stderr, "Too many file arguments\n");
	    FAIL("too many files", EXIT_FAILURE);
	}
}

static int
getvalue(ap, ip, argv)
register char		*ap;
int			*ip;
char			*argv[];
/*
 * Compile a "value".  We are currently scanning *ap, part of argv[*ip].
 * The following are possible:
 *	-x123		return (123) and set *ap to EOS so the caller
 *	ap^		cycles to the next argument.
 *
 *	-x 123		*ap == EOS and argv[*ip + 1][0] is a digit.
 *			return (123) and increment *i to skip over the
 *			next argument.
 *
 *	-xy or -x y	return(1), don't touch *ap or *ip.
 *
 * Note that the default for "flag option without value" is 1.  This
 * can only cause a problem for the -M option where the value is
 * mandatory.  However, the result of 1 is illegal as it is less
 * than INIT_BITS.
 */
{
	register int	result;
	register int	i;

	i = *ip + 1;
	if (isdigit(*ap)) {
	    result = atoi(ap);
	    *ap = EOS;
	}
	else if (*ap == EOS
	      && argv[i] != NULL
	      && isdigit(argv[i][0])) {
	    result = atoi(argv[i]);
	    *ip = i;
	}
	else {
	    result = 1;
	}
	return (result);
}

openinput()
{
#ifdef	decus
	if (infilename == NULL) {
	    infilename = malloc(257);
	    fgetname(stdin, infilename);
	    infilename = realloc(infilename, strlen(infilename) + 1);
	}
	if (freopen(infilename, "rn", stdin) == NULL) {
	    perror(infilename);
	    FAIL("can't open compressed input", ERROR_EXIT);
	}
#else
#if VMS_V4
	if ((export & EXPORT_VMS) != 0) {
	    if (infilename == NULL) {
		infilename = malloc(256 + 1);
		fgetname(stdin, infilename);
		infilename = realloc(infilename, strlen(infilename) + 1);
	    }
	    if ((fdl_input = fdl_open(infilename, NULL)) == NULL)
		FAIL("can't open compressed input (vms private)", fdl_status);
	}
	else
#endif
	{
	    if (infilename == NULL) {
#ifdef vms
		infilename = malloc(256 + 1);
		fgetname(stdin, infilename);
		infilename = realloc(infilename, strlen(infilename) + 1);
#else
		infilename = "<stdin>";
#endif
	    }
	    else {
		/*
		 * "rb" means "read, binary mode"
		 */
		if (freopen(infilename, "rb", stdin) == NULL) {
		    perror(infilename);
		    FAIL("can't open compressed input (export)", ERROR_EXIT);
		}
	    }
	}
#endif
	instream.bp = instream.bend = NULL;
	instream.bstart = inbuffer;
	instream.bsize = sizeof inbuffer;
	instream.func = lz_fill;
}

openoutput()
{
#ifdef vms
#if VMS_V4
	if ((export & EXPORT_VMS) != 0) {
	    extern FDLSTUFF *fdl_create();

	    fclose(stdout);
	    stdout = NULL;
	    fdl_output = fdl_create(&fdl_descriptor, outfilename, xab_lrl);
	    if (fdl_output == NULL) {
		fprintf(stderr, "Can't create output file\n");
		FAIL("can't create output (vms private)", fdl_status);
	    }
	    if (outfilename == NULL) {
		outfilename = malloc(256 + 1);
		fdl_getname(fdl_output, outfilename);
		outfilename = realloc(outfilename, strlen(outfilename) + 1);
	    }
	}
	else
#endif
	{
	    /*
	     * Not VMS Version 4, or export mode.
	     */
	    if (outfilename == NULL) {
		outfilename = malloc(256 + 1);
		fgetname(stdout, outfilename);
		outfilename = realloc(outfilename, strlen(outfilename) + 1);
		if ((mode & (MODE_BINARY | MODE_FIXED)) == 0)
		    goto do_reopen;
	    }
	    else {
		if ((mode & MODE_BINARY) != 0) {
		    if (freopen(outfilename, "wb", stdout,
			    "alq=256", "fop=tef") == NULL) {
			perror(outfilename);
			FAIL("can't create output (binary)", ERROR_EXIT);
		    }
		}
		else if ((mode & MODE_FIXED) != 0) {
		    if (freopen(outfilename, "wb", stdout,
			    "alq=256", "fop=tef",
			    "rfm=fix", "bls=512") == NULL) {
			perror(outfilename);
			FAIL("can't create output (fixed)", ERROR_EXIT);
		    }
		}
		else {
		    /*
		     * Try to allocate the output file in chunks.
		     */
do_reopen:	    if (freopen(outfilename, "w", stdout,
			    "alq=256", "fop=tef",
			    "rat=cr", "rfm=var") == NULL) {
			perror(outfilename);
			FAIL("can't create output (text)", ERROR_EXIT);
		    }
		}
	    }
	}
#else
#ifdef decus
	if (outfilename == NULL) {
	    outfilename = malloc(256 + 1);
	    fgetname(stdout, outfilename);
	    outfilename = realloc(outfilename, strlen(outfilename) + 1);
	}
	if (freopen(outfilename,
		((mode & (MODE_BINARY | MODE_FIXED)) != 0) ?"wn" : "w",
		stdout) == NULL) {
	    perror(outfilename);
	    FAIL("can't reopen", ERROR_EXIT);
	}
#else
	if (outfilename == NULL)
	    outfilename = "<stdout>";
	else {
	    if (freopen(outfilename, "w", stdout) == NULL) {
		perror(outfilename);
		FAIL("can't create", ERROR_EXIT);
	    }
	}
#endif
#endif
	outstream.bp = outstream.bstart = outbuffer;
	outstream.bend = outbuffer + sizeof outbuffer;
	outstream.bsize = sizeof outbuffer;
	outstream.func = lz_flush;
}
$ eod
$ create /log lzdcm2.c
$ deck
/*
 *			l z d c m 2 . c
 *
 * Actual decompression code
 */

#include "lz.h"

/*
 * These global parameters are read from the compressed file.
 * The decompressor needs them.
 */
extern short	maxbits;		/* settable max # bits/code	*/
extern code_int	maxmaxcode;		/* 1 << maxbits			*/

static flag	first_clear = TRUE;

/*
 * Big data storage stuff
 */

static char_type	stack[MAXSTACK];
#define	STACK_TOP	(&stack[MAXSTACK])
static U_short		tab_prefix[1 << BITS];	/* prefix code		*/
static char_type  	tab_suffix[1 << BITS];	/* last char in string	*/
code_int		next_code;

#if DEBUG
#define	CHECK(what)						\
	if (stp <= stack) {					\
	    fprintf(stderr, "Stack overflow -- %s\n", what);	\
	    abort();						\
	}
#else
#define CHECK(what)
#endif

int
decompress(out)
STREAM		*out;
/*
 * Decompress instream (global) to out.  Returns "end" signal:
 *	-1	end of file
 *	LZ_EOR	end of record
 *	LZ_ETX	end of segment
 */
{
	register char_type	*stp;		/* Stack pointer	*/
	register code_int code, oldcode, incode;
	register int		final;		/* End of a sequence?	*/
	register char_type	*pstp;		/* Stack pointer and	*/
	register int		previous;	/* char for diff flavor	*/
	extern code_int		getcode();

	stp = STACK_TOP;
	final = oldcode = getcode();
	PUT((char) final, out);
	previous = final;
	while ((code = getcode()) >= 0) {
test:	    if (code >= LZ_CLEAR && code < firstcode) {
		if ((mode & MODE_DELTA) != 0) {
		    for (pstp = stp; pstp < STACK_TOP;) {
#if UCHAR
			previous += *pstp;
#else
			previous += (*pstp & 0xFF);
#endif
			*pstp++ = previous;
		    }
		}
		lz_putbuf(stp, STACK_TOP - stp, out);
		stp = STACK_TOP;
		switch (code) {
		case LZ_ETX:
		case LZ_EOR:
		    goto finish;

		case LZ_SOH:			/* Unexpected		*/
		case LZ_STX:			/* Unexpected		*/
		default:
		    fprintf(stderr, "\nUnexpected control 0x%X\n", code);
		    FAIL("Unexpected control", EXIT_FAILURE);

		case LZ_CLEAR:
		    init_decompress();		/* Before getcode() !! */
		    if ((code = getcode()) < 0
		      || ((export & (EXPORT_UNIX | EXPORT_ENDMARKER))
				 == (EXPORT_UNIX | EXPORT_ENDMARKER)
		       && code == LZ_CLEAR))
			goto finish;
		    else {
			/*
			 * init_decompress has set next_code to firstcode,
			 * however, for magical reasons, we want to toss
			 * the next substring, so we set next_code so
			 * that the tab_... entry is effectively ignored.
			 * Note below that tab_prefix[next_code] is set
			 * to the character before the LZ_CLEAR and
			 * tab_suffix to the character after the LZ_CLEAR.
			 * But, these values have no relationship to one
			 * another, so, by decrementing next_code, they
			 * will be ignored.  (I think.)
			 */
			next_code--;
			goto test;
		    }
		}
	    }
	    incode = code;
	    /*
	     * Special case for KwKwK string.
	     */
	    if (code >= next_code) {
		CHECK("KwKwK");
		*--stp = final;
		code = oldcode;
	    }
	    /*
	     * Generate output characters in reverse order
	     */
#ifdef interdata
	    while (((unsigned long) code) >= ((unsigned long) NBR_CHAR)) {
#else
	    while (code >= NBR_CHAR) {
#endif
		CHECK("generate output");
		*--stp = tab_suffix[code];
		code = tab_prefix[code];
	    }
	    CHECK("final char");
	    *--stp = final = tab_suffix[code];
	    /*
	     * And put them out in forward order
	     */
	    if ((mode & MODE_DELTA) != 0) {
		for (pstp = stp; pstp < STACK_TOP;) {
#if UCHAR
		    previous += *pstp;
#else
		    previous += (*pstp & 0xFF);
#endif
		    *pstp++ = previous;
		}
	    }
	    lz_putbuf(stp, STACK_TOP - stp, out);
	    stp = STACK_TOP;
	    /*
	     * Generate the new entry.
	     */
	    if ((code = next_code) < maxmaxcode) {
		tab_prefix[code] = (U_short) oldcode;
		tab_suffix[code] = final;
		next_code++;
	    }
	    /*
	     * Remember previous code.
	     */
	    oldcode = incode;
	}
finish:
	return (code);
}

init_decompress()
/*
 * Called on cold start, or on LZ_SOH, LZ_STX, LZ_CLEAR.
 */
{
	register char_type	*cp;
	register U_short	*up;
	register int		code;

	if (first_clear) {
	    for (cp = &tab_suffix[0], code = 0; cp < &tab_suffix[NBR_CHAR];)
		*cp++ = code++;
	    first_clear = FALSE;
	}
	else {
#if ((NBR_CHAR % 8) != 0)
	    << error, the following won't work >>
#endif
	    for (up = &tab_prefix[0]; up < &tab_prefix[NBR_CHAR];) {
		*up++ = 0;
		*up++ = 0;
		*up++ = 0;
		*up++ = 0;
		*up++ = 0;
		*up++ = 0;
		*up++ = 0;
		*up++ = 0;
	    }
	}
	next_code = firstcode;
}

#if DEBUG
dump_tab(dumpfd)
FILE		*dumpfd;
/*
 * dump string table
 */
{
	register char_type	*stp;	/* Stack pointer		*/
	register int		i;
	register int		ent;
	extern char		*dumpchar();

	stp = STACK_TOP;
	fprintf(dumpfd, "%d %s in string table\n",
	    next_code, (next_code == 1) ? "entry" : "entries");
	for (i = 0; i < next_code; i++) {
	    fprintf(dumpfd, "%5d: %5d/'%s' ",
		i, tab_prefix[i], dumpchar(tab_suffix[i]));
	    for (ent = i;;) {
		*--stp = tab_suffix[ent];
		if (ent < firstcode)
		    break;
		ent = tab_prefix[ent];
	    }
	    dumptext(stp, STACK_TOP - stp, dumpfd);
	    stp = STACK_TOP;
	}
}
#endif
$ eod
$ create /log lzdcm3.c
$ deck
/*
 *			l z d c m 3 . c
 *
 * Read codes from the input stream.
 */

#include "lz.h"

#if !vax_asm && !vms_asm
static readonly char_type rmask[9] = {
	0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF
};
#endif
#if DEBUG
extern int	col;
static int	todump;
unsigned long	code_count;
unsigned long	dump_first = -1;
unsigned long	dump_last = -1;
#endif

/*
 * getcode()
 *
 * Read one code from the standard input.  If EOF, return -1.
 * Inputs:
 * 	stdin (via GET)
 * Outputs:
 * 	code or -1 is returned.
 */

extern code_int		next_code;
extern STREAM		instream;
extern code_int		maxmaxcode;
extern short		maxbits;
static short		n_bits = INIT_BITS;
static code_int		maxcode = MAXCODE(INIT_BITS);

/*
 * buf[] contains 8-bit data read from the input stream.  getcode()
 * treats buf[] as a vector of bits, repacking it into variable-bit codes.
 */
static char_type	buf[BITS];
static int		offset = 0;	/* Offset into buf    IN BITS 	*/
static int		size = 0;	/* Actual size of buf IN BITS 	*/

code_int
getcode()
{
	/*
	 * On the VAX (4.2 bsd), it is important to have the register
	 * declarations in exactly the order given, or the asm will break.
	 */
	register code_int 	code;		/* R11 extracted code	*/
	register int		r_off;		/* R10 offset		*/
	register int		bits;		/* R09 bits/code	*/
	register char_type	*bp;		/* R08 buffer pointer	*/

	bp = buf;
	if (next_code > maxcode) {
	    n_bits++;
	    if (n_bits == maxbits)
		maxcode = maxmaxcode;
	    else {
		maxcode = MAXCODE(n_bits);
	    }
	    size = 0;
#if DEBUG
	    if ((show & SHOW_DEBUG) != 0) {
		fprintf(stderr, "\nChange to %d bits", n_bits);
		col = 74;
	    }
#endif
	}
	if (offset >= size) {
	    size = lz_getbuf(buf, n_bits, &instream);
#if DEBUG
	    if ((show & SHOW_SERIOUS_DEBUG) != 0
	     || todump > 0
	     || (code_count >= dump_first && code_count <= dump_last)) {
		fprintf(stderr, "code_count %lu\n", code_count);
		dumphex(buf, size, stderr);
		if (todump > 0)
		    todump -= size;
	    }
#endif
	    if (size <= 0)
		return (-1);			/* end of file		*/
	    offset = 0;
	    /*
	     * Round size down to integral number of codes in the buffer.
	     * (Expressed as a number of bits).
	     */
	    size = (size << 3) - (n_bits - 1);
	}
	r_off = offset;
	bits = n_bits;
#if vax_asm
	asm("extzv   r10,r9,(r8),r11");
#else
#if vms_asm
	code = lib$extzv(&offset, &n_bits, bp);
#else
	/*
	 * Get to the first byte.
	 */
	bp += (r_off >> 3);
	r_off &= 7;
	/*
	 * Get first part (low order bits)
	 */
#if UCHAR
	code = (*bp++ >> r_off);
#else
	/*
	 * Don't touch the 0xFF; it prevents sign extension.
	 */
	code = ((*bp++ >> r_off) & rmask[8 - r_off]) & 0xFF;
#endif
	bits -= (8 - r_off);
	r_off = 8 - r_off;		/* now, offset into code word	*/
	/*
	 * Get any 8 bit parts in the middle (<=1 for up to 16 bits).
	 */
	if (bits >= 8) {
#if UCHAR
	    code |= *bp++ << r_off;
#else
	    code |= (*bp++ & 0xFF) << r_off;
#endif
	    r_off += 8;
	    bits -= 8;
	}
	/* high order bits. */
#if UCHAR
	code |= (*bp & rmask[bits]) << r_off;
#else
	code |= (*bp & rmask[bits]) << r_off;
#endif
	/*
	 * End of non-vax (Unix) specific code.
	 */
#endif
#endif
	offset += n_bits;
	if (code >= LZ_CLEAR && code < firstcode) {
	    switch (code) {
	    case LZ_SOH:
	    case LZ_STX:
	    case LZ_CLEAR:
		size = 0;			/* Force read next time	*/
		n_bits = INIT_BITS;
		maxcode = MAXCODE(INIT_BITS);
#if DEBUG
		if ((show & SHOW_DEBUG) != 0) {
		    fprintf(stderr, "Read %s (%d)\n",
			lz_names[code - LZ_CLEAR], code);
		    todump = 32;
		}
#endif
		break;
	    }
	}
#if DEBUG
	if ((show & SHOW_SERIOUS_DEBUG) != 0
	 || (code_count >= dump_first && code_count <= dump_last)) {
	    fprintf(stderr, "%c%5d %5d",
		((col += 12) >= 72) ? (col = 0, '\n') : ' ',
		code, next_code);
	    if (code >= LZ_CLEAR && code < firstcode) {
		fprintf(stderr, " = %s", lz_names[code - LZ_CLEAR]);
	        col = 74;
	    }
	}
	++code_count;
#endif
	return (code);
}
$ eod
$ create /log lz.hlp
$ deck
!
! compress
!
1 COMPRESS
  The COMPRESS command invokes a utility to copy a file, generating
  a file with (usually) fewer bytes.  Files compressed by COMPRESS
  are recovered by DECOMPRESS.

    COMPRESS Input-file-spec Output-file-spec

2 Parameter

 Input-file-spec

  Specifies the name of the file to be compressed.  If you have
  specified /EXPORT=VMS mode, the file must be stored on a disk.

 Output-file-spec

  Specifies the name of the file created by COMPRESS.
2 Qualifiers

  Indicate special actions to be performed by the COMPRESS utility
  or special properties of either the input or output files.
  Qualifiers apply to the entire process.

  The following list shows all the qualifiers available with the
  COMPRESS command:

   o /BITS=value

   o /EXPORT=(option,...)

   o /METHOD=option

   o /MODE=(option,...)

   o /SHOW=(option,...)

2 /BITS
 /BITS=value

  This specifies the maximum number of bits to be used in the
  compression. It implicitly controls both the "quality" of the
  compression (more bits means more compression) and the amount of
  memory needed for both compression and decompression (more bits
  requires more memory). If the compressed file is to be read by a
  computer with limited memory (such as a PDP-11), choose /BITS=12,
  else leave BITS at its default of 16.  The minimum value is 9
  and the maximum value is 16.

2 /EXPORT
 /EXPORT=(option, [,...])

  Export controls the format of the output file.  You can select
  the following:

  VMS (D)               Write a file that can only be read by VMS
                        COMPRESS.
  UNIX                  Write a format that can be read by programs
                        compatible with the Unix compress utility.
  [NO]ENDMARKER         Write a special file endmarker after the
                        data if specified.
  [NO]BLOCK             Monitor compression and reinitialize if the
                        quality decreases if specified.
  [NO]HEADER            Write a file header with information for
                        DECOMPRESS if specified.

  In general, use /EXPORT=VMS for compression where the result will
  be decompressed on a VMS system and /EXPORT=UNIX where the result
  will be decompressed on a Unix, RSX-11M, RSTS/E, or other non-VMS
  system.  If /EXPORT=UNIX is specified, BLOCK, HEADER, or ENDMARKER
  may be negated to further qualify the output file format.

3 VMS

  Specifies output in VMS ("private") mode.  In addition to the
  contents of the file itself, the "File definition block" is also
  compressed. The decompression utility can thus recreate the file
  exactly (including ISAM indexes).  This is the default, and the
  recommended mode for most uses.

3 UNIX

  Specifies an output format compatible with Unix compress v3.0.
  This allows transmitting sequential files to non-VMS systems that
  support a compress-compatible utility.  If you have specified
  /EXPORT=UNIX, the utility can be configured for variants of Unix
  compress by negating BLOCK, HEADER, and/or ENDMARKER as needed.
  Notice that file attributes are not preserved by /EXPORT=UNIX.

3 BLOCK

  Selects an algorithm whereby COMPRESS evaluates its performance
  and re-initializes the compression tables whenever performance
  degrades.  Older versions of Unix compress do not support this
  capability. If negated, ENDMARKER must also be negated.

3 HEADER

  If negated, COMPRESS does not write a header record.  This is
  for compatiblity with very old versions of Unix compress.  If
  negated, BLOCK and ENDMARKER must also be negated.

3 ENDMARKER

  If specified, a special "endmark" is written after the end of the
  file.  This is necessary if the file is to be decompressed on RT11
  or other systems that require the last block of a file to fill the
  last block.  On the other hand, some versions of Unix compress
  cannot understand the "extra" endmarker.  If you guess wrong, a
  few bytes of garbage may be appended to the decompressed file.  A
  version of Unix compress that handles endmarkers correctly is
  available.

2 /METHOD
 /METHOD=(option)

  This specifies the particular compression algorithm.  Currently,
  only /METHOD=LZW is supported.

3 LZW

  Use the Lempel-Ziv-Welch compression algorithm.

2 /MODE
 /MODE=(option)

  This allows specification of variations on the compression method.

3 BINARY

  This opens the file in "binary" mode, rather than "text" mode.
  It is ignored if /EXPORT=VMS is chosen.

3 DELTA
  Compress the difference between successive bytes, rather than the
  bytes themselves.  For certain file formats, such as bit-mapped
  graphics, this may yield a 10-15% improvement in compressibility.
  This is not compatible with some implementations of Unix compress.

2 /SHOW
 /SHOW=(option, [,...])

  Display information about the compression.  If omitted, COMPRESS
  operates silently (except for error messages).

3 ALL
  Equivalent to /SHOW=(PROGRESS,STATISTICS,FDL)

3 PROGRESS
  Print status messages at intervals, showing the operation of the
  program.  The report shows the current compression ratio (the
  ratio of input to output bytes).  If this decreases, COMPRESS
  decides that the characteristics of the file have changed, and
  resets its internal parameters.  The "gap" is the number of
  input codes used to compute the ratio.

3 STATISTICS
  Print a report at the end of the process.  Note that COMPRESS reports
  the number of bytes it compresses, which includes the file as well as
  the information that COMPRESS records about the file (the File Definition
  Language block and some internal codes), and will therefore be several
  hundred characters greater than the actual size of the file.

3 FDL
  Dump the File Definition Language block that describes a VMS input
  file.

3 DEBUG
  Print internal debugging information.

3 DEBUG_SERIOUS
  Print more internal debugging information.

3 DEBUG_IO
  Dump the output file, too.

2 LZW_Overview
  LZW stands for a compression method described in

    "A technique for High Performance Data Compression."
    Terry A. Welch. IEEE Computer, Vol 17, No. 6 (June 1984)
    pp. 8-19.

  This section is abstracted from Terry Welch's article referenced
  below.  The algorithm builds a string translation table that maps
  substrings in the input into fixed-length codes.  The compress
  algorithm may be described as follows:

  1. Initialize table to contain single-character strings.
  2. Read the first character.  Set <w> (the prefix string) to that
     character.
  3. (step): Read next input character, K.
  4. If at end of file, output code(<w>); exit.
  5. If <w>K is in the string table:
     Set <w> to <w>K; goto step 3.
  6. Else <w>K is not in the string table.
       Output code(<w>);
       Put <w>K into the string table;
       Set <w> to K; Goto step 3.

  "At each execution of the basic step an acceptable input string
  <w> has been parsed off.  The next character K is read and the
  extended string <w>K is tested to see if it exists in the string
  table.  If it is there, then the extended string becomes the
  parsed string <w> and the step is repeated.  If <w>K is not in the
  string table, then it is entered, the code for the successfully
  parsed string <w> is put out as compressed data, the character K
  becomes the beginning of the next string, and the step is
  repeated."

  The decompression algorithm translates each received code into a
  prefix string and extension [suffix] character. The extension
  character is stored (in a push-down stack), and the prefix
  translated again, until the prefix is a single character, which
  completes decompression of this code.  The entire code is then
  output by popping the stack.  I.e., the last code put into the
  stack was the first code in the original file.

  "An update to the string table is made for each code received
  (except the first one).  When a code has been translated, its
  final character is used as the extension character, combined with
  the prior string, to add a new string to the string table.  This
  new string is assigned a unique code value, which is the same
  code that the compressor assigned to that string.  In this way,
  the decompressor incrementally reconstructs the same string table
  that the decompressor used.... Unfortunately ... [the algorithm]
  does not work for an abnormal case.

  The abnormal case occurs whenever an input character string
  contains the sequence K<w>K<w>K, where K<w> already appears in
  the compressor string table."

  The decompression algorithm, augmented to handle the abnormal
  case, is as follows:

  1. Read first input code;
     Store in CODE and OLDcode;
     With CODE = code(K), output(K); FINchar = K;
  2. Read next code to CODE; INcode = CODE;
     If at end of file, exit;
  3. If CODE not in string table (special case) then
       Output(FINchar);
       CODE = OLDcode;
       INcode = code(OLDcode, FINchar);
  4. If CODE == code(<w>K) then
       Push K onto the stack;
       CODE == code(<w>);
       Goto 4.

  5. If CODE == code(K) then
       Output K;
       FINchar = K;

  6. While stack not empty
       Output top of stack;
       Pop stack;

  7. Put OLDcode,K into the string table.
       OLDcode = INcode;
       Goto 2.

  The algorithm as implemented here introduces two additional
  complications.

  The actual codes are transmitted using a variable-length
  encoding.  The lowest-level routines increase the number of
  bits in the code when the largest possible code is transmitted.

  Periodically, the algorithm checks that compression is still
  increasing.  If the ratio of input bytes to output bytes
  decreases, the entire process is reset.  This can happen if the
  characteristics of the input file change. (This can be supressed
  by /EXPORT=(UNIX, NOBLOCK)).
2 Unix

  Is a trademark of AT&T Bell Laboratories.

!
! decompress
!
1 DECOMPRESS
  The DECOMPRESS command invokes a utility to restore copy a file
  that had been compressed by COMPRESS.

    DECOMPRESS Input-file-spec [Output-file-spec]

2 Command_Parameters

 Input-file-spec

  Specifies the name of the compressed input file.

 Output-file-spec

  Specifies the name of the file created by COMPRESS.  If the file
  was compressed by /EXPORT=VMS, the original file name will be
  used if the Output file spec. is omitted.  If the file was
  compressed by /EXPORT=UNIX and no Output file spec. is provided,
  the file will be written to SYS$OUTPUT:

2 Command_Qualifiers

  Indicate special actions to be performed by the COMPRESS utility
  or special properties of either the input or output files.
  Qualifiers apply to the entire process.

  The following list shows all the qualifiers available with the
  DECOMPRESS command:

   o /BITS=value

   o /EXPORT=(option,...)

   o /METHOD=option

   o /MODE=(option,...)

   o /SHOW=(option,...)

2 /BITS
 /BITS=value

  If a header was not provided, this specifies the maximum number
  of bits that were used in the compression.  This parameter is
  ignored if the compressed file contains a header.

2 /EXPORT
 /EXPORT=(option, [,...])

  Export describes the format of the input file.  You can select
  the following:

  VMS (D)               The file was created by VMS COMPRESS.
  UNIX                  The file was created by Unix compress
			or a compatible program.
                        compatible with the Unix compress utility.
  [NO]ENDMARKER         A special file endmarker follows the data.
  [NO]BLOCK             The compress program may have reinitialized
			compression.
  [NO]HEADER            The compress program wrote its parameters
			into a file header.

  In general, the program can determine the proper value of these
  flags by reading the first few bytes of the file.  If valid,
  the file header overrides the command line specification.
  Generally, this option is needed only if you are trying to read
  a file generated by a version of Unix compress that did not
  write a header.  See the description of COMPRESS for details.

2 /METHOD
 /METHOD=(option)

  This specifies the particular compression algorithm.  Currently,
  only /METHOD=LZW is supported.

3 LZW

  Use the Lempel-Ziv-Welch compression algorithm.

2 /MODE
 /MODE=(option)

  This allows specification of variations on the output file
  format.  These values will be taken from the source file
  description if /EXPORT=VMS is chosen.

3 BINARY

  This creates the file in "binary" mode, rather than "text" mode.
  It is ignored if COMPRESS created the file in /EXPORT=VMS mode.
  The output file will be created in RMS "Stream-LF" format.

3 DELTA
  Compress used the difference between successive bytes, rather
  than the bytes themselves.  For certain file formats, such as
  bit-mapped graphics, this may yield a 10-15% improvement in
  compressibility. This is not compatible with some implementations
  of Unix compress.  This value is normally read from the file
  header, and generally need not be specified by DECOMPRESS.

3 FIXED

  Create the file in "fixed-block, 512-byte record" format.
  This is probably the best format to use for decompressing
  binary files (such as tar archives)  created on Unix.

3 TEXT

  Create the file in "variable-length carriage-control" format.
  This is appropriate for decompressing readable text files
  created by Unix compress.

2 /SHOW
 /SHOW=(option, [,...])

  Display information about the compression.  If omitted, DECOMPRESS
  operates silently (except for error messages).

3 ALL
  Equivalent to /SHOW=(PROGRESS,STATISTICS,FDL)

3 PROGRESS
  Print status messages at intervals, showing the operation of the
  program.

3 STATISTICS
  Print a report at the end of the process.

3 FDL
  Dump the File Definition Language block that describes the output
  file.

3 DEBUG
  Print internal debugging information.

3 DEBUG_SERIOUS
  Print more internal debugging information.

3 DEBUG_IO
  Dump the input file, too.

2 Unix
  Is a trademark of AT&T Bell Laboratories.
$ eod
$ create /log lz.h
$ deck
/* #define DEBUG	TRUE	*/
/*
 * Header file for all lz compression/decompression routines.
 *
 * Machine/Operating system/compiler selection: (#ifdef'ed)
 * vax				Vax/Unix or Vax/VMS
 * pdp11			makes a small compressor
 * M_XENIX			"large-model" Z8000
 * interdata			Signed long compare is slow
 * unix				Defined on true Unix systems
 * decus			Decus C (no signal)
 * vms				Vax/VMS (VMS_V4 may be set automatically)
 * #define readonly		If the compiler doesn't support it correctly.
 *
 * Compiler configuration (#if'ed):
 * #define vax_asm   TRUE/FALSE	TRUE on Vax (4bsd) if the compiler supports
 *				the asm() operator.  Check the generated code!
 * #define vms_asm   TRUE/FALSE	TRUE on Vax/VMS to use the run-time library
 *				insv and extv routines.
 * #define UCHAR     TRUE/FALSE	TRUE if compiler supports unsigned char
 * #define DEBUG     TRUE/FALSE	TRUE to compile in debug printouts
 *
 * Algorithm Tuning parameters:
 * #define USERMEM   <n>	Memory available to compress.
 *				If large enough, a faster algorithm is used.
 * #define SACREDMEM <n>	Don't use this part of USERMEM.
 * #define BITS      <n>	Maximum number of code bits.
 * #define MAXIO     <n>	Output buffer size (squeeze memory if needed)
 */

#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#ifndef decus
# include <signal.h>
/*
 * Arguments to signal():
 */
extern int	abort();		/* Debugging interrupt trap	*/
extern int	interrupt();		/* Non-debugging interrupt trap	*/
extern int	address_error();	/* "Segment" violation		*/
#endif

#ifndef	TRUE
# define FALSE		0
# define TRUE		1
#endif
#ifndef	EOS
# define EOS		'\0'
#endif
#define	streq(a, b)	(strcmp((a), (b)) == 0)
#define min(a,b)	((a) > (b)) ? (b) : (a))

/*
 * Set USERMEM to the maximum amount of physical user memory available
 * in bytes.  USERMEM is used to determine the maximum BITS that can be used
 * for compression.
 *
 * SACREDMEM is the amount of physical memory saved for others; compress
 * will hog the rest.
 */

#ifndef SACREDMEM
# define SACREDMEM	0
#endif

/*
 * Set machine-specific parameters
 */

#ifdef vax
# ifdef unix
#  define vax_asm	TRUE		/* If asm() supported on vax	*/
# endif
# ifdef vms
#  define vms_asm	TRUE
# endif
#endif
#ifndef	vax_asm
# define vax_asm	FALSE
#endif
#ifndef vms_asm
# define vms_asm	FALSE
#endif

#ifdef pdp11
# define BITS	12	/* max bits/code for 16-bit machine		*/
# define USERMEM 0	/* Force no user memory				*/
# define UCHAR	FALSE	/* TRUE if compiler supports unsigned char	*/
# define MAXIO 512	/* Buffer size for PDP-11 I/O buffers		*/
#endif

/*
 * Set default values for some parameters.
 */

#ifndef DEBUG
# define DEBUG	FALSE
#endif

#ifdef interdata
# define SIGNED_COMPARE_SLOW TRUE
#endif
#ifndef SIGNED_COMPARE_SLOW
# define SIGNED_COMPARE_SLOW FALSE
#endif

#ifndef USERMEM
# define USERMEM 750000	/* default user memory				*/
#endif

#ifndef	UCHAR
# define UCHAR	TRUE	/* Compiler supports unsigned char		*/
#endif

#ifndef MAXIO
# define MAXIO	2048	/* I/O buffer size				*/
#endif

/*
 * Set derived tuning parameters.
 */

#ifndef USERMEM
# define USERMEM	 0
#endif
#if USERMEM >=			(433484 + SACREDMEM)
# define PBITS		16
#else
# if USERMEM >=			(229600 + SACREDMEM)
#  define PBITS		15
# else
#  if USERMEM >=		(127536 + SACREDMEM)
#   define PBITS	14
#   else
#    if USERMEM >=		( 73464 + SACREDMEM)
#     define PBITS	13
#    else			/* Smaller systems			*/
#     define PBITS	12
#    endif
#   endif
# endif
#endif

#ifndef BITS
# define BITS PBITS
#endif

#ifdef M_XENIX
# if BITS >= 16
#  define XENIX_16		/* Enable special vector access macros	*/
# else
#  if BITS > 13
#   undef BITS
#   define BITS 13		/* Code only handles BITS = 12, 13, 16	*/
#  endif
# endif
#endif

/*
 * HSIZE is the size of the hash lookup table.  It is set to
 * 1 << BITS + fudge factor, rounded up to a prime number.
 * If it is too big, the "clear the hash" routine will take
 * too long.  The same numbers are replicated in the getsize()
 * routine's data table.
 */

#if BITS == 16
# define HSIZE	69001		/* 95% occupancy			*/
#endif
#if BITS == 15
# define HSIZE	35023		/* 94% occupancy			*/
#endif
#if BITS == 14
# define HSIZE	18013		/* 91% occupancy			*/
#endif
#if BITS == 13
# define HSIZE	 9001		/* 91% occupancy			*/
#endif
#if BITS <= 12
# define HSIZE	 5003		/* 80% occupancy			*/
#endif

/*
 * typedef's -- somewhat machine specific.
 */

/*
 * a code_int must be able to hold 2**BITS values of type int, and also -1
 */
#if BITS > 15
typedef long int	code_int;
#else
typedef int		code_int;
#endif

/*
 * A count_int must hold ((2**BITS)-1) + (255<<BITS)) and -1.
 *
 * count_int's also hold counters.
 *
 * count_short's hold small counters (for the interdata)
 *
 * Some implementations don't support unsigned char (Decus C, for example)
 * Decus C is also brain damaged with regards to unsigned shorts.
 */
#if SIGNED_COMPARE_SLOW
typedef unsigned long int count_int;
typedef unsigned short int count_short;
#else
typedef long int	count_int;
#endif

#if UCHAR
typedef	unsigned char	char_type;
#else
typedef char		char_type;
#endif

#ifdef decus
typedef unsigned	U_short;
#define	readonly			/* Dummy out readonly modifier	*/
#else
typedef unsigned short	U_short;
#endif

#ifdef unix
#define	readonly
#endif

typedef short		flag;		/* Boolean flag or parameter	*/

/*
 * The following define the "magic cookie" header
 */
#define	HEAD1_MAGIC	0x1F
#define HEAD2_MAGIC	0x9D
#define	VMS_HEAD2_MAGIC	0x9E		/* vms-private output format	*/

/*
 * Defines for third byte of header
 */
#define BIT_MASK	0x1F		/* Gets NBITS in the code	*/
#define BLOCK_MASK	0x80		/* Gets block_compress flag	*/
#define	DIFF_MASK	0x40		/* Gets differential cmprs flag	*/
/*
 * Mask0x20 is free.  I think 0x20 should mean that there is
 * a fourth header byte (for expansion).
 */

/*
 * This is for backwards compatibilty with an old version of Unix compress.
 */
#ifdef COMPATIBLE			/* Compatible, but wrong!	*/
# define MAXCODE(n_bits)	(1 << (n_bits) - 1)
#else
# define MAXCODE(n_bits)	((1 << (n_bits)) - 1)
#endif

#define INIT_BITS 9			/* initial number of bits/code */

/*
 * One code could conceivably represent (1<<BITS) characters, but
 * to get a code of length N requires an input string of at least
 * N*(N-1)/2 characters.  With 5000 chars in the stack, an input
 * file would have to contain a 25Mb string of a single character.
 * This seems unlikely.
 */
#define MAXSTACK    8000		/* size of lzdcmp output stack	*/

#ifndef CHECK_GAP
# define CHECK_GAP 	10000		/* ratio check interval		*/
#endif

#ifndef __LINE__
# define NO__LINE__
#endif
#ifndef __FILE__
# define NO__LINE__
#endif
#if DEBUG
# define VERBOSE_DEFAULT    1
# ifndef NO__LINE__
#  define FAIL(why, status)				\
	fprintf(stderr, "\nfatal: %s (%s at %d)\n",	\
	    why, __FILE__, __LINE__); 			\
	longjmp(failure, status);
# else
#  define FAIL(why, status)				\
	fprintf(stderr, "\nfatal: %s\n", why); 		\
	longjmp(failure, status);
# endif
#else
# define VERBOSE_DEFAULT    0
# define FAIL(why, status)	longjmp(failure, status);
#endif

/*
 * Note -- for compatibility with Unix compress,
 * NBR_CHAR and LZ_CLEAR must equal 256.
 * Also, (1 << (MIN_BITS - 1) should equal or exceed NBR_CHR
 */
#define	NBR_CHAR      256		/* Number of input codes	*/
#define MIN_BITS	9		/* Smallest code is 9 bits	*/
#if ((1 << BITS) < NBR_CHAR) || (BITS < MIN_BITS)
    << Can't compile: not enough bits for the input character set size >>
#endif
#define	LZ_CLEAR	(NBR_CHAR)	/* Clear code			*/
#define	LZ_SOH		(LZ_CLEAR + 1)	/* Start of header block	*/
#define	LZ_STX		(LZ_SOH   + 1)	/* Start of text block		*/
#define	LZ_EOR		(LZ_STX   + 1)	/* End of text record		*/
#define	LZ_ETX		(LZ_EOR   + 1)	/* End of header/text block	*/
#define	LZ_FIRST	(LZ_ETX   + 1)	/* First user (data) code	*/

#ifdef	vms
#include		errno
#include		ssdef
#include		stsdef
#include		rms
#include		descrip
#ifndef	EXIT_SUCCESS
#define	EXIT_SUCCESS	(SS$_NORMAL | STS$M_INHIB_MSG)
#define	EXIT_FAILURE	(SS$_ABORT)
#endif
#define VMS_V4		L_cuserid >= 16		/* Enable new stuff	*/
#else
#define VMS_V4		0			/* Disable new stuff	*/
extern int		errno;
#ifdef decus
#define	errno		$$ferr
#endif
#endif

/*
 * Define exit() codes.
 */

#ifndef	EXIT_SUCCESS
#ifdef decus
#define	EXIT_SUCCESS	IO_SUCCESS
#define	EXIT_FAILURE	IO_ERROR
#else
#define	EXIT_SUCCESS	0			/* Normal exit		*/
#define	EXIT_FAILURE	1			/* Error exit		*/
#endif
#endif
#if EXIT_FAILURE == 0
  << error, EXIT_FAILURE must be non-zero for longjmp to work >>
#endif
#ifdef vms
#define	ERROR_EXIT	(errno)
#else
#define	ERROR_EXIT	EXIT_FAILURE
#endif

/*
 * Parameter values are converted to internal values (to simplify
 * processing of VMS DCL)
 */
#define EXPORT_VMS		0x0001
#define EXPORT_UNIX		0x0002
#define EXPORT_BLOCK		0x0004
#define	EXPORT_HEADER		0x0008
#define EXPORT_ENDMARKER	0x0010

#define	METHOD_LZ		0x0001

#define MODE_TEXT		0x0001
#define	MODE_BINARY		0x0002
#define	MODE_FIXED		0x0004
#define	MODE_DELTA		0x0008

#define	SHOW_STATISTICS		0x0001
#define	SHOW_PROGRESS		0x0002
#define	SHOW_FDL		0x0004
#define	SHOW_DEBUG		0x0008
#define SHOW_SERIOUS_DEBUG	0x0010
#define SHOW_IO_DEBUG		0x0020


/*
 * All I/O is done by way of "streams".  To establish a stream,
 * set the parameters appropriately and off you go.  The following
 * functions are provided:
 *	lz_fill(stream)		fills the buffer from stdin
 *	lz_flush(stream)	writes the buffer to stdout
 *	lz_eof(stream)		returns EOF (for fill from memory)
 *	lz_fail(stream)		abort (for writing to memory).
 *	lz_dummy(stream)	throw an output stream away.
 * Note: if VMS_V4 is enabled and the private (non-export) format
 * chosen, lz_fill and lz_flush access the files appropriately.
 * Stream elements are initialized as follows:
 *	Input:	bp = NULL;	bend = NULL;
 *	Output:	bp = bstart;	bend = bstart + bsize;
 */

typedef struct STREAM {
    char_type	*bp;		/* Next character to get/put		*/
    char_type	*bend;		/* -> end of stream buffer		*/
    char_type	*bstart;	/* Start of stream buffer		*/
    short	bsize;		/* Stream buffer size			*/
    int		(*func)();	/* Read/write a buffer function		*/
} STREAM;

/*
 * Note also that the compress routine uses putbuf(buf, count, outstream)
 * and the decompress routine uses getbuf(buf, count, instream) to (quickly)
 * transfer multiple bytes.
 */
#if UCHAR
#define	GET(s)		\
	(((s)->bp < (s)->bend) ? *(s)->bp++        : (*(s)->func)(s))
#else
#define	GET(s)		\
	(((s)->bp < (s)->bend) ? *(s)->bp++ & 0xFF : (*(s)->func)(s))
#endif
#define	PUT(c, s)	\
	((((s)->bp >= (s)->bend) ? (*(s)->func)(s) : 0), *(s)->bp++ = (c))

extern int lz_fill();
extern int lz_flush();
extern int lz_eof();
extern int lz_fail();
extern int lz_dummy();

#if DEBUG
extern readonly char *lz_names[];		/* "LZ_CLEAR" etc.	*/
#endif

/*
 * Options and globals.
 */
#if VMS_V4
#include devdef

#define	ATT_NAME	"vms$attributes "
#define	ATT_SIZE	15			/* strlen(ATT_NAME)	*/

typedef struct FDLSTUFF {
	struct	RAB	rab;		/* Record access buffer		*/
	struct	FAB	fab;		/* File access buffer		*/
	struct	NAM	nam;		/* File name buffer		*/
	struct	XABFHC	xabfhc;		/* Attributes (file header blk)	*/
	struct	XABSUM	xabsum;		/* Attributes (isam summary)	*/
	char		starname[NAM$C_MAXRSS + 1]; /* Wild file name	*/
	char		filename[NAM$C_MAXRSS + 1]; /* Open file name	*/
} FDLSTUFF;

extern int	fdl_status;	/* Error code from fdl library		*/
#endif

extern flag	export;		/* -x, /EXPORT=(...)			*/
extern flag	method;		/* LZ, of course			*/
extern flag	show;		/* -v  /SHOW=(...) Verbose logging	*/
extern flag	mode;		/* Binary and friends			*/
extern short	maxbits;	/* -b /BITS=<n>				*/
extern readonly flag is_compress; /* TRUE if compress, FALSE if decomp.	*/
extern char	*infilename;	/* For error printouts			*/
extern char	*outfilename;	/* For more error printouts		*/
/*  18-May-1994 Chris Hebert and Terry Lemons  Digital Equipment Corporation
 *  n_bits is defined as STATIC in all .C files that call it, so we'll
 *  disable it here.
extern short    n_bits;            Current # of bits in compressed file
*/
extern int	firstcode;	/* First value past signals		*/
extern jmp_buf	failure;	/* For longjmp() return			*/
$ eod
$ create /log lzcomp.cld
$ deck
!
! Command language description for COMPRESS
!

MODULE lz_dcl_table

DEFINE VERB		COMPRESS

PARAMETER P1,				! Input file
	LABEL=INPUT
	PROMPT="Input file",
	VALUE (REQUIRED, TYPE=$FILE)

PARAMETER P2,				! Output file
	LABEL=OUTPUT
	PROMPT="Output file",
	VALUE (REQUIRED, TYPE=$FILE)

QUALIFIER BITS,				! /BITS=<value>
	NONNEGATABLE,
	PLACEMENT=GLOBAL,
	VALUE(TYPE=$NUMBER)

QUALIFIER EXPORT,			! /EXPORT=(VMS UNIX BLOCK ENDMARKER)
	NONNEGATABLE,
	PLACEMENT=GLOBAL,
	VALUE(TYPE=EXPORT_KEYWORDS, LIST),
	DISALLOW (VMS AND UNIX)
	DISALLOW (BLOCK AND NEG HEADER)
	DISALLOW (ENDMARKER AND NEG BLOCK)
	DISALLOW (BLOCK AND NEG HEADER)
	DISALLOW (VMS AND NEG BLOCK)
	DISALLOW (VMS AND NEG HEADER)
	DISALLOW (VMS AND NEG ENDMARKER)

QUALIFIER METHOD,			! /METHOD=LZ
	NONNEGATABLE,
	PLACEMENT=GLOBAL,
	VALUE(TYPE=METHOD_KEYWORDS)

QUALIFIER SHOW,				! /SHOW=(PROGRESS, STATISTICS, DEBUG)
	NEGATABLE,
	PLACEMENT=GLOBAL,
	VALUE(TYPE=SHOW_KEYWORDS, LIST)

QUALIFIER MODE,				! /MODE=(DELTA)
	NONNEGATABLE,
	PLACEMENT=GLOBAL,
	VALUE(TYPE=MODE_KEYWORDS, LIST),

DEFINE TYPE EXPORT_KEYWORDS
	KEYWORD VMS, DEFAULT
	KEYWORD UNIX,
	KEYWORD BLOCK, NEGATABLE
	KEYWORD HEADER, NEGATABLE
	KEYWORD ENDMARKER, NEGATABLE

DEFINE TYPE METHOD_KEYWORDS
	KEYWORD	LZW, DEFAULT

DEFINE TYPE MODE_KEYWORDS
	KEYWORD BINARY
	KEYWORD DELTA

DEFINE TYPE SHOW_KEYWORDS
	KEYWORD PROGRESS, NEGATABLE
	KEYWORD STATISTICS, NEGATABLE
	KEYWORD FDL, NEGATABLE
	KEYWORD DEBUG, NEGATABLE
	KEYWORD DEBUG_SERIOUS, NEGATABLE
	KEYWORD DEBUG_IO, NEGATABLE
	KEYWORD ALL			! Actually, all but debug
$ eod
$ create /log lzdcmp.cld
$ deck
!
! Command language description for DECOMPRESS
!

MODULE lz_dcl_table

DEFINE VERB		DECOMPRESS

PARAMETER P1,				! Input file
	LABEL=INPUT
	PROMPT="Input file",
	VALUE (REQUIRED, TYPE=$FILE)

PARAMETER P2,				! Output file
	LABEL=OUTPUT
	PROMPT="Output file",
	VALUE (TYPE=$FILE)

QUALIFIER BITS,				! /BITS=<value>
	NONNEGATABLE,
	PLACEMENT=GLOBAL,
	VALUE(TYPE=$NUMBER)

QUALIFIER EXPORT,			! /EXPORT=(VMS UNIX BLOCK ENDMARKER)
	NONNEGATABLE,			! Only /EXPORT=(UNIX, NOHEADER)
	PLACEMENT=GLOBAL,		! is meaningful.
	VALUE(TYPE=EXPORT_KEYWORDS, LIST),
	DISALLOW (VMS AND UNIX)
	DISALLOW (BLOCK AND NEG HEADER)
	DISALLOW (VMS AND NEG BLOCK)
	DISALLOW (VMS AND NEG HEADER)
	DISALLOW (VMS AND NEG ENDMARKER)

QUALIFIER METHOD,			! /METHOD=LZ
	NONNEGATABLE,
	PLACEMENT=GLOBAL,
	VALUE(TYPE=METHOD_KEYWORDS)

QUALIFIER SHOW,				! /SHOW=(PROGRESS, STATISTICS, DEBUG)
	NEGATABLE,
	PLACEMENT=GLOBAL,
	VALUE(TYPE=SHOW_KEYWORDS, LIST)

QUALIFIER MODE,				! /MODE=(TEXT | BINARY | FIXED | DELTA)
	NONNEGATABLE,
	PLACEMENT=GLOBAL,
	VALUE(TYPE=MODE_KEYWORDS, LIST),
	DISALLOW ANY2(TEXT, BINARY, FIXED)

DEFINE TYPE EXPORT_KEYWORDS
	KEYWORD VMS, DEFAULT
	KEYWORD UNIX,
	KEYWORD BLOCK, NEGATABLE
	KEYWORD HEADER, NEGATABLE
	KEYWORD ENDMARKER, NEGATABLE

DEFINE TYPE METHOD_KEYWORDS
	KEYWORD	LZ, DEFAULT

DEFINE TYPE MODE_KEYWORDS
	KEYWORD	TEXT, DEFAULT
	KEYWORD BINARY,
	KEYWORD FIXED,
	KEYWORD DELTA

DEFINE TYPE SHOW_KEYWORDS
	KEYWORD PROGRESS, NEGATABLE
	KEYWORD STATISTICS, NEGATABLE
	KEYWORD FDL, NEGATABLE
	KEYWORD DEBUG, NEGATABLE
	KEYWORD DEBUG_SERIOUS, NEGATABLE
	KEYWORD DEBUG_IO, NEGATABLE
	KEYWORD ALL			! Actually, all but debug
$ eod
$ create /log lzdcl.c
$ deck
/*
 * DCL parser for lz... stuff (swings both ways!)
 *
 * Edit History
 * 18-May-1994 Chris Hebert and Terry Lemons Digital Equipment Corporation
 *         Fix a non-null terminated string problem.
 */
#include "lz.h"
#include climsgdef
#define	DYNAMIC_STRING(name)			\
    struct dsc$descriptor_d name = {		\
	0, DSC$K_DTYPE_T, DSC$K_CLASS_D, NULL	\
    }

typedef struct {
	char		*key;		/* Option name			*/
	flag		bit;		/* Sets this bit		*/
} KEYWORD;

static char		errname[257];	/* Error text stored here	*/
static $DESCRIPTOR(err, errname);	/* descriptor for error text	*/

extern char	*vms_etext();

static DYNAMIC_STRING(command_line);
static DYNAMIC_STRING(cli_command);
static DYNAMIC_STRING(cli_prompt);
static DYNAMIC_STRING(result);		/* cli_getvalue result datum	*/
static $DESCRIPTOR(prompt_compress, "Compress: ");
static $DESCRIPTOR(prompt_decompress, "Decompress: ");

globalref int		*lz_dcl_table;	/* Different table for cmp/dcmp	*/
extern int		lib$get_input();
char			*cli_savevalue();

/*
 * Note: these tables are inclusive (for both lzcomp and lzdcmp).
 * The code thus must accept "missing" element errors.
 */

KEYWORD	key_export[] = {
 { "VMS",		EXPORT_VMS		},
 { "UNIX",		EXPORT_UNIX		},
 { "BLOCK",		EXPORT_BLOCK		},
 { "HEADER",		EXPORT_HEADER		},
 { "ENDMARKER",		EXPORT_ENDMARKER	},
 { NULL,		0x0000			}
};

KEYWORD key_method[] = {
 { "LZW",		METHOD_LZ		},
 { NULL,		0x0000			}
};

KEYWORD key_mode[] = {
 { "TEXT",		MODE_TEXT		},
 { "BINARY",		MODE_BINARY		},
 { "FIXED",		MODE_FIXED		},
 { "DELTA",		MODE_DELTA		},
 { NULL,		0x0000			}
};

KEYWORD key_show[] = {
 { "ALL",		SHOW_PROGRESS | SHOW_STATISTICS	 | SHOW_FDL },
 { "PROGRESS",		SHOW_PROGRESS		},
 { "STATISTICS",	SHOW_STATISTICS		},
 { "FDL",		SHOW_FDL		},
 { "DEBUG",		SHOW_DEBUG		},
 { "DEBUG_SERIOUS",	SHOW_SERIOUS_DEBUG	},
 { "DEBUG_IO",		SHOW_IO_DEBUG		},
 { NULL,		0x0000			}
};

int
lzdcl(argc, argv)
int		argc;
char		*argv[];
{
	register int		i;
	register int		status;
	struct dsc$descriptor_s	temp;
	extern int		ignore_dcl_error();

#if 0
	for (i = 1; i < argc; i++) {
	    printf("%2d: %s\n", i, argv[i]);
	}
#endif
	descriptor(&temp, (is_compress) ? "COMPRESS " : "DECOMPRESS ");
	if (argc <= 1) {
	    status = lib$get_foreign(	/* Read the command line	*/
		&command_line,
		(is_compress) ? &prompt_compress : &prompt_decompress);
	    if (status == RMS$_EOF)
		exit(status);
	    else if (status != SS$_NORMAL)
		lib$stop(status);
	    str$concat(&cli_prompt, &temp, &command_line);
	}
	else {
	    for (i = 1; i < argc; i++) {
		str$append(&cli_command, &temp);
		descriptor(&temp, argv[i]);
		str$append(&cli_command, &temp);
		descriptor(&temp, " ");
	    }
	}
#if 0
	printf("command: \"%.*s\"\n",
	    cli_command.dsc$w_length, cli_command.dsc$a_pointer);
#endif
	VAXC$ESTABLISH(ignore_dcl_error);
	status = cli$dcl_parse(		/* Parse the DCL		*/
		&cli_command,		/* Input from lib$get_foreign	*/
		&lz_dcl_table,		/* Parsing table		*/
		&lib$get_input,	    	/* Gets a required parameter	*/
		&lib$get_input,	    	/* Gets a continuation prompt	*/
		&cli_prompt);
#if 0
	printf("cli$dcl_parse returns %s\n", vms_etext(status));
#endif
	if ((status & STS$M_SUCCESS) != 0) {
	    /*
	     * Process all arguments
	     */
	    infilename = cli_savevalue("INPUT");
	    if (cli_present("OUTPUT") == CLI$_PRESENT)
		outfilename = cli_savevalue("OUTPUT");
	    if (cli_present("BITS") == CLI$_PRESENT) {
		register int		i;
		register int		c;

		cli_getvalue("BITS");
		for (maxbits = 0, i = 0; i < result.dsc$w_length; i++) {
		    c = result.dsc$a_pointer[i];
		    if (!isdigit(c))
			goto nogood_bits;
		    maxbits = (maxbits * 10) + (c - '0');
		}
		if (maxbits < MIN_BITS) {
nogood_bits:	    fprintf(stderr, "Illegal bits value \"%.*s\"\n",
			result.dsc$w_length, result.dsc$a_pointer);
		    lib$signal(CLI$_IVVALU);
		}
	    }
	    cli_list("METHOD", &method, key_method);
	    cli_list("EXPORT", &export, key_export);
	    if ((export & EXPORT_UNIX) != 0)
		export &= ~EXPORT_VMS;
	    cli_list("MODE",   &mode,   key_mode);
	    cli_list("SHOW",   &show,   key_show);
	    status = SS$_NORMAL;
	}
	str$free1_dx(&command_line);
	str$free1_dx(&cli_command);
	str$free1_dx(&cli_prompt);
	str$free1_dx(&result);
	return (status);
}

cli_list(what, flag_word, keytable)
char			*what;
flag			*flag_word;
KEYWORD			*keytable;
{
	register int		key_status;
	register int		status;
	register KEYWORD	*kp;

	switch(cli_present(what)) {
	case CLI$_NEGATED:
#if 0
	    printf("found %s (negated)\n", what);
#endif
	    *flag_word = 0;
	    break;

	case CLI$_PRESENT:
	    for (kp = keytable; kp->key != NULL; kp++) {
		key_status = cli_present(kp->key);
		switch (key_status) {
		case CLI$_PRESENT:
		    *flag_word |= kp->bit;
#if 0
		    printf("found %s %s\n", what, kp->key);
#endif
		    break;

		case CLI$_NEGATED:
		    *flag_word &= ~kp->bit;
#if 0
		    printf("found %s %s (negated)\n", what, kp->key);
#endif
		    break;
		}
	    }
	    break;

	default:
	    break;
	}
}

int
cli_present(what)
char		*what;
/*
 * TRUE if the argument is present in the command line
 */
{
	struct dsc$descriptor_s	parm;

	descriptor(&parm, what);
	return(cli$present(&parm));
}

descriptor(descr, what)
register struct dsc$descriptor_s	*descr;
char					*what;
/*
 * Turn a C string into a (static) descriptor.
 */
{
	descr->dsc$w_length = strlen(what);
	descr->dsc$b_class = DSC$K_CLASS_S;
	descr->dsc$b_dtype = DSC$K_DTYPE_T;
	descr->dsc$a_pointer = what;
}

int
cli_getvalue(what)
char		*what;
/*
 * Get the value (storing it in result).  Return the status.
 * Note the following (successful) statuses:
 *	CLI$_COMMA		Another entry (in the list) may be read
 *	SS$_NORMAL		The last -- or only -- value.
 * A non-successful status is signaled.
 */
{
	struct dsc$descriptor_s	parm;
	int			status;

	descriptor(&parm, what);
	if (((status = cli$get_value(&parm, &result)) & STS$M_SUCCESS) == 0)
	    lib$signal(status);
	return (status);
}

char *
cli_savevalue(what)
char		*what;
/*
 * Find the value and store it as a C string in malloc'ed memory.
 * Return NULL on errors (which should not happen).
 */
{
	register char		*string;

	if ((cli_getvalue(what) & STS$M_SUCCESS) == 0)
	    return (NULL);
	else {
	    string = malloc(result.dsc$w_length + 1);
	    strncpy(string, result.dsc$a_pointer, result.dsc$w_length);
/* Next line added 18-May-1994 Chris Hebert and Terry Lemons	*/
	    string[result.dsc$w_length] = '\0';
	    return (string);
	}
}

dumpoptions()
/*
 * Debug: dump all option values.
 */
{
	fprintf(stderr, "(Debug) Option dump, bits = %d\n", maxbits);
	fprintf(stderr, "Input file: %s\n",
	    (infilename == NULL) ? "<unspecified>" : infilename);
	fprintf(stderr, "Output filename: %s\n",
	    (outfilename == NULL) ? "<unspecified>" : outfilename);
	option_dump("METHOD", method, key_method);
	option_dump("EXPORT", export, key_export);
	option_dump("MODE",   mode,   key_mode);
	option_dump("SHOW",   show,   key_show);
}

option_dump(what, flag_word, keytable)
char			*what;
flag			flag_word;
KEYWORD			*keytable;
{
	register KEYWORD	*kp;
	register int		first;

	for (first = TRUE, kp = keytable; kp->key != NULL; kp++) {
	    if ((flag_word & kp->bit) == kp->bit) {
		if (first) {
		    fprintf(stderr, " %s=(%s", what, kp->key);
		    first = FALSE;
		}
		else {
		    fprintf(stderr, ", %s", kp->key);
		}
	    }
	}
	if (first)
	    fprintf(stderr, " %s -- no options specified.\n", what);
	else {
	    fprintf(stderr, ")\n");
	}
}

#if 0
/*
 * This is in lzvio.c, too.
 */
char *
vms_etext(errorcode)
int		errorcode;
{
	char		*bp;
	short		errlen;		/* Actual text length		*/

	lib$sys_getmsg(&errorcode, &errlen, &err, &15);
	/*
	 * Trim trailing junk.
	 */
	for (bp = &errname[errlen]; --bp >= errname;) {
	    if (isgraph(*bp) && *bp != ' ')
		break;
	}
	bp[1] = EOS;
	return(errname);
}
#endif

#define	CODE(v)	((v) & STS$M_CODE)

ignore_dcl_error(signal_arg, mech_arg)
long int	signal_arg[];
long int	mech_arg[];
/*
 * VMS exception handler.  Needs extension to handle "all" errors.
 */
{
	switch (CODE(signal_arg[1])) {
	case CODE(SS$_UNWIND):			/* Currently unwinding	*/
	    return;

	case CODE(SS$_CONTROLC):		/* CTRL/C trap		*/
	case CODE(SS$_ACCVIO):
	case CODE(SS$_ROPRAND):
	    lib$stop(signal_arg[1]);
	    break;

	default:
	    sys$putmsg(signal_arg);
	    break;
	}
	return;
}
$ eod
$ create /log lzio.c
$ deck
/*
 *			l z i o . c
 *
 * I/O buffer management.  All input/output I/O is done through these
 * routines (and the macros in lz.h).  The rules of the game are:
 *
 * input via GET() and getbuf().
 *	GET returns an 8-bit byte, or -1 on eof/error.
 *	getbuf() returns the number of things gotten, or -1 on eof/error.
 *	No return on error: longjmp's to the main-line.
 *
 * output via PUT() and lz_putbuf().
 *	No return on error: longjmp's to the main-line.
 * flush output by lz_flush() before closing files -- or you'll lose data.
 */

/*LINTLIBRARY*/

#include	"lz.h"
#if VMS_V4
#include	<rmsdef.h>
#ifndef FDLSTUFF
#define FDLSTUFF char
#endif
extern FDLSTUFF *fdl_input;
extern FDLSTUFF *fdl_output;
extern int	fdl_status;
#endif

int
lz_fill(s)
register STREAM		*s;
{
	register int	i;
	extern char	*infilename;

#if VMS_V4
	if ((export & EXPORT_VMS) == 0) {
	    i = fread((char *) s->bstart, 1, s->bsize, stdin);
	    if (ferror(stdin)) {
		perror(infilename);
		FAIL("compress (export) read error", ERROR_EXIT);
	    }
	}
	else {			/* Decompress and export/private	*/
	    i = fdl_read(s->bstart, s->bsize, fdl_input);
	    if (i < 0
	     && fdl_status != RMS$_EOF)
		fdl_message(fdl_input, "Read error");
	}
#else
#ifdef unix
	i = read(fileno(stdin), (char *) s->bstart, s->bsize);
	if (i < 0) {
	    perror(infilename);
	    FAIL("read error", ERROR_EXIT);
	}
#else
	i = fread((char *) s->bstart, 1, s->bsize, stdin);
	if (ferror(stdin)) {
	    perror(infilename);
	    FAIL("read error", ERROR_EXIT);
	}
#endif
#endif
	if (i <= 0)
	    return (EOF);
	else {
	    s->bp = s->bstart;
	    s->bend = &s->bstart[i];
#if UCHAR
	    return (*s->bp++);
#else
	    return (*s->bp++ & 0xFF);
#endif
	}
}

lz_flush(s)
register STREAM	*s;
{
	register int	count;
	extern char	*outfilename;

	count = s->bp - s->bstart;
#if DEBUG
	if (!is_compress
	 && (show & SHOW_IO_DEBUG) != 0) {
	    fprintf(stderr, "lz_flush %d:  ", count);
	    dumptext(s->bstart, count, stderr);
	}
#endif
#if VMS_V4
	if ((export & EXPORT_VMS) == 0) {
	    if (is_compress)
		fwrite((char *) s->bstart, count, 1, stdout);
	    else {
		register char *bp, *bend;

		for (bp = s->bstart, bend = bp + count; bp < bend; bp++)
		    putchar(*bp);
	    }
	    if (ferror(stdout)) {
		perror(outfilename);
		FAIL("write error", ERROR_EXIT);
	    }
	}
	else {
	    if (fdl_write((char *) s->bstart, count, fdl_output) == -1) {
		fdl_message(fdl_output, "Write error");
		FAIL("write (fdl) error", ERROR_EXIT);
	    }
	}
#else
#ifdef unix
	if (write(fileno(stdout), (char *) s->bstart, count) != count) {
	    perror(outfilename);
	    FAIL("write error", ERROR_EXIT);
	}
#else
	fwrite((char *) s->bstart, 1, count, stdout);
	if (ferror(stdout)) {
	    perror(outfilename);
	    FAIL("write error", ERROR_EXIT);
	}
#endif
#endif
	s->bp = s->bstart;
}

int
lz_getbuf(buffer, count, s)
char_type		*buffer;
int			count;
register STREAM		*s;
/*
 * Read a block of data -- be clever.  Return number gotten, or -1
 * on eof.
 */
{
	register char_type	*bp;		/* -> buffer		*/
	register char_type	*ip;		/* -> I/O buffer	*/
	register char_type	*ep;		/* End of segment	*/
	register int		remaining;	/* Size of segment	*/
	int			datum;

	if (count == 0)				/* Shouldn't happen	*/
	    return (0);
	bp = buffer;
	while (--count >= 0) {
	    if ((datum = GET(s)) == EOF)	/* Maybe fill LZ buff	*/
		break;
	    *bp++ = datum;
	    remaining = s->bend - (ip = s->bp);
	    if (remaining > count)
		remaining = count;
	    ep = &ip[remaining];
	    while (ip < ep)
		*bp++ = *ip++;
	    count -= remaining;
	    s->bp = ip;				/* Refresh buffer	*/
	}
	return ((bp == buffer) ? -1 : bp - buffer);
}

int
lz_putbuf(bp, count, s)
register char_type	*bp;
int			count;
register STREAM		*s;
/*
 * Write a block of data -- be clever.
 */
{
	register char_type	*op;		/* -> I/O buffer	*/
	register char_type	*ep;		/* End of segment	*/
	register int		remaining;	/* Size of segment	*/

	while (--count >= 0) {
	    PUT(*bp++, s);			/* Forces a buffer	*/
	    remaining = s->bend - (op = s->bp);
	    if (remaining > count)
		remaining = count;
	    ep = &op[remaining];
	    while (op < ep)
		*op++ = *bp++;
	    count -= remaining;
	    s->bp = op;				/* Refresh buffer	*/
	}
}

/*ARGUSED*/
int
lz_eof(s)
STREAM		*s;
/*
 * Dummy routine for read from memory -- returns EOF.
 */
{
#ifdef decus
	return (s, EOF);
#else
	return (EOF);
#endif
}

int
lz_fail(s)
STREAM		*s;
/*
 * Dummy routine for write to memory -- called if buffer fills.
 */
{
	fprintf(stderr, "Memory buffer [%d bytes] filled -- fatal.\n",
		s->bsize);
	FAIL("crash (lz_fail)", EXIT_FAILURE);
}

int
lz_dummy(s)
STREAM		*s;
/*
 * Dummy routine for write to memory -- writes to the bit-bucket.
 */
{
	s->bp = s->bstart;
}

#ifndef decus
/*
 * Signal error handlers.
 */
#ifdef vms
#define unlink	delete
#endif

interrupt()
{
	if (outfilename != NULL
	 && !streq(outfilename, "<stdout>"))
	    unlink(outfilename);
	exit(EXIT_FAILURE);
}

address_error()
{
	if (!is_compress)
	    fprintf(stderr, "Decompress: corrupt input file\n");
	else {
	    fprintf(stderr, "fatal address error\n");
	}
#ifdef vms
	lib$signal(SS$_ACCVIO);
#else
	interrupt();
#endif
}
#endif

/*
 * getredirection() is intended to aid in porting C programs
 * to VMS (Vax-11 C) which does not support '>' and '<'
 * I/O redirection.  With suitable modification, it may
 * useful for other portability problems as well.
 */

#ifdef	vms

int
getredirection(argc, argv)
int		argc;
char		**argv;
/*
 * Process vms redirection arg's.  Exit if any error is seen.
 * If getredirection() processes an argument, it is erased
 * from the vector.  getredirection() returns a new argc value.
 *
 * Warning: do not try to simplify the code for vms.  The code
 * presupposes that getredirection() is called before any data is
 * read from stdin or written to stdout.
 *
 * Normal usage is as follows:
 *
 *	main(argc, argv)
 *	int		argc;
 *	char		*argv[];
 *	{
 *		argc = getredirection(argc, argv);
 *	}
 */
{
	register char		*ap;	/* Argument pointer	*/
	int			i;	/* argv[] index		*/
	int			j;	/* Output index		*/
	int			file;	/* File_descriptor 	*/

	for (j = i = 1; i < argc; i++) {   /* Do all arguments	*/
	    switch (*(ap = argv[i])) {
	    case '<':			/* <file		*/
		if (freopen(++ap, "r", stdin) == NULL) {
		    perror(ap);		/* Can't find file	*/
		    exit(ERROR_EXIT);	/* Is a fatal error	*/
		}
		break;

	    case '>':			/* >file or >>file	*/
		if (*++ap == '>') {	/* >>file		*/
		    /*
		     * If the file exists, and is writable by us,
		     * call freopen to append to the file (using the
		     * file's current attributes).  Otherwise, create
		     * a new file with "vanilla" attributes as if
		     * the argument was given as ">filename".
		     * access(name, 2) is TRUE if we can write on
		     * the specified file.
		     */
		    if (access(++ap, 2) == 0) {
			if (freopen(ap, "a", stdout) != NULL)
			    break;	/* Exit case statement	*/
			perror(ap);	/* Error, can't append	*/
			exit(ERROR_EXIT); /* After access test	*/
		    }			/* If file accessable	*/
		}
		/*
		 * On vms, we want to create the file using "standard"
		 * record attributes.  create(...) creates the file
		 * using the caller's default protection mask and
		 * "variable length, implied carriage return"
		 * attributes. dup2() associates the file with stdout.
		 */
		if ((file = creat(ap, 0, "rat=cr", "rfm=var")) == -1
		 || dup2(file, fileno(stdout)) == -1) {
		    perror(ap);		/* Can't create file	*/
		    exit(ERROR_EXIT);	/* is a fatal error	*/
		}			/* If '>' creation	*/
		break;			/* Exit case test	*/

	    default:
		argv[j++] = ap;		/* Not a redirector	*/
		break;			/* Exit case test	*/
	    }
	}				/* For all arguments	*/
	argv[j] = NULL;			/* Terminate argv[]	*/
	return (j);			/* Return new argc	*/
}
#endif

#if 1 || DEBUG

int		col;

readonly char *lz_names[] = {
    "LZ_CLEAR", "LZ_SOH", "LZ_STX", "LZ_EOR", "LZ_ETX", "???"
};

dumphex(buffer, count, fd)
register char_type	*buffer;
register int		count;
FILE			*fd;
{
	if (col > 0) {
	    putc('\n', fd);
	    col = 0;
	}
	fprintf(fd, "%2d:", count);
	while (--count >= 0) {
	    fprintf(fd, " %02x", (int) (*buffer++ & 0xFF));
	}
	fprintf(fd, "\n");
}

dumptext(buffer, count, fd)
register char_type	*buffer;
int			count;
FILE			*fd;
{
	extern char	*dumpchar();

	putc('"', fd);
	while (--count >= 0)
	    fputs(dumpchar((int) *buffer++), fd);
	fputs("\"\n", fd);
}

char *
dumpchar(c)
register int	c;
/*
 * Make a character printable.  Returns a static pointer.
 */
{
	static char	dump_buffer[8];

	c &= 0xFF;
	if (isascii(c) && isprint(c)) {
	    dump_buffer[0] = c;
	    dump_buffer[1] = EOS;
	}
	else {
	    switch (c) {
	    case '\n':	return ("<LF>");
	    case '\t':	return ("<TAB>");
	    case '\b':	return ("<BS>");
	    case '\f':	return ("<FF>");
	    case '\r':	return ("<CR>");
	    }
	    sprintf(dump_buffer, "<x%02x>", c);
	}
	return (dump_buffer);
}
#endif

/*
 * Cputime returns the elapsed process time (where available) in msec.
 * Note: Unix doesn't seem to have a good way to determine ticks/sec.
 */

#ifdef	decus
#include	<timeb.h>

long
cputime()
{
	struct timeb		buf;
	static struct timeb	origin;
	long			result;
	int			msec;

	if (origin.time == 0)
	    ftime(&origin);
	ftime(&buf);
	result = (buf.time - origin.time) * 1000;
	msec = ((int) buf.millitm) - ((int) origin.millitm);
	return (result + ((long) msec));
}
#else
#ifdef vms
#include	<types.h>
struct tms {
	time_t	tms_utime;
	time_t	tms_stime;
	time_t	tms_uchild;	/* forgot the */
	time_t	tms_uchildsys;	/* real names */
};
#define HERTZ	100.0				/* 10 msec units	*/
#else
#include	<sys/types.h>
#include	<sys/times.h>
#ifndef HERTZ
#define HERTZ	60.0				/* Change for Europe	*/
#endif
#endif

long
cputime()
{
	struct tms	tms;
	double		temp;
	long		result;

	times(&tms);
	result = tms.tms_utime + tms.tms_stime;
	temp = result * 1000.0 / HERTZ;		/* Time in msec.	*/
	result = temp;
	return (result);
}
#endif
$ eod
$ create /log lzvio.c
$ deck
/*
 *			l z v i o . c
 * For VMS V4 only.
 */

/*
 * Problems:
 *	If you open a second input file (getting rms attributes)
 *	it aborts with an internal "fatal" error (15820C LIB-F-FATERRLIB)
 */

/*
 * Make TESTING_FDLIO non-zero to enable test code.
 *
 * Edit History
 * 26-Dec-85	MM	Create files with "large" allocation quantity
 *			to improve processing speed.
 * 18-Apr-86	Hein	Don't set xrb_lrl if the file isn't sequential.
 *  2-Jun-87	MM	fdl$generate needs a dynamic string
 */
#ifndef	TESTING_FDLIO
#define	TESTING_FDLIO	0
#endif

/*
 * RMS/FDL record level i/o routines for Vax-11 C V4 or greater only.
 * Rather crude.
 *
 * The following are provided:
 *
 *	#define	FDLSTUFF	char
 *	#include descrip
 *
 *	FDLSTUFF *
 *	fdl_open(filename, fdl_descriptor)
 *	char			*filename;
 *	struct	dsc$descriptor	*fdl_descriptor;
 *		Initializes internal buffers and opens this existing
 *		file for input.  The filename may not contain wildcards.
 *		On (successful) return, fdl_descriptor will point to
 *		an initialized fdl specification.  The description
 *		string will be in malloc'ed memory.  The caller does not
 *		initialize the fdl_descriptor.  Returns NULL on error.
 *		(Note an error will be returned if the file is not
 *		block-oriented.)
 *
 *		When you don't need the fdl_descriptor information
 *		any more, free it by calling
 *		    fdl_free(fdl_descriptor);
 *		if fdl_descriptor is NULL on entry, the file is opened
 *		normally (fdl information is not collected).
 *
 *	FDLSTUFF *
 *	fdl_create(fdl_descriptor, override_filename, xab_lrl)
 *	struct	dsc$descriptor	*fdl_descriptor;
 *	char			*override_filename;
 *	int			xab_lrl;
 *		Creates a file using the fdl specification.
 *		If override_filename is not NULL and not equal to "",
 *		it will override the filename specified in the fdl.
 *		fdl_write() is used to write data to the file.
 *		Returns NULL on error.
 *
 *		if fdl_descriptor is NULL, the file is created using
 *		the name in override_filename (which must be present).
 *		The file is created in "undefined" record format.
 *
 *		xab_lrl initializes the XAB$W_LRL field.
 *
 *	fdl_free(fdl_descriptor)
 *	struct	dsc$descriptor	*fdl_descriptor;
 *		Releases the fdl descriptor block.
 *
 *	int
 *	fdl_read(buffer, buffer_length, r)
 *	char		*buffer;
 *	int		buffer_length;
 *	FDLSTUFF	*r;
 *		Read buffer_length bytes from the file (using SYS$READ).
 *		No expansion or interpretation.  buffer_length had
 *		better be even or you're asking for trouble.  Returns
 *		the actual number of bytes read.  The file has been
 *		opened by fdl_open.
 *
 *	int
 *	fdl_write(buffer, buffer_length, r)
 *	char		*buffer;
 *	int		buffer_length;
 *	FDLSTUFF	*r;
 *		Write buffer_length bytes to the file (using SYS$WRITE).
 *		No expansion or interpretation.  buffer_length had
 *		better be even or you're asking for trouble.  Returns
 *		the actual number of bytes written.  The file was opened
 *		by fdl_create();
 *
 *	fdl_getname(r, buffer)
 *	FDLSTUFF	*r;
 *	char		*buffer;
 *		Copies the currently open file's name to the caller's
 *		data buffer buffer.
 *
 *	long
 *	fdl_fsize(r)
 *		Returns the size in bytes of the opened file.
 *
 *	fdl_dump(fdl_descriptor, fd)
 *	struct	dsc$descriptor	*fdl_descriptor;
 *	FILE			*fd;
 *		Writes the fdl info to the indicated file with
 *		line breaks in appropriate places.
 *
 *	fdl_message(r, why)
 *	FDLSTUFF	*r;
 *	char		*why;
 *		All system-level routines set a global value, fdl_status.
 *		fdl_message() prints the error message text corresponding
 *		to the current value of fdl_status.  The message printed
 *		has the format:
 *			why current_filename: error_message.
 *		If why is NULL, only the error_message is printed.
 */

#include "lz.h"
#if VMS_V4
#ifndef	FDL$M_FDL_SIGNAL
#define FDL$M_FDL_SIGNAL	1	/* Signal errors if set		*/
#endif
#ifndef	FDL$M_FDL_STRING
#define FDL$M_FDL_STRING	2	/* Use string for fdl text	*/
#endif
#if TESTING_FDLIO
#define	SIGNAL_ON_ERROR	FDL$M_FDL_SIGNAL
#else
#define	SIGNAL_ON_ERROR	0
#endif
#define MSG(what, code)	\
	fprintf(stderr, "Unexpected error at %s, code %X:\n\"%s\"\n", \
	what, code, vms_etext(code));

int		fdl_status;		/* Set to last rms call status	*/

static char *
vms_etext(errorcode)
int		errorcode;
{
	char		*bp;
	short		errlen;		/* Actual text length		*/
	static char	errname[257];	/* Error text stored here	*/
	static $DESCRIPTOR(err, errname); /* descriptor for error text	*/

	lib$sys_getmsg(&errorcode, &errlen, &err, &15);
	/*
	 * Trim trailing junk.
	 */
	for (bp = &errname[errlen]; --bp >= errname;) {
	    if (isgraph(*bp) && *bp != ' ')
		break;
	}
	bp[1] = EOS;
	return(errname);
}

static FDLSTUFF *
fail(r, why, name)
FDLSTUFF	*r;			/* Buffer			*/
char		*why;			/* A little commentary		*/
char		*name;			/* Argument to perror		*/
/*
 * Problem exit routine
 */
{
	if (name == NULL
	 && r != NULL)
	    name = r->fab.fab$l_fna;
	if (name == NULL)
	    name = "<unknown file name>";
	if ($VMS_STATUS_SUCCESS(fdl_status)) {
	    fprintf(stderr, "%s: restriction -- %s\n", name, why);
	    fdl_status = SS$_ABORT;	/* Force an error status	*/
	}
	else {
	    message(r, why, name);
	}
	freefdlstuff(r);
	return (NULL);
}

FDLSTUFF *
fdl_open(filename,  fdl_descriptor)
char			*filename;		/* What to open		*/
struct	dsc$descriptor	*fdl_descriptor;	/* Result descriptor	*/
/*
 * Open the file.  Returns NULL on failure, else a pointer to RMS stuff.
 * Which is equivalently a pointer to the RAB. (Note that the RAB points
 * in turn to the FAB.)
 *
 * Return the file's fdl descriptor in the user-supplied (uninitialized)
 * descriptor.
 */
{
	register FDLSTUFF	*r;
	extern FDLSTUFF		*fdl_setup();

	if ((r = fdl_setup(filename)) == NULL)
	    return (NULL);
	/*
	 * Now open the file.
	 */
	r->fab.fab$b_fac = FAB$M_GET | FAB$M_BRO;
	if ((fdl_status = sys$open(&r->fab)) != RMS$_NORMAL) {
	    return (fail(r, "opening file", NULL));
	}
	if ((r->fab.fab$l_dev & DEV$M_REC) != 0) {
	    fail(r, "can't read from record only device", NULL);
	    fdl_close(r);
	    return (NULL);
	}
	if (r->fab.fab$b_org == FAB$C_IDX) {
	    /*
	     * (Much) more hacking for indexed-sequential
	     * to get all the key and allocation blocks.
	     */
	    int			i, nall, nkey;
	    char		**chain;	/* Links xab's together	*/
	    struct myxabkey {
		struct XABKEY	xabkey;		/* The xab		*/
		char		keyname[32];	/* Optional key name	*/
	    }			*key;
	    struct XABALL	*all;

	    nall = r->xabsum.xab$b_noa;	/* Allocation control blocks	*/
	    nkey = r->xabsum.xab$b_nok;	/* Isam keys			*/
	    fdl_close(r);		/* Close it to try again	*/
	    if ((r = fdl_setup(filename)) == NULL)
		return (NULL);
	    chain = &r->xabsum.xab$l_nxt;	/* Start xab block list	*/
	    for (i = 0; i < nkey; i++) {
		key = malloc(sizeof (struct myxabkey));
		key->xabkey = cc$rms_xabkey;
		key->xabkey.xab$b_ref = i;	/* Set key ref number	*/
	 	key->xabkey.xab$l_knm = &key->keyname[0];
		*chain = key;
		chain = &key->xabkey.xab$l_nxt;
	    }
	    for (i = 0; i < nall; i++) {
		all = malloc(sizeof (struct XABALL));
		*all = cc$rms_xaball;
		all->xab$b_aid = i;		/* Allo area ref number	*/
		*chain = all;
		chain = &all->xab$l_nxt;
	    }
	    r->fab.fab$b_fac = FAB$M_GET | FAB$M_BRO;
	    if ((fdl_status = sys$open(&r->fab)) != RMS$_NORMAL) {
		MSG("fab status", r->fab.fab$l_sts);
		MSG("fab stv   ", r->fab.fab$l_stv);
		return (fail(r, "reopening ISAM file", NULL));
	    }
	}
	r->rab.rab$l_rop = RAB$M_BIO;		/* Block I/O only	*/
	if ((fdl_status = sys$connect(&r->rab)) != RMS$_NORMAL)
	    return (fail(r, "connecting after open", NULL));
	if (fdl_descriptor != NULL) {
	    struct FAB		*fab_add;
	    struct RAB		*rab_add;
	    static int		flags = (FDL$M_FDL_STRING | SIGNAL_ON_ERROR);
	    auto int		badblk;
	    auto int		retlen;
	    /*
	     * Now, get the file attributes
	     */
	    fdl_descriptor->dsc$w_length = 0;
	    fdl_descriptor->dsc$b_dtype = DSC$K_DTYPE_T;
	    fdl_descriptor->dsc$b_class = DSC$K_CLASS_D;
	    fdl_descriptor->dsc$a_pointer = NULL;
	    fab_add = &r->fab;
	    rab_add = &r->rab;
	    if ((fdl_status = fdl$generate(
		    &flags,
		    &fab_add,
		    &rab_add,
		    0L,			/* file_dst (unused)		*/
		    0L,			/* file_resnam (unused)		*/
		    fdl_descriptor,
		    &badblk,
		    &retlen)) != SS$_NORMAL) {
		fdl_free(fdl_descriptor);
		sys$close(&r->fab);
		return(fail(r, "getting fdl info", NULL));
	    }
	}
	return (r);
}

FDLSTUFF *
fdl_create(fdl_descriptor, override_filename, xab_lrl)
struct	dsc$descriptor	*fdl_descriptor;	/* Result descriptor	*/
char			*override_filename;	/* What to open		*/
int			xab_lrl;		/* Longest record len	*/
/*
 * Create the file, Returns NULL on failure, else a pointer to RMS stuff.
 * Which is equivalently a pointer to the RAB. (Note that the RAB points
 * in turn to the FAB.)  The file is open for writing using fdl_write.
 *
 * Uses the filename in the descriptor block, or the override filename
 * if supplied (non-NULL and not == "");
 *
 * If fdl_descriptor is NULL, the override_filename is opened normally.
 *
 * If xab_lrl is non-zero, it sets the file-header "longest record"
 * field.
 */
{
	register FDLSTUFF	*r;
	int			retlen;
	int			badblk;
	static int		flags = (FDL$M_FDL_STRING | SIGNAL_ON_ERROR);
	struct	dsc$descriptor	newname;
	struct	dsc$descriptor	*newname_ptr;
	int			fid_block[3];
	auto int		rms_status, rms_stv;
	char			created_name[NAM$C_MAXRSS + 1];
	struct	dsc$descriptor	created_name_des = {
				    NAM$C_MAXRSS,
				    DSC$K_DTYPE_T,
				    DSC$K_CLASS_S,
				    &created_name[0]
				};
	extern FDLSTUFF		*fdl_setup();

	if (fdl_descriptor == NULL) {
	    /*
	     * Normal file open.
	     */
	    if ((r = fdl_setup(override_filename)) == NULL)
		return (NULL);
	    r->fab.fab$l_alq = 256;		/* Allocation quantity	*/
	    r->fab.fab$b_fac = FAB$M_PUT | FAB$M_BIO; /* Block I/O only	*/
	    r->fab.fab$l_fop |=
		(FAB$M_TEF | FAB$M_NAM | FAB$M_SQO | FAB$M_BIO);
	    r->fab.fab$b_org = FAB$C_SEQ;	/* Sequential only	*/
	    r->fab.fab$b_rfm = FAB$C_UDF;	/* Undefined format	*/
	    r->xabfhc.xab$w_lrl = xab_lrl;	/* Set record length	*/
	    fdl_status = sys$create(&r->fab);
	    if (!$VMS_STATUS_SUCCESS(fdl_status))
		return (fail(r, "creating file", NULL));
	    goto exit;				/* Normal exit		*/
	}
	if (override_filename == NULL || override_filename[0] == '\0')
	    newname_ptr = NULL;
	else {
	    newname_ptr = &newname;
	    newname.dsc$w_length = strlen(override_filename);
	    newname.dsc$b_dtype = DSC$K_DTYPE_T;
	    newname.dsc$b_class = DSC$K_CLASS_S;
	    newname.dsc$a_pointer = override_filename;
	}

	fdl_status = fdl$create(fdl_descriptor,
		newname_ptr,		/* New file name if any		*/
		0,			/* Default filename		*/
		&created_name_des,	/* Resultant filename		*/
		&fid_block[0],		/* File ID block		*/
		&flags,			/* FDL flag bits		*/
		0,			/* Statement number		*/
		&retlen,		/* Created name length		*/
		&rms_status,		/* FAB$L_STS code		*/
		&rms_stv		/* FAB$L_STV			*/
	);
	if (!$VMS_STATUS_SUCCESS(fdl_status)) {
	    MSG("FAB$L_STS", rms_status);
	    MSG("FAB$L_STV", rms_stv);
	    return(fail(NULL, "creating file", NULL));
	}
	created_name[retlen] = '\0';
	if ((r = fdl_setup(created_name)) == NULL)
	    return (NULL);
	/*
	 * Now, open the file for output.
	 */
	r->fab.fab$b_fac =
	    (xab_lrl == 0) ? FAB$M_PUT | FAB$M_BIO
			   : FAB$M_PUT | FAB$M_BRO;
	if ((fdl_status = sys$open(&r->fab)) != RMS$_NORMAL) {
	    return (fail(r, "opening created file", NULL));
	}
exit:	if ((r->fab.fab$l_dev & DEV$M_REC) != 0) {
	    fail(r, "Can't write record only device", NULL);
	    fdl_close(r);
	    return (NULL);
	}
	/*
	 * We do a bit of a hack to force "longest record length"
	 */
	if (xab_lrl != 0
	 && r->fab.fab$b_org == FAB$C_SEQ) {	/* Seq. files only	*/
	    char		*tempbuf;
	    extern char		outbuffer[MAXIO];

	    if (xab_lrl <= MAXIO)
		tempbuf = &outbuffer[0];
	    else if ((tempbuf = malloc(xab_lrl)) == NULL)
		return (fail(r, "allocating memory for longest record", NULL));
	    r->rab.rab$l_rop = 0;		/* PUT I/O 		*/
	    if ((fdl_status = sys$connect(&r->rab)) != RMS$_NORMAL)
		return (fail(r, "connect after create to set length", NULL));
	    r->rab.rab$l_rbf = tempbuf;
	    r->rab.rab$w_rsz = xab_lrl;
	    if ((fdl_status = sys$put(&r->rab)) != RMS$_NORMAL) {
		if (tempbuf != &outbuffer[0])
		    free(tempbuf);
		return (fail(r, "putting longest record", NULL));
	    }
	    if (tempbuf != &outbuffer[0])
		free(tempbuf);
	    if ((fdl_status = sys$disconnect(&r->rab)) != RMS$_NORMAL)
		return (fail(r, "disconnecting after setting length", NULL));
	}
	r->rab.rab$l_rop = RAB$M_BIO;	/* Block I/O only	*/
	if ((fdl_status = sys$connect(&r->rab)) != RMS$_NORMAL)
	    return (fail(r, "connecting after create", NULL));
	return (r);
}

static FDLSTUFF *
fdl_setup(filename)
char		*filename;
/*
 * Initializes rms blocks and parses file name.  Returns the
 * FDL data block on success, NULL on error.
 */
{
	register FDLSTUFF	*r;

	if ((r = (char *)malloc(sizeof (FDLSTUFF))) == NULL)
	    return (NULL);
	r->fab = cc$rms_fab;			/* Preset fab,		*/
	r->nam = cc$rms_nam;			/*   name block		*/
	r->rab = cc$rms_rab;			/*   and record block	*/
	r->xabfhc = cc$rms_xabfhc;		/*   file header block	*/
	r->xabsum = cc$rms_xabsum;		/*   isam summary block	*/
	r->fab.fab$l_nam = &r->nam;		/* fab -> name block	*/
	r->fab.fab$l_xab = &r->xabfhc;		/* fab -> file header	*/
	r->fab.fab$l_fna = filename;		/* Argument filename	*/
	r->fab.fab$b_fns = strlen(filename);	/* ... size		*/
	r->rab.rab$l_fab = &r->fab;		/* rab -> fab		*/
						/* Stuff the name block	*/
	r->nam.nam$l_esa = r->starname;		/* Expanded filename	*/
	r->nam.nam$b_ess = NAM$C_MAXRSS;	/* ... size		*/
	r->nam.nam$b_rss = NAM$C_MAXRSS;	/* ... max size		*/
	r->xabfhc.xab$l_nxt = &r->xabsum;	/* And build xab chain	*/
	if ((fdl_status = sys$parse(&r->fab)) != RMS$_NORMAL) {
	    return (fail(r, "parsing", filename));
	}
	((char *)r->nam.nam$l_esa)[r->nam.nam$b_esl] = EOS;
	r->fab.fab$l_fna = r->nam.nam$l_esa;	/* File name		*/
	r->fab.fab$b_fns = r->nam.nam$b_esl;	/* Length		*/
	r->fab.fab$l_fop |= FAB$M_NAM;		/* Use name block	*/
	return (r);
}

fdl_free(fdl_descriptor)
struct	dsc$descriptor	*fdl_descriptor;
/*
 * Release the descriptor
 */
{
	register int status;

	if ((status = str$free1_dx(fdl_descriptor)) != SS$_NORMAL) {
	    fprintf(stderr, "LZ fatal internal error \"%s\"\n", status);
	    lib$signal(status);
	}
}

fdl_close(r)
register FDLSTUFF	*r;
{
	if ((fdl_status = sys$close(&r->fab)) != RMS$_NORMAL)
	    return(fail(r, "closing", NULL));
	freefdlstuff(r);
}

static
freefdlstuff(r)
register FDLSTUFF	*r;
/*
 * Free fdl block and all XAB blocks.
 */
{
	register struct XABSUM	*xthis;
	register struct XABSUM	*xnext;

	if (r == NULL)
	    return;
	for (xnext = r->xabsum.xab$l_nxt; xnext != NULL;) {
	    xthis = xnext;
	    xnext = xnext->xab$l_nxt;
	    free(xthis);
	}
	free(r);
}

int
fdl_read(buffer, buffer_length, r)
char		*buffer;		/* Record			*/
int		buffer_length;		/* Record length		*/
register FDLSTUFF *r;			/* Record info.			*/
/*
 * Read the next record from the file.  Returns number of bytes read or
 * -1 on any error. fdl_status has the status.
 */
{
	r->rab.rab$l_ubf = buffer;
	r->rab.rab$w_usz = buffer_length;
	r->rab.rab$l_bkt = 0;
	if ((fdl_status = sys$read(&r->rab)) != RMS$_NORMAL) {
#if TESTING_FDLIO
	    if (fdl_status != RMS$_EOF) {
		fdl_message(r, "error return from sys$read");
		sleep(1);
	    }
#endif
	    return (-1);
	}
	return (r->rab.rab$w_rsz);
}

int
fdl_write(buffer, buffer_length, r)
char		*buffer;		/* Record			*/
int		buffer_length;		/* Record length		*/
register FDLSTUFF *r;			/* Record info.			*/
/*
 * Write the next record to the file.  Returns number of bytes written or
 * -1 on any error. fdl_status has the status.
 */
{
	r->rab.rab$l_rbf = buffer;
	r->rab.rab$w_rsz = buffer_length;
	r->rab.rab$l_bkt = 0;
	if ((fdl_status = sys$write(&r->rab)) != RMS$_NORMAL) {
#if 1 || TESTING_FDLIO
	    fdl_message(r, "error return from sys$write");
	    fprintf(stderr, "writing %d bytes\n", buffer_length);
	    sleep(1);
#endif
	    return (-1);
	}
	return (r->rab.rab$w_rsz);
}

fdl_getname(r, buffer)
FDLSTUFF	*r;			/* File pointer			*/
char		*buffer;		/* Where to put it		*/
/*
 * Return current file name
 */
{
	strcpy(buffer, r->fab.fab$l_fna);
	return (buffer);
}

long
fdl_fsize(r)
FDLSTUFF	*r;			/* File pointer			*/
/*
 * Return current file size
 */
{
	return (((long) r->xabfhc.xab$l_ebk * 512) + r->xabfhc.xab$w_ffb);
}

fdl_message(r, why)
FDLSTUFF	*r;
char		*why;
/*
 * Print error message
 */
{
	extern char	*vms_etext();

	if (why == NULL) {
	    fprintf(stderr, "\n%s\n\n", vms_etext(fdl_status));
	}
	else {
	    fprintf(stderr, "\n%s%s%s:\n  %s\n\n",
		why,
		(why[0] == EOS) ? "" : " ",
		(r == NULL) ? "" : r->fab.fab$l_fna,
		vms_etext(fdl_status));
	}
}

static
message(r, why, name)
FDLSTUFF	*r;			/* Buffer			*/
char		*why;			/* A little commentary		*/
char		*name;			/* File name			*/
/*
 * Print error message
 */
{
	fprintf(stderr, "\nRMS error when %s %s\n",
	    why, (name == NULL) ? "" : name);
	fprintf(stderr, "\"%s\"\n", vms_etext(fdl_status));
}

fdl_dump(fdl_descriptor, fd)
struct	dsc$descriptor	*fdl_descriptor;
FILE			*fd;
/*
 * Dump the descriptor to fd.
 */
{
	register char	*tp, *end;

	tp = fdl_descriptor->dsc$a_pointer;
	end = tp + fdl_descriptor->dsc$w_length;
	while (tp < end) {
	    if (*tp == '"') {
		do {
		    putc(*tp++, fd);
		} while (*tp != '"');
	    }
	    putc(*tp, fd);
	    if (*tp++ == ';')
		putc('\n', fd);
	}
}


#if	TESTING_FDLIO
/*
 * Test program for rms io
 */
#include <stdio.h>

char			line[133];
char			filename[133];
char			buffer[2048];

main(argc, argv)
int		argc;
char		*argv[];
{
	FDLSTUFF	*old;
	FDLSTUFF	*new;
	int		size, total, nrecords;
	struct	dsc$descriptor	fdl_info;	/* Result descriptor	*/

	for (;;) {
	    fprintf(stderr, "Old file name: ");
	    fflush(stdout);
	    if (gets(line) == NULL)
		break;
	    if (line[0] == EOS)
		continue;
	    if ((old = fdl_open(line, &fdl_info)) == NULL) {
		fprintf(stderr, "open failed\n");
		continue;
	    }
	    fprintf(stderr, "New file name: ");
	    if (gets(line) == NULL)
		break;
	    if ((new = fdl_create(&fdl_info, line, 0)) == NULL) {
		fprintf(stderr, "create failed\n");
		fdl_free(&fdl_info);
		continue;
	    }
	    fdl_getname(old, buffer);
	    fprintf(stderr, "Fdl for \"%s\", size %ld\n",
		buffer, fdl_fsize(old));
	    fdl_dump(&fdl_info, stderr);
	    total = nrecords = 0;
	    while ((size = fdl_read(buffer, sizeof buffer, old)) > 0) {
		fdl_write(buffer, size, new);
		nrecords++;
		total += size;
	    }
	    fdl_close(old);
	    fdl_close(new);
	    fprintf(stderr, "copied %d records, %d bytes total\n",
		nrecords, total);
	    fdl_free(&fdl_info);
	}
}
#endif
#endif
$ eod
$ write sys$output "Now invoking LZ.COM to build LZCOMP and LZDCMP"
$	@LZ
$ write sys$output "Creating foreign commands LZC*OMP and LZD*CMP"
$	LZC*OMP	:== $'F$ENVIRONMENT("DEFAULT")'LZCOMP
$	LZD*CMP	:== $'F$ENVIRONMENT("DEFAULT")'LZDCMP
$ write sys$output "Done."
