[comp.sources.unix] v19i064: NN, a Usenet news reader, Part03/15

rsalz@uunet.uu.net (Rich Salz) (06/23/89)

Submitted-by: mcvax!tidk!storm@uunet.UU.NET (Kim F. Storm)
Posting-number: Volume 19, Issue 64
Archive-name: nn/part03

#!/bin/sh
# this is part 3 of a multipart archive
# do not concatenate these parts, unpack them in order with /bin/sh
# file collect.c continued
#
CurArch=3
if test ! -r s2_seq_.tmp
then echo "Please unpack part 1 first!"
     exit 1; fi
( read Scheck
  if test "$Scheck" != $CurArch
  then echo "Please unpack part $Scheck next!"
       exit 1;
  else exit 0; fi
) < s2_seq_.tmp || exit 1
echo "x - Continuing file collect.c"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' >> collect.c
X	putc('@', data);
X	hdr.dh_subject_length--;
X    }
X    
X    if (hdr.dh_subject_length)
X	Fwrite(subj_buf, sizeof(char), (int)hdr.dh_subject_length, data);
X
X    return;
X}
NO_NEWS_IS_GOOD_NEWS
echo "File collect.c is complete"
chmod 0644 collect.c || echo "restore of collect.c fails"
set `wc -c collect.c`;Sum=$1
if test "$Sum" != "6420"
then echo original size 6420, current size $Sum;fi
echo "x - extracting config.h-dist (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > config.h-dist &&
X/**************************** NN CONFIGURATION ***************************
X *
X *	Configuration file for nn
X *
X *	You must edit this file to reflect your local configuration
X *	and environment.
X *
X *	Follow the instructions given in the comments.  See the files
X *	INSTALLATION, NNTP, and PROBLEMS for more details.
X */
X
X#define	RELEASE 	6
X#define	VERSION 	3
X
X
X#include <stdio.h>
X#include <ctype.h>
X
X
X/*********************** NETWORK DEPENDENT DEFINITIONS **********************
X *
X *	Define NETWORK_DATABASE if you share the database through NFS on 
X *	a network with different, non-compatible machines, e.g. SUNs and
X *	VAXen, or SUN-3 and SUN-4, or if you are using different compilers
X *	on the same architecture.
X *
X *	In a homogenous network, you can leave it undefined for higher
X *	performance (no data conversion is needed).
X */
X
X/* #define NETWORK_DATABASE	/* */
X
X
X/********************************** NNTP *********************************
X *
X * 	Define NNTP to enable nntp support.
X *
X *	With NNTP, the nnmaster still maintains a local database of 
X *	all article headers for fast access (and because NNTP does not 
X *	support nn - yet), while the articles are fetched from the
X *	nntp server when they are read or saved.
X *
X *	You may still share this database through NFS locally (see the 
X *	description of NETWORK_DATABASE above) if you don't want to
X *	have separate nn databases on all your local systems.
X *
X *	Consult the file NNTP for further information on the use of NNTP.
X */
X
X/* #define NNTP			/* */
X
X/* 
X * 	Define NNTP_SERVER to the name of a file containing the name of the
X * 	nntp server.
X *
X * 	It is vital that both the nnmaster and all nn users on a machine 
X *	uses the same nntp server, because the nn database is synchronized
X *	with a specific news active file. 
X *
X *	If the file name does not start with a slash, it is relative to 
X *	LIB_DIRECTORY defined below.
X */
X
X#define NNTP_SERVER	"/usr/lib/nntp_server"
X
X
X/***************** OPERATING SYSTEM DEPENDENT DEFINITIONS *******************
X *
X *  	Include the appropriate s- file for your system below.
X *
X *	If a file does not exist for your system, you can use s-template.h
X *	as a starting point for writing you own.
X */
X
X#include "s-usg3-1.h"
X
X
X/********************** MACHINE DEPENDENT DEFINITIONS **********************
X *
X *	Include the appropriate m- file for your system below.
X *	
X *	If a file does not exist for your system, you can use m-template.h
X *	as a starting point for writing you own.
X */
X
X#include "m-m680x0.h"
X
X
X/******************** SITE DEPENDENT DEFINITIONS **********************
X *
X *	Edit the following part to suit your local system setup
X */
X
X/*
X *	Specify where programs and data should be placed
X *
X *	BIN_DIRECTORY   - the location of the user programs
X *	LIB_DIRECTORY	- the location of auxiliary programs and files
X *	DB_DIRECTORY	- the directory containing the nn database
X *
X *
X * notice: if you share the news directory accross a network, you should
X *	   use something like /usr/spool/news/.nn for DB_DIRECTORY.
X */
X
X#define BIN_DIRECTORY	"/usr/local/bin"
X#define LIB_DIRECTORY	"/usr/local/lib/nn"
X#define DB_DIRECTORY	"/usr/spool/nn"
X
X/*
X * 	Specify directories for the user and system manuals
X *
X * 	Adapt this to your local standards; the manuals will be named 
X * 		$(MAN_DIR)/program.$(MAN_SECTION)
X */
X
X#define USER_MAN_DIR 	"/usr/man/man1"
X#define USER_MAN_SECTION 	"1"
X
X#define SYS_MAN_DIR 	"/usr/man/man1"
X#define SYS_MAN_SECTION 	"1m"
X
X/*
X *	Specify where to put temporary files.  Overriden by $TMPDIR.
X *	Notice that nn does not create "large" temp files.
X */
X
X#define TMP_DIRECTORY	"/tmp"
X
X/*
X * Specify owner and group for files belonging to this package.
X *
X * Specifically, the nnmaster will run suid/sgid to this owner and group.
X *
X * The only requirements are that the ownership allows the nnmaster to
X * READ the news related files and directories, and the ordinary users
X * to read the database and execute the nn* programs.
X *
X * Normal choices are:  (news, news)  and   (your uid, your gid)
X */
X
X#define OWNER	"news"
X#define	GROUP	"news"
X
X/*
X *	Define STATISTICS if you want to keep a record of how much
X *	time the users spend on news reading.
X *
X *	Sessions shorter than the specified number of minutes are not
X *	recorded (don't clutter up the log file).
X *
X *	This is entered into the file $LIB_DIRECTORY/Log with code U
X */
X
X/* #define STATISTICS	5 /* minutes */
X
X/*
X *	Define HAVE_ROUTING if your mailer understands domain based
X *	adresses (...@...) and performs the necessary rerouting (e.g. 
X *	Sendmail or Smail).
X *
X *	Otherwise, nn will provide a simple routing facility using
X *      routing information specified in the file LIB_DIRECTORY/routes.
X */
X
X#define HAVE_ROUTING			/* */
X
X/*
X *	If HAVE_ROUTING is NOT defined, nn needs to know the name of
X * 	your host.  To obtain the host name it will use either of the
X *	'uname' or 'gethostname' system calls as specified in the s- file 
X *	included above.
X *
X *	If neither 'uname' nor 'gethostname' is available, you must
X *	define HOSTNAME to be the name of your host.  Otherwise, leave
X *	it undefined (it will not be used anyway).
X */
X
X/* #define HOSTNAME	"myhost"	/* Not used if HAVE_ROUTING */
X
X/*
X *	Specify the location of your news programs and files
X */
X
X#define	INEWS_PATH	"/usr/lib/news/inews"
X#define NEWS_ACTIVE	"/usr/lib/news/active"
X#define NEWS_DIRECTORY	"/usr/spool/news"
X
X/*
X *	Specify a mailer that accepts a letter WITH a header IN THE TEXT.
X *
X * 	A program named 'recmail' program is normally delivered with 
X *	the news system.
X *	On BSD systems you can also use "/usr/lib/sendmail -t".
X */
X
X#define REC_MAIL	"/usr/lib/news/recmail"
X
X/*
X *	Define APPEND_SIGNATURE if you want nn to ask users to append
X *	~/.signature to mail messages (reply/forward/mail).  
X *	
X *	If the mailer defined in REC_MAIL automatically includes .signature
X *	you should not define this (it will fool people to include it twice).
X *
X *	I think 'recmail' includes .signature, but 'sendmail -t' doesn't.
X */
X
X/* #define APPEND_SIGNATURE		/* */
X
X/*
X *	Default folder directory
X */
X
X#define FOLDER_DIRECTORY	"~/News"
X
X/*
X *	Max length of authors name (in "edited" format).
X *	Also size of "Name" field on the article menus.
X *	You may want to increase this if your terminals are wider than
X *	80 columns.
X */
X
X#define NAME_LENGTH 		16
X
X/*
X * 	Define RESIZING to make nn understand dynamic window-resizing.
X * 	(It uses the TIOCGWINSZ ioctl found on most 4.3BSD systems)
X */
X
X/* #define RESIZING		/* */
X
X
X/************************ CONFIGURATION COMPLETED ************************
X *
X *	The rest of this file will not need any changes.
X */
X
X#include "global.h"
NO_NEWS_IS_GOOD_NEWS
chmod 0644 config.h-dist || echo "restore of config.h-dist fails"
set `wc -c config.h-dist`;Sum=$1
if test "$Sum" != "6667"
then echo original size 6667, current size $Sum;fi
echo "x - extracting cvt-help.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > cvt-help.c &&
X#include <stdio.h>
X
Xmain()
X{
X    register int c;
X
X    while ((c = getchar()) != EOF) {
X	if (c == ';') {
X	    c = getchar();
X	    if (c == ':') {
X		c = getchar();
X		putchar(c & 0xf);
X		continue;
X	    }
X	    putchar(';');
X	    putchar(c);
X	    continue;
X	}
X	if (c >= 1 && c <= 7) {
X	    putchar(';');
X	    putchar(':');
X	    putchar(c | 0x40);
X	    continue;
X	}
X	putchar(c);
X    }
X    
X    exit(0);
X}
X
X
X	    
NO_NEWS_IS_GOOD_NEWS
chmod 0644 cvt-help.c || echo "restore of cvt-help.c fails"
set `wc -c cvt-help.c`;Sum=$1
if test "$Sum" != "407"
then echo original size 407, current size $Sum;fi
echo "x - extracting data.h (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > data.h &&
X/*
X *	Internal representation of the master, group, and article
X *	information read from the database.
X *
X *	For each article read from the database, an article_header
X *	structure is initialized.
X */
X
X/*
X *	global master data
X */
X
Xtypedef struct {
X    time_t		last_scan; /* age of active file at last scan */
X    group_number	number_of_groups;
X    off_t		next_group_write_offset; /* in .groups */
X    int			free_groups; /* allocated during first visit */
X} master_header;
X
X/*
X *	group information
X */
X
Xtypedef struct group_header {
X
X    /* this part of the header is read from */
X    /* the .master file			    */
X
X    article_number	first_l_article;
X    article_number	last_l_article;
X
X    off_t		index_write_offset;
X    off_t		data_write_offset;
X
X    int			group_name_length;
X
X    int32		group_flag;
X
X#	define MF(n)	(1<<(n-1))
X#	define CF(n)	(1<<(n+15))
X
X#	define G_MASTER_FLAGS	(MF(17)-1) /* flags that are saved on file */
X
X    /* master flags */
X
X
X#	define G_MODERATED	MF(1)	/* group is moderated */
X#	define G_CONTROL	MF(2)	/* group is control group */
X#	define G_NO_DIRECTORY	MF(3)	/* group directory not found */
X#	define G_ALWAYS_DIGEST	MF(4)	/* always decode articles as digests */
X#	define G_NEVER_DIGEST	MF(5)	/* never decode articles as digests */
X#	define G_EXPIRE		MF(6)	/* expire in progress or pending */
X#	define G_BLOCKED	MF(7)	/* don't trust this entry */
X
X    /* client flags */
X
X#	define G_SUBSCRIPTION	CF(1)	/* from .rc */
X#	define G_READ		CF(2)	/* group has been read	*/
X#	define G_RC_UPDATED	CF(3)	/* .rc is updated */
X#	define G_DONE		CF(4)	/* finished with this group */
X#	define G_NEW		CF(5)	/* new group */
X#	define G_FOLDER		CF(6)	/* "group" is a folder file */
X#	define G_DIRECTORY	CF(7)	/* "group" is directory */
X#	define G_SELECTION	CF(8)	/* a selection exist (use it) */
X#	define G_UNREAD_COUNT	CF(9)	/* is included in unread_articles */
X#	define G_MAILBOX	CF(10)	/* user's mail box file */
X
X    /* this part is initialized during reading of the .groups file */
X
X    /* DO NOT CHANGE THE POSITION OF group_num AS THE FIRST FIELD */
X    /* AFTER THE PART WHICH IS SAVED IN THE MASTER FILE */
X
X    group_number	group_num;
X
X    char *		group_name;
X    
X    /* this part is used by the master to hold active file data */
X    /* and the reader to hold information from the .rc file */
X
X    article_number	first_article;
X    article_number	last_article;
X
X    struct group_header *next_group;	/* group sequence */
X    struct group_header	*prev_group;
X
X    char		*kill_list;
X    char		*save_file; 	/* default save file from init */
X
X    off_t		rc_offset;	/* offset in rc_file */
X} group_header;
X
X
X/* size of the part of the group header placed on backing storage */
X
X
X#define SAVED_GROUP_HEADER_SIZE(group) \
X    (((char *)(&((group).group_num))) - ((char *)(&(group))))
X
X/*
X *	Internal article header information.
X */
X
Xtypedef struct {
X    union {
X	article_number au_number;	/* article number in the group	*/
X	char *au_string;
X    } au_union;
X				/* indexes to header line text	*/
X    off_t	hpos;		/* first byte of header		*/
X    off_t	fpos;		/* first byte in article text	*/
X    off_t	lpos;		/* last pos of article		*/
X
X    time_stamp 	t_stamp;	/* encoded time_stamp		*/
X    
X    char *	sender;		/*   sender's name		*/
X    char *	subject;	/*   subject (w/o Re:)		*/
X
X    int16	replies;	/*   no of Re:			*/
X    int16	lines;		/*   no of lines		*/
X
X    group_header *a_group;	/* if merged article menu	*/
X    
X    int	flag;			/* flags: 			*/
X
X#	define AF(n) (1<<(n-1))
X
X#	define A_SELECT   	AF(1)	/* article has been selected	*/
X#	define A_SAME	    	AF(2)	/* same subject as prev. article */
X#	define A_DIGEST   	AF(3)	/* digest sub article		*/
X#	define A_FULL_DIGEST	AF(4)	/* full digest			*/
X#	define A_FAKED    	AF(5)	/* only 'number' is valid	*/
X#	define A_FOLDER		AF(6)	/* article file = "folder_path"	*/
X#	define A_CANCEL		AF(7)	/* folder entry cancelled	*/
X#	define A_SEEN		AF(8)	/* article presented on menu	*/
X
X} article_header;
X
X
X#define	a_number	au_union.au_number
X#define a_string	au_union.au_string
X
NO_NEWS_IS_GOOD_NEWS
chmod 0644 data.h || echo "restore of data.h fails"
set `wc -c data.h`;Sum=$1
if test "$Sum" != "4039"
then echo original size 4039, current size $Sum;fi
echo "x - extracting date_regexp.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > date_regexp.c &&
X/*
X * produce input for nngoback
X *
X *	generates a regular expression for egrep that will
X *	match the last N days, execute egrep with this pattern
X *	and output a sequence of "group-name article" pairs
X */
X
X#include "config.h"
X#include <time.h>
X
X#define DAYS	* 24 * 60 * 60
X
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X{
X    time_t now, then;
X    struct tm *tm, *localtime();
X    int then_year, then_month, then_day;
X    int first;
X    
X    if (argc != 2) {
X	fprintf(stderr, "usage: nngoback1 <days>\n");
X	exit(1);
X    }
X
X    time(&now);    
X
X    then = now - (atoi(argv[1]) DAYS);
X    tm = localtime(&then);
X    then_year = tm->tm_year;
X    then_month = tm->tm_mon;
X    then_day = tm->tm_mday;
X    
X    tm = localtime(&now);
X
X    printf("\t(");
X    
X    first = 0;
X    while (tm->tm_year > then_year) {
X	printf("%s%02d", first == 0 ? "../../(" : "|", tm->tm_year);
X	first = 1;
X
X	tm->tm_year--;
X	tm->tm_mon = 11;
X	tm->tm_mday = 31;
X    }
X    if (first == 1) putchar(')');
X    
X    while (tm->tm_mon > then_month) {
X	printf(first == 0 ? "(" : first == 1 ? "|(" : "|");
X	first = 2;
X	printf("%02d", tm->tm_mon + 1);
X	tm->tm_mon --;
X	tm->tm_mday = 31;
X    }
X    if (first == 2) printf(")/../%02d", then_year);
X    
X    while (tm->tm_mday >= then_day) {
X	if (first != 0)
X	    printf("|");
X	if (first != 3) 
X	    printf("%02d/(", then_month + 1);
X	first = 3;
X	printf("%02d", tm->tm_mday);
X	tm->tm_mday--;
X    }
X    if (first == 3) printf(")/%02d", then_year);
X    
X    printf(")\n");
X    
X    exit(0);
X}
NO_NEWS_IS_GOOD_NEWS
chmod 0644 date_regexp.c || echo "restore of date_regexp.c fails"
set `wc -c date_regexp.c`;Sum=$1
if test "$Sum" != "1495"
then echo original size 1495, current size $Sum;fi
echo "x - extracting db.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > db.c &&
X/*
X * database access and update
X */
X
X#include "config.h"
X#include "db.h"
X
Xexport master_header master;
Xexport group_header *active_groups, **sorted_groups;
X
X/*
X * Init access to a group
X */
X
Xexport group_header *current_group = NULL;
X
Xexport char	group_path_name[FILENAME];
Xexport char	*group_file_name = NULL;
X
Xstatic	char	*group_position = NULL;
X
Xinit_group(gh)
Xregister group_header *gh;
X{
X    register	char	*p, *q;
X
X    if (gh == NULL) return 0;
X    if (gh == current_group) return 1;
X    
X    current_group = gh;
X
X    if (gh->group_flag & G_NO_DIRECTORY) return 0;
X
X    if (gh->group_flag & G_FOLDER) {
X	group_position = NULL;
X	group_file_name = NULL;
X	strcpy(group_path_name, gh->group_name);
X	return 1;
X    }
X
X#ifdef NNTP
X    if (use_nntp && nntp_set_group(gh) < 0)
X	return 0;
X#endif /* NNTP */
X
X    if (group_position == NULL)
X	if (is_master) 
X	    group_position = group_path_name;
X	else {
X	    strcpy(group_path_name, news_directory);
X	    group_position = group_path_name + strlen(group_path_name);
X	    *group_position++ = '/';
X	}
X    
X    for (p = group_position, q = gh->group_name; *q; q++)
X	*p++ = (*q == '.') ? '/' : *q;
X
X    if (is_master) {
X
X	/*
X	 *	The master will chdir to the group's directory to
X	 *	get better performance (can use relative path names).
X	 *
X	 *	We cannot do the same for the user client, because of
X	 *	the 'save' commands.
X	 */
X
X	*p++ = NUL;
X	
X#ifdef NNTP
X	if (!use_nntp) {
X#endif
X	    if (chdir(news_directory) < 0)
X		sys_error(news_directory);
X	    
X	    if (chdir(group_path_name) < 0)
X		return 0;
X#ifdef NNTP
X	}
X#endif /* NNTP */
X
X	group_file_name = group_path_name;
X
X    } else {
X
X	/* client */
X
X	*p++ = '/';
X	group_file_name = p;
X    }
X
X    return 1;
X}
X
X
XFILE *open_groups(mode)
X{
X    return open_file(relative(db_directory, "GROUPS"), mode);
X}
X
X    
X
X/*
X *	Open master file; read it in if first open.
X */
X
XFILE *master_file = NULL;
X
Xopen_master(mode)
X{
X    FILE 			*g;
X    int 			entries, n, cur_group;
X    char 			*strings;
X    register group_header 	*gh;
X    static int 			first_open = 1;
X    
X    
X    master_file = open_file(relative(db_directory, "MASTER"), mode|MUST_EXIST);
X
X    if (mode == OPEN_CREATE || !first_open) return;
X    
X    first_open = 0;
X    
X    if (!db_read_master(master_file, &master))
X	sys_error("Incomplete MASTER");
X    
X    master.free_groups = master.number_of_groups / 10;
X    
X    entries = master.free_groups + master.number_of_groups;
X	
X    active_groups = (group_header *) calloc(entries, sizeof(group_header));
X    mem_check(active_groups, entries, "group headers");
X	
X    sorted_groups = (group_header **) 
X	calloc(entries, sizeof(group_header *));
X    mem_check(sorted_groups, entries, "sorted group header pointers");
X
X    strings = malloc((int)master.next_group_write_offset);
X    mem_check(strings, (int)master.next_group_write_offset, 
X	      "bytes for group names");
X
X    g = open_groups(OPEN_READ|MUST_EXIST);
X
X    n = fread(strings, sizeof(char), (int)master.next_group_write_offset, g);
X    if (n != (int)master.next_group_write_offset)
X	sys_error("Incomplete GROUPS file");
X    fclose(g);
X    
X    for (cur_group = 0, gh = active_groups;
X	 cur_group < master.number_of_groups;
X	 cur_group++, gh++) {
X
X	sorted_groups[cur_group] = gh;
X	
X	if (!db_read_group(master_file, gh, -1))
X	    sys_error("Incomplete MASTER file");
X	
X	gh->group_num = cur_group;
X	gh->group_name = strings;
X	strings += gh->group_name_length;
X	*strings++ = NUL;
X    }	
X
X    sort_groups();
X}
X
X
Xclose_master()
X{
X    if (master_file != NULL) {
X	fclose(master_file);
X	master_file = NULL;
X    }
X}
X
X
Xupdate_group(gh)
Xgroup_header *gh;
X{
X    int flag;
X
X    flag = gh->group_flag & ~G_MASTER_FLAGS;
X
X    if (!db_read_group(master_file, gh, gh->group_num)) return -1;
X    
X    gh->group_flag |= flag;
X
X    if (gh->group_flag & G_BLOCKED) return -1;
X    
X    return 1;
X}
X
X    
Xstatic group_name_cmp(g1, g2)
Xgroup_header **g1, **g2;
X{
X    return strcmp((*g1)->group_name, (*g2)->group_name);
X}
X
X
Xsort_groups()
X{
X    qsort(sorted_groups, master.number_of_groups,
X	  sizeof(group_header *), group_name_cmp);
X}
X
X
Xgroup_header *lookup(name)
Xchar *name;
X{
X    register i, j, k, t;
X    
X    i = 0; j = master.number_of_groups - 1; 
X    
X    while (i <= j) {
X	k = (i + j) / 2;
X
X	if ( (t=strcmp(name, sorted_groups[k]->group_name)) > 0) 
X	    i = k+1;
X	else
X	if (t < 0)
X	    j = k-1;
X	else
X	    return sorted_groups[k];
X    }
X    
X    return NULL;
X}
X
X
Xart_collected(gh, art_num)
Xgroup_header *gh;
Xarticle_number art_num;
X{
X    return gh->first_l_article <= art_num && gh->last_l_article >= art_num;
X}
X
X
XFILE *open_data_file(gh, d_or_x, mode)
Xgroup_header *gh;
Xchar d_or_x;
Xint mode;
X{
X    char data_file[FILENAME];
X    
X    sprintf(data_file, "%s/DATA/%d.%c", db_directory, gh->group_num, d_or_x);
X
X    if (mode == -1) {
X	unlink(data_file);
X	return (FILE *)NULL;
X    } else
X	return open_file(data_file, mode);
X}
X
X
X#ifdef NETWORK_DATABASE
X
X#define MASTER_FIELDS	3
X#define	GROUP_FIELDS	6
X#define	ARTICLE_FIELDS	10
X
X
Xtypedef int32 net_long;
X
X
X#ifdef NETWORK_BYTE_ORDER
X
X#define net_to_host(buf, n)
X#define host_to_net(buf, n)
X
X#else
X
Xstatic net_to_host(buf, lgt)
Xregister net_long *buf;
Xint lgt;
X{
X    while (--lgt >= 0) {
X	*buf = ntohl(*buf);
X	buf++;
X    }
X}
X
Xstatic host_to_net(buf, lgt)
Xregister net_long *buf;
Xint lgt;
X{
X    while (--lgt >= 0) {
X	*buf = htonl(*buf);
X	buf++;
X    }
X}
X#endif /* not NETWORK_BYTE_ORDER */
X#endif /* NETWORK_DATABASE */
X
X
Xdb_read_master(f, masterp)
XFILE *f;
Xmaster_header *masterp;  
X{
X#ifdef NETWORK_DATABASE
X    net_long buf[MASTER_FIELDS];
X    
X    if (fread(buf, sizeof(net_long), MASTER_FIELDS, f) != MASTER_FIELDS)
X	return 0;
X
X    net_to_host(buf, MASTER_FIELDS);
X    
X    masterp->last_scan = buf[0];
X    masterp->number_of_groups = buf[1];
X    masterp->next_group_write_offset = buf[2];
X#else
X
X    if (fread(masterp, sizeof(master_header), 1, f) != 1) return 0;
X#endif
X    return 1;
X}
X
X
Xdb_write_master(f, masterp)
XFILE *f;
Xmaster_header *masterp;  
X{
X#ifdef NETWORK_DATABASE
X    net_long buf[MASTER_FIELDS];
X    
X    buf[0] = masterp->last_scan;
X    buf[1] = masterp->number_of_groups;
X    buf[2] = masterp->next_group_write_offset;
X
X    host_to_net(buf, MASTER_FIELDS);
X    if (fwrite(buf, sizeof(net_long), MASTER_FIELDS, f) != MASTER_FIELDS) return 0;
X#else
X
X    if (fwrite(masterp, sizeof(master_header), 1, f) != 1) return 0;
X#endif
X    return 1;
X}
X
Xdb_read_group(f, gh, n)
XFILE *f;
Xregister group_header *gh;
Xgroup_number n;
X{
X#ifdef NETWORK_DATABASE
X    net_long buf[GROUP_FIELDS];
X
X    if (n >= 0) 
X	fseek(f, MASTER_FIELDS * sizeof(net_long) + GROUP_FIELDS * sizeof(net_long) * n, 0);
X
X    if (fread(buf, sizeof(net_long), GROUP_FIELDS, f) != GROUP_FIELDS)
X	return 0;
X
X    net_to_host(buf, GROUP_FIELDS);
X
X    gh->first_l_article = buf[0];
X    gh->last_l_article = buf[1];
X    gh->index_write_offset = buf[2];
X    gh->data_write_offset = buf[3];
X    gh->group_name_length = buf[4];
X    gh->group_flag = buf[5];
X#else
X    
X    if (n >= 0)
X	fseek(f, (off_t)(sizeof(master_header) + SAVED_GROUP_HEADER_SIZE(*gh) * n), 0);
X
X    if (fread(gh, SAVED_GROUP_HEADER_SIZE(*gh), 1, f) != 1)
X	return 0;
X
X#endif
X    return 1;
X}
X
X
Xdb_write_group(f, gh, n)
XFILE *f;
Xregister group_header *gh;
Xgroup_number n;
X{
X#ifdef NETWORK_DATABASE
X    net_long buf[GROUP_FIELDS];
X
X    if (n >= 0) 
X	fseek(f, MASTER_FIELDS * sizeof(net_long) + GROUP_FIELDS * sizeof(net_long) * n, 0);
X
X    buf[0] = gh->first_l_article;
X    buf[1] = gh->last_l_article;
X    buf[2] = gh->index_write_offset;
X    buf[3] = gh->data_write_offset;
X    buf[4] = gh->group_name_length;
X    buf[5] = gh->group_flag;
X
X    host_to_net(buf, GROUP_FIELDS);
X    if (fwrite(buf, sizeof(net_long), GROUP_FIELDS, f) != GROUP_FIELDS)
X	return 0;
X
X#else
X    if (n >= 0)
X	fseek(f, (off_t)(sizeof(master_header) + SAVED_GROUP_HEADER_SIZE(*gh) * n), 0);
X
X    if (fwrite(gh, SAVED_GROUP_HEADER_SIZE(*gh), 1, f) != 1)
X	return 0;
X#endif
X
X    return 1;
X}
X
X
Xdb_read_art(f, dh, offset)
XFILE *f;
Xdata_header *dh;
Xoff_t *offset;
X{
X#ifdef NETWORK_DATABASE
X    net_long buf[ARTICLE_FIELDS];
X    
X    if (fread(buf, sizeof(net_long), ARTICLE_FIELDS, f) != ARTICLE_FIELDS)
X	return 0;
X    
X    net_to_host(buf, ARTICLE_FIELDS);
X
X    dh->dh_number = buf[0];
X    dh->dh_date = buf[1];
X    dh->dh_hpos = buf[2];
X    dh->dh_lpos = buf[3];
X    dh->dh_fpos = buf[4];
X    dh->dh_lines = buf[5];
X    dh->dh_replies = buf[6];
X    dh->dh_cross_postings = buf[7];
X    dh->dh_subject_length = buf[8];
X    dh->dh_sender_length = buf[9];
X
X    if (offset) *offset += ARTICLE_FIELDS * sizeof(net_long);
X#else
X    
X    if (fread(dh, sizeof(data_header), 1, f) != 1) return 0;
X    if (offset) *offset += sizeof(data_header);
X#endif
X    return 1;
X}
X
Xdb_write_art(f, dh)
XFILE *f;
Xdata_header *dh;
X{
X#ifdef NETWORK_DATABASE
X    net_long buf[ARTICLE_FIELDS];
X
X    buf[0] = dh->dh_number;
X    buf[1] = dh->dh_date;
X    buf[2] = dh->dh_hpos;
X    buf[3] = dh->dh_lpos;
X    buf[4] = dh->dh_fpos;
X    buf[5] = dh->dh_lines;
X    buf[6] = dh->dh_replies;
X    buf[7] = dh->dh_cross_postings;
X    buf[8] = dh->dh_subject_length;
X    buf[9] = dh->dh_sender_length;
X
X    host_to_net(buf, ARTICLE_FIELDS);
X
X    if (fwrite(buf, sizeof(net_long), ARTICLE_FIELDS, f) != ARTICLE_FIELDS)
X	return 0;
X#else
X    
X    if (fwrite(dh, sizeof(data_header), 1, f) != 1) return 0;
X
X#endif
X
X    return 1;
X}
X
X
X
Xoff_t get_index_offset(gh, art_num)
Xgroup_header *gh;
Xarticle_number art_num;
X{
X#ifdef NETWORK_DATABASE
X    return (off_t)((art_num - gh->first_l_article) * sizeof(net_long));
X#else
X    return (off_t)((art_num - gh->first_l_article) * sizeof(off_t));
X#endif
X}
X
Xoff_t get_data_offset(gh, art_num)
Xgroup_header *gh;
Xarticle_number art_num;
X{
X    FILE *index;
X    off_t data_offset;
X    
X    if (gh->first_l_article == art_num) return (off_t)0;
X    
X    index = open_data_file(gh, 'x', OPEN_READ);
X    if (index == NULL) return (off_t)(-1);
X    
X    fseek(index, get_index_offset(gh, art_num), 0);
X    if (!db_read_offset(index, &data_offset))
X	return (off_t)(-1);
X
X    fclose(index);
X    
X    return data_offset;
X}
X
X
Xdb_read_offset(f, offset)
XFILE *f;
Xoff_t *offset;
X{
X#ifdef NETWORK_DATABASE
X    net_long temp;
X
X    if (fread(&temp, sizeof(net_long), 1, f) != 1) return 0;
X
X#ifndef NETWORK_BYTE_ORDER
X    temp = ntohl(temp);
X#endif
X    *offset = temp;
X#else    
X
X    if (fread((char *)offset, sizeof(off_t), 1, f) != 1) return 0;
X#endif
X    return 1;
X}
X
Xdb_write_offset(f, offset)
XFILE *f;
Xoff_t *offset;
X{
X#ifdef NETWORK_DATABASE
X    net_long temp;
X
X    temp = *offset;
X
X#ifndef NETWORK_BYTE_ORDER
X    temp = htonl(temp);
X#endif
X    if (fwrite(&temp, sizeof(net_long), 1, f) != 1) return 0;
X
X#else    
X
X    if (fwrite((char *)offset, sizeof(off_t), 1, f) != 1) return 0;
X#endif
X    return 1;
X}
X
NO_NEWS_IS_GOOD_NEWS
chmod 0644 db.c || echo "restore of db.c fails"
set `wc -c db.c`;Sum=$1
if test "$Sum" != "10668"
then echo original size 10668, current size $Sum;fi
echo "x - extracting db.h (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > db.h &&
X/*
X
X * DATABASE ORGANIZATION:
X *
X *	The central nn information is contained in following files:
X *		DB_DIRECTORY/MASTER
X *		DB_DIRECTORY/GROUPS
X *		DB_DIRECTORY/DATA/nnn.x
X *		DB_DIRECTORY/DATA/nnn.d
X *
X * 	The MASTER file consists of a header and one entry for each news
X *	group.  The sequence of the group headers defines the group
X *	number associated with the group.
X *
X * 	The GROUPS file contains the names of the news groups; the names
X *	occur in the same sequence as in the MASTER file.
X *
X *	For each news group, the DATA directory contains two files whose
X *	name is constructed from the group number 'nnn':
X *
X *		nnn.x	Index file
X *		nnn.d	Data file
X *
X *	The index file provides a a mapping from article numbers to offsets
X *	in the data file.
X *
X *	The data file contains the actual header data.  Each article is
X *	represented by a Header, an array of Cross Postings, and the
X *	strings representing the sender name and the article subject:
X *
X *		header
X *		group_number 1 [ if cross posted ]
X *		group_number 2
X *		...
X *		sender name (null terminated) [if sender_length > 0]
X *		subject (null terminated) [if subject_length > 0]	
X *
X *	For a digest, cross posted groups are only specified for first 
X *	the entry.
X *
X *	The format of the MASTER file is specifed in the data.h
X *	file.  The format of the index and data files are specified below. 
X *
X *	Unless NETWORK_DATABASE is defined, the database will
X *	will contain machine dependent binary data.
X */
X
Xtypedef struct {
X    off_t		data_offset;
X} index_entry;
X
Xtypedef struct {
X    article_number	dh_number;
X
X    time_stamp	dh_date; /* encoded Date: filed (not a time_t value!!) */
X	    
X    off_t	dh_hpos; /* absolute offset for first byte of header */
X    off_t	dh_lpos; /* absolute offset for last byte of article */
X    int16	dh_fpos; /* relative offset for first byte in article text */
X    
X    int16	dh_lines;
X    int8	dh_replies;
X    
X    int8	dh_cross_postings;
X    int8	dh_subject_length;
X    int8	dh_sender_length;
X} data_header;
X
X/*
X * 	The article_number is negative for digest article header and 
X * 	zero for following sub articles.
X */
X
Xarticle_number	current_digest_article;
X
X#define	IS_DIGEST_HEADER(e1) \
X	((e1).dh_number < 0 && (current_digest_article = -((e1).dh_number)))
X#define	IS_SUB_DIGEST(e1) \
X	(((e1).dh_number) == 0)
X#define	ARTICLE_NUMBER(e1) \
X	(((e1).dh_number <= 0) ? current_digest_article : ((e1).dh_number))
X
X
X
X#ifdef NETWORK_DATABASE
Xtypedef int32 cross_post_number;
X#else
Xtypedef group_number cross_post_number;
X#endif
X
X
X/* open database files */
X
XFILE *open_groups(), *open_data_file();
X
X/* data access */
X
Xoff_t get_index_offset(), get_data_offset();
X
X
X
NO_NEWS_IS_GOOD_NEWS
chmod 0644 db.h || echo "restore of db.h fails"
set `wc -c db.h`;Sum=$1
if test "$Sum" != "2670"
then echo original size 2670, current size $Sum;fi
echo "x - extracting debug.h (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > debug.h &&
X/*
X *	Debug flags and defines
X *
X *	Notice:  no modules are conditioned by this file in the
X *		 makefile.  touch the source file to have a change
X *		 in debugging setup to reflect the program behaviour
X */
X
X
X/* debugging */
X
X#define RC_TEST		1	/* rc file updates */
X#define DG_TEST		2 	/* digest decoding */
X#define SEQ_TEST	4	/* sequence file decoding */
X#define SEQ_DUMP	8	/* dump sequence after read */
X
Xextern int Debug;
NO_NEWS_IS_GOOD_NEWS
chmod 0644 debug.h || echo "restore of debug.h fails"
set `wc -c debug.h`;Sum=$1
if test "$Sum" != "427"
then echo original size 427, current size $Sum;fi
echo "x - extracting digest.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > digest.c &&
X/*
X * digest article handling
X */
X
X#include "config.h"
X#include "news.h"
X#include "match.h"
X#include "debug.h"
X
X#ifdef DG_TEST
X
X#define TEST(fmt, x, y) if (Debug & DG_TEST) printf(fmt, x, y)
X
X#else
X
X#define TEST(fmt, x, y)
X
X#endif
X
X
X
X/*
X * test if global 'news' header is header of a digest.
X * body points to a buffer (NUL term)
X * containing the first part of the article.
X */
X
Xstatic char match_digest[128] = {
X    
X/*  NUL SOH STX ETX EOT ENQ ACK BEL BS  TAB NL  VT  FF  CR  SO  SI  */
X    00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 
X
X/*  DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM  SUB ESC FS  GS  RS  US  */
X    00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 
X
X/*  SP  !   "   #   $   %   &   '   (   )   *   +   ,   -   .   /   */
X    00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 
X
X/*  0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?   */
X     1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 00, 00, 00, 00, 00, 00, 
X
X/*  @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O   */
X    00, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
X
X/*  P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _   */
X    26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 00, 00, 
X
X/*  `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o   */
X    00, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
X
X/*  p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~   DEL */
X    26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 00, 00
X
X};
X
Xstatic char digest_pattern[] = "digest";
X
Xinit_digest_parsing()
X{
X    init_quick_match(digest_pattern);
X}
X
X
Xis_digest(body)
Xregister char *body;
X{
X    char *dpos, *quick_match();
X    register char *sp;
X    register int l;
X
X    /* articles without a subject line are not digests (per definition) */
X    if (news.ng_subj == NULL) return 0;
X    
X
X    if (dpos = quick_match(news.ng_subj, digest_pattern)) {
X	int lgt = dpos - news.ng_subj;
X	int maxl = 10;
X	
X	/* look for a line matching the subject */
X	while (*body && maxl) {
X	    sp = news.ng_subj;
X	    l = lgt;
X	    if (*body == *sp && strncmp(body, sp, l) == 0) 
X		goto ok;
X	    while (*body && *body != NL) {
X		while (*sp && MATCH_DROP(match_digest, *sp)) {
X		    if (--l == 0) goto ok;
X		    ++sp;
X		}
X		
X		if (MATCH_DROP(match_digest, *body)) {
X		    ++body;
X		    continue;
X		}
X		
X		if (*sp && MATCH_EQ(match_digest, *body, *sp)) {
X		    if (--l == 0) goto ok;
X		    ++sp;
X		}
X		++body;
X	    }
X	    
X	    if (*body) ++body, --maxl;
X	}
X    }
X    return 0;
X
X ok:
X    TEST("is_digest: %s\n", news.ng_subj, 0);
X    return 1;
X}
X
X
X/*
X * expect that f is positioned at header of an article
X */
X
Xget_digest_article(f, hdrbuf)
XFILE *f;
Xnews_header_buffer hdrbuf;
X{
X    int cont;
X    
X    digest.dg_hpos = ftell(f);
X    TEST("GET DIGEST hp=%ld\n", digest.dg_hpos, 0);
X    
X    do {
X	if (!parse_digest_header(f, 0, hdrbuf)) return -1;
X	digest.dg_fpos = ftell(f);
X	TEST("END HEADER hp=%ld fp=%ld\n", digest.dg_hpos, digest.dg_fpos);
X    } while ((cont = skip_digest_body(f)) < 0);
X
X    TEST("END BODY lp=%ld next=%ld\n", digest.dg_lpos, ftell(f));
X
X    return cont;
X}	
X	
X#define BACKUP_LINES	 50	/* remember class + offset for parsed lines */
X
X#define	LN_BLANK	0x01	/* blank line */
X#define	LN_DASHED	0x02	/* dash line */
X#define	LN_HEADER	0x04	/* (possible) header line */
X#define	LN_ASTERISK	0x08	/* asterisk line (near end) */
X#define	LN_END_OF	0x10	/* End of ... line */
X#define	LN_TEXT		0x20	/* unclassified line */
X
X
X/*
X * skip until 'Subject: ' (or End of digest) line is found
X * then backup till start of header
X */
X
X/*
X * Tuning parameters:
X *
X *	MIN_HEADER_LINES:	number of known header lines that must
X *				be found in a block to identify a new
X *				header
X *
X *	MAX_BLANKS_DASH		max no of blanks on a 'dash line'
X *
X *	MIN_DASHES		min no of dashes on a 'dash line'
X *
X *	MAX_BLANKS_ASTERISKS	max no of blanks on an 'asterisk line'
X *
X *	MIN_ASTERISKS		min no of asterisks on an 'asterisk line'
X *
X *	MAX_BLANKS_END_OF	max no of blanks before "End of "
X */
X
X#define	MIN_HEADER_LINES	2
X#define	MAX_BLANKS_DASH		3
X#define	MIN_DASHES		16
X#define	MAX_BLANKS_ASTERISK	1
X#define	MIN_ASTERISKS		10
X#define	MAX_BLANKS_END_OF	1
X
Xskip_digest_body(f)
Xregister FILE *f;
X{
X    off_t  backup_p[BACKUP_LINES];
X    int	   line_type[BACKUP_LINES];
X    register int backup_index, backup_count;
X    int    more_header_lines, end_or_asterisks, blanks;
X    char   line[1024];
X    register char *cp;
X    char **dg_hdr_field();
X
X#define	decrease_index()	\
X    if (--backup_index < 0) backup_index = BACKUP_LINES - 1
X
X    backup_index = -1;
X    backup_count = 0;
X    end_or_asterisks = 0;
X    
X    digest.dg_lines = 0;
X    
X    
X next_line:
X    more_header_lines = 0;
X
X next_possible_header_line:
X    digest.dg_lines++;
X
X    if (++backup_index == BACKUP_LINES) backup_index = 0;
X    if (backup_count < BACKUP_LINES) backup_count++;
X	
X    backup_p[backup_index] = ftell(f);
X    line_type[backup_index] = LN_TEXT;
X
X    if (fgets(line, 1024, f) == NULL) {
X	TEST("end_of_file, bc=%d, lines=%d\n", backup_count, digest.dg_lines);
X	
X	/* end of file => look for "****" or "End of" line */
X
X	if (end_or_asterisks)
X	    while (--backup_count >= 0) {
X		--digest.dg_lines;
X		decrease_index();
X		if (line_type[backup_index] & (LN_ASTERISK | LN_END_OF)) break;
X	    }
X	
X	if (digest.dg_lines == 0) return 0;
X	
X	while (--backup_count >= 0) {
X	    --digest.dg_lines;
X	    digest.dg_lpos = backup_p[backup_index];
X	    decrease_index();
X	    if ((line_type[backup_index] & 
X		(LN_ASTERISK | LN_END_OF | LN_BLANK | LN_DASHED)) == 0)
X		break;
X	}
X
X	return 0;	/* no article follows */
X    }
X
X    TEST("\n>>%-.50s ==>>", line, 0);
X    
X    for (cp = line; *cp && isascii(*cp) && isspace(*cp); cp++);
X
X    if (*cp == NUL) {
X	TEST("BLANK", 0, 0);
X	line_type[backup_index] = LN_BLANK;
X	goto next_line;
X    }
X    
X    blanks = cp - line;
X    
X    if (*cp == '-') {
X	if (blanks > MAX_BLANKS_DASH) goto next_line;
X	
X	while (*cp == '-') cp++;
X	if (cp - line - blanks > MIN_DASHES) {
X	    while (*cp && (*cp == '-' || (isascii(*cp) && isspace(*cp)))) cp++;
X	    if (*cp == NUL) {
X		TEST("DASHED", 0, 0);
X		
X		line_type[backup_index] = LN_DASHED;
X	    }
X	    
X	}
X	goto next_line;
X    }
X	    
X    if (*cp == '*') {
X	if (blanks > MAX_BLANKS_ASTERISK) goto next_line;
X	
X	while (*cp == '*') cp++;
X	if (cp - line - blanks > MIN_ASTERISKS) {
X	    while (*cp && (*cp == '*' || (isascii(*cp) && isspace(*cp)))) cp++;
X	    if (*cp == NUL) {
X		TEST("ASTERISK", 0, 0);
X		line_type[backup_index] = LN_ASTERISK;
X		end_or_asterisks++;
X	    }
X	}
X	goto next_line;
X    }
X
X    if (blanks <= MAX_BLANKS_END_OF && 
X	*cp == 'E' && strncmp(cp, "End of ", 7) == 0) {
X	TEST("END_OF_", 0, 0);
X	line_type[backup_index] = LN_END_OF;
X	end_or_asterisks++;
X	goto next_line;
X    }
X    
X    if (blanks == 0) {
X	if (dg_hdr_field(line, 0)) {
X	    TEST("HEADER", 0, 0);
X	    
X	    line_type[backup_index] = LN_HEADER;
X	    if (++more_header_lines < MIN_HEADER_LINES) 
X		goto next_possible_header_line;
X
X	    /* found block with MIN_HEADER_LINES */
X
X	    /* search for beginning of header */
X
X	    TEST("\nSearch for start of header\n", 0, 0);
X
X	    for (;;) {
X		fseek(f, backup_p[backup_index], 0);
X		--digest.dg_lines;
X		if (--backup_count == 0) break;
X		decrease_index();
X		if ((line_type[backup_index] & (LN_HEADER | LN_TEXT)) == 0)
X		    break;
X	    }
X
X	    if (digest.dg_lines == 0) {
X		TEST("Skipped empty article\n", 0, 0);
X		return 0;
X	    }
X	    
X	    for (;;) {
X		digest.dg_lpos = backup_p[backup_index];
X		if (--backup_count < 0) break;
X		decrease_index();
X		if ((line_type[backup_index] & (LN_BLANK | LN_DASHED)) == 0)
X		    break;
X		--digest.dg_lines;
X	    }
X
X	    return (digest.dg_lines == 0) ? -1 : 1;
X	}
X	goto next_possible_header_line;
X    }
X    
X    goto next_line;
X}    
X
X
Xparse_digest_header(f, all, hdrbuf)
XFILE *f;
Xint all;
Xnews_header_buffer hdrbuf;
X{
X    extern char *parse_header(), **dg_hdr_field();
X    
X    digest.dg_date = digest.dg_from = digest.dg_subj = digest.dg_to = NULL;
X     
X    parse_header(f, dg_hdr_field, all, hdrbuf);
X    
X    return digest.dg_from || digest.dg_subj;
X}
X
X
Xstatic char **dg_hdr_field(lp, all)
Xregister char *lp;
Xint all;
X{
X    
X#define check(name, lgt, field) \
X    if (strncmp(name, lp, lgt) == 0) { \
X	TEST("MATCH: field ", 0, 0); \
X	return &digest.field; \
X    }
X    
X
X    TEST("\nPARSE[%.20s] ==>> ", lp, 0);
X    
X    switch (*lp++) {
X
X     case 'D':
X     case 'd':	
X	check("ate: ",	5, dg_date);
X	break;
X
X     case 'F':
X     case 'f':
X	check("rom: ",	5, dg_from);
X	break;
X
X     case 'R':
X     case 'r':
X	if (!all) break;
X	check("e: ",	3, dg_subj);
X	break;
X
X     case 'S':
X     case 's':
X	check("ubject",	6, dg_subj);
X	break;
X
X     case 'T':
X     case 't':
X	check("itle: ",	6, dg_subj);
X	if (!all) break;
X	check("o: ",	3, dg_to);
X	break;
X    }
X	
X#undef check
X    TEST("NOT MATCHED ", 0, 0);
X    
X    return NULL;
X}
NO_NEWS_IS_GOOD_NEWS
chmod 0644 digest.c || echo "restore of digest.c fails"
set `wc -c digest.c`;Sum=$1
if test "$Sum" != "8891"
then echo original size 8891, current size $Sum;fi
echo "x - extracting execute.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > execute.c &&
X#include <signal.h>
X#include <errno.h>
X#include "config.h"
X#include "term.h"
X
Xchar *user_shell;
X
Xinit_execute()
X{
X    if ((user_shell = getenv("SHELL")) == NULL)
X	user_shell = SHELL;
X}
X
Xexecute(path, args)
Xchar *path, **args;
X{
X    int was_raw, pid, i, status;
X    sig_type  (*quit)(), (*intr)(), (*cont)();
X    extern int errno;
X    
X    was_raw = no_raw();
X    
X    while ((pid = fork()) == -1) sleep(1);
X    
X    if (pid == 0) {
X	for (i = 3 ; i < 20 ; i++)
X	    close(i);
X	
X	execv(path, args);
X	
X	fprintf(stderr, "%s: not found\n", path);
X	nn_exit(20);
X    }
X    quit = signal(SIGQUIT, SIG_IGN);
X    intr = signal(SIGINT,  SIG_IGN);
X#ifdef HAVE_JOBCONTROL
X    cont = signal(SIGCONT, SIG_DFL);
X#endif
X    while ((i = wait(&status)) != pid && (i != -1 || errno == EINTR));
X    
X    signal(SIGQUIT, quit);
X    signal(SIGINT,  intr);
X#ifdef HAVE_JOBCONTROL
X    signal(SIGCONT, cont);
X#endif
X    
X    if (was_raw) raw();
X    
X    return status != 0;
X}
X
X
Xshell_escape()
X{
X    static char command[FILENAME] = "";
X    char *cmd;
X    int first = 1;
X
X    prompt("!");
X    
Xagain:    
X    
X    cmd = get_s(command, NONE, NONE, NO_COMPLETION);
X    if (cmd == NULL) return !first;
X    
X    strcpy(command, cmd);
X    
X    if (!run_shell(command, first)) return !first;
X    first = 0;
X    
X    if (any_key(0) == '!') {    	/* should use key map here */
X	putchar(CR);
X	putchar('!');
X	clrline();
X	goto again;
X    }
X    
X    return 1;
X}
X
X
Xstatic char *exec_sh_args[] = {
X    "nnsh", 
X    "-c", 
X    (char *)NULL, /* cmdstring */
X    (char *)NULL 
X};
X    
Xrun_shell(command, clear)
Xchar *command;
Xint clear;
X{
X    char cmdstring[512];
X    
X    if (!expand_file_name(cmdstring, command))
X	return 0;
X    
X    if (clear) {
X	clrdisp();
X	fl;
X    } else {
X	putchar(CR);
X	putchar(NL);
X    }
X
X    exec_sh_args[2] = cmdstring;
X    
X    execute(user_shell, exec_sh_args);
X    return 1;
X}
X    
X#ifndef HAVE_JOBCONTROL
Xstatic char *exec_suspend_args[] = {
X    "nnsh", 
X    "-i", 
X    (char *)NULL
X};
X#endif
X
Xsuspend_nn()
X{
X    int was_raw;
X    
X    was_raw = no_raw();
X    gotoxy(0, Lines-1);
X    clrline();
X
X#ifdef HAVE_JOBCONTROL
X    kill(process_id, SIGTSTP);
X#else
X    execute(user_shell, exec_suspend_args);
X#endif
X
X    s_redraw++;
X    if (was_raw) raw();
X    
X    return 1;
X}
NO_NEWS_IS_GOOD_NEWS
chmod 0644 execute.c || echo "restore of execute.c fails"
set `wc -c execute.c`;Sum=$1
if test "$Sum" != "2254"
then echo original size 2254, current size $Sum;fi
echo "x - extracting expire.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > expire.c &&
X/*
X *	Expire will remove all entries in the index and data files
X *	corresponding to the articles before the first article registered
X *	in the active file.  No attempt is made to eliminate other
X *	expired articles.
X */
X
X#include "config.h"
X#include "db.h"
X
Ximport int trace;
X
X#define expire_error(msg) { \
X    err_message = msg; \
X    goto error_handler; \
X}
X
Xexpire_group(gh)
Xregister group_header *gh;
X{
X    FILE *old_x, *old_d;
X    FILE		*new;
X    off_t		index_offset, data_offset, new_offset;
X    long		count, expire_count;
X    char 		*err_message;
X
X    old_x = old_d = new = NULL;
X    
X
X    if (trace)
X	log_entry('T', "Exp %s (%d -> %d)",
X		  gh->group_name, gh->first_l_article, gh->first_article);
X    
X    /*
X     * check whether new first article is collected
X     */
X
X    if (!art_collected(gh, gh->first_article)) {
X	expire_count = gh->first_l_article - gh->last_l_article + 1;
X	err_message = NULL;
X	goto error_handler;	/* renumbering, collect from start */
X    }
X    
X    expire_count = gh->first_article - gh->first_l_article;
X
X    new = NULL;
X
X    /*
X     *  Open old files, unlink after open
X     */
X
X    old_x = open_data_file(gh, 'x', OPEN_READ|OPEN_UNLINK);
X    old_d = open_data_file(gh, 'd', OPEN_READ|OPEN_UNLINK);
X
X    if (old_x == NULL || old_d == NULL)
X	expire_error("INDEX or DATA file missing");
X    
X    /*
X     *	Create new index file; copy from old
X     */
X
X    new = open_data_file(gh, 'x', OPEN_CREATE);
X    if (new == NULL)
X	expire_error("INDEX: cannot create");
X
X    /*
X     *	index_offset is the offset into the old index file for the
X     *	first entry in the new index file
X     */
X
X    index_offset = get_index_offset(gh, gh->first_article);
X
X    /*
X     *	adjust the group's index write offset (the next free entry)
X     */
X
X    gh->index_write_offset -= index_offset;
X
X    /*
X     *	calculate the number of entries to copy
X     */
X
X    count = gh->index_write_offset / sizeof(off_t);
X
X    /*
X     *	data offset is the offset into the old data file for the
X     *	first byte in the new data file; it is initialized in the
X     *	loop below, by reading the entry in the old index file at
X     *	offset 'index_offset'.
X     */
X
X    data_offset = (off_t)0;
X
X    /*
X     *	read 'count' entries from the old index file starting from
X     *	index_offset, subtract the 'data_offset', and output the
X     *	new offset to the new index file.
X     */
X
X    fseek(old_x, index_offset, 0);
X    
X    while (--count >= 0) {
X	if (!db_read_offset(old_x, &new_offset))
X	    expire_error("INDEX: too short");
X
X	if (data_offset == (off_t)0) data_offset = new_offset;
X
X	new_offset -= data_offset;
X	if (!db_write_offset(new, &new_offset))
X	    expire_error("NEW INDEX: cannot write");
X    }
X
X    fclose(new);
X    fclose(old_x); old_x = NULL;
X    
X    /*
X     *	copy from old data file to new data file
X     */
X
X    new = open_data_file(gh, 'd', OPEN_CREATE);
X    if (new == NULL)
X	expire_error("DATA: cannot create");
X    
X    /*
X     *	calculate offset for next free entry in the new data file
X     */
X
X    gh->data_write_offset -= data_offset;
X
X    /*
X     *	calculate number of bytes to copy (piece of cake)
X     */
X
X    count = gh->data_write_offset;
X    
X    /*
X     *	copy 'count' bytes from the old data file, starting at offset
X     * 	'data_offset', to the new data file
X     */
X
X    fseek(old_d, data_offset, 0);
X    while (count > 0) {
X	char block[1024];
X	int  count1;
X	
X	count1 = fread(block, sizeof(char), 1024, old_d);
X	if (count1 <= 0)
X	    expire_error("DATA: read error");
X
X	if (fwrite(block, sizeof(char), count1, new) != count1)
X	    expire_error("DATA: write error");
X	
X	count -= count1;
X    }
X    
X    fclose(new);
X    fclose(old_d);
X    
X    /*
X     *	Update group entry
X     */
X
X    gh->first_l_article = gh->first_article;
X
X    /*
X     *	Return number of expired articles
X     */
X
X    return expire_count;
X    
X
X
X    /*
X     *	Errors end up here.
X     *	We simply recollect the whole group once more.
X     */
X
Xerror_handler:
X
X    if (new) fclose(new);
X    if (old_x) fclose(old_x);
X    if (old_d) fclose(old_d);
X    
X    if (err_message)
X	log_entry('E', "Expire Error (%s): %s", gh->group_name, err_message);
X
X    clean_group(gh);
X
X    /* will be saved & unblocked later */
X
X    /*
X     *	We cannot say whether any articles actually had to be expired,
X     *	but then we must guess...
X     */
X
X    return expire_count;
X}
NO_NEWS_IS_GOOD_NEWS
chmod 0644 expire.c || echo "restore of expire.c fails"
set `wc -c expire.c`;Sum=$1
if test "$Sum" != "4363"
then echo original size 4363, current size $Sum;fi
echo "x - extracting folder.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > folder.c &&
X/*
X * folder handling
X */
X
X#include <errno.h>
X#include "config.h"
X#include "articles.h"
X#include "news.h"
X#include "term.h"
X#include "menu.h"
X
X
Xexport int  dont_sort_folders = 0;
Xexport char *folder_directory  = NULL;
X
X/*
X * 	file name completion and expansion
X */
X
X
Xexpand_file_name(dest, src)
Xchar *dest, *src;
X{
X    register char *cp, *dp, c;
X    int parse, remap;
X    char *cur_grp, *cur_art;
X    
X    cur_grp = current_group ? current_group->group_name : NULL;
X    cur_art = (group_file_name && *group_file_name) ? group_path_name : NULL;
X    
X    for (dp = dest, parse = 1; c = *src; src++) {
X	
X	if (parse) {
X	    
X	    if (c == '+') {
X		if (folder_directory == NULL) {
X		    if (!(cp = getenv("FOLDER")))
X			cp = FOLDER_DIRECTORY;
X		    folder_directory = home_relative(cp);
X		}
X		
X		cp = folder_directory;
X		goto cp_str;
X	    }
X	    
X	    if (c == '~') {
X		if (src[1] != '/') {
X		    msg("Can't handle ~user expansion (yet)");
X		    return 0;
X		}
X		
X		cp = home_directory;
X		
X	     cp_str:
X		while (*cp) *dp++ = *cp++;
X		if (dp[-1] != '/') *dp++ = '/';
X		goto no_parse;
X	    }
X
X	    if (cur_art && c == '%' && (src[1] == ' ' || src[1] == NUL)) {
X		cp = cur_art;
X		while (*cp) *dp++ = *cp++;
X		goto no_parse;
X	    }
X	    
X	}
X
X	if (c == '$' && !isalnum(src[2])) {
X	    remap = 0;
X	    cp = NULL;
X	    
X	    switch (src[1]) {
X	     case 'A':
X		cp = cur_art;
X		break;
X	     case 'F':
X		cp = cur_grp;
X		remap = 1;
X		break;
X	     case 'G':
X		cp = cur_grp;
X		break;
X	     case 'L':
X		if (cp = strrchr(cur_grp, '.')) 
X		    cp++;
X		else 
X		    cp = cur_grp;
X		break;
X	     case 'N':
X		if (cur_art) cp = group_file_name;
X		break;
X	     default:
X		goto copy;
X	    }	    
X	    src++;
X
X	    if (!cp) {
X		msg("$%c not defined on this level", c);
X		return 0;
X	    }
X
X	    while (*cp) 
X		if (remap && *cp == '.')
X		    cp++, *dp++ = '/';
X		else
X		    *dp++ = *cp++;
X	    goto no_parse;
X	}
X	
X	if (c == '/')
X	    if (dp != dest && dp[-1] == '/') goto no_parse;
X		
X     copy:
X	*dp++ = c;
X	parse = isspace(c);
X	continue;
X
X     no_parse:
X        parse = 0;
X    }
X
X    *dp = NUL;
X
X    return 1;
X}
X
X
Xfile_completion(path, index)
Xchar *path;
Xint index;
X{
X    static dir_in_use = 0;
X    static char *head, *tail = NULL;
X    static int  tail_offset;
X    
X    char nbuf[FILENAME], buffer[FILENAME];
X    char *dir, *base;
X
X    if (path) {
X	if (dir_in_use) {
X	    close_directory();
X	    dir_in_use = 0;
X	}
X	
X	if (index < 0) return 0;
X
X	head = path;
X	tail = path + index;
X    }
X    
X    if (!dir_in_use) {
X	path = head;
X	*tail = NUL;
NO_NEWS_IS_GOOD_NEWS
echo "End of part 3"
echo "File folder.c is continued in part 4"
echo "4" > s2_seq_.tmp
exit 0
---
Kim F. Storm        storm@texas.dk        Tel +45 429 174 00
Texas Instruments, Marielundvej 46E, DK-2730 Herlev, Denmark
	  No news is good news, but nn is better!

-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.