
/*
 *  RNEWS.C
 *
 *  RNEWS [file]    (if not specified, stdin is used)
 *
 *  Copyright 1988 by William Loftus.  All rights reserved.
 *  Extensive Changes Copyright 1990 by Matthew Dillon, All Rights Reserved
 *
 *  This is rnews for compressed news.	News 2.11 will uux
 *  a file to this system that will be in compressed format.
 *  This program will strip off the "#! cunbatch", uncompress
 *  the news, and call unbatch.  If the news is not in compressed
 *  format it will just pass it to unbatch.
 *
 *  Each newsgroup directory has a .next file which indicates the next
 *  article number to write.  This number rolls over at 32767.
 */

#include <exec/types.h>
#include <exec/ports.h>
#include "news.h"
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/stat.h>	    /* outrageous place for the mkdir() proto! */
#include <log.h>
#include <ForwardNews.h>    /* MRR, 09-23-90. */

typedef struct MsgPort	MsgPort;

typedef struct NGInfo {
    struct NGInfo *Next;
    char    *NewsGroup;
    long    Articles;
    long    Bytes;
    short   JunkFlag;
} NGInfo;

IDENT(".06");

static int dofile(void);
static void unbatch(char *);
static long store_art(long size, char *);
static void uncompress_news(void);
static void news_exit(int stat);

#define exit news_exit

static char TmpBuf[2048];	/*  at least 1K */
static char NewsGroups[1024];
static char Path[4096]; 	/*  Path: line	*/
char homedir[256];

char *NewsDir;
char *WKName;
short DDebug;
short UHaveNews;
NGInfo *NGBase; 	/*  list of news handled    */

void copy_uualtspool();
void NewNewsHandler();

#define PROG_NAME "RNews"

int
main(ac, av)
int ac;
char *av[];
{
    int ret = 0;
    short i;

    LogProgram = PROG_NAME;

    getcwd(homedir, sizeof(homedir));
    NewsDir = GetConfigDir(UUNEWS);

    if (chdir(NewsDir) != 0) {
	ulog(0, "NewsDir %s doesn't exist; quitting", NewsDir);
	exit(1);
    }
    WKName = strdup(TmpFileName("news-work"));

    for (i = 1; i < ac; ++i) {
	char *ptr = av[i];
	if (*ptr != '-')
	    break;
	switch(ptr[1]) {
	case 'd':
	    DDebug = (ptr[2]) ? atoi(ptr + 2) : 1;
	    break;
	case 'x':
	    LogLevel = atoi(ptr + 2);
	    break;
	default:
	    printf("unknown flag '%s' ignored\n", ptr + 1);
	    break;
	}
    }

    if (GetForwardingInfo() != 0) {     /* MRR, 09-23-90. */
	ulog(0,"Failed to get news forwarding information - abort.");
	news_exit(1);
    }

    {
	short usestdin = 1;

	for (i = 1; i < ac; ++i) {
	    char *ptr = av[i];

	    if (*ptr == '-')
		continue;
	    usestdin = 0;

	    chdir(homedir);         /* so relative file names will work */
	    ulog(1, "Starting file %s", ptr);
	    if (freopen(ptr, "r", stdin) == NULL) {
		ulog(0, "Cannot open news file %s", ptr);
		continue;
	    }
	    chdir(NewsDir);
	    if (dofile() < 0)
		ret = 1;
	}
	if (usestdin) {
	    ulog(1, "Starting standard input");

	    if (atoi(GetConfig(RNEWSDEBUG, "0"))) {
		ulog(-1, "RNewsDebug, copying to UUAltSpool:");
		copy_uualtspool();
	    }
	    if (dofile() < 0)
		ret = 1;
	}
    }
    if (UHaveNews) {
	MsgPort *port;
	Forbid();
	if ((port = FindPort("T:NewsRdy")) && port->mp_SigTask) {
	    Signal(port->mp_SigTask, 1 << port->mp_SigBit);
	    UHaveNews = 0;
	}
	Permit();
    }
    if (UHaveNews) {
	int fd = open("T:NewsRdy", O_RDONLY);
	if (fd < 0) {
	    char *cmd;

	    if (cmd = FindConfig(NEWSREADYCMD)) {
		fd = open("T:NewsRdy", O_CREAT|O_TRUNC, 0666);
		if (fd >= 0)
		    close(fd);
		sprintf(TmpBuf, "Run %s -x T:NewsRdy", cmd);
		Execute(TmpBuf, NULL, NULL);
	    }
	} else {
	    close(fd);
	}
    }

    if (ret == 0) {
	if (DDebug == 0)
	    remove(WKName);
    }

    news_exit(ret);
}

static int
dofile(void)
{
    char buf[64];

    if (fgets(buf, sizeof(buf), stdin)) {
	if (strncmp(buf, "#! cunbatch", 11) == 0) {
	    uncompress_news();
	} else {
	    unbatch(buf);
	}
    }
    fclose(stdin);
    return(0);
}

/*
 * Unbatch, an Amiga unbatcher.
 *
 *	Written by Fred Cassirer, 10/8/88.
 *	Some unbatch code originally taken from News2.11 sources.
 *
 *	Ported to Lattice 5.0
 *	Added config.h file
 *	Added use of ERRORFILE
 *	11/23/1988  Dan 'Sneakers' Schein
 *
 * This code (as well as the unbatch stuff) is free for anyone who thinks
 * they can get some use out of it.
 *
 *	Articles will only be placed in newsgroups as defined in the
 *	"LIB/NewsGroups" control file.  Articles which are not listed
 *	in the control file are placed in the "junk" directory.  Articles
 *	are sequenced by the sequencer in "LIB/seqnews". This could
 *	possibly be updated to use a sequencer within each of the news
 *	subdirectories, to more closely resemble the News system under Unix.
 *
 *	(sequence numbers are not kept in the NewsGroups file itself but in
 *	each news directory.  This protects against accidents)
 *
 *	The NewsGroups file contains one newsgroup per line, plus an optional
 *	# days expiration.  For example:
 *
 *	    comp.sys.amiga  30
 *
 *	Unbatch will also take command line args of files to be unbatched.
 *	Files on the command line are not removed, they should be removed
 *	after running unbatch.
 */

static struct group {
    struct group *next;
    char name[1];
} groups;

static void
initgroups(void)
{
    struct group *gp;
    long len;
    FILE *fp;
    char buf[50];

    if (groups.next != NULL)
	return;

    if ((fp = openlib("newsgroups")) == NULL) {
	ulog(0, "Couldn't open LIB/newsgroups file");
	exit(2);
    }

    gp = &groups;
    while (fgets(buf, sizeof(buf), fp)) {
	short c;
	for (len = 0; (c = buf[len]) != ' ' && c != 9 && c != '\n'; ++len);
	buf[len] = 0;
	if ((gp->next = (struct group *)malloc(4+len+1)) == NULL) {
	    ulog(0, "Malloc failed!");
	    exit(4);
	}
	gp = gp->next;
	strcpy(gp->name, buf);
    }
    gp->next = NULL;
    fclose(fp);
}

static char *
finddir(dir, artsize)
char *dir;
{
    struct group *gp = NULL;
    static struct group *cache;
    static struct NGInfo *ngcache;

    if (ngcache == NULL || stricmp(ngcache->NewsGroup, dir) != 0) {
	NGInfo *ng;
	for (ng = NGBase; ng; ng = ng->Next) {
	    if (stricmp(ng->NewsGroup, dir) == 0)
		break;
	}
	if (ng == NULL) {
	    ng = malloc(sizeof(NGInfo) + strlen(dir) + 1);
	    ng->NewsGroup = (char *)(ng + 1);
	    ng->Articles = 0;
	    ng->Bytes	 = 0;
	    ng->JunkFlag = 0;
	    ng->Next = NGBase;
	    strcpy(ng->NewsGroup, dir);
	    NGBase = ng;
	}
	ngcache = ng;
    }
    ++ngcache->Articles;
    ngcache->Bytes += artsize;

    if (cache && stricmp(cache->name, dir) == 0)
	return(cache->name);

    for (gp = groups.next; gp; gp = gp->next) {
	if (stricmp(gp->name, dir) == 0) {
	    cache = gp;
	    break;
	}
    }
    if (gp)
	return (gp->name);
    ngcache->JunkFlag = 1;
    return ("junk");        /* Oops, not a known newsgroup */
}

/*
 * unbatchnews: extract news in batched format and process it one article
 * at a time.  The format looks like
 *	#! rnews 1234
 *	article containing 1234 characters
 *	#! rnews 4321
 *	article containing 4321 characters
 */

static void
unbatch(hdr)
char *hdr;
{
    long size;

    ulog(10, "unbatch: %s", hdr);
    if (hdr) {
	strcpy(TmpBuf, hdr);
    } else {
	TmpBuf[0] = 0;
	fgets(TmpBuf, sizeof(TmpBuf), stdin);
    }
    if (strncmp(TmpBuf, "#! rnews ", 9) != 0) {
	store_art(10000000L, TmpBuf);
	return;
    }
    do {
	/* second strcmp is kludge for bug */

	if (strncmp(TmpBuf, "#! rnews ", 9) != 0 && strncmp(TmpBuf, "! rnews ", 8) != 0) {
	    char *cp;

	    for (cp = TmpBuf; *cp; ++cp) {
		if (!isprint(*cp) && !isspace(*cp))
		    *cp = '?';
	    }
	    *--cp = '\0';
	    ulog(0, "out of sync, skipping <%s>", TmpBuf);
	    continue;
	}

	size = atol(TmpBuf + 8 + (TmpBuf[0] == '#'));
	if (size <= 0) {
	    ulog(0, "nonsense size %ld", size);
	    continue;
	}

	if (store_art(size, NULL) > 0) {
	    /*
	     * If we got a truncated batch, don't process the
	     * last article; it will probably be received again.
	     */
	    ulog(0, "truncated batch");
	    break;
	}
    } while (fgets(TmpBuf, sizeof(TmpBuf), stdin));
}

/*
 *  STORE_ART()
 *
 *  note:   if ibuf != NULL then it may be TmpBuf
 */

static long
store_art(size, ibuf)
long size;
char *ibuf;
{
    unsigned long seqno;
    long saveSize = size;
    char *dir;

    ulog(10, "store_art: %d %s", size, ibuf);

    initgroups();

    ulog(2, "SIZE %ld", size);

    {
	FILE *pfn;

	NewsGroups[0] = 0;
	Path[0] = 0;

	if ((pfn = fopen("ArticleTemp", "w")) == NULL) {
	    ulog(0, "Could not creat article temp file");
	    exit(3);
	}
	if (ibuf) {
	    ulog(1, "IBuf = %s\n", ibuf);
	    fputs(ibuf, pfn);
	    if (strncmp(ibuf, "Path:", 5) == 0)
		strcpy(Path, ibuf + 5);
	}

	/*
	 *  Search for Newsgroups: line in a manner independent of line
	 *  length, which can be anything.  Once found, multiple newsgroups
	 *  might be specified so we loop on it.  At the same time keep track
	 *  of the article size
	 *
	 *  Also look for and save the Path: line.
	 */

	{
	    int i = 0;
	    short c;

	    while (size && (c = getc(stdin)) != EOF) {
		putc(c, pfn);
		--size;
		TmpBuf[i++] = c;	    /*	save for later strncmp()          */
		if (i == sizeof(TmpBuf))    /*  if a very long non-NewsGroups: hdr*/
		    i = 0;
		if (c == '\n')              /*  or end of line      */
		    i = 0;
		if (c == ':') {             /*  some kind of header */
		    i = 0;		    /*	reset i     */
		    if (strncmp(TmpBuf, "Path:", 5) == 0) {
			if (fgets(Path, sizeof(Path), stdin)) {
			    fputs(Path, pfn);
			    size -= strlen(Path);
			}
		    }
		    if (strncmp(TmpBuf, "Newsgroups:", 11) == 0) {
			if (fgets(NewsGroups, sizeof(NewsGroups), stdin)) {
			    fputs(NewsGroups, pfn);
			    size -= strlen(NewsGroups);
			    break;
			}
		    }
		}
	    }
	}

	while (size > 0) {
	    int n = (size > sizeof(TmpBuf)) ? sizeof(TmpBuf) : size;
	    int r = fread(TmpBuf, 1, n, stdin);

	    if (r <= 0)
		break;
	    fwrite(TmpBuf, 1, r, pfn);
	    size -= r;
	}
	fflush(pfn);
	if (ferror(pfn)) {              /* disk full? */
	    fclose(pfn);
	    remove("ArticleTemp");
	    ulog(0, "error writing temporary file");
	    return (-1);
	}
	fclose(pfn);
    }

    /*
     *	For each newsgroup in the NewsGroups: line, deal with it
     */

    {
	short copies = 0;			/*  # of copies */
	short error = 0;
	static char LastFile[128];

	dir = strtok(NewsGroups, " \t\n,");
	if (dir == NULL)                        /*  whoops!     */
	    dir = "Rejects";                    /*  dummy name  */

	for (; dir; dir = strtok(NULL, " \t\n,")) {
	    ulog(10, "RNews req: %s", dir);
	    dir = finddir(dir, saveSize);
	    ulog(10, "RNews sto: %s", dir);
	    {
		FILE *seq;
		short localError = 0;

		sprintf(TmpBuf, "%s/.next", dir);

		LockFile(TmpBuf);
		if (IsDir(dir)) {
		    FILE *seq;
		    if ((seq = fopen(TmpBuf, "r")) == NULL) {
			ulog(0, "sequencer file '%s' not found, will create", TmpBuf);
			seqno = 1;
		    } else {
			fscanf(seq, "%ld", &seqno);
			fclose(seq);
		    }
		} else if (mkdir(dir) != 0) {
		    ulog(0, "Cannot create directory for '%s'; article dropped", dir);
		    localError = -1;
		} else {
		    seqno = 1;
		}
		if (localError == 0) {
		    if ((seq = fopen(TmpBuf, "w")) == NULL) {
			ulog(0, "Couldn't create sequencer for '%s'; article dropped", dir);
			localError = -1;
		    } else {
			fprintf(seq, "%ld\n", (seqno + 1) & 0x7FFF);
			fclose(seq);
		    }
		}
		UnLockFile(TmpBuf);
		if (localError)
		    return(-1);
	    }

	    if (copies == 0) {          /*  rename ArticleTemp  */
		sprintf(TmpBuf, "%s/%ld", dir, seqno);
		error = rename("ArticleTemp", TmpBuf);
		++copies;
	    } else {			/*  make copy of file	*/
		sprintf(TmpBuf, "%s/%ld", dir, seqno);
		error = copy_file(LastFile, TmpBuf);
		++copies;
	    }
	    ++UHaveNews;
	    strcpy(LastFile, TmpBuf);

	    if (error) {
		ulog(0, "Unable to rename/copy '%s'; article dropped", LastFile);
	    } else {
		/*
		 *  Forward article to systems that do not appear in our path
		 */

		ForwardArticle(dir, LastFile, Path);    /* MRR, 09-23-90. */
	    }
	}
	return (size);
    }
}

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

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

#ifdef AMIGA			/* Commodore Amiga */
# define BITS		16
# define M_XENIX	/* so we can use small model (faster) */
# undef USERMEM
#endif

#ifdef interdata		/* (Perkin-Elmer) */
#define SIGNED_COMPARE_SLOW	/* signed compare is slower than unsigned */
#endif

#ifdef pdp11
# define BITS	12	/* max bits/code for 16-bit machine */
# define NO_UCHAR	/* also if "unsigned char" functions as signed char */
# undef USERMEM
#endif /* pdp11 */	/* don't forget to compile with -i */

#ifdef z8000
# define BITS	12
# undef vax		/* weird preprocessor */
# undef USERMEM
#endif /* z8000 */

#ifdef pcxt
# define BITS	12
# undef USERMEM
#endif /* pcxt */

#ifdef USERMEM
# 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
#     define PBITS	12
#    endif
#   endif
#  endif
# endif
# undef USERMEM
#endif /* USERMEM */

#ifdef PBITS		/* Preferred BITS for this memory size */
# ifndef BITS
#  define BITS PBITS
# endif BITS
#endif /* PBITS */

#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

#ifdef M_XENIX			/* Stupid compiler can't handle arrays with */
# if BITS == 16 		/* more than 65535 bytes - so we fake it */
#  define XENIX_16
# else
#  if BITS > 13 		/* Code only handles BITS = 12, 13, or 16 */
#   define BITS 13
#  endif
# endif
#endif

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

#ifdef SIGNED_COMPARE_SLOW
typedef unsigned long int count_int;
#else
typedef long int	count_int;
#endif

#ifdef NO_UCHAR
typedef char		char_type;
#else
typedef unsigned char	char_type;
#endif /* UCHAR */
char_type magic_header[] = { "\037\235" };      /* 1F 9D */

/* Defines for third byte of header */
#define BIT_MASK	0x1f
#define BLOCK_MASK	0x80
/* Masks 0x40 and 0x20 are free.  I think 0x20 should mean that there is
   a fourth header byte (for expansion).
*/
#define INIT_BITS	9	/* initial number of bits/code */

int n_bits;			/* number of bits/code */
int maxbits = BITS;		/* user settable max # bits/code */
code_int maxcode;		/* maximum code, given n_bits */
code_int maxmaxcode;	/* 1 << BITS */ /* should NEVER generate this code */
#define MAXCODE(n_bits)         ((((code_int)1) << (n_bits)) - 1)

#ifdef XENIX_16
count_int * htab[9] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
unsigned short * codetab[5] = { NULL, NULL, NULL, NULL, NULL };
#else	/* Normal machine */
count_int	htab[HSIZE];
unsigned short	codetab[HSIZE];
#endif /* XENIX_16 */
code_int hsize = HSIZE; 	/* for dynamic table sizing */

/*
 * The tab_suffix table needs 2**BITS characters.  We get this from the
 * beginning of htab.  The output stack uses the rest of htab, and contains
 * characters.	There is plenty of room for any possible stack (stack used
 * to be 8000 characters).
 */

#ifdef XENIX_16
# define tab_prefixof(i)        (codetab[(i)>>14][(i) & 0x3FFF])
# define tab_suffixof(i)        ((char_type *)htab[(i)>>15])[(i) & 0x7FFF]
# define de_stack		((char_type *)(htab[2]))
#else	/* Normal machine */
# define tab_prefixof(i)        codetab[i]
# define tab_suffixof(i)        (((char_type *)htab)[i])
/* FIXME??? This next line is suspect..... */
# define de_stack		((char_type *)(((long)htab)+(((code_int)1)<<BITS)))
#endif /* XENIX_16 */

code_int free_ent = 0;	/* first unused entry */

static code_int getcode(void);
static void writeerr(void);

/*
 * block compression parameters -- after all codes are used up,
 * and compression rate changes, start over.
 */
int block_compress = BLOCK_MASK;
int clear_flg = 0;

/*
 * the next two codes should not be changed lightly, as they must not
 * lie within the contiguous general code space.
 */
#define FIRST	257	/* first free entry */
#define CLEAR	256	/* table clear output code */

/*
 * Decompress stdin to stdout.	This routine adapts to the codes in the
 * file building the "string" table on-the-fly, requiring no table to
 * be stored in the compressed file.
 */

#ifdef XENIX_16

static void *
safealloc(long size)
{
    int retry = 30;
    void *p;

    while ((p = malloc(size)) == NULL) {
	if (--retry < 0) {
	    ulog(0, "Can't get memory to decompress news batch -- terminating");
	    exit(20);
	}
	ulog(0, "Can't get memory to decompress news batch -- will retry");
	sleep(30);
    }
    return p;
}

#endif /* XENIX_16 */

static void
decompress(void)
{
	register char_type *stackp;
	register int finchar;
	register code_int code, oldcode, incode;

	maxcode = MAXCODE(n_bits = INIT_BITS);
	free_ent = ((block_compress) ? FIRST : 256);
	finchar = oldcode = getcode();
	if(oldcode == -1)       /* EOF already? */
		return; 	/* Get out of here */

#ifdef XENIX_16
	htab[0] = (code_int *)safealloc(8192*sizeof(code_int));
	htab[1] = (code_int *)safealloc(8192*sizeof(code_int));
	htab[2] = (code_int *)safealloc(8192*sizeof(char));
	for (code = 0; code < 5; ++code)
		codetab[code] = (unsigned short *)
				safealloc(16384*sizeof(unsigned short));
#endif /* XENIX_16 */

	/*
	 * As above, initialize the first 256 entries in the table.
	 */
	for (code = 255; code >= 0; code--) {
		tab_prefixof(code) = 0;
		tab_suffixof(code) = (char_type)code;
	}

	putchar((char)finchar); /* first code must be 8 bits = char */
	if(ferror(stdout))      /* Crash if can't write */
		writeerr();
	stackp = de_stack;

	while ((code = getcode()) > -1) {
		if ((code == CLEAR) && block_compress) {
			for (code = 255; code >= 0; code--)
				tab_prefixof(code) = 0;
			clear_flg = 1;
			free_ent = FIRST - 1;
			if ((code = getcode()) == -1)   /* O, untimely death! */
				break;
		}
		incode = code;
		/*
		 * Special case for KwKwK string.
		 */
		if (code >= free_ent) {
			*stackp++ = finchar;
			code = oldcode;
		}

		/*
		 * Generate output characters in reverse order
		 */
#ifdef SIGNED_COMPARE_SLOW
		while (((unsigned long)code) >= ((unsigned long)256))
#else
		while (code >= 256)
#endif
		{
			*stackp++ = tab_suffixof(code);
			code = tab_prefixof(code);
		}
		*stackp++ = finchar = tab_suffixof(code);

		/*
		 * And put them out in forward order
		 */
		do {	register int c = *--stackp;
			putc(c, stdout);        /* putc is often a macro! */
		} while (stackp > de_stack);

		/*
		 * Generate the new entry.
		 */
		if ((code=free_ent) < maxmaxcode) {
			tab_prefixof(code) = (unsigned short)oldcode;
			tab_suffixof(code) = finchar;
			free_ent = code+1;
		}
		/*
		 * Remember previous code.
		 */
		oldcode = incode;
	}

	fflush(stdout);
	if(ferror(stdout))
		writeerr();

#ifdef XENIX_16
	free(htab[0]), htab[0] = NULL;
	free(htab[1]), htab[1] = NULL;
	free(htab[2]), htab[2] = NULL;
	for (code = 0; code < 5; ++code)
		free(codetab[code]), codetab[code] = NULL;
#endif /* XENIX_16 */
}

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

#ifndef vax
char_type rmask[9] = {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};
#endif /* vax */

static code_int
getcode(void)
{
	/*
	 * On the VAX, it is important to have the register declarations
	 * in exactly the order given, or the asm will break.
	 */
	register code_int code;
	static int offset = 0, size = 0;
	static char_type buf[BITS];
	register int r_off, bits;
	register char_type *bp = buf;

	if (clear_flg > 0 || offset >= size || free_ent > maxcode) {
		/*
		 * If the next entry will be too big for the current code
		 * size, then we must increase the size.  This implies reading
		 * a new buffer full, too.
		 */
		if (free_ent > maxcode) {
			n_bits++;
			if (n_bits == maxbits)
				/* won't get any bigger now */
				maxcode = maxmaxcode;
			else
				maxcode = MAXCODE(n_bits);
		}
		if (clear_flg > 0) {
			maxcode = MAXCODE(n_bits = INIT_BITS);
			clear_flg = 0;
		}
		size = fread(buf, 1, n_bits, stdin);
		if (size <= 0)
			return -1;	/* end of file */
		offset = 0;
		/* Round size down to integral number of codes */
		size = (size << 3) - (n_bits - 1);
	}
	r_off = offset;
	bits = n_bits;
#ifdef vax
	asm("extzv      r10,r9,(r8),r11");
#else /* not a vax */
	/*
	 * Get to the first byte.
	 */
	bp += (r_off >> 3);
	r_off &= 7;
	/* Get first part (low order bits) */
#ifdef NO_UCHAR
	code = ((*bp++ >> r_off) & rmask[8 - r_off]) & 0xff;
#else
	code = (*bp++ >> r_off);
#endif /* NO_UCHAR */
	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) {
#ifdef NO_UCHAR
		code |= (*bp++ & 0xff) << r_off;
#else
		code |= *bp++ << r_off;
#endif /* NO_UCHAR */
		r_off += 8;
		bits -= 8;
	}
	/* high order bits. */
	code |= (*bp & rmask[bits]) << r_off;
#endif /* vax */
	offset += n_bits;

	return code;
}

#ifdef DEBUG

static void _fprintf(FILE *fi, const char *ctl, ...) { }

static /*volatile*/ void
printcodes(void)
{
	/*
	 * Just print out codes from input file.  For debugging.
	 */
	code_int code;
	int col = 0, bits;

	bits = n_bits = INIT_BITS;
	maxcode = MAXCODE(n_bits);
	free_ent = ((block_compress) ? FIRST : 256);
	while ((code = getcode()) >= 0) {
		if ((code == CLEAR) && block_compress) {
			free_ent = FIRST - 1;
			clear_flg = 1;
		} else if (free_ent < maxmaxcode)
			free_ent++;
		if (bits != n_bits) {
			_fprintf(stderr, "\nChange to %d bits\n", n_bits);
			bits = n_bits;
			col = 0;
		}
		_fprintf(stderr, "%5d%c", code,
			(col+=6) >= 74 ? (col = 0, '\n') : ' ');
	}
	_fprintf(stderr, "\n");
	exit(0);
}

static int
in_stack(register int c, register int stack_top)
{
	if ((isprint(c) && c != '\\') || c == ' ') {
		de_stack[--stack_top] = c;
	} else {
		switch(c) {
		case '\n': de_stack[--stack_top] = 'n'; break;
		case '\t': de_stack[--stack_top] = 't'; break;
		case '\b': de_stack[--stack_top] = 'b'; break;
		case '\f': de_stack[--stack_top] = 'f'; break;
		case '\r': de_stack[--stack_top] = 'r'; break;
		case '\\': de_stack[--stack_top] = '\\'; break;
		default:
			de_stack[--stack_top] = '0' + c % 8;
			de_stack[--stack_top] = '0' + (c / 8) % 8;
			de_stack[--stack_top] = '0' + c / 64;
			break;
		}
		de_stack[--stack_top] = '\\';
	}
	return stack_top;
}
#endif /* DEBUG */

static void
writeerr(void)
{
    ulog(0, "Error while writing to '%s'", WKName);
    remove(WKName);
    exit(1);
}

static void
uncompress_news(void)
{
    if (freopen(WKName, "w", stdout) == NULL) {
	ulog(0, "Can't open uncompressed file for output");
	return;
    }

    /* Check the magic number */

    if ((getc(stdin)!=(magic_header[0] & 0xFF))
      || (getc(stdin)!=(magic_header[1] & 0xFF))) {
	    ulog(0, "input file not in compressed format");
	    exit(1);
    }
    maxbits = getc(stdin);  /* set -b from file */
    block_compress = maxbits & BLOCK_MASK;
    maxbits &= BIT_MASK;
    maxmaxcode = ((code_int)1) << maxbits;

    decompress();

    fclose(stdout);
    if (freopen(WKName, "r", stdin) == NULL) {
	ulog(0, "Can't reopen uncompressed file for input");
	return;
    }
    unbatch(NULL);
}

#undef exit
static /*volatile*/ void
news_exit(int stat)
{

    chdir(homedir);

    {
	NGInfo *ng;
	long ttlArts = 0;
	long ttlBytes= 0;
	int nng = 0;

	for (ng = NGBase; ng; ++nng, ng = ng->Next) {
	    ulog(-1, "%-20s %3ld Arts %6ld Bytes", ng->NewsGroup, ng->Articles, ng->Bytes);
	    ttlArts += ng->Articles;
	    ttlBytes += ng->Bytes;
	}
	ulog(-1, "%-20s %3ld ARTS %6ld BYTES", "TOTAL", ttlArts, ttlBytes);
    }
    exit(stat);
}

/*
 *  When debugging RNews, this copied the input file to somewhere else to save
 *  it.
 */

void
copy_uualtspool()
{
    char *ident = MakeConfigPath(UUALTSPOOL, "altseqno");
    FILE *fi;
    int seqno = 0;
    int error = 0;
    char buf[32];

    if (fi = fopen(ident, "r+")) {
	fscanf(fi, "%d", &seqno);
	fclose(fi);
    }
    if (fi = fopen(ident, "w")) {
	fprintf(fi, "%d\n", seqno + 1);
	fclose(fi);
    } else {
	ulog(-1, "Unable to update %s\n", ident);
    }

    sprintf(buf, "ND.%d", seqno);
    ident = MakeConfigPath(UUALTSPOOL, buf);

    if (fi = fopen(ident, "w")) {
	int n;

	while ((n = fread(TmpBuf, 1, sizeof(TmpBuf), stdin)) > 0) {
	    if (fwrite(TmpBuf, 1, n, fi) != n) {
		ulog(-1, "Error writing to alternate spool!");
		error = 1;
		break;
	    }
	}
	fclose(fi);

	/*
	 *  if error use stdin.  If re-openning of ident fails use stdin.
	 */

	rewind(stdin);
	if (error == 0)
	    freopen(ident, "r", stdin);
    }
}

/*
 *  make a copy of a file
 *
 *  can't use TmpBuf
 */

int
copy_file(s, d)
char *s;
char *d;
{
    FILE *fs;
    FILE *fd;
    int error = -1;

    if (fs = fopen(s, "r")) {
	if (fd = fopen(d, "w")) {
	    short c;

	    error = 0;
	    while ((c = getc(fs)) != EOF)
		putc(c, fd);
	    fclose(fd);
	}
	fclose(fs);
    }
    return(error);
}

