[net.sources] vn

bobm@rtech.UUCP (Bob Mcqueer) (05/21/86)

cut here
--------------------------------
#!/bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #!/bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	vn.c
#	reader.c
# This archive created: Tue May 20 12:52:10 1986
export PATH; PATH=/bin:$PATH
echo shar: extracting "'vn.c'" '(20696 characters)'
if test -f 'vn.c'
then
	echo shar: over-writing existing file "'vn.c'"
fi
cat << \SHAR_EOF > 'vn.c'
/*
	vn news reader for visual page oriented display of news
	aimed at scanning large numbers of articles.

	Known bugs:

		non-erasure of stuff on prompt line when the new
		string includes an escape sequence (like PS1 maybe)
		because it doesn't realize that the escape sequence
		won't overprint the existing stuff

		control-w may not update pages which have been scanned
		in funny orders by jumping into the middle of groups

	R. L. McQueer, Author
*/
#include <stdio.h>
#include <setjmp.h>
#include "tty.h"
#include "vn.h"

/* UNIX error number */
extern int errno;

extern NODE *Newsorder [];
extern char Erasekey, Killkey;
extern int Rot;
extern char *Ps1,*Printer;
extern char *Orgdir,*Savefile,*Savedir;
extern int Ncount, Cur_page, Lrec, L_allow, C_allow;
extern int Headflag;
extern PAGE Page;
extern int Digest;
extern char *No_msg;
extern char *Help_msg;
extern char *Hdon_msg;
extern char *Hdoff_msg;
extern char *Roton_msg;
extern char *Rotoff_msg;

extern char *Aformat;

extern char *Contstr;

static int C_info;
static int Dskip, Drec;

static char *Unsub_msg = "Unsubscribed";
static char *Egroup_msg = "Entire newsgroup";

/*
	Help message table.  Character for command, plus its help
	message.  Table order is order of presentation to user.
*/
static struct HELPTAB
{
	char cmd, *msg;
	int dig;
	char *amsg;
} 
Helptab [] =
{
	{ QUIT, "quit", 1, NULL},
	{ UP, "move up [number of lines]", 1, NULL},
	{ DOWN, "move down [number of lines]", 1, NULL},
	{ BACK, "previous page [number of pages]", 1, NULL},
	{ FORWARD, "next page [number of pages]", 1, NULL},
	{ DIGEST, "unpack digest", 1, "exit digest"},
	{ READ, "read article [number of articles]", 1, NULL},
	{ ALTREAD, "read article (alternate 'r')", 1, NULL},
	{ READALL, "read all articles on page", 1, NULL},
	{ READSTRING, "specify articles to read", 1, NULL},
	{ SAVE, "save or pipe article [number of articles]", 1, NULL},
	{ SAVEALL, "save or pipe all articles on page", 1, NULL},
	{ SAVESTRING, "specify articles to save", 1, NULL},
	{ ALTSAVE, "specify articles to save (alternate ctl-s)", 1, NULL},
	{ PRINT, "print article [number of articles]", 1, NULL},
	{ PRINTALL, "print all article on page", 1, NULL},
	{ PRINTSTRING, "specify articles to print", 1, NULL},
	{ UPDATE, "update .newsrc status to cursor", 0, NULL},
	{ UPALL, "update .newsrc status for whole newsgroup", 0, NULL},
	{ UPSEEN, "update .newsrc status for all pages displayed", 0, NULL},
	{ ORGGRP, "recover original .newsrc status for newsgroup", 0, NULL},
	{ ORGSTAT, "recover all original .newsrc status", 0, NULL},
	{ SSTAT, "display count of groups and pages - shown and total", 0, NULL},
	{ GRPLIST, "list newsgroups with new article, updated counts", 0, NULL},
	{ NEWGROUP, "specify newsgroup to display and/or resubscribe to", 1, NULL},
	{ UNSUBSCRIBE, "unsubscribe from group", 0, NULL},
	{ MARK, "mark/unmark article [number of articles]", 1, NULL},
	{ ART_MARK, "mark/unmark article [number of articles]", 1, NULL},
	{ UNMARK, "erase marks on articles", 1, NULL},
	{ HEADTOG, "toggle flag for display of headers when reading", 1, NULL},
	{ SETROT, "toggle rotation for reading", 1, NULL},
	{ REDRAW, "redraw screen", 1, NULL},
	{ UNESC, "escape to UNIX to execute a command", 1, NULL},
	{ HELP, "show this help menu", 1, NULL}
};

main(argc,argv)
int argc;
char **argv;
{
	/*
		initialize environment variables,
		scan .newsrc file, using any command line options present.
	 */
	term_set (START);
	envir_set ();
	sig_set (BRK_IN);

	scan_newsrc (argc-1,argv+1);
	tty_set (BACKSTOP);

	if (Lrec >= 0)
		session ();
	else
	{
		new_groups ();
		fprintf (stderr,"\nNo News\n");
	}

	tty_set (COOKED);
	wr_newsrc ();
	term_set (STOP);
}

/*
	main session handler processing input commands
	locals:
		count - count attached to command
		highrec - highest line on current page
		crec - current line

	NOTE: this is where a setjmp call is made to set the break reentry
		location.  Keep the possible user states in mind.
*/
session ()
{
	char alist [RECLEN], c;
	int newg, i, j, count, highrec, crec;
	jmp_buf brkbuf;

	tty_set (RAWMODE);
	newg = new_groups();
	find_page (0);
	Digest = 0;

	/* reentry point for break from within session interaction */
	setjmp (brkbuf);
	sig_set (BRK_SESS,brkbuf);
	Headflag = FALSE;
	Rot = 0;

	/* done this way so that user gets "really quit?" break treatment */
	if (newg > 0)
	{
		printf ("\n%s",Contstr);
		getnoctl();
		newg = 0;
	}
	/* if breaking from a digest, recover original page */
	if (Digest)
	{
		find_page(Cur_page);
		Digest = 0;
	}
	show ();
	crec = RECBIAS;
	highrec = Page.h.artnum + RECBIAS;
	term_set (MOVE,0,crec);

	/*
		handle commands until QUIT, update global/local status
		and display for each.
	 */
	for (count = getkey(&c); c != QUIT; count = getkey(&c))
	{
		for (i=0; i < (j = sizeof(Helptab)/sizeof(struct HELPTAB)); ++i)
			if (Helptab[i].cmd == c)
				break;

		if (i >= j || (Digest && !Helptab[i].dig))
		{
			preinfo (UDKFORM,(int) c);
			term_set (MOVE, 0, crec);
			continue;
		}

		switch (c)
		{
		case HEADTOG:
			if (Headflag)
			{
				Headflag = FALSE;
				prinfo (Hdoff_msg);
			}
			else
			{
				Headflag = TRUE;
				prinfo (Hdon_msg);
			}
			term_set (MOVE,0,crec);
			break;
		case SETROT:
			if (Rot == 0)
			{
				Rot = 13;
				prinfo (Roton_msg);
			}
			else
			{
				Rot = 0;
				prinfo (Rotoff_msg);
			}
			term_set (MOVE,0,crec);
			break;
		case SSTAT:
			count_msg ();
			term_set (MOVE,0,crec);
			break;
		case GRPLIST:
			tot_list ();
			show();
			term_set (MOVE,0,crec);
			break;
		case REDRAW:
			show();
			term_set (MOVE,0,crec);
			break;
		case UNSUBSCRIBE:
			(Page.h.group)->state &= ~FLG_SUB;
			wr_newsrc ();
			prinfo (Unsub_msg);
			term_set (MOVE,0,crec);
			break;

		case UPDATE:
			(Page.h.group)->rdnum = Page.b[crec-RECBIAS].art_id;
			wr_show ();
			wr_newsrc();
			term_set (MOVE,0,crec);
			break;
		case UPALL:
			(Page.h.group)->rdnum = (Page.h.group)->art;
			wr_newsrc();
			wr_show();
			prinfo (Egroup_msg);
			term_set (MOVE,0,crec);
			break;
		case ORGGRP:
			(Page.h.group)->rdnum = (Page.h.group)->orgrd;
			wr_newsrc();
			wr_show();
			prinfo (Egroup_msg);
			term_set (MOVE,0,crec);
			break;
		case UPSEEN:
			for (i = 0; i < Ncount; ++i)
				if ((Newsorder[i])->rdnum < (Newsorder[i])->pgrd)
					(Newsorder[i])->rdnum = (Newsorder[i])->pgrd;
			prinfo ("All pages displayed to this point updated");
			wr_show();
			wr_newsrc();
			term_set (MOVE,0,crec);
			break;
		case ORGSTAT:
			for (i = 0; i < Ncount; ++i)
				(Newsorder[i])->rdnum = (Newsorder[i])->orgrd;
			prinfo ("Original data recovered");
			wr_show();
			wr_newsrc();
			term_set (MOVE,0,crec);
			break;
		case UP:
			if (crec != RECBIAS)
			{
				crec -= count;
				if (crec < RECBIAS)
					crec = RECBIAS;
				term_set (MOVE, 0, crec);
			}
			else
				putchar ('\07');
			break;
		case DOWN:
			if (crec < (highrec - 1))
			{
				crec += count;
				if (crec >= highrec)
					crec = highrec - 1;
				term_set (MOVE, 0, crec);
			}
			else
				putchar ('\07');
			break;
		case MARK:
		case ART_MARK:
			count += crec - 1;
			if (count >= highrec)
				count = highrec - 1;
			for (i=crec; i <= count; ++i)
			{
				if (Page.b[i-RECBIAS].art_mark != ART_MARK)
					Page.b[i-RECBIAS].art_mark = ART_MARK;
				else
					Page.b[i-RECBIAS].art_mark = ' ';
				if (i != crec)
					term_set (MOVE, 0, i);
				printf ("%c\010",Page.b[i-RECBIAS].art_mark);
			}
			if (count != crec)
				term_set (MOVE, 0, crec);
			write_page ();
			break;
		case UNMARK:
			for (i=0; i < Page.h.artnum; ++i)
			{
				if (Page.b[i].art_mark == ART_MARK)
				{
					Page.b[i].art_mark = ' ';
					term_set (MOVE, 0, i+RECBIAS);
					putchar (' ');
				}
			}
			term_set (MOVE, 0, crec);
			write_page ();
			break;
		case BACK:
			count *= -1;	/* fall through */
		case FORWARD:
			if (forward (count, &crec, &highrec) >= 0)
				show();
			else
				preinfo ("No more pages");
			term_set (MOVE,0,crec);
			break;
		case DIGEST:
			if (Digest)
			{
				Digest = 0;
				find_page (Cur_page);
				show();
				crec = Drec + RECBIAS + 1;
				highrec = Page.h.artnum + RECBIAS;
				if (crec >= highrec)
					crec = highrec - 1;
				term_set (MOVE,0,crec);
				break;
			}
			Dskip = count - 1;
			Drec = crec - RECBIAS;
			if (digest_page(Drec,Dskip) >= 0)
			{
				show();
				crec = RECBIAS;
				highrec = Page.h.artnum + RECBIAS;
				term_set (MOVE,0,crec);
				break;
			}
			Digest = 0;
			preinfo ("Can't unpack the article");
			term_set (MOVE,0,crec);
			break;
		case NEWGROUP:
			if ((i = spec_group()) < 0)
			{
				term_set (MOVE,0,crec);
				break;
			}
			Digest = 0;
			show();
			crec = RECBIAS;
			highrec = Page.h.artnum + RECBIAS;
			term_set (MOVE,0,crec);
			break;

		case SAVE:
			genlist (alist,crec-RECBIAS,count);
			savestr (alist);
			term_set (MOVE,0,crec);
			break;
		case SAVEALL:
			genlist (alist,0,L_allow);
			savestr (alist);
			term_set (MOVE,0,crec);
			break;
		case SAVESTRING:
		case ALTSAVE:
			userlist (alist);
			savestr (alist);
			term_set (MOVE,0,crec);
			break;
		case READ:
		case ALTREAD:
			genlist (alist,crec-RECBIAS,count);
			readstr (alist,&crec,&highrec,count);
			break;
		case READALL:
			genlist (alist,0,L_allow);
			readstr (alist,&crec,&highrec,0);
			break;
		case READSTRING:
			userlist (alist);
			readstr (alist,&crec,&highrec,0);
			break;
		case PRINT:
			genlist (alist,crec-RECBIAS,count);
			printstr (alist);
			term_set (MOVE,0,crec);
			break;
		case PRINTALL:
			genlist (alist,0,L_allow);
			printstr (alist);
			term_set (MOVE, 0, crec);
			break;
		case PRINTSTRING:
			userlist (alist);
			printstr (alist);
			term_set (MOVE, 0, crec);
			break;

		case HELP:
			help ();
			show ();
			term_set (MOVE, 0, crec);
			break;
		case UNESC:
			user_str (alist,Ps1);
			term_set (ERASE);
			fflush (stdout);
			tty_set (SAVEMODE);
			if (chdir(Orgdir) < 0)
				printf ("change to original directory, %s, failed",Orgdir);
			else
				system (alist);
			tty_set (RESTORE);
			printf (Contstr);
			getnoctl ();
			cd_group ();
			show ();
			term_set (MOVE, 0, crec);
			break;
		default:
			printex ("Unhandled key: %c", c);
			break;
		}
	}

	Digest = 0;
	for (i=0; i < Ncount; ++i)
	{
		if ((Newsorder[i])->rdnum < (Newsorder[i])->pgrd)
			break;
	}
	if (i < Ncount)
	{
		user_str (alist,"Some displayed pages not updated - update ? ");
		if (alist[0] == 'y')
		{
			for (i=0; i<Ncount; ++i)
			{
				if ((Newsorder[i])->rdnum < (Newsorder[i])->pgrd)
					(Newsorder[i])->rdnum = (Newsorder[i])->pgrd;
			}
		}
	}
	sig_set (BRK_OUT);
}

/*
	count_msg displays count information
*/
count_msg ()
{
	int i, gpnum, gscan, gpage;
	unsigned long mask;
	gpnum = 1;
	for (gscan = gpage = i = 0; i<Ncount; ++i)
	{
		if (((Newsorder[i])->state & FLG_PAGE) != 0)
		{
			if (((Newsorder[i])->pnum + (Newsorder[i])->pages - 1) < Cur_page)
				++gpnum;
			++gpage;
			for (mask=1; mask != 0L; mask <<= 1)
				if (((Newsorder[i])->pgshwn & mask) != 0L)
					++gscan;
		}
	}
	prinfo (CFORMAT,Cur_page+1,Lrec+1,gscan,gpnum,gpage);
}

/*
	forward utility handles paging to allow it to happen globally.
	(from readstr, for instance)
*/
forward (count, crec, highrec)
int count, *crec, *highrec;
{
	if (!Digest)
	{
		if ((count < 0 && Cur_page <= 0) || (count > 0 && Cur_page >= Lrec))
			return (-1);
		Cur_page += count;
		if (Cur_page < 0)
			Cur_page = 0;
		if (Cur_page > Lrec)
			Cur_page = Lrec;
		find_page (Cur_page);
		*crec = RECBIAS;
		*highrec = Page.h.artnum + RECBIAS;
		return (0);
	}
	/*
	** in digests, paging past the end of the digest returns to
	** page extracted from.
	*/
	if (Dskip > 0 && (Dskip + count*L_allow) < 0)
		Dskip = 0;
	else
		Dskip += count * L_allow;
	find_page (Cur_page);
	if (Dskip >= 0)
	{
		if (digest_page(Drec,Dskip) >= 0)
		{
			*crec = RECBIAS;
			*highrec = Page.h.artnum + RECBIAS;
			return (0);
		}
	}
	Digest = 0;
	*crec = Drec + RECBIAS + 1;
	*highrec = Page.h.artnum + RECBIAS;
	if (*crec >= *highrec)
		*crec = *highrec - 1;
	return (0);
}

/*
	error/abnormal condition cleanup and abort routine
	pass stack to printf
*/
printex (s,a,b,c,d,e,f)
char *s;
long a,b,c,d,e,f;
{
	static int topflag=0;
	if (topflag == 0)
	{
		++topflag;
		term_set (STOP);
		tty_set (COOKED);
		fflush (stdout);
		fprintf (stderr,s,a,b,c,d,e,f);
		fprintf (stderr," (error code %d)\n",errno);
		exit (1);
	}
	else
		fprintf (stderr,s,a,b,c,d,e,f);
}

/*
	getkey obtains user keystroke with count from leading
	numerics, if any.
*/
getkey (c)
char *c;
{
	int i;
	for (i = 0; (*c = getchar() & 0x7f) >= '0' && *c <= '9'; i = i * 10 + *c - '0')
		;
	/* @#$!!! flakey front ends that won't map newlines in raw mode */
	if (*c == '\012' || *c == '\015')
		*c = '\n';
	if (i <= 0)
		i = 1;
	return (i);
}


/*
	get user key ignoring most controls
*/
getnoctl ()
{
	char c;
	while ((c = getchar() & 0x7f) < ' ' || c == '\177')
	{
		if (c == '\015' || c == '\012' || c == '\b' || c == '\t')
			return (c);
	}
	return ((int) c);
}

/*
	generate list of articles on current page,
	count articles, starting with first.
*/
genlist (list,first,count)
char *list;
int first,count;
{
	int i;
	for (i=first; i < Page.h.artnum && count > 0; ++i)
	{
		sprintf (list,"%d ",Page.b[i].art_id);
		list += strlen(list);
		--count;
	}
}

/*
	send list of articles to printer
*/
printstr (s)
char *s;
{
	char *ptr, cmd [RECLEN], *strpbrk();
	prinfo ("preparing print command ....");
	for (ptr = s; (ptr = strpbrk(ptr, LIST_SEP)) != NULL; ++ptr)
		*ptr = ' ';
	while (*s == ' ')
		++s;
	if (Digest)
		dig_list (s);
	if (*s != '\0')
	{
		sprintf (cmd,"%s %s 2>/dev/null",Printer,s);
		if (system (cmd) == 0)
			prinfo ("Sent to printer");
		else
			preinfo ("Print failed");
	}
	else
		preinfo (No_msg);
	if (Digest)
		dig_ulist (s);
}

/*
	concatenate articles to save file with appropriate infoline messages.
	prompt for save file, giving default.  If save file begins with "|"
	handle as a filter to pipe to.  NOTE - every user specification of
	a new Savefile "loses" some storage, but it shouldn't be a very great
	amount.
*/
savestr (s)
char *s;
{
	char *ptr, cmd [RECLEN], newfile [MAX_C+1], prompt[MAX_C];
	char *strtok(), *strpbrk(), *str_store();

	for (ptr = s; (ptr = strpbrk(ptr, LIST_SEP)) != NULL; ++ptr)
		*ptr = ' ';
	while (*s == ' ')
		++s;
	if (Digest)
		dig_list (s);
	if (*s != '\0')
	{
		sprintf (prompt,SAVFORM,Savefile);
		user_str (newfile,prompt);
		ptr = newfile;
		if (*ptr == '|')
		{
			sprintf(cmd,"cat %s %s",s,ptr);
			term_set (ERASE);
			fflush (stdout);
			tty_set (SAVEMODE);
			system (cmd);
			tty_set (RESTORE);
			printf (Contstr);
			getnoctl ();
			show ();
		}
		else
		{
			prinfo ("saving .... ");
			if (*ptr == '\0')
				ptr = Savefile;
			else
				Savefile = str_store(ptr);
			if (*ptr != '/' && *ptr != '$')
				sprintf(cmd,"cat %s >>%s/%s 2>/dev/null",s,Savedir,ptr);
			else
				sprintf(cmd,"cat %s >>%s 2>/dev/null",s,ptr);
			if (system (cmd) == 0)
				prinfo ("Saved");
			else
				preinfo ("Could not append save file");
		}
	}
	else
		preinfo (No_msg);
	if (Digest)
		dig_ulist (s);
}

/*
	basic page display routine.  erase screen and format current page
*/
show ()
{
	int i;
	unsigned long mask;

	term_set (ERASE);
	C_info = 0;
	i = Cur_page - (Page.h.group)->pnum + 1;
	if (Digest)
		printf (DHFORMAT,Page.h.name);
	else
		printf (HFORMAT,Page.h.name,i,(Page.h.group)->pages);

	mask = 1L << (i-1);
	(Page.h.group)->pgshwn |= mask;
	mask = 1;
	for (--i; i > 0 && (mask & (Page.h.group)->pgshwn) != 0 ; --i)
		mask <<= 1;
	if (i <= 0)
		(Page.h.group)->pgrd = Page.b[(Page.h.artnum)-1].art_id;

	for (i=0; i < Page.h.artnum; ++i)
	{
		if (Digest)
		{
			printf(Aformat,Page.b[i].art_mark,ART_UNWRITTEN,Page.b[i].art_id);
			printf("%s",Page.b[i].art_t);
			continue;
		}

		if ((Page.h.group)->rdnum >= Page.b[i].art_id)
			printf(Aformat,Page.b[i].art_mark,ART_WRITTEN,Page.b[i].art_id);
		else
			printf(Aformat,Page.b[i].art_mark,ART_UNWRITTEN,Page.b[i].art_id);
		printf("%s",Page.b[i].art_t);
	}

	if (!Digest && ((Page.h.group)->state & FLG_SUB) == 0)
		prinfo ("%s, %s",Unsub_msg,Help_msg);
	else
		prinfo (Help_msg);
}

/*
	update written status marks on screen
*/
wr_show ()
{
	int i,row;
	char c;

	row = RECBIAS;
	for (i=0; i < Page.h.artnum; ++i)
	{
		term_set (MOVE,WRCOL,row);
		if ((Page.h.group)->rdnum >= Page.b[i].art_id)
			c = ART_WRITTEN;
		else
			c = ART_UNWRITTEN;
		printf("%c",c);
		++row;
	}
}

/*
	obtain user input of group name, becomes current page if valid.
	returns -1 or page number.  calling routine does the show, if needed
*/
spec_group ()
{
	char nbuf [MAX_C + 1];
	NODE *p, *hashfind();
	user_str (nbuf,"Newsgroup ? ");
	if (*nbuf == '\0' || (p = hashfind(nbuf)) == NULL)
	{
		preinfo ("Not a newsgroup");
		return (-1);
	}
	if ((p->state & FLG_PAGE) == 0)
	{
		if ((p->state & FLG_SUB) == 0)
		{
			p->state |= FLG_SUB;
			wr_newsrc ();
			prinfo ("Not subscribed: resubscribed for next reading session");
		}
		else
			prinfo ("No news for that group");
		return (-1);
	}
	if ((p->state & FLG_SUB) == 0)
	{
		p->state |= FLG_SUB;
		wr_newsrc ();
	}
	find_page (p->pnum);
	return (p->pnum);
}

/*
	obtain user input with prompt p on the info line.
	handle erase and kill characters, suppresses leading
	white space.
*/
user_str (s,p)
char *s;
char *p;
{
	int i;
	prinfo ("%s",p);
	for (i=0; C_info < C_allow && (s[i] = getchar() & 0x7f) != '\012' && s[i] != '\015'; ++i)
	{
		if (s[i] == Erasekey)
		{
			if (i > 0)
			{
				term_set (RUBSEQ);
				i -= 2;
				--C_info;
			}
			continue;
		}
		if (s[i] == Killkey)
		{
			prinfo ("%s",p);
			i = -1;
			continue;
		}
		if ((s[i] == ' ' || s[i] == '\t') && i == 0)
		{
			i = -1;
			continue;
		}
		++C_info;
		putchar (s[i]);
	}
	s[i] = '\0';
}


/*
	print something on the information line,
	clearing any characters not overprinted.
*/
preinfo (s,a,b,c,d,e,f)
{
	int l;
	char buf[RECLEN];

	term_set (MOVE,0,INFOLINE);
	putchar ('\07');
	term_set (ONREVERSE);
	sprintf (buf,s,a,b,c,d,e,f);
	printf (" %s ",buf);
	term_set (OFFREVERSE);
	l = strlen(buf) + 2;
	if (l < C_info)
		term_set (ZAP,l,C_info);
	C_info = l;
}

prinfo (s,a,b,c,d,e,f)
char *s;
long a,b,c,d,e,f;
{
	int l;
	char buf[RECLEN];
	term_set (MOVE,0,INFOLINE);
	sprintf (buf,s,a,b,c,d,e,f);
	printf ("%s",buf);
	l = strlen(buf);
	if (l < C_info)
		term_set (ZAP,l,C_info);
	C_info = l;
}

/*
	help menu
*/
help ()
{
	int i,lcount,lim; 
	term_set (ERASE);
	lim = L_allow + RECBIAS - 2;
	printf("%s\n",HELP_HEAD);
	lcount = HHLINES;
	for (i=0; i < (sizeof(Helptab))/(sizeof(struct HELPTAB)); ++i)
	{
		if (Digest && !(Helptab[i].dig))
			continue;
		++lcount;
		if (Digest && Helptab[i].amsg != NULL)
			h_print (Helptab[i].cmd,Helptab[i].amsg);
		else
			h_print (Helptab[i].cmd,Helptab[i].msg);
		if (lcount >= lim)
		{
			printf ("\n%s",Contstr);
			getnoctl ();
			term_set (MOVE,0,lim+1);
			term_set (ZAP,0,strlen(Contstr));
			term_set (MOVE,0,lim-1);
			putchar ('\n');
			lcount = 0;
		}
	}
	if (lcount > 0)
	{
		printf ("\n%s",Contstr);
		getnoctl ();
	}
}

/*
	h_print prints a character and a legend for a help menu.
*/
h_print(c,s)
char c,*s;
{
	if (strlen(s) > (C_allow - 14))
		s [C_allow - 14] = '\0';
	if (c > ' ' && c != '\177')
		printf ("	 %c - %s\n",c,s);
	else
	{
		switch (c)
		{
		case '\177':
			printf ("  <delete> - %s\n",s);  
			break;
		case '\040':
			printf ("   <space> - %s\n",s);  
			break;
		case '\033':
			printf ("  <escape> - %s\n",s);  
			break;
		case '\n':
			printf ("  <return> - %s\n",s);  
			break;
		case '\t':
			printf ("     <tab> - %s\n",s);  
			break;
		case '\b':
			printf (" <back sp> - %s\n",s);  
			break;
		case '\f':
			printf ("<formfeed> - %s\n",s);  
			break;
		case '\07':
			printf ("    <bell> - %s\n",s);  
			break;
		case '\0':
			printf ("    <null> - %s\n",s);  
			break;
		default:
			if (c < '\033')
			{
				c += 'a' - 1;
				printf(" control-%c - %s\n",c,s);
			}
			else
				printf("       %c0%o - %s\n",'\\',(int) c,s);
			break;
		}
	}
}

tot_list ()
{
	int i,max,len;
	char ff[80];

	term_set (ERASE);

	for (max=i=0; i < Ncount; ++i)
	{
		if ((Newsorder[i])->pages == 0)
			continue;
		if ((len = strlen((Newsorder[i])->nd_name)) > max)
			max = len;
	}

	sprintf (ff,"%%%ds: %%3d new %%3d updated\n",max);

	for (len=i=0; i < Ncount; ++i)
	{
		if ((Newsorder[i])->pages == 0)
			continue;
		printf (ff, (Newsorder[i])->nd_name,
				(Newsorder[i])->art - (Newsorder[i])->orgrd,
				(Newsorder[i])->rdnum - (Newsorder[i])->orgrd);
		++len;
		if (len == L_allow && i < (Ncount-1))
		{
			printf("\nq to quit, anything else to continue ... ");
			if (getnoctl() == 'q')
				break;
			printf ("\n\n");
			len = 0;
		}
	}
	if (i >= Ncount)
	{
		printf(Contstr);
		getnoctl();
	}
}
SHAR_EOF
echo shar: extracting "'reader.c'" '(13632 characters)'
if test -f 'reader.c'
then
	echo shar: over-writing existing file "'reader.c'"
fi
cat << \SHAR_EOF > 'reader.c'
#include <stdio.h>
#include "tty.h"
#include "vn.h"
#include "head.h"
#include "reader.h"

#define PERTAB 8	/* tab expansion factor */
#define BACKTRACK 24

extern char *Editor,*Mailer,*Poster,*Orgdir,*Savefile,*Savedir;
extern int L_allow;
extern int C_allow;
extern int Rot;
extern int Headflag;
extern int Digest;
extern char *No_msg;
extern char *Roton_msg;
extern char *Rotoff_msg;
extern char *Hdon_msg;
extern char *Hdoff_msg;

extern char *T_head, *FT_head, *N_head, *L_head;
extern char *F_head, *P_head, *M_head, *R_head;

static FILE *Fpread;
static char *Fname;

/*
	readstr routine is the "funnel" to the reading state,
	and controls signal setting.  Some "session" context is passed
	along to allow jumping back to a different display

	WARNING:

	NOTHING below readstr should call strtok()
*/

readstr (s,crec,highrec,count)
char *s;
int *crec, *highrec;
int count;
{
	char *fnext, *strtok();
	int pc;
	Fname = strtok(s,LIST_SEP);
	if (Fname != NULL)
	{
		term_set (ERASE);
		sig_set (BRK_READ,&Fpread);
		fnext = strtok(NULL,LIST_SEP);
		while (Fname != NULL && readfile(fnext,&pc) >= 0)
		{
			if (Digest)
				unlink (Fname);
			Fname = fnext;
			fnext = strtok (NULL,LIST_SEP);
		}
		if (Digest && Fname != NULL)
			unlink (Fname);
		if (pc != 0)
			forward (pc, crec, highrec);
		else
		{
			*crec += count;
			if (*crec >= *highrec)
				*crec = *highrec - 1;
		}
		sig_set (BRK_RFIN);
		show ();
		term_set (MOVE, 0, *crec);
	}
	else
	{
		preinfo ("%s",No_msg);
		term_set (MOVE, 0, *crec);
	}
}

/*
	readfile presents article:
		sn - name of next article, NULL if last.
		pages - pages to advance on return, if applicable
	returns 0 for "continue", <0 for "quit"
*/
static readfile (sn,pages)
char *sn;
int *pages;
{
	FILE *fopen();
	int lines,hlines,rlines,percent,artlin;
	long rew_pos, ftell();
	float total,lcount;
	double atof();
	char *optr;
	char c, *rf, buf[RECLEN], path[RECLEN], mid[RECLEN], ngrp[RECLEN];
	char from[RECLEN], title[RECLEN], flto[RECLEN], pstr[24];
	char dgname[48], getpgch(), *index(), *digest_extract();
	char *tgetstr();

	*pages = 0;

	term_set(ERASE);

	if (Digest)
	{
		lines = atoi(Fname);
		if ((Fname = digest_extract(dgname,lines)) == NULL)
		{
			printf ("couldn't extract article %d",lines);
			return (0);
		}
	}

	if ((Fpread = fopen(Fname,"r")) == NULL)
	{
		printf ("couldn't open article %s",Fname);
		return (0);
	}

	hlines = gethead (path, mid, from, title, ngrp, flto, &artlin);
	total = (float) artlin + 1.0;
	if (total < .99)
		total = 1.0;
	lcount = 0.0;
	printf (ANFORM,Fname);
	lines = 1;
	rlines = 0;
	rew_pos = ftell(Fpread);
	if (Headflag)
	{
		rewind(Fpread);
	}
	else
	{
		/* use do_out to guard against control sequences */
		sprintf (buf,"%s%s\n",T_head,title);
		lines += do_out(buf,&optr,1);
		sprintf (buf,"%s%s\n",F_head,from);
		lines += do_out(buf,&optr,1);
		if (index(ngrp,',') != NULL)
		{
			sprintf (buf,"%s%s\n",N_head,ngrp);
			lines += do_out(buf,&optr,1);
		}
		if (*flto != '\0')
		{
			sprintf (buf,"%s%s\n",FT_head,flto);
			lines += do_out(buf,&optr,1);
		}
		printf ("%s%d\n",L_head,artlin);	/* no controls */
		++lines;
	}

	/* will return out of outer while loop */
	optr = NULL;
	while (1)
	{
		/*
		** lines counts folded lines from do_out.
		** hlines, rlines and lcount refer to records.
		*/
		rf = buf;
		lines += do_out(optr,&optr,L_allow-lines);
		while (lines < L_allow && (rf = fgets(buf,RECLEN-1,Fpread)) != NULL)
		{
			lcount += 1.0;

			if (Rot != 0)
			{
				if (!Headflag || rlines >= hlines)
					rot_line(buf);
				++rlines;
			}
			lines += do_out(buf,&optr,L_allow-lines);
		}

		if (rf != NULL)
		{
			if (Headflag)
				percent = 100.0*(lcount/(total+hlines)) + .5;
			else
				percent = 100.0*(lcount/total) + .5;
			sprintf (pstr,PAGE_MID,percent);
		}
		else
		{
			if (sn == NULL)
				strcpy (pstr,PAGE_END);
			else
			strcpy (pstr,PAGE_NEXT);
		}
		c = getpgch(pstr,path,mid,from,title,ngrp,flto);

		/*
			handle user input:
			CAUTION!!  return cases must close Fpread.
		*/
		switch (c)
		{
		case PG_NEXT:
			fclose (Fpread);
			return (0);
		case PG_FLIP:
			*pages = 1;	/* fall through */
		case PG_QUIT:
			fclose (Fpread);
			return (-1);
		case PG_REWIND:
			if (Headflag)
				rewind (Fpread);
			else
				fseek (Fpread,rew_pos,0);
			lcount = 0.0;
			rlines = 0;
			lines = 2 - RECBIAS;
			break;
		case PG_WIND:
			fseek (Fpread,0L,2);
			lines = 2 - RECBIAS;
			break;
		case PG_STEP:
			if (rf == NULL)
			{
				fclose (Fpread);
				return (0);
			}
			lines = L_allow - 1;
			break;
		default:
			if (rf == NULL)
			{
				fclose (Fpread);
				return (0);
			}
			lines = 2 - RECBIAS;
			break;
		}
	}
}

/*
	gethead obtains subject, path, message id, from, lines, newsgroup and
	followup-to lines of article for later use in mailing replies and
	posting followups, does not rewind, but leaves file at end of header
	lines.  Returns number of header lines.
*/
static gethead (path, mid, from, title, ngrp, flto, lin)
char *path, *mid, *from, *title, *ngrp, *flto;
int *lin;
{
	int count;
	char buf [RECLEN],*index();
	long pos,ftell();

	*lin = 0;
	*path = *mid = *from = *title = *ngrp = *flto = '\0';

	/* for conditional is abnormal - expected exit is break */
	for (count = 0; count < HDR_LINES && fgets(buf,RECLEN-1,Fpread) != NULL; ++count)
	{

		/* reset position and bail out at first non-header line */
		if (index(buf,':') == NULL)
		{
			pos = ftell(Fpread);
			pos -= strlen(buf);
			fseek (Fpread,pos,0);
			break;
		}

		if (strncmp(buf,P_head,PHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (path,buf+PHDLEN);
			continue;
		}
		if (strncmp(buf,M_head,MHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (mid,buf+MHDLEN);
			continue;
		}
		if (strncmp(buf,F_head,FHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (from,buf+FHDLEN);
			continue;
		}
		if (strncmp(buf,T_head,THDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (title,buf+THDLEN);
			continue;
		}
		if (strncmp(buf,N_head,NHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (ngrp,buf+NHDLEN);
			continue;
		}
		if (strncmp(buf,FT_head,FTHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (flto,buf+FTHDLEN);
			continue;
		}
		if (strncmp(buf,L_head,LHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			*lin = atoi(buf+LHDLEN);
			continue;
		}
	}
	return (count);
}

/*
	getpgch prints prompt and gets command from user
	handles "mail", "save" and "followup" internally
	as well as flag resets.
*/
static char getpgch(prompt,path,mid,from,title,ngrp,flto)
char *prompt, *path, *mid, *from, *title, *ngrp, *flto;
{
	char c;
	int ic;
	term_set (ONREVERSE);
	printf("%s\015",prompt);
	term_set (OFFREVERSE);
	while ((ic=getnoctl()) != EOF)
	{
		switch (c = ic)
		{
		case SETROT:
			term_set (ZAP,0,PPR_MAX);
			if (Rot == 0)
			{
				Rot = 13;
				printf ("%s\n",Roton_msg);
			}
			else
			{
				Rot = 0;
				printf ("%s\n",Rotoff_msg);
			}
			break;
		case HEADTOG:
			term_set (ZAP,0,PPR_MAX);
			if (Headflag)
			{
				Headflag = FALSE;
				printf ("%s\n",Hdoff_msg);
			}
			else
			{
				Headflag = TRUE;
				printf ("%s\n",Hdon_msg);
			}
			break;
		case PG_HELP:
			term_set (ZAP,0,PPR_MAX);
			help_pg ();
			break;
		case PG_REPLY:
			mail (path,title,from);
			break;
		case PG_FOLLOW:
			followup (mid,title,ngrp,flto);
			break;
		case SAVE:
			saver ();
			break;
		default:
			term_set (ZAP,0,PPR_MAX);
			return (c);
		}

		term_set (ONREVERSE);
		printf("%s\015",prompt);
		term_set (OFFREVERSE);
	}
	term_set (ZAP,0,PPR_MAX);
	return (c);
}

/*
	save article
	Like the savestr routine, it "loses" some storage every time
	the user specifies a new file, but this should not be significant
*/
static saver ()
{
	char *fn,cmd[RECLEN],*str_store(),*rprompt();

	tty_set (SAVEMODE);
	sprintf (cmd,SAVFORM,Savefile);
	fn = rprompt(cmd,cmd);
	if (fn != NULL)
		Savefile = str_store(fn);
	if (*Savefile != '/' && *Savefile != '$')
		sprintf (cmd,"cat %s >>%s/%s",Fname,Savedir,Savefile);
	else
		sprintf (cmd,"cat %s >>%s",Fname,Savefile);
	system (cmd);
	tty_set (RESTORE);
}

/*
	invoke editor on new temp file, mail using path line,
	first allowing user to overide the path if wished.
*/
static mail (p, t, f)
char *p, *t, *f;
{
	char *new, fn[L_tmpnam], cmd [RECLEN+60], *rprompt ();
	FILE *fp, *fopen();

	tmpnam (fn);
	if ((fp = fopen(fn,"w")) == NULL)
		printex ("can't open %s\n",fn);
	fprintf (fp,"%s%s%s\n\n%s:\n", T_head, FPFIX, t, f);
	edcopy (fp);
	fclose (fp);
	tty_set (SAVEMODE);
	sprintf (cmd,"ADDRESS: %s\nreturn to accept, or input new address: ",p);
	if ((new = rprompt(cmd,cmd)) != NULL)
		strcpy (p,new);
	sprintf (cmd,"%s %s", Editor, fn);
	chdir (Orgdir);
	system (cmd);
	cd_group ();
	new = rprompt ("still want to mail it ? ",cmd);
	if (new != NULL && (*new == 'y' || *new == 'Y'))
	{
		sprintf (cmd,"%s %s <%s", Mailer, p, fn);
		system (cmd);
		printf ("given to mailer\n");
	}
	else
		printf ("not mailed\n");
	unlink (fn);
	tty_set (RESTORE);
}

/*
	post a followup article, invoking editor for user after
	creating new temp file.  remove after posting.  Hack in
	".followup" if posting newsgroup ends in ".general" -
	should really be more thorough and parse the whole string.
	User can change, anyway.
*/
static followup (m,t,n,ft)
char *m, *t, *n, *ft;
{
	char fn[L_tmpnam], *new, cmd [RECLEN], *rprompt();
	FILE *f, *fopen();
	char *rindex();
	tmpnam (fn);
	if ((f = fopen(fn,"w")) == NULL)
		printex ("can't open %s\n",fn);
	if (*ft != '\0')
		strcpy (cmd,ft);
	else
		strcpy (cmd,n);
	if ((new = rindex(cmd,'.')) != NULL && strcmp(new,".general") == 0)
		strcpy (new,".followup");
	fprintf (f,"%s%s%s\n%s%s\n%s%s\n",T_head,FPFIX,t,N_head,cmd,R_head,m);
	edcopy (f);
	fclose (f);
	tty_set (SAVEMODE);
	sprintf (cmd,"%s %s", Editor, fn);
	chdir (Orgdir);
	system (cmd);
	cd_group ();
	new = rprompt("still want to post it ? ",cmd);
	if (new != NULL && (*new == 'y' || *new == 'Y'))
	{
		sprintf (cmd,"%s <%s", Poster, fn);
		system (cmd);
		printf ("given to posting program\n");
	}
	else
		printf ("not posted\n");
	unlink (fn);
	tty_set (RESTORE);
}

/*
	get user buffer, return whitespace delimited token
	without using strtok().  buffer is allowed to overwrite
	prompt string.
*/
char *rprompt(s,buf)
char *s,*buf;
{
	printf("%s",s);
	fgets (buf,RECLEN-1,stdin);
	while (*buf == ' ' || *buf == '\t')
		++buf;
	if (*buf == '\n' || *buf == '\0')
		return (NULL);
	for (s = buf; *s != ' ' && *s != '\t' && *s != '\n' && *s != '\0'; ++s)
		;
	*s = '\0';
	return (buf);
}

/*
	edcopy copies article to file which user is editting for
	a reply or followup, so it may be referenced.  It places
	ED_MARK in the left hand margin.
*/
edcopy(fp)
FILE *fp;
{
	long current;
	char buf[RECLEN];
	int i;

	/* save position, rewind and skip over header lines */
	current = ftell(Fpread);
	rewind (Fpread);
	for (i=0; i < HDR_LINES; ++i)
	{
		if (fgets(buf,RECLEN-1,Fpread) == NULL)
			break;
		if (strncmp(buf,L_head,LHDLEN) == 0)
			break;
	}

	/* if line already begins with ED_MARK, forget about the space */
	while (fgets(buf,RECLEN-1,Fpread) != NULL)
	{
		if (buf[0] == ED_MARK)
			fprintf(fp,"%c%s",ED_MARK,buf);
		else
			fprintf(fp,"%c %s",ED_MARK,buf);
	}

	/* restore position */
	fseek(Fpread,current,0);
}

/*
	help menus
*/
static help_pg()
{
	h_print (PG_NEXT,HPG_NEXT);
	h_print (PG_QUIT,HPG_QUIT);
	h_print (PG_FLIP,HPG_FLIP);
	h_print (PG_REWIND,HPG_REWIND);
	h_print (PG_WIND,HPG_WIND);
	h_print (PG_STEP,HPG_STEP);
	h_print (PG_REPLY,HPG_REPLY);
	h_print (PG_FOLLOW,HPG_FOLLOW);
	h_print (SAVE,HPG_SAVE);
	h_print (SETROT,HPG_ROT);
	h_print (HEADTOG,HPG_HEAD);
	h_print (PG_HELP,HPG_HELP);
	printf ("%s\n",HPG_DEF);
}

rot_line (s)
unsigned char *s;
{
	for ( ; *s != '\0'; ++s)
	{
		if (*s >= 'A' && *s <= 'Z')
		{
			*s += Rot;
			if (*s > 'Z')
				*s -= 26;
			continue;
		}
		if (*s >= 'a' && *s <= 'z')
		{
			*s += Rot;
			if (*s > 'z')
				*s -= 26;
		}
	}
}

/*
** output record.  folds record to terminal width on word boundaries,
** returning number of lines output.  If limit is reached, remainder
** of buffer waiting to be output is returned.  Processes several
** special characters:
**	form-feed - return "lim" lines so we stop on that line
**	tabs - counts "expanded" width
**	backspace - assumes they work, -1 width unless in first col.
**	bell - pass through with zero width
**	newline - end of record.
**	del - turns into '_'
**	other control - 'A' - 1 added ('01' = ctl-A).  Makes escape = "[".
**		(prevents "letter bombs" containing inappropriate control
**			sequences for the terminal).
*/
static do_out(s,rest,lim)
char *s;
char **rest;
int lim;
{
	int len,i,j;
	char cs,*word,*start;

	*rest = NULL;
	if (s == NULL)
		return(0);
	len = 0;
	start = word = s;

	/*
	** NOTE: "normal" return is buried inside switch, at newline
	** ending record
	*/
	for (i=0; i < lim; ++i)
	{
		for ( ; len < C_allow; ++s)
		{
			switch (*s)
			{
			case '\n':
				*s = '\0';	/* fall through */
			case '\0':
				printf("%s\n",start);
				return(i+1);
			case '\t':
				len = ((len/PERTAB)+1)*PERTAB;
				word = s;
				break;
			case '\b':
				if (len > 0)
					--len;
				break;
			case '\014':
				*s = ' ';
				i = lim-1;	/* fall through */
			case ' ':
				word = s+1;
				++len;
				break;
			case '\177':
				*s = '_';
				++len;
				break;
			default:
				if (*s < ' ')
					*s += 'A' - 1;
				++len;		/* fall through */
			case '\07':
				break;
			}
		}
		cs = *s;
		*s = '\0';
		if ((len = strlen(word)) < BACKTRACK)
		{
			*s = cs;
			s = word;
			cs = *s;
			*s = '\0';
		}
		else
			len = 0;
		printf("%s\n",start);
		start = s;
		*s = cs;
	}
	*rest = start;
	return(lim);
}
SHAR_EOF
#	End of shell archive
exit 0

bobm@rtech.UUCP (Bob Mcqueer) (05/21/86)

cut here
---------------------------------
#!/bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #!/bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	digest.c
#	envir_set.c
#	groupdir.c
#	hash.c
#	newsrc.c
#	pagefile.c
#	reg.c
#	sig_set.c
#	storage.c
#	strings.c
#	term_set.c
#	tty_set.c
#	ucb.c
#	userlist.c
#	vnglob.c
# This archive created: Tue May 20 12:54:05 1986
export PATH; PATH=/bin:$PATH
echo shar: extracting "'digest.c'" '(4722 characters)'
if test -f 'digest.c'
then
	echo shar: over-writing existing file "'digest.c'"
fi
cat << \SHAR_EOF > 'digest.c'
#include <stdio.h>
#include "vn.h"
#include "head.h"

extern int Digest;
extern int L_allow;
extern int C_allow;
extern PAGE Page;

extern char *F_head, *T_head, *L_head, *D_head;

digest_page (idx,skip)
int idx;
{
	char *ptr,name[24],*title,*index();
	FILE *fp;
	int i,len;
	char subj[RECLEN],date[RECLEN],from[RECLEN],junk[RECLEN],*str_store();
	long pos;

	Digest = Page.b[idx].art_id;
	sprintf (name,"%d",Digest);

	if ((fp = fopen(name,"r")) == NULL)
		return (-1);

	skip_header (fp);

	/* skip over some articles if requested to */
	for (i=skip; i > 0; --i)
	{
		if (dig_advance(fp,from,subj,date,junk,&pos) < 0)
			return (-1);
	}

	/* every new call to a digest Page "loses" a small amount of storage */
	title = str_store(Page.b[idx].art_t);
	if ((ptr = index(title,'~')) != 0)
		*ptr = '\0';
	title [C_allow - 20] = '\0';

	for (i=0; i < L_allow &&
			(len = dig_advance(fp,from,subj,date,junk,&pos)) >= 0; ++i)
	{
		Page.b[i].art_id = i+1+skip;
		Page.b[i].art_mark = ' ';
		subj [C_allow] = '\0';
		from [C_allow] = '\0';
		sprintf (name,"%d",len);
		form_title (date,subj,name,from,100);
		strcpy (Page.b[i].art_t,date);
	}

	fclose (fp);

	if (i == 0)
		return (-1);

	Page.h.name = title;
	Page.h.artnum = i;
	return (i);
}

/*
	returns name of file containing "article", NULL for failure
*/
char * digest_extract (s,art)
char *s;
int art;
{
	char name[24];
	FILE *fout,*fin;
	char subj[RECLEN],date[RECLEN],from[RECLEN],bufr[RECLEN];
	char extra[RECLEN];
	char *index();
	long pos;
	int lines;

	sprintf(name,"%d",Digest);
	if ((fin = fopen(name,"r")) == NULL)
		return (NULL);

	for (skip_header (fin); art > 0; --art)
		if ((lines = dig_advance(fin,from,subj,date,extra,&pos)) < 0)
		{
			fclose (fin);
			return (NULL);
		}

	tmpnam(s);

	if ((fout = fopen(s,"w")) == NULL)
	{
		fclose (fin);
		unlink (s);
		return (NULL);
	}

	fseek(fin,0L,0);

	while (fgets(bufr,RECLEN-1,fin) != NULL && index(bufr,':') != NULL)
	{
		if (strncmp(bufr,F_head,FHDLEN) == 0)
		{
			fprintf (fout,"%s%s\n",F_head,from);
			continue;
		}
		if (strncmp(bufr,T_head,THDLEN) == 0)
		{
			fprintf (fout,"%s%s\n",T_head,subj);
			continue;
		}
		if (strncmp(bufr,D_head,DHDLEN) == 0)
		{
			fprintf (fout,"%s%s\n",D_head,date);
			continue;
		}
		/* defer line count header - it comes last */
		if (strncmp(bufr,L_head,LHDLEN) == 0)
			continue;
		fprintf (fout,"%s",bufr);
	}

	/* toss in extra header lines, line count header, extra newline */
	fprintf (fout,"%s%s%d\n\n",extra,L_head,lines);

	fseek (fin,pos,0);

	while (fgets(bufr,RECLEN-1,fin) != NULL && strncmp(bufr,"--------",8) != 0)
		fprintf(fout,"%s",bufr);

	fclose (fin);
	fclose (fout);
	return (s);
}

dig_list (s)
char *s;
{
	char *ptr,*out,*new,ns[L_tmpnam],tmp[RECLEN],*strtok();
	int i;

	prinfo ("Extracting articles .....");
	strcpy (tmp,s);
	out = s;

	for (ptr = strtok(tmp," "); ptr != NULL; ptr = strtok(NULL," "))
	{
		i = atoi(ptr);
		if ((new = digest_extract(ns,i)) != NULL)
		{
			sprintf (out,"%s ",new);
			out += strlen(new) + 1;
		}
	}

	*out = '\0';

	if (*s == '\0')
		strcpy (s,"NULLDIGEST");
}

dig_ulist (s)
char *s;
{
	char *strtok();
	for (s = strtok(s," "); s != NULL; s = strtok(NULL," "))
		unlink (s);
}

/*
	returns # lines in article, -1 for failure
	scans past article, returns position of start.
	also returns "extra" header lines encountered, WITH newlines.
*/
static dig_advance (fp,from,subj,date,extra,pos)
FILE *fp;
char *from,*subj,*date,*extra;
long *pos;
{
	char buf[RECLEN];
	char *ptr, *index();
	int state,lcount;

	lcount = state = 0;
	*extra = '\0';

	while (fgets(buf,RECLEN-1,fp) != NULL)
	{
		buf[strlen(buf) - 1] = '\0';

		switch(state)
		{
		case 0:
			/* skip blank lines before header */
			if (strlen(buf) == 0)
				break;
			state = 1;	/* fall through */
		case 1:
			if (strncmp(buf,F_head,FHDLEN) == 0)
			{
				strcpy (from,buf+FHDLEN);
				break;
			}
			if (strncmp(buf,T_head,THDLEN) == 0)
			{
				strcpy (subj,buf+THDLEN);
				break;
			}
			if (strncmp(buf,D_head,DHDLEN) == 0)
			{
				strcpy (date,buf+DHDLEN);
				break;
			}
			/* put wierd header lines in extra */
			if ((ptr = index(buf,':')) != NULL)
			{
				*ptr = '\0';
				if (index(buf, ' ') == NULL)
				{
					*ptr = ':';
					sprintf(extra,"%s\n",buf);
					extra += strlen(extra);
					break;
				}
				*ptr = ':';
			}
			state = 2;

			/* remember the newline we lopped off */
			*pos = ftell(fp)-strlen(buf)-1;	/* fall through */
		case 2:
			++lcount;
			if (strncmp("--------",buf,8) == 0)
			{
				--lcount;
				return (lcount);
			}
			break;
		}
	}

	return (-1);
}

static skip_header (fp)
FILE *fp;
{
	char buf[RECLEN];

	while (fgets(buf,RECLEN-1,fp) != NULL)
		if (strncmp("--------",buf,8) == 0)
			break;
}
SHAR_EOF
echo shar: extracting "'envir_set.c'" '(1238 characters)'
if test -f 'envir_set.c'
then
	echo shar: over-writing existing file "'envir_set.c'"
fi
cat << \SHAR_EOF > 'envir_set.c'
#include <stdio.h>
#include <pwd.h>
#include <sys/param.h>
#include "config.h"

extern char *Editor,*Ps1,*Mailer,*Printer,*Poster;
extern char *Onews,*Newsrc,*Orgdir,*Savedir;	/* path names */

/*
	environment variable, original directory string setup.
*/

envir_set ()
{
	char dbuf [MAXPATHLEN], *rcname, *getenv(), *getwd(), *str_store();
	struct passwd *ptr, *getpwuid();

	if ((Ps1 = getenv("PS1")) == NULL)
		Ps1 = DEF_PS1;
	if ((Editor = getenv("EDITOR")) == NULL)
		Editor=DEF_ED;
	if ((Mailer = getenv("MAILER")) == NULL)
		Mailer=DEF_MAIL;
	if ((Poster = getenv("POSTER")) == NULL)
		Poster=DEF_POST;
	if ((Printer = getenv("PRINTER")) == NULL)
		Printer=DEF_PRINT;
	if ((rcname = getenv("NEWSRC")) == NULL)
		rcname=DEF_NEWSRC;
	Savedir = getenv("VNSAVE");

	/*
		set original directory strings.  create empty Newsrc if it doesn't exist
	 */

	ptr = getpwuid (getuid());
	if ((Orgdir = getwd(dbuf)) == NULL)
		printex ("cannot stat pwd");
	Orgdir = str_store (Orgdir);
	if (Savedir == NULL)
		Savedir = Orgdir;
	sprintf (dbuf, "%s/%s",ptr->pw_dir,rcname);
	Newsrc = str_store (dbuf);
	sprintf (dbuf, "%s/%s%s",ptr->pw_dir,".vn","XXXXXX");
	Onews = str_store (mktemp(dbuf));
	if (access (Newsrc,0) != 0)
		creat (Newsrc,0666);
}
SHAR_EOF
echo shar: extracting "'groupdir.c'" '(531 characters)'
if test -f 'groupdir.c'
then
	echo shar: over-writing existing file "'groupdir.c'"
fi
cat << \SHAR_EOF > 'groupdir.c'
#include <stdio.h>
#include "config.h"
#include "vn.h"

extern PAGE Page;

/*
	g_dir converts newsgroup name to directory string
*/
g_dir(s,t)
char *s,*t;
{
	char *ptr, *index();
	sprintf (t,"%s/%s",SPOOLDIR,s);
	for (ptr=t+strlen(SPOOLDIR)+1; (ptr = index(ptr,'.')) != NULL; *ptr = '/')
		;
}


/*
	change directory to group
*/
cd_group ()
{
	char dbuf [RECLEN];
	g_dir ((Page.h.group)->nd_name,dbuf);
	if (chdir(dbuf) < 0)
	{
		Page.h.artnum = 1;
		Page.b[0].art_id = 0;
		strcpy (Page.b[0].art_t, "CANNOT FIND NEWSGROUP");
	}
}
SHAR_EOF
echo shar: extracting "'hash.c'" '(1153 characters)'
if test -f 'hash.c'
then
	echo shar: over-writing existing file "'hash.c'"
fi
cat << \SHAR_EOF > 'hash.c'
#include <stdio.h>
#include "config.h"
#include "vn.h"

/* hash table manipulation routines */

static NODE *Tab [HASHSIZE];	/* hash Table */

hashinit ()
{
	int i;
	for (i=0; i < HASHSIZE; ++i)
		Tab[i] = NULL;
}

/*
	enter new node (name s, articles n, low l) in hash Table, 
	initial flags = 0.
*/
NODE *hashenter(s,n,l)
char *s;
int n;
int l;
{
	static int tcount=0;
	char *str_store();
	NODE *node_store();
	int i;

	if (tcount >= HASHMAX)
	{
		fgprintf("too many newsgroups - can't enter %s",s);
		return(NULL);
	}
	for (i=hash(s); Tab[i] != NULL; i = (++i) % HASHSIZE)
		;
	Tab[i] = node_store();
	if (l > n)
		l = n;
	(Tab[i])->rdnum = l;
	(Tab[i])->state = 0;
	(Tab[i])->art = n;
	(Tab[i])->nd_name = str_store(s);
	++tcount;
	return (Tab[i]);
}

/*
	find hash Table entry s, return node with nd_name NULL for insertion
	if non-existant
*/
NODE *hashfind(s)
char *s;
{
	int idx;
	for (idx = hash(s); Tab[idx] != NULL && strcmp((Tab[idx])->nd_name,s) != 0;
					idx = (++idx) % HASHSIZE)
		    ;
	return (Tab[idx]);
}

static hash (s)
char *s;
{
	int rem;
	for (rem=0; *s != '\0'; ++s)
		rem = (rem*128 + (*s&0x7f)) % HASHSIZE;
	return (rem);
}
SHAR_EOF
echo shar: extracting "'newsrc.c'" '(10338 characters)'
if test -f 'newsrc.c'
then
	echo shar: over-writing existing file "'newsrc.c'"
fi
cat << \SHAR_EOF > 'newsrc.c'
#include <stdio.h>
#include <ctype.h>
#include "config.h"
#include "tty.h"
#include "vn.h"

extern NODE *Newsorder [];
extern FILTER Filter [];
extern char *Onews, *Newsrc;
extern int Nfltr, Ncount, Lrec, C_allow;

/*
	global flags signifying options set
*/
#define GF_ALL 1	/* -x option - scan everything */
#define GF_SPEC 2	/* -n option(s) - user specified groups */
#define GF_OVER 4	/* command line specification - overide marks */

static char *Options[OPTLINES];
static int New_idx, Max_name, Optlines;
static unsigned Gflags = 0;

/*
	routines for dealing with the .newsrc file and options
*/

/*
	command name argument is already omitted from argv argc in this
	routine.  Only the option arguments are present.  We process
	options before we scan the rest of .newsrc, which redoes Newsorder,
	ie. we don't clobber Ncount until options are processed.
*/
scan_newsrc (argc,argv)
int argc;
char **argv;
{
	FILE *fp, *fopen();
	static char marks[] =
	{ 
		NEWS_ON, NEWS_OFF, '\0' 	};
	char *str_store ();
	int line, len, num;
	char buf [RECLEN], trail, optpflag, submark, *fret, *ptr, *strpbrk(), *strtok();


	/* initialize hash table, open temp file, fill table with active articles */
	hashinit ();
	fill_active ();
	temp_open();

	if (argc > 0)
	{
		Gflags |= GF_OVER;
		arg_opt(argc,argv);
		optpflag = 'y';
	}
	else
		optpflag = 'n';

	if ((fp = fopen (Newsrc,"r")) == NULL)
		printex ("can't open %s for reading",Newsrc);

	Optlines = 0;

	for (line = 1; (fret = fgets(buf,RECLEN-1,fp)) != NULL && emptyline(buf) == 1; ++line)
		;
	if (fret != NULL && strncmp (buf,"options",7) == 0)
	{
		Options[0] = str_store(buf);
		Optlines = 1;
		trail = buf [strlen(buf)-2];
		for ( ; (fret = fgets(buf,RECLEN-1,fp)) != NULL; ++line)
		{
			if (trail != '\\' && buf[0] != ' ' && buf[0] != '\t')
				break;
			if (Optlines >= OPTLINES)
				printex ("%s - too many option lines (%d allowed)",Newsrc,OPTLINES);
			Options[Optlines] = str_store(buf);
			++Optlines;
			if ((len = strlen(buf)) >= 2 && buf[len-2] != '\\')
				trail = buf[len-2];
			else
				trail = '\0';
		}
	}

	/* do the options from the newsrc file if there weren't command line args */
	if (Optlines > 0 && optpflag == 'n')
		newsrc_opt ();

	Ncount = 0;
	for ( ; fret != NULL; ++line, fret = fgets(buf,RECLEN-1,fp))
	{
		if (emptyline(buf) == 1)
			continue;
		if ((ptr = strpbrk(buf,marks)) == NULL)
		{
			fprintf (stderr,"\nwarning: line %d of %s (%s) - bad syntax\n",
			line,Newsrc,buf);
			continue;
		}
		submark = *ptr;
		*ptr = '\0';
		++ptr;
		num = 0;
		for (ptr = strtok(ptr," ,-\n"); ptr != NULL; ptr = strtok(NULL," ,-\n"))
		{
			len = atoi (ptr);
			for ( ; *ptr >= '0' && *ptr <= '9'; ++ptr)
				;
			if (*ptr != '\0' || len < num)
			{
				num = -1;
				fprintf (stderr,"\nwarning: line %d of %s (%s) - bad syntax\n",
				line,Newsrc,buf);
				break;
			}
			num = len;
		}
		if (num < 0)
			continue;
		chkgroup (buf,submark,num);
	}
	fclose (fp);

	/* now take care of groups not specified in .newsrc */
	art_active();

	/* free up the filter string storage */
	for (num=0; num < Nfltr; ++num)
		regfree (Filter[num].rex);
	Nfltr = 0;
}

static emptyline(s)
char *s;
{
	while (isspace(*s))
		++s;
	if (*s == '\0')
		return (1);
	return (0);
}

/*
	fill hash table from active news group list
	temporarily makes "Newsorder" active list order.
	This is needed to be able to process options
	before scanning user order.
*/
static fill_active ()
{
	FILE *f,*fopen ();
	char *nread, act_rec[RECLEN], *strtok();
	int num,lownum;
	NODE *hashenter();
	Ncount = Max_name = 0;
	if ((f = fopen (ACTFILE,"r")) == NULL)
		printex ("couldn't open %s\n",ACTFILE);
	while (fgets(act_rec, RECLEN-1, f) != NULL)
	{
		if (strtok (act_rec," \n") == NULL)
			continue;
		nread = strtok (NULL, " \n");
		if (nread != NULL)
			num = atoi(nread);
		else
			num = 0;
		nread = strtok (NULL, " \n");
		if (nread != NULL)
			lownum = atoi(nread);
		else
			lownum = 0;
		if (strlen(act_rec) > Max_name)
			Max_name = strlen(act_rec);
		Newsorder[Ncount] = hashenter (act_rec, num, lownum);
		if (Newsorder[Ncount] == NULL)
			break;
		++Ncount;
	}
	fclose (f);
}

/*
	check active newsgroups not mentioned in NEWSRC file
	(FLG_SCAN not set)
*/
static art_active ()
{
	char act_rec[RECLEN], *strtok();
	NODE *ptr,*hashfind();
	FILE *f,*fopen();
	if ((f = fopen (ACTFILE,"r")) == NULL)
		printex ("couldn't open %s\n",ACTFILE);
	New_idx = Ncount;
	while (fgets(act_rec, RECLEN-1, f) != NULL)
	{
		if (strtok (act_rec," \n") == NULL)
			continue;
		if ((ptr = hashfind (act_rec)) == NULL)
		{
			fgprintf ("%s - unexpected hash table failure\n",act_rec);
			continue;
		}
		if ((ptr->state & FLG_SCAN) == 0)
			chkgroup (ptr->nd_name, NEWS_ON, 0);
	}
}

/*
	check group for new articles:
	s - group
	c - subscription indicator from NEWSRC
	n - number read
*/
static chkgroup (s,c,n)
char *s,c;
int n;
{
	NODE *ptr, *hashfind();
	int lold,lowart;
	lold = Lrec;
	if ((ptr = hashfind(s)) != NULL && (ptr->state & FLG_SCAN) == 0)
	{
		Newsorder [Ncount] = ptr;
		++Ncount;
		ptr->pages = 0;
		ptr->state |= FLG_SCAN;
		if (c == NEWS_ON)
			ptr->state |= FLG_SUB;
		/* if "read" more than exist reset to zero */
		if (n > ptr->art)
			n = 0;
		lowart = ptr->rdnum;
		if (n < ptr->rdnum)
			n = ptr->rdnum;
		ptr->orgrd = ptr->pgrd = ptr->rdnum = n;
		ptr->pgshwn = 0L;

		/*
			scan directory decision is rather complex, since GF_ALL setting
			overides "n" value, and GF_SPEC indicates FLG_SPEC flag to be used.
			if GF_OVER set, FLG_SPEC overides subscription mark, otherwise
			FLG_SPEC AND subscribed is neccesary.
		    */
		if ((Gflags & GF_SPEC) != 0)
		{
			if ((ptr->state & FLG_SPEC) == 0)
				c = NEWS_OFF;
			else
			{
				if ((Gflags & GF_OVER) != 0)
					c = NEWS_ON;
			}
		}
		if ((Gflags & GF_ALL) != 0)
			n = lowart;
		if (c == NEWS_ON && ptr->art > n)
		{
			outgroup (s,n,ptr->art);
			if (lold != Lrec)
			{
				ptr->pnum = lold+1;
				ptr->pages = Lrec - lold;
				ptr->state |= FLG_PAGE;
			}
		}
	}
}

/*
	wr_newsrc writes the .newsrc file
*/
wr_newsrc ()
{
	FILE *fp,*fopen();
	NODE *p;
	char c;
	int i,rc;

	if (link(Newsrc,Onews) < 0)
		printex ("can't backup %s to %s before writing",Newsrc,Onews);

	if (unlink(Newsrc) < 0 || (fp = fopen(Newsrc,"w")) == NULL)
		printex ("can't open %s for writing (backed up in %s)",Newsrc,Onews);
	else
	{
		clearerr(fp);
		for (i=0; (rc = ferror(fp)) == 0 && i < Optlines; ++i)
			fprintf (fp,"%s",Options[i]);
		for (i=0; rc == 0 && i < Ncount; ++i)
		{
			p = Newsorder[i];
			if ((p->state & FLG_SUB) == 0)
				c = NEWS_OFF;
			else
				c = NEWS_ON;
			if (p->rdnum > 0)
				fprintf (fp,"%s%c 1-%d\n",p->nd_name,c,p->rdnum);
			else
				fprintf (fp,"%s%c 0\n",p->nd_name,c);
			rc = ferror(fp);
		}
		fclose (fp);
		if (rc != 0)
			printex ("write of %s failed, old copy stored in %s",Newsrc,Onews);
		else
			unlink (Onews);
	}
}

new_groups ()
{
	int i,wrem,w;
	char fs[24],c_end;
	if (New_idx >= Ncount || C_allow < (w = Max_name+1))
		return (0);
	term_set (ERASE);
	printf (NEWGFORM,Newsrc);
	sprintf (fs,"%%-%ds%%c",Max_name);
	wrem = C_allow;
	for (i=New_idx; i < Ncount; ++i)
	{
		if ((wrem -= w) < w)
		{
			wrem = C_allow;
			c_end = '\n';
		}
		else
			c_end = ' ';
		printf (fs,(Newsorder[i])->nd_name,c_end);
	}
	if ((++wrem) < C_allow)
		putchar ('\n');
	return (i-New_idx);
}

/*
	arg_opt must be called prior to option scanning, since
	it uses the options array.  This is a bit of a kludge,
	but it saves a bunch of work.  NOTE - no command name argument
*/
static arg_opt (argc,argv)
int argc;
char **argv;
{
	if (argc > OPTLINES)
		printex ("too many command line options (%d allowed)\n",OPTLINES);
	for (Optlines=0; Optlines < argc; ++Optlines)
	{
		Options[Optlines] = *argv;
		++argv;
	}
	newsrc_opt();
}

/*
	option setting routine:
	sets global flags: GF_ALL for -x option GF_SPEC for -n.
	sets up filter array for article scanning
*/
static newsrc_opt()
{
	int i;
	char curopt,tmp[RECLEN],*tok,*strtok(),*index();

	Nfltr = 0;
	curopt = '\0';
	for (i=0; i < Optlines; ++i)
	{
		strcpy(tmp,Options[i]);
		for (tok = strtok(tmp,",\\ \t\n"); tok != NULL; tok = strtok(NULL,",\\ \t\n"))
		{
			if (*tok != '-')
				do_opt (curopt,tok);
			else
			{
				for (++tok; *tok != '\0' && index(NARGOPT,*tok) != NULL; ++tok)
				{
					if (*tok == 'x')
						Gflags |= GF_ALL;
				}
				curopt = *tok;
				if (*(++tok) != '\0')
					do_opt (curopt,tok);
			}
		}
	}
}

static do_opt (opt,str)
char opt, *str;
{
	switch (opt)
	{
	case 'S':
		Gflags &= ~GF_OVER;
		break;
	case 'n':
		Gflags |= GF_SPEC;
		specmark(str);
		break;
	case 'w':
		specfilter (FIL_AUTHOR,str);
		break;
	case 't':
		specfilter (FIL_TITLE,str);
		break;
	default:
		break;
	}
}

static specfilter (comp,str)
char comp,*str;
{
	char *reg,neg,*regcmp();
	if (Nfltr >= NUMFILTER)
		printex ("%s: too many a,w option strings (%d allowed)",str,NUMFILTER);
	if ((neg = *str) == '!')
		++str;
	if ((reg = regcmp(str,(char *) 0)) == NULL)
		printex ("a,w option regular expression syntax: %s",str);
	Filter[Nfltr].rex = reg;
	Filter[Nfltr].neg = neg;
	Filter[Nfltr].hcomp = comp;
	++Nfltr;
}

/*
	handle the newsgroup specification string.
	("all" convention - braack!!!)
*/
static specmark (s)
char *s;
{
	unsigned ormask,andmask;
	int i,len;
	char *ptr,*re,pattern[RECLEN],*regex(),*regcmp();

	if (*s == '!')
	{
		++s;
		ormask = 0;
		andmask = ~FLG_SPEC;
		if (*s == '\0')
			return;
	}
	else
	{
		ormask = FLG_SPEC;
		andmask = 0xffff;
	}

	/* convert "all" not bounded by alphanumerics to ".*". ".all" becomes ".*" */
	for (ptr = s; (len = findall(ptr)) >= 0; ptr += len+1)
	{
		if (len > 0 && isalnum (s[len-1]))
			continue;
		if (isalnum (s[len+3]))
			continue;
		if (len > 0 && s[len-1] == '.')
		{
			--len;
			strcpy (s+len,s+len+1);
		}
		s[len] = '.';
		s[len+1] = '*';
		strcpy (s+len+2,s+len+3);
	}

	/* now use regular expressions */
	sprintf (pattern,"^%s$",s);
	if ((re = regcmp(pattern,(char *) 0)) == NULL)
		printex ("n option regular expression syntax: %s",s);
	for (i=0; i < Ncount; ++i)
	{
		if (regex(re,(Newsorder[i])->nd_name) != NULL)
		{
			(Newsorder[i])->state |= ormask;
			(Newsorder[i])->state &= andmask;
		}
	}
	regfree (re);
}

static findall (s)
char *s;
{
	int len;
	for (len=0; *s != '\0'; ++s,++len)
	{
		if (*s == 'a' && strncmp(s,"all",3) == 0)
			return (len);
	}
	return (-1);
}
SHAR_EOF
echo shar: extracting "'pagefile.c'" '(4933 characters)'
if test -f 'pagefile.c'
then
	echo shar: over-writing existing file "'pagefile.c'"
fi
cat << \SHAR_EOF > 'pagefile.c'
#include <stdio.h>
#include <sys/file.h>
#include "vn.h"
#include "head.h"

extern int Nfltr,Ncount,Lrec,L_allow,Cur_page,C_allow;
extern FILTER Filter[];
extern NODE *Newsorder[];
extern PAGE Page;
extern int Digest;

extern char *Aformat;

extern char *T_head, *F_head, *L_head;

static int Tdes;	/* temp file descriptor */
static int Pgsize;	/* block size for seeking file */

/*
	routines which deal with the temp file containing
	display pages.  Note the "invisible" file feature -
	tempfile is unlinked from /usr/tmp immediately.  when
	Tdes is closed by UNIX the disk space will be given back.
*/

temp_open ()
{
	char tmpart [L_tmpnam];
	Lrec = -1;
	tmpnam (tmpart);
	Pgsize = sizeof (HEAD) + L_allow * sizeof(BODY);
	if ((Tdes = open(tmpart,O_RDWR|O_CREAT)) < 0)
		printex ("can't open %s",tmpart);
	unlink (tmpart);
}

/*
	create page records for newsgroup s
	all articles between low and hi are to be included.
*/
outgroup (s,low,hi)
char *s;
int low,hi;
{
	int i,aid;
	char title[RECLEN],gd[RECLEN];
	g_dir(s,gd);
	if (chdir(gd) < 0)
	{
		grp_indic(s,0);
		return;
	}
	grp_indic(s,1);
	aid = 0;
	for (i=low+1; i <= hi; ++i)
	{
		if (digname (i,title) >= 0)
		{
			Page.b[aid].art_id = i;
			Page.b[aid].art_mark = ' ';
			strcpy (Page.b[aid].art_t, title);
			if ((++aid) >= L_allow)
			{

				/* start next page */
				Page.h.artnum = L_allow;
				do_write ();
				++Lrec;
				aid = 0;
			}
		}
	}

	/* last page (partial) */
	if (aid != 0)
	{
		Page.h.artnum = aid;
		do_write ();
		++Lrec;
	}
}

/*
	set current page to n.  use Pgsize and lseek to find it in
	temp file (descriptor Tdes).
*/
find_page (n)
int n;
{
	long off,lseek();
	int i,last;
	Cur_page = n;
	off = Pgsize;
	off *= (long) n;
	lseek (Tdes, off, 0);
	if (read(Tdes, (char *) &(Page.h), sizeof(HEAD)) < sizeof(HEAD))
		printex("bad temp file read");
	i = Pgsize - sizeof(HEAD);
	if (read(Tdes, (char *) Page.b, i) < i)
		printex("bad temp file read");
	last = -1;
	for (i=0; i < Ncount; ++i)
	{
		if ((Newsorder[i])->pages > 0)
		{
			if ((Newsorder[i])->pnum > n)
				break;
			last = i;
		}
	}
	if (last < 0)
		printex ("can't find page %d",n);
	Page.h.group = Newsorder[last];
	Page.h.name = (Page.h.group)->nd_name;
	cd_group ();
}

write_page ()
{
	long off,lseek();
	if (!Digest)
	{
		off = Pgsize;
		off *= (long) Cur_page;
		lseek (Tdes, off, 0);
		do_write();
	}
}

static do_write()
{
	int num;

	if (write(Tdes, (char *) &(Page.h), sizeof(HEAD)) < sizeof(HEAD))
		printex ("Bad temp file write");
	num = L_allow * sizeof(BODY);
	if (write(Tdes, (char *) Page.b, num) < num)
		printex ("Bad temp file write");
}

/*
	find article title:
	n - articles id
	t - returned title - must have storage for 3 times max title.
*/
static digname (n, t)
int n;
char *t;
{
	int i,tfilter,afilter;
	FILE *fp,*fopen();
	char ff [MAX_C+1],fn [MAX_C+1],fl [MAX_C+1],*index();

	/* open article */
	sprintf (t,"%d",n);
	if ((fp = fopen(t,"r")) == NULL)
		return (-1);

	/* get subject, from and lines by reading article */
	ff[0] = fn[0] = fl[0] = '?';
	ff[1] = fn[1] = fl[1] = '\0';
	ff[C_allow] = fn[C_allow] = fl[C_allow] = '\0';
	for (i = 0; i < HDR_LINES && fgets(t,RECLEN-1,fp) != NULL; ++i)
	{
		if (index(CHFIRST,t[0]) == NULL)
			continue;
		t[strlen(t) - 1] = '\0';
		if (strncmp(T_head,t,THDLEN) == 0)
		{
			strncpy(fn,t+THDLEN,C_allow);
			continue;
		}
		if (strncmp(F_head,t,FHDLEN) == 0)
		{
			strncpy(ff,t+FHDLEN,C_allow);
			continue;
		}
		if (strncmp(L_head,t,LHDLEN) == 0)
		{
			strncpy(fl,t+LHDLEN,C_allow);
			break;
		}
	}
	fclose (fp);

	/* filter the article [at]filter = 1 if no options, 0 if OK -1 if rejected */
	afilter = tfilter = 1;
	for (i=0; i < Nfltr; ++i)
	{
		switch (Filter[i].hcomp)
		{
		case FIL_AUTHOR:
			if (afilter == 0)
				break;
			if (afilter > 0)
				afilter = -1;
			if (do_filt(Filter[i].neg,Filter[i].rex,ff) >= 0)
				afilter = 0;
			break;
		case FIL_TITLE:
			if (tfilter == 0)
				break;
			if (tfilter > 0)
				tfilter = -1;
			if (do_filt(Filter[i].neg,Filter[i].rex,fn) >= 0)
				tfilter = 0;
			break;
		default:
			printex ("unexpected filter comparison: x%x",(int) Filter[i].hcomp);
		}
		if (afilter == 0 && tfilter == 0)
			break;
	}
	if (afilter < 0 || tfilter < 0)
		return (-1);

	form_title (t,fn,fl,ff,n);
	return (0);
}

form_title (t,fn,fl,ff,n)
char *t,*fn,*fl,*ff;
int n;
{
	char *ptr,*index();
	int i;

	if ((ptr = index(ff,'(')) != NULL && strlen(ptr) > 3)
		ff = ptr;
	sprintf (t,TFORMAT,fn,fl,ff);
	sprintf(ff,Aformat,' ',' ',n);
	i = C_allow - strlen(ff) + 1;	/* remember newline in Aformat */
	t[i] = '\0';
	ctl_xlt(t);
	return (0);
}

/* replace control characters in titles */
static ctl_xlt(s)
char *s;
{
	while (*s != '\0')
	{
		if (*s < ' ')
			*s += 'A' - 1;
		++s;
	}
}


static do_filt (neg,reg,str)
char neg,*reg,*str;
{
	char *regex();
	if (regex(reg,str) != NULL)
	{
		if (neg != '!')
			return (0);
		return (-1);
	}
	if (neg != '!')
		return (-1);
	return (0);
}
SHAR_EOF
echo shar: extracting "'reg.c'" '(1500 characters)'
if test -f 'reg.c'
then
	echo shar: over-writing existing file "'reg.c'"
fi
cat << \SHAR_EOF > 'reg.c'
#include <stdio.h>

#define RGBLKSIZE 20

struct _regtab
{
	struct _regtab *link;
	char *regstr;
};

typedef struct _regtab REGTAB;

static REGTAB *Chain = NULL;
static REGTAB *Free = NULL;
static REGTAB *Compiled = NULL;

regfree(s)
char *s;
{
	REGTAB *ptr,*cmp,*old;

	cmp = (REGTAB *) s;
	old = NULL;

	for (ptr = Chain; ptr != NULL; ptr = (old = ptr)->link)
	{
		if (ptr == cmp)
		{
			if (old == NULL)
				Chain = Chain->link;
			else
				old->link = ptr->link;
			ptr->link = Free;
			Free = ptr;
			break;
		}
	}
}

char *regcmp(str)
char *str;
{
	int i;
	char *str_store();
	char *re_comp();

	if (re_comp(str) != NULL)
	{
		Compiled = NULL;	/* make sure we're OK */
		return(NULL);
	}

	if (Free == NULL)
	{
		Free = (REGTAB *) malloc(RGBLKSIZE * sizeof(REGTAB));
		if (Free == NULL)
			printex ("regcmp: memory allocation failure");
		for (i = 0; i < RGBLKSIZE - 1; ++i)
			Free[i].link = Free + i + 1;
		Free[i].link = NULL;
	}

	Compiled = Free;
	Free = Free->link;

	Compiled->link = Chain;
	Chain = Compiled;
	Compiled->regstr = str_store(str);

	return ((char *) Compiled);
}

char *regex(reg,str)
char *reg,*str;
{
	REGTAB *cmp;

	cmp = (REGTAB *) reg;

	if (cmp == Compiled)
	{
		if (re_exec(str))
			return(str);
		return (NULL);
	}

	for (Compiled = Chain; Compiled != NULL; Compiled = Compiled->link)
	{
		if (Compiled == cmp)
			break;
	}

	if (Compiled == NULL)
		printex ("regex: bad pointer");

	re_comp(Compiled->regstr);

	if (re_exec(str))
		return(str);

	return(NULL);
}
SHAR_EOF
echo shar: extracting "'sig_set.c'" '(4276 characters)'
if test -f 'sig_set.c'
then
	echo shar: over-writing existing file "'sig_set.c'"
fi
cat << \SHAR_EOF > 'sig_set.c'
#include <stdio.h>
#include <sys/signal.h>
#include <sgtty.h>
#include <setjmp.h>
#include "tty.h"
#include "vn.h"

extern int L_allow;
extern char *Version;

static int Sigflag=BRK_INIT;	/* phase of interaction */
static FILE **Fpseek;		/* article reading file pointer pointer */
static int Foreground;
static jmp_buf Jumploc;		/* for BRK_SESS phase */
static char *Cur_scn;		/* current group name being scanned */

/*
	interrupt handler - unusual termination (longjmp and printex aborts)
	if not abort, remember to reset signal trap
	CAUTION - the passing of a jump buffer is a little dicey - assumes
	type jump_buf is an array.

	sigcatch and sig_set control a lot of i/o on stderr also, since
	it is so intimately related to signal interaction.  Note that the
	SIGTSTP action causes a "stopped on tty output" if raw terminal
	mode is restored by tty_set(RESTORE).  We don't get it if we were
	already cooked since tty_set avoids calling ioctl if it doesn't
	have to.
*/
static sigcatch (sig)
int sig;
{
	char buf [MAX_C+1];
	int pgrp;

	/* disable signal while processing it */
	signal (sig,SIG_IGN);

	switch (sig)
	{
	case SIGINT:
	case SIGQUIT:
		break;
	case SIGTSTP:
		/* ignore SIGTTOU so we don't get stopped if [kc]sh grabs the tty */
		signal(SIGTTOU, SIG_IGN);
		tty_set (SAVEMODE);
		term_set (MOVE,0,L_allow+RECBIAS-1);
		printf ("\n");
		Foreground = 0;
		fflush (stdout);
		fflush (stderr);
		signal(SIGTTOU, SIG_DFL);

		/* Send the TSTP signal to suspend our process group */
		signal(SIGTSTP, SIG_DFL);
		sigsetmask(0);
		kill (0, SIGTSTP);

		/* WE ARE NOW STOPPED */

		/*
				WELCOME BACK!
				if terminals process group is ours, we are foregrounded again
				and can turn newsgroup name printing back on
			*/
		tty_set (RESTORE);
		switch (Sigflag)
		{
		case BRK_SESS:
			signal (SIGTSTP,sigcatch);
			longjmp (Jumploc,1);
		case BRK_IN:
			ioctl (1,TIOCGPGRP,&pgrp);
			if (pgrp == getpgrp(0))
			{
				Foreground = 1;
				if (Cur_scn != NULL)
					fgprintf ("    %s\n",Cur_scn);
			}
			break;
		default:
			break;
		}
		signal (SIGTSTP,sigcatch);
		return;
	default:
		printex (BRK_MSG,sig);
	}

	/* QUIT and INTERRUPT signals */
	switch (Sigflag)
	{
	case BRK_SESS:
		/* if in session, ask if really a quit, do longjump if not */
		term_set (ERASE);
		tty_set (RAWMODE);
		user_str (buf, BRK_PR);
		if (buf[0] == 'y')
			printex (BRK_MSG,sig);
		signal (sig,sigcatch);
		longjmp (Jumploc,1);
	case BRK_READ:
		/* if reading seek file to end to abort page printing */
		printf ("\n");
		if (*Fpseek == NULL || fseek(*Fpseek,0L,2) < 0)
			putchar ('\07');
		break;
	default:
		printex (BRK_MSG,sig);
	}
	signal (sig,sigcatch);
}

/*
	sig_set controls what will be done with a signal when picked up by
	sigcatch.  grp_indic / fgprintf is included here to keep knowledge
	of TSTP state localized.
*/
/* VARARGS */
sig_set (flag,dat)
int flag, *dat;
{
	int i, *xfer, pgrp;
	if (Sigflag == BRK_INIT)
	{
		Cur_scn = NULL;
		signal (SIGINT,sigcatch);
		signal (SIGQUIT,sigcatch);
		signal (SIGHUP,sigcatch);
		signal (SIGTERM,sigcatch);
		signal (SIGTSTP,sigcatch);
		ioctl (1,TIOCGPGRP,&pgrp);
		if (pgrp == getpgrp(0))
		{
			Foreground = 1;
			fgprintf ("Visual News, Release %s, reading:\n",Version);
		}
		else
			Foreground = 0;
	}
	switch (flag)
	{
	case BRK_IN:
	case BRK_OUT:
		Sigflag = flag;
		break;
	case BRK_READ:
		if (Sigflag != BRK_SESS)
			printex ("unexpected read state, sig_set\n");
		Fpseek = (FILE **) dat;
		Sigflag = BRK_READ;
		break;
	case BRK_SESS:
		xfer = (int *) Jumploc;
		for (i=0; i < sizeof(Jumploc) / sizeof(int); ++i)
			xfer[i] = dat[i];
		Sigflag = BRK_SESS;
		break;
	case BRK_RFIN:
		if (Sigflag != BRK_READ)
			printex ("unexpected finish state, sig_set\n");
		Sigflag = BRK_SESS;
		break;
	default:
		printex ("bad state %d, sig_set\n",flag);
	}
}

grp_indic (s,ok)
char *s;
int ok;
{
	NODE *ptr,*hashfind();

	/* we go to hash table because s might be a temporary buffer */
	if ((ptr = hashfind(s)) != NULL)
	{
		Cur_scn = ptr->nd_name;
		if (Foreground)
		{
			if (ok)
				fgprintf("    %s\n",Cur_scn);
			else
				fgprintf("    %s - Can't access spool directory\n",Cur_scn);
		}
	}
}

fgprintf (fs,a,b,c,d,e)
char *fs;
int a,b,c,d,e;
{
	if (Foreground)
		fprintf (stderr,fs,a,b,c,d,e);
	fflush (stderr);
}
SHAR_EOF
echo shar: extracting "'storage.c'" '(1127 characters)'
if test -f 'storage.c'
then
	echo shar: over-writing existing file "'storage.c'"
fi
cat << \SHAR_EOF > 'storage.c'
#include <stdio.h>
#include "vn.h"

extern char *malloc();

extern int L_allow;

extern PAGE Page;
/*
	all calls to malloc here.  storage allocaters.
*/

char *str_store (s)
char *s;
{
	static unsigned av_len = 0;	/* current storage available */
	static char *avail;
	int len;

	if ((len = strlen(s)+1) > av_len)
	{
		if (len > STRBLKSIZE)
			av_len = len;
		else
			av_len = STRBLKSIZE;
		if ((avail = malloc(av_len)) == NULL)
			printex ("can't allocate memory for string storage");
	}
	strcpy (avail,s);
	s = avail;
	avail += len;
	av_len -= len;
	return (s);
}

/*
** called after number of terminal lines (L_allow) is known, to set
** up storage for Page.
*/
page_alloc ()
{
	char *body;

	if ((body = malloc(L_allow*sizeof(BODY))) == NULL)
		printex ("can't allocate memory for display storage");

	Page.b = (BODY *) body;
}

NODE
*node_store()
{
	static int nd_avail = 0;
	static NODE *nd;
	NODE *ret;

	if (nd_avail <= 0)
	{
		if ((nd = (NODE *) malloc(sizeof(NODE)*NDBLKSIZE)) == NULL)
			printex ("can't allocate memory for newsgroup table");
		nd_avail = NDBLKSIZE;
	}
	--nd_avail;
	ret = nd;
	++nd;
	return(ret);
}
SHAR_EOF
echo shar: extracting "'strings.c'" '(553 characters)'
if test -f 'strings.c'
then
	echo shar: over-writing existing file "'strings.c'"
fi
cat << \SHAR_EOF > 'strings.c'
#include "vn.h"
#include "head.h"

char *Version = "5/86";

char *Help_msg = "? for help";
char *No_msg = "No articles";
char *Hdon_msg = "Headers being printed";
char *Hdoff_msg = "Headers being suppressed";
char *Roton_msg = "ROT 13";
char *Rotoff_msg = "NO ROT";

char *Aformat = AFORMAT;

char *Contstr = "  ******** any key to continue ********";

char *R_head = RHEAD;
char *M_head = MHEAD;
char *P_head = PHEAD;
char *D_head = DHEAD;
char *F_head = FHEAD;
char *FT_head = FTHEAD;
char *T_head = THEAD;
char *L_head = LHEAD;
char *N_head = NHEAD;
SHAR_EOF
echo shar: extracting "'term_set.c'" '(3065 characters)'
if test -f 'term_set.c'
then
	echo shar: over-writing existing file "'term_set.c'"
fi
cat << \SHAR_EOF > 'term_set.c'
#include <stdio.h>
#include "tty.h"
#include "vn.h"

extern int L_allow, C_allow;

static outc (c)
char c;
{
	putchar (c);
}

/*
	term_set controls terminal through termcap
	START sets global parameters related to terminal also,
	as well as allocating display buffer which depends on
	terminal lines, and allocating escape strings.
*/

/*
** Escape strings.
*/
static char *Cm,*Cl,*So,*Se,*Te,*Bc,*Ce;
static int Backspace;		/* backspace works */
static int Overstrike;

static t_setup()
{
	int i;
	char *tgetstr(), *getenv(), *str_store();
	char *c, tc_buf[2048],optstr[2048];

	c = optstr;
	if (tgetent(tc_buf,getenv("TERM")) != 1)
		printex ("%s - unknown terminal",getenv("TERM"));

	/* get needed capabilities */
	Cm = str_store(tgetstr("cm",&c));
	Cl = str_store(tgetstr("cl",&c));
	So = str_store(tgetstr("so",&c));
	Se = str_store(tgetstr("se",&c));
	Te = str_store(tgetstr("te",&c));
	Bc = str_store(tgetstr("bc",&c));
	Ce = str_store(tgetstr("ce",&c));
	Backspace = tgetflag("bs");
	Overstrike = tgetflag("os");
	if ( *Cm == '\0' || *Cl == '\0')
	{
		printex ("cursor control and erase capability needed");
	}
	if (Overstrike)
		fgprintf ("WARNING: terminal overstrikes - can't update display without erase\n");
	i = RECBIAS+1 < HHLINES+2 ? HHLINES+2 : RECBIAS+1;
	if ((L_allow = tgetnum("li")) < i)
	{
		if (L_allow < 0)
			printex ("can't determine number of lines on terminal");
		printex ("too few lines for display - %d needed", i);
	}

	/*
	** C_allow set so as to not use extreme right column.
	** Avoids "bad wraparound" problems - we're deciding it's best
	** to ALWAYS assume no automargin, and take care of it ourselves
	*/
	if((C_allow = tgetnum("co")) > MAX_C)
		C_allow = MAX_C;
	else
		--C_allow;
	if (C_allow < MIN_C)
	{
		if (C_allow < 0)
			printex("can't determine number of columns on terminal.");
		printex ("too few columns for display - %d needed",MIN_C);
	}

	L_allow -= RECBIAS;
	page_alloc();
	tputs(tgetstr("ti",&c),1,outc);
}

/* VARARGS */
term_set(cmd,x,y)
int cmd,x,y;
{
	char *tgoto();
	int i;
	switch (cmd)
	{
	case MOVE:
		tputs (tgoto(Cm,x,y),1,outc);
		break;
	case ERASE:
		tputs(Cl,1,outc);
		break;
	case ONREVERSE:
		tputs(So,1,outc);
		break;
	case OFFREVERSE:
		tputs(Se,1,outc);
		break;
	case START:
		t_setup();
		break;
	case STOP:
		term_set (MOVE,0,L_allow+RECBIAS-1);
		printf ("\n");
		tputs(Te,1,outc);
		break;
	case RUBSEQ:
		if (Overstrike)
		{
			/* space overprint is futile */
			if (Backspace)
				putchar('\010');
			else
				tputs(Bc,1,outc);
			break;
		}
		if (Backspace)
			printf("%c %c",'\010','\010');
		else
		{
			tputs(Bc,1,outc);  
			putchar(' ');  
			tputs(Bc,1,outc);
		}
		break;
	case ZAP:
		if (Ce != NULL && *Ce != NULL)
			tputs(Ce,1,outc);
		else
		{
			if (Overstrike)
				break;		/* punt */
			for (i=x; i < y; ++i)
				putchar(' ');
			if (Backspace)
			{
				for (i=x; i < y; ++i)
					putchar('\010');
			}
			else
			{
				for (i=x; i < y; ++i)
					tputs(Bc,1,outc);
			}
		}
		break;
	default:
		printex ("term_set unknown code (%d)",cmd);
		break;
	}
	return (0);
}
SHAR_EOF
echo shar: extracting "'tty_set.c'" '(1888 characters)'
if test -f 'tty_set.c'
then
	echo shar: over-writing existing file "'tty_set.c'"
fi
cat << \SHAR_EOF > 'tty_set.c'
#include <sgtty.h>
#include "tty.h"

extern char Erasekey,Killkey;

static struct sgttyb C_tp;
static unsigned short O_lflag;
static unsigned S_flag=0;
static int R_ignore=0;		/* up/down counter of reset calls to ignore */

#define IO_GOT 1	/* have polled for original terminal mode */
#define IO_RAW 2	/* in RAW (CBREAK actually) mode */

/*
	tty_set handles ioctl calls.  SAVEMODE, RESTORE are used around
	system calls and interrupts to assure cooked mode, and restore
	raw if raw on SAVEMODE.  The pair results in no calls to ioctl
	if we are cooked already when SAVEMODE is called, and may be nested,
	provided we desire no "restore" of cooked mode after restoring raw.

	When we get the original terminal mode, we also save erase and kill.

	sig_set makes an ioctl call to get process group leader.  Otherwise
	ioctl calls should come through here.
*/
tty_set(cmd)
int cmd;
{
	int rc;
	unsigned mask;

	switch (cmd)
	{
	case BACKSTOP:
		if ((rc = ioctl(1,TIOCLGET,&mask)) != 0)
			break;
		mask |= LTOSTOP;
		rc = ioctl(1,TIOCLSET,&mask);
		break;
	case RAWMODE:
		if ((S_flag & IO_RAW) != 0)
		{
			rc = 0;
			break;
		}
		if ((S_flag & IO_GOT) == 0)
		{
			rc = ioctl(0,TIOCGETP,&C_tp);
			O_lflag = C_tp.sg_flags;
			Erasekey = C_tp.sg_erase;
			Killkey = C_tp.sg_kill;
		}
		C_tp.sg_flags |= CBREAK;
		C_tp.sg_flags &= ~ECHO;
		rc = ioctl(0,TIOCSETP,&C_tp);
		S_flag = IO_GOT|IO_RAW;
		break;
	case COOKED:
		if ((S_flag & IO_RAW) != 0)
		{
			C_tp.sg_flags = O_lflag;
			rc = ioctl(0,TIOCSETP,&C_tp);
			S_flag &= ~IO_RAW;
		}
		else
			rc = 0;
		break;
	case SAVEMODE:
		if ((S_flag & IO_RAW) != 0)
		{
			tty_set(COOKED);
			R_ignore = 0;
		}
		else
			++R_ignore;
		rc = 0;
		break;
	case RESTORE:
		if (R_ignore <= 0)
		{
			tty_set(RAWMODE);
		}
		else
			--R_ignore;
		rc = 0;
		break;
	default:
		rc = -1;
	}
	if (rc < 0)
		printex ("ioctl failure, tty_set: %d",cmd);
}
SHAR_EOF
echo shar: extracting "'ucb.c'" '(1139 characters)'
if test -f 'ucb.c'
then
	echo shar: over-writing existing file "'ucb.c'"
fi
cat << \SHAR_EOF > 'ucb.c'
#include <stdio.h>

char *strpbrk (s,del)
char *s, *del;
{
	char *ptr,*index();
	if (s == NULL)
		return (NULL);
	for (; *del != '\0'; ++del)
		if ((ptr = index(s,*del)) != NULL)
			return (ptr);
	return (NULL);
}

char *strtok(str,delim)
char *str, *delim;
{
	char *tokstart, *tokend, *first_ch (), *last_ch();
	static char *save=NULL;

	if (str != NULL)
		save = str;

	if (save == NULL)
		return (NULL);

	tokstart = first_ch (save, delim);
	tokend = last_ch (tokstart, delim);
	save = first_ch (tokend, delim);
	*tokend = '\0';

	if (*tokstart == '\0')
		return (NULL);

	return (tokstart);
}

static char *first_ch (str,delim)
char *str,*delim;
{
	char *index ();
	char *f;

	for (f = str; *f != '\0' && index(delim,*f) != NULL; ++f)
		;

	return (f);
}

static char *last_ch (str,delim)
char *str,*delim;
{
	char *index ();
	char *f;

	for (f = str; *f != '\0' && index(delim,*f) == NULL; ++f)
		;

	return (f);
}

char *tmpnam (buf)
char *buf;
{
	static char *ptr = "/usr/tmp/vnXXXXXX";

	/* depends on string initialized above */
	sprintf (ptr+11,"XXXXXX");

	mktemp (ptr);

	if (buf != NULL)
		strcpy (buf,ptr);

	return (ptr);
}
SHAR_EOF
echo shar: extracting "'userlist.c'" '(1782 characters)'
if test -f 'userlist.c'
then
	echo shar: over-writing existing file "'userlist.c'"
fi
cat << \SHAR_EOF > 'userlist.c'
#include <stdio.h>
#include "vn.h"

extern PAGE Page;

/*
	generate user list of articles - either article numbers
	are input directly (numeric list), or input is a search
	string - invoke regular expression library and examine titles
	search string "*" reserved for marked articles.  Strings may
	be prefixed with '!' for negation.
*/
userlist (list)
char *list;
{
	int i,j,anum[RECLEN/2],acount;
	char neg, *s, sbuf[MAX_C+1], *reg, *regex(), *regcmp(), *index(), *strtok();

	user_str (sbuf,"Articles or title search string : ");
	if (sbuf[0] == '!')
	{
		neg = '!';
		s = sbuf+1;
	}
	else
	{
		neg = '\0';
		s = sbuf;
	}
	for (i=0; s[i] != '\0'; ++i)
	{
		if (index(LIST_SEP,s[i]) == NULL)
		{
			if (s[i] < '0' || s[i] > '9')
				break;
		}
	}
	acount = 0;

	if (s[i] == '\0')
	{
		for (s = strtok(s,LIST_SEP); s != NULL; s = strtok(NULL,LIST_SEP))
		{
			anum[acount] = atoi(s);
			++acount;
		}
	}
	else
	{
		if (s[0] == ART_MARK)
		{
			for (i=0; i < Page.h.artnum; ++i)
			{
				if (Page.b[i].art_mark == ART_MARK)
				{
					anum[acount] = Page.b[i].art_id;
					++acount;
				}
			}
		}
		else
		{
			reg = regcmp(s,(char *) 0);
			if (reg != NULL)
			{
				for (i=0; i < Page.h.artnum; ++i)
				{
					if (regex(reg,Page.b[i].art_t) != NULL)
					{
						anum[acount] = Page.b[i].art_id;
						++acount;
					}
				}
				regfree (reg);
			}
			else
				preinfo ("bad regular expression syntax");
		}
	}

	/* algorithm is inefficient, but we're only handling a few numbers */
	*list = '\0';
	for (i=0; i < Page.h.artnum; ++i)
	{
		for (j=0; j < acount && anum[j] != Page.b[i].art_id; ++j)
			;
		if (neg == '!')
		{
			if (j < acount)
				continue;
		}
		else
		{
			if (j >= acount)
				continue;
		}
		sprintf (list,"%d ",Page.b[i].art_id);
		list += strlen(list);
	}
}
SHAR_EOF
echo shar: extracting "'vnglob.c'" '(922 characters)'
if test -f 'vnglob.c'
then
	echo shar: over-writing existing file "'vnglob.c'"
fi
cat << \SHAR_EOF > 'vnglob.c'
#include <stdio.h>
#include "config.h"
#include "vn.h"

/*
	global data structure
*/
NODE *Newsorder [HASHMAX];	/* .newsrc file order */
char *Editor,*Ps1,*Mailer,*Printer,*Poster;

char Erasekey, Killkey;		/* user keys from stty */
char *Newsrc, *Orgdir;		/* .newsrc file, and original pwd */
char *Onews;			/* temp. file for backing up .newsrc */
char *Savefile = DEF_SAVE;	/* file in which to save articles */
char *Savedir;			/* default directory for saved articles */

int Rot;	/* rotation */
int Headflag;	/* header printing flag */
int Digest;	/* if non-zero, digest article */

/*
	cur_page - current page displayed;
	lrec - last record
	l_allow - lines allowed for article display
	c_allow - columns allowed
	ncount = newsorder index
	nfltr - number of filters
*/
int Cur_page, Lrec, L_allow, C_allow, Ncount, Nfltr;

/*
	article filters from options
*/
FILTER Filter [NUMFILTER];

/*
	current page
*/
PAGE Page;
SHAR_EOF
#	End of shell archive
exit 0