/*
 *		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.)
	.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.

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
		$(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 = 0;		/* Assume VMS private		*/
#else
flag		export = 1;		/* Assume Unix compatible	*/
#endif
flag		binary = FALSE;		/* Read text if false		*/
flag		block_compress = TRUE;	/* TRUE if CLEAR enabled	*/
flag		noheader = FALSE;	/* No magic header if TRUE	*/
flag		verbose = VERBOSE_DEFAULT; /* Non-zero for status/debug	*/
flag		background = FALSE;	/* TRUE (Unix) if detached	*/
flag		is_compress = FALSE;	/* For lzio.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];
static STREAM	mem_stream;
#if VMS_V4
#include <descrip.h>
#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[];
/*
 * Decompress mainline
 */
{
	int		result;

#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 (verbose > 0)
		signal(SIGINT, abort);
	    else {
		signal(SIGINT, interrupt);
		signal(SIGSEGV, address_error);
	    }
	}
#endif
	if (setjmp(failure) == 0) {
	    setup(argc, argv);
	    openinput();
	    get_magic_header();			/* Sets export, etc.	*/
	    openoutput();
	    if (verbose > 0)
		start_time = cputime();
	    init_decompress();
	    result = decompress(&outstream);
	    if (!export
	     && result != LZ_ETX
	     && getcode() != (code_int) LZ_ETX) {
		fprintf(stderr, "Decompress didn't finish correctly.\n");
		goto fail;
	    }
	    lz_flush(&outstream);
#if DEBUG
	    if ((verbose & 04) != 0)
		dump_tab(stdout);
#endif
	    if (verbose > 0) {
		start_time = cputime() - start_time;
		fprintf(stderr,
		    "%ld.%02ld seconds (process time) for decompression.\n",
		    start_time / 1000L, (start_time % 1000L) / 10L);
	    }
	    exit(IO_SUCCESS);
	}
	else {
fail:	    fprintf(stderr, "Error when decompressing \"%s\" to \"%s\"\n",
		(infilename  == NULL) ? 
		    "<input file unknown>" : infilename,
		(outfilename == NULL) ?
		    "<no output file>" : outfilename);
	    if (errno != 0)
		perror("lzdcmp fatal error");
	    exit(IO_ERROR);
	}
}

static
get_magic_header()
{
	int		head1;
	int		head2;
	int		head3;

	head2 = 0;
	if (export != 2) {
	    if ((head1 = GET(&instream)) != HEAD1_MAGIC) {
		fprintf(stderr, "Incorrect first header byte 0x%X\n",
		    head1);
		FAIL("can't get header");
	    }
	    head2 = GET(&instream);
	    head3 = GET(&instream);
	    switch (head2) {
	    case HEAD2_MAGIC:
		export = 1;
		break;

	    case VMS_HEAD2_MAGIC:
		export = 0;
		break;

	    default:
		fprintf(stderr, "Incorrect second header byte 0x%X\n",
		    head2);
		FAIL("can't get header");
	    }
	    maxbits = head3 & BIT_MASK;
	    block_compress = ((head3 & BLOCK_MASK) != 0) ? TRUE : FALSE;
#if DEBUG
	    if (verbose > 1) {
		fprintf(stderr, "%s: compressed with %d bits,",
		   infilename, maxbits);
		fprintf(stderr, " block compression %s.\n",
		    (block_compress != 0) ? "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");
	}
	maxmaxcode = 1 << maxbits;
	if (export == 0)
	    firstcode = GET(&instream) + 0x100;	/* From compressed file	*/
	else if (block_compress)
	    firstcode = LZ_CLEAR + 1;		/* Default		*/
	else
	    firstcode = 256;			/* Backwards compatible	*/
#if VMS_V4
	if (!export) {
	    register code_int	code;
	    char		text[256];
	    /*
	     * Get the attribute record.
	     */
	    if ((code = getcode()) != LZ_SOH) {
		fprintf(stderr, "Expected header, read 0x%X\n", code);
		FAIL("can't get header (private)");
	    }
	    init_decompress();
	    code = mem_decompress(text, sizeof text);
	    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");
	    }
	    code = atoi(text + ATT_SIZE);
	    fdl_descriptor.dsc$a_pointer = malloc(code);
	    fdl_descriptor.dsc$w_length = code;
	    if ((code = mem_decompress(fdl_descriptor.dsc$a_pointer, 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");
	    }
	    if (verbose > 1) {
		fprintf(stderr, "\nFDL information read from \"%s\"\n",
		    infilename);
		fdl_dump(&fdl_descriptor, stderr);
	    }
	    if ((code = getcode()) != LZ_STX) {
		fprintf(stderr, "\nExpecting start of text, got 0x%X\n", code);
		FAIL("no start of text");
	    }
	}
#endif
}

int
mem_decompress(buffer, size)
char_type	*buffer;
int		size;
/*
 * Decompress up to size bytes to buffer.  Return actual size.
 */
{
	int		result;

	mem_stream.bp = mem_stream.bstart = buffer;
	mem_stream.bend = buffer + size;
	mem_stream.bsize = size;
	mem_stream.func = lz_fail;
	if ((result = decompress(&mem_stream)) == LZ_EOR
	 || result == LZ_ETX)
	    return (mem_stream.bp - buffer);
	else {
	    fprintf(stderr, "Decompress to memory failed.\n");
	    FAIL("can't decompress to memory");
	}
	return (-1);				/* Can't happen		*/
}

static readonly char *helptext[] = {
	"The following options are valid:",
	"-B\tBinary file (important on VMS/RSX, ignored on Unix)",
	"-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;

#ifdef	vms
	argc = getredirection(argc, argv);
#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':
			binary = TRUE;
			break;

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

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

		    case 'X':
			export = getvalue(ap, &i, argv);
			if (export < 0 || export > 3) {
			    fprintf(stderr, "Illegal -X value: %d\n", export);
			    goto usage;
			}
			block_compress = (export < 2);
			noheader = (export == 3);
			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");
		    }				/* 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");
	}
}

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");
	}
#else
#ifdef vms
#if VMS_V4
	if (!export) {
	    if (infilename == NULL) {
		infilename = malloc(256 + 1);
		fgetname(stdin, infilename);
		infilename = realloc(infilename, strlen(infilename) + 1);
	    }
	    if ((fdl_input = fdl_open(infilename, NULL)) == NULL) {
		fdl_message(NULL, infilename);
		FAIL("can't open compressed input (vms private)");
	    }
	}
	else
#endif
	{
	    if (infilename == NULL) {
		infilename = malloc(256 + 1);
		fgetname(stdin, infilename);
		infilename = realloc(infilename, strlen(infilename) + 1);
	    }
	    else {
		if (freopen(infilename, "r", stdin) == NULL) {
		    perror(infilename);
		    FAIL("can't open compressed input (export)");
		}
	    }
	}
#else
	if (infilename == NULL)
	    infilename = "<stdin>";
	else {
	    if (freopen(infilename, "r", stdin) == NULL) {
		perror(infilename);
		FAIL("can't open input");
	    }
	}
#endif
#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) {
	    fclose(stdout);
	    stdout = NULL;
	    if ((fdl_output =
		    fdl_create(&fdl_descriptor, outfilename)) == NULL) {
		fprintf(stderr, "Can't create output file\n");
		if ((fdl_status & 01) == 0)
		    fdl_message(NULL, outfilename);
		FAIL("can't create output (vms private)");
	    }
	    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 (!binary)
		    goto do_reopen;
	    }
	    else {
		if (binary) {
		    if (freopen(outfilename, "w", stdout) == NULL) {
			perror(outfilename);
			FAIL("can't create output (binary)");
		    }
		}
		else {
		    int		i;
do_reopen:
		    if ((i = creat(outfilename, 0, "rat=cr", "rfm=var")) == -1
		     || dup2(i, fileno(stdout)) == -1) {
			perror(outfilename);
			FAIL("can't create output (text)");
		    }
		}
	    }
	}
#else
#ifdef decus
	if (outfilename == NULL) {
	    outfilename = malloc(256 + 1);
	    fgetname(stdout, outfilename);
	    outfilename = realloc(outfilename, strlen(outfilename) + 1);
	    if (binary) {
		if (freopen(outfilename, "wn", stdout) == NULL) {
		    perror(outfilename);
		    FAIL("can't create (binary)");
		}
	    }
	}
	else {
	    if (freopen(outfilename, (binary) ? "wn" : "w", stdout) == NULL) {
		perror(outfilename);
		FAIL("can't create");
	    }
	}
#else
	if (outfilename == NULL)
	    outfilename = "<stdout>";
	else {
	    if (freopen(outfilename, "w", stdout) == NULL) {
		perror(outfilename);
		FAIL("can't create");
	    }
	}
#endif
#endif
	outstream.bp = outstream.bstart = outbuffer;
	outstream.bend = outbuffer + sizeof outbuffer;
	outstream.bsize = sizeof outbuffer;
	outstream.func = lz_flush;
}
