v23i063: Complete reposting of TRN at patchlevel 1, Part04/14

Rich Salz rsalz at bbn.com
Fri Jan 4 04:54:06 AEST 1991


Submitted-by: Wayne Davison <0004475895 at mcimail.com>
Posting-number: Volume 23, Issue 63
Archive-name: trn/part04

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix at uunet.uu.net if you want that tool.
# Contents:  mt-process.c rt-select.c threads.h
# Wrapped by rsalz at litchi.bbn.com on Thu Dec 27 11:34:02 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 4 (of 14)."'
if test -f 'mt-process.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'mt-process.c'\"
else
  echo shar: Extracting \"'mt-process.c'\" \(34673 characters\)
  sed "s/^X//" >'mt-process.c' <<'END_OF_FILE'
X/* $Header: mt-process.c,v 4.3.3.2 90/08/20 16:40:31 davison Trn $
X**
X** $Log:	mt-process.c,v $
X** Revision 4.3.3.2  90/08/20  16:40:31  davison
X** Added check of caught_interrupt flag into main loops.
X** 
X** Revision 4.3.3.1  90/07/28  18:04:45  davison
X** Initial Trn Release
X** 
X*/
X
X#include "EXTERN.h"
X#include "common.h"
X#include "mthreads.h"
X#ifdef SERVER
X#include "server.h"
X#endif
X
X#include <time.h>
X#ifndef TZSET
X# include <sys/timeb.h>
X#endif
X
Xchar buff[1024];
X
Xchar references[1024];
X
Xchar subject_str[80];
Xbool found_Re;
X
Xchar author_str[20];
X
Xextern int log_verbosity;
X
Xextern time_t getdate();
X
XDOMAIN *next_domain;
X
Xvoid insert_article(), expire(), trim_roots(), order_roots(), trim_authors();
Xvoid make_root(), use_root(), merge_roots(), set_root(), unlink_root();
Xvoid link_child(), unlink_child();
Xvoid free_article(), free_domain(), free_subject(), free_root(), free_author();
Xvoid get_subject_str(), get_author_str();
XARTICLE *get_article();
XSUBJECT *new_subject();
XAUTHOR *new_author();
X
X#ifdef TZSET
Xextern time_t tnow;
X#else
Xextern struct timeb ftnow;
X#endif
X
X#ifndef SERVER
Xstatic FILE *fp_article;
X#endif
X
X/* Given the upper/lower bounds of the articles in the current group, add all
X** the ones that we don't know about and remove all the ones that have expired.
X** The current directory must be the newgroup's spool directory.
X*/
Xvoid
Xprocess_articles( first_article, last_article )
XART_NUM first_article, last_article;
X{
X    register char *cp, *str;
X    register ARTICLE *article;
X    register ART_NUM i;
X    time_t date;
X    int len;
X#ifdef SERVER
X    bool orig_extra = extra_expire;
X#endif
X    extern int errno;
X    extern int sys_nerr;
X    extern char *sys_errlist[];
X
X    if( first_article > (i = total.last+1) ) {
X	i = first_article;
X    }
X    added_count = last_article - i + 1;
X    expired_count = 0;
X
X    for( ; i <= last_article; i++ ) {
X	if( caught_interrupt ) {
X	    return;
X	}
X#ifdef SERVER
X	sprintf( buff, "HEAD %ld", (long)i );
X	put_server( buff );
X	if( get_server( buff, sizeof buff ) < 0 || *buff == CHAR_FATAL ) {
X	    last_article = i - 1;
X	    extra_expire = FALSE;
X	    break;
X	}
X	if( *buff != CHAR_OK ) {
X	    added_count--;
X	    continue;
X	}
X#else
X	/* Open article in current directory. */
X	sprintf( buff, "%ld", (long)i );
X	/* Set errno for purely paranoid reasons */
X	errno = 0;
X	if( (fp_article = fopen( buff, "r" )) == Nullfp ) {
X	    /* Missing files are ok -- they've just been expired or canceled */
X	    if( errno != 0 && errno != ENOENT ) {
X		if( errno < 0 || errno > sys_nerr ) {
X		    log_error( "Can't open `%s': Error %d.\n", buff, errno );
X		} else {
X		    log_error( "Can't open `%s': %s.\n", buff,
X		      sys_errlist[errno] );
X		}
X	    }
X	    added_count--;
X	    continue;
X	}
X#endif
X
X	article = Nullart;
X	*references = '\0';
X	*author_str = '\0';
X	*subject_str = '\0';
X	found_Re = 0;
X	date = 0;
X
X#ifdef SERVER
X	while( get_server( cp = buff, sizeof buff ) == 0 ) {
X	  process_line:
X	    if( *cp == '.' ) {
X		break;
X	    }
X#else
X	while( (cp = fgets( buff, sizeof buff, fp_article )) != Nullch ) {
X	  process_line:
X	    if( *cp == '\n' ) {		/* check for end of header */
X		break;			/* break out when found */
X	    }
X#endif
X	    if( (unsigned char)*cp <= ' ' ) {	 /* skip continuation lines */
X		continue;		/* (except references -- see below) */
X	    }
X	    if( (str = index( cp, ':' )) == Nullch ) {
X		break;			/* end of header if no colon found */
X	    }
X	    if( (len = str - cp) > 10 ) {
X		continue;		/* skip keywords > 10 chars */
X	    }
X#ifndef SERVER
X	    cp[strlen(cp)-1] = '\0';	/* remove newline */
X#endif
X	    while( cp < str ) {		/* lower-case the keyword */
X		if( (unsigned char)*cp <= ' ' ) { /* stop at any whitespace */
X		    break;
X		}
X		if( isupper(*cp) ) {
X		    *cp = tolower(*cp);
X		}
X		cp++;
X	    }
X	    *cp = '\0';
X	    cp = buff;
X	    if( len == 4 && strEQ( cp, "date" ) ) {
X#ifdef TZSET
X	        date = getdate( str + 1, tnow, timezone );
X#else
X		date = getdate( str + 1, ftnow.time, (long) ftnow.timezone );
X#endif
X	    } else
X	    if( len == 4 && strEQ( cp, "from" ) ) {
X		get_author_str( str + 1 );
X	    } else
X	    if( len == 7 && strEQ( cp, "subject" ) ) {
X		get_subject_str( str + 1 );
X	    } else
X	    if( len == 10 && strEQ( cp, "message-id" ) ) {
X		if( !article ) {
X		    article = get_article( str + 1 );
X		} else {
X		    if( log_verbosity ) {
X			log_error( "Found multiple Message-IDs! [%ld].\n",
X				(long)i );
X		    }
X		}
X	    } else
X	    if( len == 10 && strEQ( cp, "references" ) ) {
X		/* include preceding space in saved reference */
X		len = strlen( str + 1 );
X		bcopy( str + 1, references, len + 1 );
X		str = references + len;
X		/* check for continuation lines */
X#ifdef SERVER
X		while( get_server( cp = buff, sizeof buff ) == 0 ) {
X#else
X		while( (cp = fgets( buff, sizeof buff, fp_article )) != Nullch ) {
X#endif
X		    if( *cp != ' ' && *cp != '\t' ) {
X			goto process_line;
X		    }
X		    while( *++cp == ' ' || *cp == '\t' ) {
X			;
X		    }
X		    *--cp = ' ';
X		    /* If the references are too long, shift them over to
X		    ** always save the most recent ones.
X		    */
X		    if( (len += strlen( cp )) > 1023 ) {
X			strcpy( buff, buff + len - 1023 );
X			str -= len - 1023;
X			len = 1023;
X		    }
X		    strcpy( str, cp );
X		}/* while */
X		break;
X	    }/* if */
X	}/* while */
X	if( article ) {
X	    insert_article( article, date, i );
X	} else {
X	    if( log_verbosity ) {
X		log_error( "Message-ID line missing! [%ld].\n", (long)i );
X	    }
X	}
X#ifndef SERVER
X	fclose( fp_article );
X#endif
X    }
X
X    if( extra_expire || first_article > total.first ) {
X	expire( first_article );
X    }
X    if( caught_interrupt ) {
X	return;
X    }
X    trim_roots();
X    order_roots();
X    trim_authors();
X
X    total.first = first_article;
X    total.last = last_article;
X#ifdef SERVER
X    extra_expire = orig_extra;
X#endif
X}
X
X/* Search all articles for numbers less than new_first.  Traverse the list
X** using the domain links so we don't have to deal with the tree structure.
X** If extra_expire is true, stat() all valid articles to make sure they are
X** really there and expire them if they're not.
X*/
Xvoid
Xexpire( new_first )
XART_NUM new_first;
X{
X    register DOMAIN *domain;
X    register ARTICLE *article, *next_art, *hold;
X
X    for( domain = &unk_domain; domain; domain = next_domain ) {
X	next_domain = domain->link;
X	for( article = domain->ids; article; article = next_art ) {
X	    if( caught_interrupt ) {
X		return;
X	    }
X	    next_art = article->id_link;
X	    if( !article->subject || (article->flags & NEW_ARTICLE) ) {
X		continue;
X	    }
X	    if( extra_expire && article->num >= new_first ) {
X#ifdef SERVER
X		sprintf( buff, "STAT %ld", (long)article->num );
X		put_server( buff );
X		if( get_server( buff, sizeof buff ) == 0 && *buff == CHAR_OK ) {
X		    continue;
X		}
X#else
X		sprintf( buff, "%ld", (long)article->num );
X		if( !stat( buff, &filestat ) || errno != ENOENT ) {
X		    continue;
X		}
X#endif
X	    }
X	    if( extra_expire || article->num < new_first ) {
X		article->subject->count--;
X		article->subject = 0;
X		article->author->count--;
X		article->author = 0;
X		/* Free expired article if it has no children.  Then check
X		** if the parent(s) are also fake and can be freed.  We'll
X		** free any empty roots later.
X		*/
X		while( !article->children ) {
X		    hold = article->parent;
X		    unlink_child( article );
X		    free_article( article );
X		    if( hold && !hold->subject ) {
X			if( (article = hold) == next_art ) {
X			    next_art = next_art->id_link;
X			}
X		    } else {
X			break;
X		    }
X		}
X		expired_count++;
X	    }/* if */
X	}/* for */
X    }/* for */
X    next_domain = Null(DOMAIN*);
X}
X
X/* Trim the article chains down so that we don't have more than one faked
X** article between the root any real ones.
X*/
Xvoid
Xtrim_roots()
X{
X    register ROOT *root, *last_root;
X    register ARTICLE *article, *next;
X    register SUBJECT *subject, *last_subj;
X    register int found;
X
X#ifndef lint
X    last_root = (ROOT *)&root_root;
X#else
X    last_root = Null(ROOT*);
X#endif
X    for( root = root_root; root; root = last_root->link ) {
X	for( article = root->articles; article; article = article->siblings ) {
X	    /* If an article has no subject, it is a "fake" reference node.
X	    ** If all of its immediate children are also fakes, delete it
X	    ** and graduate the children to the root.  If everyone is fake,
X	    ** the chain dies.
X	    */
X	    while( !article->subject ) {
X		found = 0;
X		for( next = article->children; next; next = next->siblings ) {
X		    if( next->subject ) {
X			found = 1;
X			break;
X		    }
X		}
X		if( !found ) {
X		    /* Remove this faked article and move all its children
X		    ** up to the root.
X		    */
X		    next = article->children;
X		    unlink_child( article );
X		    free_article( article );
X		    for( article = next; article; article = next ) {
X			next = article->siblings;
X			article->parent = Nullart;
X			link_child( article );
X		    }
X		    article = root->articles;	/* start this root over */
X		} else {
X		    break;			/* else, on to next article */
X		}
X	    }
X	}
X	/* Free all unused subject strings.  Begin by trying to find a
X	** subject for the root's pointer.
X	*/
X	for( subject = root->subjects; subject && !subject->count; subject = root->subjects ) {
X	    root->subjects = subject->link;
X	    free_subject( subject );
X	    root->subject_cnt--;
X	}
X	/* Then free up any unsed intermediate subjects.
X	*/
X	if( (last_subj = subject) != Null(SUBJECT*) ) {
X	    while( (subject = subject->link) != Null(SUBJECT*) ) {
X		if( !subject->count ) {
X		    last_subj->link = subject->link;
X		    free_subject( subject );
X		    root->subject_cnt--;
X		    subject = last_subj;
X		} else {
X		    last_subj = subject;
X		}
X	    }
X	}
X	/* Now, free all roots without articles.  Flag unexpeced errors.
X	*/
X	if( !root->articles ) {
X	    if( root->subjects ) {
X		log_error( "** Empty root still had subjects remaining! **\n" );
X	    }
X	    last_root->link = root->link;
X	    free_root( root );
X	} else {
X	    last_root = root;
X	}
X    }
X}
X
X/* Descend the author list, find any author names that aren't used
X** anymore and free them.
X*/
Xvoid
Xtrim_authors()
X{
X    register AUTHOR *author, *last_author;
X
X#ifndef lint
X    last_author = (AUTHOR *)&author_root;
X#else
X    last_author = Null(AUTHOR*);
X#endif
X    for( author = author_root; author; author = last_author->link ) {
X	if( !author->count ) {
X	    last_author->link = author->link;
X	    free_author( author );
X	} else {
X	    last_author = author;
X	}
X    }
X}
X
X/* Reorder the roots to place the oldest ones first (age determined by
X** date of oldest article).
X*/
Xvoid
Xorder_roots()
X{
X    register ROOT *root, *next, *search;
X
X    /* If we don't have at least two roots, we're done! */
X    if( !(root = root_root) || !(next = root->link) ) {
X	return;						/* RETURN */
X    }
X    /* Break the old list off after the first root, and then start
X    ** inserting the roots into the list by date.
X    */
X    root->link = Null(ROOT*);
X    while( (root = next) != Null(ROOT*) ) {
X	next = next->link;
X	if( (search = root_root)->articles->date >= root->articles->date ) {
X	    root->link = root_root;
X	    root_root = root;
X	} else {
X	    while( search->link
X	     && search->link->articles->date < root->articles->date ) {
X		search = search->link;
X	    }
X	    root->link = search->link;
X	    search->link = root;
X	}
X    }
X}
X
X#define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y))
X
X/* Parse the subject into 72 characters or less.  Remove any "Re[:^]"s from
X** the front (noting that it's there), and any "(was: old)" stuff from
X** the end.  Then, compact multiple whitespace characters into one space,
X** trimming leading/trailing whitespace.  If it's still too long, unmercifully
X** cut it off.  We don't bother with subject continuation lines either.
X*/
Xvoid
Xget_subject_str( str )
Xregister char *str;
X{
X    register char *cp;
X    register int len;
X
X    while( *str && (unsigned char)*str <= ' ' ) {
X	str++;
X    }
X    if( !*str ) {
X	bcopy( "<None>", subject_str, 7 );
X	return;						/* RETURN */
X    }
X    cp = str;
X    while( EQ( cp[0], 'r' ) && EQ( cp[1], 'e' ) ) {	/* check for Re: */
X	cp += 2;
X	if( *cp == '^' ) {				/* allow Re^2: */
X	    while( *++cp <= '9' && *cp >= '0' ) {
X		;
X	    }
X	}
X	if( *cp != ':' ) {
X	    break;
X	}
X	while( *++cp == ' ' ) {
X	    ;
X	}
X	found_Re = 1;
X	str = cp;
X    }
X    /* Remove "(was Re: oldsubject)", because we already know the old subjects.
X    ** Also match "(Re: oldsubject)".  Allow possible spaces after the ('s.
X    */
X    for( cp = str; (cp = index( cp+1, '(' )) != Nullch; ) {
X	while( *++cp == ' ' ) {
X	    ;
X	}
X	if( EQ( cp[0], 'w' ) && EQ( cp[1], 'a' ) && EQ( cp[2], 's' )
X	 && (cp[3] == ':' || cp[3] == ' ') )
X	{
X	    *--cp = '\0';
X	    break;
X	}
X	if( EQ( cp[0], 'r' ) && EQ( cp[1], 'e' )
X	 && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':')) ) {
X	    *--cp = '\0';
X	    break;
X	}
X    }
X    /* Copy subject to a temporary string, compacting multiple spaces/tabs */
X    for( len = 0, cp = subject_str; len < 72 && *str; len++ ) {
X	if( (unsigned char)*str <= ' ' ) {
X	    while( *++str && (unsigned char)*str <= ' ' ) {
X		;
X	    }
X	    *cp++ = ' ';
X	} else {
X	    *cp++ = *str++;
X	}
X    }
X    if( cp[-1] == ' ' ) {
X	cp--;
X    }
X    *cp = '\0';
X}
X
X/* Try to fit the author name in 16 bytes.  Use the comment portion in
X** parenthesis if present.  Cut off non-commented names at the '@' or '%'.
X** Then, put as many characters as we can into the 16 bytes, packing multiple
X** whitespace characters into a single space.
X** We should really implement a nice name shortening algorithm, or simply
X** grab the name packing code from nn.
X*/
Xvoid
Xget_author_str( str )
Xchar *str;
X{
X    register char *cp, *cp2;
X
X    if( (cp = index( str, '(' )) != Nullch ) {
X	str = cp+1;
X	if( (cp = rindex( str, ')' )) != Nullch ) {
X	    *cp = '\0';
X	}
X    } else {
X	if( (cp = index( str, '@' )) != Nullch ) {
X	    *cp = '\0';
X	}
X	if( (cp = index( str, '%' )) != Nullch ) {
X	    *cp = '\0';
X	}
X    }
X    for( cp = str, cp2 = author_str; *cp && cp2-author_str < 16; ) {
X	/* Pack white space and turn ctrl-chars into spaces. */
X	if( *cp <= ' ' ) {
X	    while( *++cp && *cp <= ' ' ) {
X		;
X	    }
X	    if( cp2 != author_str ) {
X		*cp2++ = ' ';
X	    }
X	} else {
X	    *cp2++ = *cp++;
X	}
X    }
X    *cp2 = '\0';
X}
X
X/* Take a message-id and see if we already know about it.  If so, return it.
X** If not, create it.  We separate the id into its id at domain parts, and
X** link all the unique ids to one copy of the domain portion.  This saves
X** a bit of space.
X*/
XARTICLE *
Xget_article( msg_id )
Xchar *msg_id;
X{
X    register DOMAIN *domain;
X    register ARTICLE *article;
X    register char *cp, *after_at;
X
X    /* Take message id, break it up into <id at domain>, and try to match it.
X    */
X    while( *msg_id == ' ' ) {
X	msg_id++;
X    }
X    cp = msg_id + strlen( msg_id ) - 1;
X    if( msg_id >= cp ) {
X	if( log_verbosity ) {
X	    log_error( "Message-ID is empty!\n" );
X	}
X	return Nullart;
X    }
X    if( *msg_id++ != '<' ) {
X	if( log_verbosity ) {
X	    log_error( "Message-ID doesn't start with '<'.\n" );
X	}
X	msg_id--;
X    }
X    if( *cp != '>' ) {
X	if( log_verbosity ) {
X	    log_error( "Message-ID doesn't end with '>'.\n" );
X	}
X	cp++;
X    }
X    *cp = '\0';
X    if( msg_id == cp ) {
X	if( log_verbosity ) {
X	    log_error( "Message-ID is null!\n" );
X	}
X	return Nullart;
X    }
X
X    if( (after_at = index( msg_id, '@' )) == Nullch ) {
X	domain = &unk_domain;
X    } else {
X	*after_at++ = '\0';
X	for( cp = after_at; *cp; cp++ ) {
X	    if( isupper(*cp) ) {
X		*cp = tolower(*cp);		/* lower-case domain portion */
X	    }
X	}
X	*cp = '\0';
X	/* Try to find domain name in database. */
X	for( domain = unk_domain.link; domain; domain = domain->link ) {
X	    if( strEQ( domain->name, after_at ) ) {
X		break;
X	    }
X	}
X	if( !domain ) {		/* if domain doesn't exist, create it */
X	  register int len = cp - after_at + 1;
X	    domain = (DOMAIN *)safemalloc( sizeof (DOMAIN) );
X	    total.domain++;
X	    domain->name = safemalloc( len );
X	    total.string2 += len;
X	    bcopy( after_at, domain->name, len );
X	    domain->ids = Nullart;
X	    domain->link = unk_domain.link;
X	    unk_domain.link = domain;
X	}
X    }
X    /* Try to find id in this domain. */
X    for( article = domain->ids; article; article = article->id_link ) {
X	if( strEQ( article->id, msg_id ) ) {
X	    break;
X	}
X    }
X    if( !article ) {		/* If it doesn't exist, create an article */
X      register int len = strlen( msg_id ) + 1;
X	article = (ARTICLE *)safemalloc( sizeof (ARTICLE) );
X	bzero( article, sizeof (ARTICLE) );
X	total.article++;
X	article->num = 0;
X	article->id = safemalloc( len );
X	total.string2 += len;
X	bcopy( msg_id, article->id, len );
X	article->domain = domain;
X	article->id_link = domain->ids;
X	domain->ids = article;
X    }
X    return article;
X}
X
X/* Take all the data we've accumulated about the article and shove it into
X** the article tree at the best place we can possibly imagine.
X*/
Xvoid
Xinsert_article( article, date, num )
XARTICLE *article;
Xtime_t date;
XART_NUM num;
X{
X    register ARTICLE *node, *last;
X    register char *cp, *end;
X    int len;
X
X    if( article->subject ) {
X	if( log_verbosity ) {
X	    log_error( "We've already seen article #%ld (%s@%s)\n",
X		(long)num, article->id, article->domain->name );
X	}
X	return;						/* RETURN */
X    }
X    article->date = date;
X    article->num = num;
X    article->flags = NEW_ARTICLE;
X
X    if( !*references && found_Re ) {
X	if( log_verbosity > 1 ) {
X	    log_error( "Missing reference line!  [%ld]\n", (long)num );
X	}
X    }
X    /* If the article has a non-zero root, it is already in a thread somewhere.
X    ** Unlink it to try to put it in the best possible spot.
X    */
X    if( article->root ) {
X	/* Check for a real or shared-fake parent.  Articles that have never
X	** existed have a num of 0.  Expired articles that remain as references
X	** have a valid num.  (Valid date too, but no subject.)
X	*/
X	for( node = article->parent;
X	     node && !node->num && node->child_cnt == 1;
X	     node = node->parent )
X	{
X	    ;
X	}
X	unlink_child( article );
X	if( node ) {			/* do we have decent parents? */
X	    /* Yes: assume that our references are ok, and just reorder us
X	    ** with our siblings by date.
X	    */
X	    link_child( article );
X	    use_root( article, article->root );
X	    /* Freshen the date in any faked parent articles. */
X	    for( node = article->parent;
X		 node && !node->num && date < node->date;
X		 node = node->parent )
X	    {
X		node->date = date;
X		unlink_child( node );
X		link_child( node );
X	    }
X	    return;					/* RETURN */
X	}
X	/* We'll assume that this article has as good or better references
X	** than the child that faked us initially.  Free the fake reference-
X	** chain and process our references as usual.
X	*/
X	for( node = article->parent; node; node = node->parent ) {
X	    unlink_child( node );
X	    free_article( node );
X	}
X	article->parent = Nullart;		/* neaten up */
X	article->siblings = Nullart;
X    }
X  check_references:
X    if( !*references ) {	/* If no references but "Re:" in subject, */
X	if( found_Re ) {	/* search for a reference in any cited text */
X#ifndef SERVER
X	    for( len = 4; len && fgets( buff, sizeof buff, fp_article ); len-- ) {
X		if( (cp = index( buff, '<' )) && (end = index( cp, ' ' )) ) {
X		    if( end[-1] == ',' ) {
X			end--;
X		    }
X		    *end = '\0';
X		    if( (end = index( cp, '>' )) == Nullch ) {
X			end = cp + strlen( cp ) - 1;
X		    }
X		    if( valid_message_id( cp, end ) ) {
X			strcpy( references+1, cp );
X			*references = ' ';
X			if( log_verbosity > 2 ) {
X			    log_error( "Found cited-text reference: '%s' [%ld]\n",
X				references+1, (long)num );
X			}
X			break;
X		    }
X		}
X	    }
X#endif
X	} else {
X	    article->flags |= ROOT_ARTICLE;
X	}
X    }
X    /* If we have references, process them from the right end one at a time
X    ** until we either run into somebody, or we run out of references.
X    */
X    if( *references ) {
X	last = article;
X	node = Nullart;
X	end = references + strlen( references ) - 1;
X	while( (cp = rindex( references, ' ' )) != Nullch ) {
X	    *cp++ = '\0';
X	    while( end >= cp && ((unsigned char)*end <= ' ' || *end == ',') ) {
X		end--;
X	    }
X	    end[1] = '\0';
X	    /* Quit parsing references if this one is garbage. */
X	    if( !valid_message_id( cp, end ) ) {
X		if( log_verbosity ) {
X		    log_error( "Bad ref '%s' [%ld]\n", cp, (long)num );
X		}
X		break;
X	    }
X	    /* Dump all domains that end in '.', such as "..." & "1 at DEL." */
X	    if( end[-1] == '.' ) {
X		break;
X	    }
X	    node = get_article( cp );
X	    /* Check for duplicates on the reference line.  Brand-new data has
X	    ** no date.  Data we just allocated earlier on this line has a
X	    ** date but no root.  Special-case the article itself, since it
X	    ** MIGHT have a root.
X	    */
X	    if( (node->date && !node->root) || node == article ) {
X		if( log_verbosity ) {
X		    log_error( "Reference line contains duplicates [%ld]\n",
X			(long)num );
X		}
X		if( (node = last) == article ) {
X		    node = Nullart;
X		}
X		continue;
X	    }
X	    last->parent = node;
X	    link_child( last );
X	    if( node->root ) {
X		break;
X	    }
X	    node->date = date;
X	    last = node;
X	    end = cp-2;
X	}
X	if( !node ) {
X	    *references = '\0';
X	    goto check_references;
X	}
X	/* Check if we ran into anybody that was already linked.  If so, we
X	** just use their root.
X	*/
X	if( node->root ) {
X	    /* See if this article spans the gap between what we thought
X	    ** were two different roots.
X	    */
X	    if( article->root && article->root != node->root ) {
X		merge_roots( node->root, article->root );
X		/* Set the roots of any children we brought with us. */
X		set_root( article, node->root );
X	    }
X	    use_root( article, node->root );
X	} else {
X	    /* We didn't find anybody we knew, so either create a new root or
X	    ** use the article's root if it was previously faked.
X	    */
X	    if( !article->root ) {
X		make_root( node );
X		use_root( article, node->root );
X	    } else {
X		use_root( article, article->root );
X		node->root = article->root;
X		link_child( node );
X	    }
X	}
X	/* Set the roots of the faked articles we created as references. */
X	for( node = article->parent; node && !node->root; node = node->parent ) {
X	    node->root = article->root;
X	}
X	/* Make sure we didn't circularly link to a child article(!), by
X	** ensuring that we run into the root before we run into ourself.
X	*/
X	while( node && node->parent != article ) {
X	    node = node->parent;
X	}
X	if( node ) {
X	    /* Ugh.  Someone's tweaked reference line with an incorrect
X	    ** article order arrived first, and one of our children is
X	    ** really one of our ancestors. Cut off the bogus child branch
X	    ** right where we are and link it to the root.
X	    */
X	    if( log_verbosity ) {
X		log_error("Found ancestral child -- fixing.\n");
X	    }
X	    unlink_child( node );
X	    node->parent = Nullart;
X	    link_child( node );
X	}
X    } else {
X	/* The article has no references.  Either turn it into a new root, or
X	** re-attach fleshed-out (previously faked) article to its old root.
X	*/
X	if( !article->root ) {
X	    make_root( article );
X	} else {
X	    use_root( article, article->root );
X	    link_child( article );
X	}
X    }
X}
X
X/* Check if the string we've found looks like a valid message-id reference.
X*/
Xint
Xvalid_message_id( start, end )
Xregister char *start, *end;
X{
X    int lower_case;
X    char *mid;
X
X    if( *end != '>' ) {
X	/* Compensate for spacecadets who include the header in their
X	** subsitution of all '>'s into another citation character.
X	*/
X	if( *end == '<' || *end == '-' || *end == '!' || *end == '%'
X	 || *end == ')' || *end == '|' || *end == ':' || *end == '}'
X	 || *end == '*' || *end == '+' || *end == '#' || *end == ']'
X	 || *end == '@' ) {
X	    if( log_verbosity ) {
X		log_error( "Reference ended in '%c'.\n", *end );
X	    }
X	    *end = '>';
X	}
X    }
X    /* Id must be "<... at ...>" */
X    if( *start != '<' || *end != '>' || (mid = index( start, '@' )) == Nullch
X     || mid == start+1 || mid+1 == end ) {
X	return 0;					/* RETURN */
X    }
X    /* Try to weed-out non-ids (user at domain) by looking for lower-case without
X    ** digits in the unique portion.  B news ids are all digits; standard C
X    ** news are digits with mixed case; and Zeeff message ids are any mixture
X    ** of digits, certain punctuation characters and upper-case.
X    */
X    lower_case = 0;
X    do {
X	if( *start <= '9' && *start >= '0' ) {
X	    return 1;					/* RETURN */
X	}
X	lower_case = lower_case || (*start >= 'a' && *start <= 'z');
X    } while( ++start < mid );
X
X    return !lower_case;
X}
X
X/* Remove an article from its parent/siblings.  Leave parent pointer intact.
X*/
Xvoid
Xunlink_child( child )
Xregister ARTICLE *child;
X{
X    register ARTICLE *last;
X
X    if( !(last = child->parent) ) {
X	child->root->thread_cnt--;
X	if( (last = child->root->articles) == child ) {
X	    child->root->articles = child->siblings;
X	} else {
X	    goto sibling_search;
X	}
X    } else {
X	last->child_cnt--;
X	if( last->children == child ) {
X	    last->children = child->siblings;
X	} else {
X	    last = last->children;
X	  sibling_search:
X	    while( last->siblings != child ) {
X		last = last->siblings;
X	    }
X	    last->siblings = child->siblings;
X	}
X    }
X}
X
X/* Link an article to its parent article.  If its parent pointer is zero,
X** link it to its root.  Sorts siblings by date.
X*/
Xvoid
Xlink_child( child )
Xregister ARTICLE *child;
X{
X    register ARTICLE *node;
X    register ROOT *root;
X
X    if( !(node = child->parent) ) {
X	root = child->root;
X	root->thread_cnt++;
X	node = root->articles;
X	if( !node || child->date < node->date ) {
X	    child->siblings = node;
X	    root->articles = child;
X	} else {
X	    goto sibling_search;
X	}
X    } else {
X	node->child_cnt++;
X	node = node->children;
X	if( !node || child->date < node->date ) {
X	    child->siblings = node;
X	    child->parent->children = child;
X	} else {
X	  sibling_search:
X	    for( ; node->siblings; node = node->siblings ) {
X		if( node->siblings->date > child->date ) {
X		    break;
X		}
X	    }
X	    child->siblings = node->siblings;
X	    node->siblings = child;
X	}
X    }
X}
X
X/* Create a new root for the specified article.  If the current subject_str
X** matches any pre-existing root's subjects, we'll instead add it on as a
X** parallel thread.
X*/
Xvoid
Xmake_root( article )
XARTICLE *article;
X{
X    register ROOT *new, *node;
X    register SUBJECT *subject;
X
X#ifndef NO_SUBJECT_MATCHING
X    /* First, check the other root's subjects for a match. */
X    for( node = root_root; node; node = node->link ) {
X	for( subject = node->subjects; subject; subject = subject->link ) {
X	    if( subject_equal( subject->str, subject_str ) ) {
X		use_root( article, node );		/* use it instead */
X		link_child( article );
X		return;					/* RETURN */
X	    }
X	}
X    }
X#endif
X
X    /* Create a new root. */
X    new = (ROOT *)safemalloc( sizeof (ROOT) );
X    total.root++;
X    new->articles = article;
X    new->root_num = article->num;
X    new->thread_cnt = 1;
X    if( article->num ) {
X	article->author = new_author();
X	new->subject_cnt = 1;
X	new->subjects = article->subject = new_subject();
X    } else {
X	new->subject_cnt = 0;
X	new->subjects = Null(SUBJECT*);
X    }
X    article->root = new;
X    new->link = root_root;
X    root_root = new;
X}
X
X/* Add this article's subject onto the indicated root's list.  Point the
X** article at the root.
X*/
Xvoid
Xuse_root( article, root )
XARTICLE *article;
XROOT *root;
X{
X    register SUBJECT *subject;
X    register ROOT *root2;
X    SUBJECT *hold, *child_subj = Null(SUBJECT*);
X    ARTICLE *node;
X
X    article->root = root;
X
X    /* If it's a fake, there's no subject to add. */
X    if( !article->num ) {
X	return;						/* RETURN */
X    }
X
X    /* If we haven't picked a unique message number to represent this root,
X    ** use the first non-zero number we encounter.  Which one doesn't matter.
X    */
X    if( !root->root_num ) {
X	root->root_num = article->num;
X    }
X    article->author = new_author();
X
X    /* Check if the new subject matches any of the other subjects in this root.
X    ** If so, we just update the count.  If not, check all the other roots for
X    ** a match.  If found, the new subject is common between the two roots, so
X    ** we merge the two roots together.
X    */
X    root2 = root;
X#ifndef NO_SUBJECT_MATCHING
X    do {
X#endif
X	for( subject = root2->subjects; subject; subject = subject->link ) {
X	    if( subject_equal( subject->str, subject_str ) ) {
X		article->subject = subject;
X		subject->count++;
X#ifndef NO_SUBJECT_MATCHING
X		if( root2 != root ) {
X		    merge_roots( root, root2 );
X		}
X#endif
X		return;					/* RETURN */
X	    }
X	}
X#ifndef NO_SUBJECT_MATCHING
X	if( (root2 = root2->link) == Null(ROOT*) ) {
X	    root2 = root_root;
X	}
X    } while( root2 != root );
X#endif
X
X    article->subject = hold = new_subject();
X    root->subject_cnt++;
X
X    /* Find subject of any pre-existing children.  We want to insert the new
X    ** subject before a child's to keep the subject numbering intuitive
X    ** in the newsreader.
X    */
X    for( node = article->children; node; node = node->children ) {
X	if( node->subject ) {
X	    child_subj = node->subject;
X	    break;
X	}
X    }
X    if( !(subject = root->subjects) || subject == child_subj ) {
X	hold->link = root->subjects;
X	root->subjects = hold;
X    } else {
X	while( subject->link && subject->link != child_subj ) {
X	    subject = subject->link;
X	}
X	hold->link = subject->link;
X	subject->link = hold;
X    }
X}
X
X/* Check subjects in a case-insignificant, punctuation ignoring manner.
X*/
Xint
Xsubject_equal( str1, str2 )
Xregister char *str1, *str2;
X{
X    register char ch1, ch2;
X
X    while( (ch1 = *str1++) ) {
X	if( ch1 == ' ' || ispunct( ch1 ) ) {
X	    while( *str1 && (*str1 == ' ' || ispunct( *str1 )) ) {
X		str1++;
X	    }
X	    ch1 = ' ';
X	} else if( isupper( ch1 ) ) {
X	    ch1 = tolower( ch1 );
X	}
X	if( !(ch2 = *str2++) ) {
X	    return 0;
X	}
X	if( ch2 == ' ' || ispunct( ch2 ) ) {
X	    while( *str2 && (*str2 == ' ' || ispunct( *str2 )) ) {
X		str2++;
X	    }
X	    ch2 = ' ';
X	} else if( isupper( ch2 ) ) {
X	    ch2 = tolower( ch2 );
X	}
X	if( ch1 != ch2 ) {
X	    return 0;
X	}
X    }
X    if( *str2 ) {
X	return 0;
X    }
X    return 1;
X}
X
X/* Create a new subject structure. */
XSUBJECT *
Xnew_subject()
X{
X    register int len = strlen( subject_str ) + 1;
X    register SUBJECT *subject;
X
X    subject = (SUBJECT *)safemalloc( sizeof (SUBJECT) );
X    total.subject++;
X    subject->count = 1;
X    subject->link = Null(SUBJECT*);
X    subject->str = safemalloc( len );
X    total.string1 += len;
X    bcopy( subject_str, subject->str, len );
X
X    return subject;
X}
X
X/* Create a new author structure. */
XAUTHOR *
Xnew_author()
X{
X    register len = strlen( author_str ) + 1;
X    register AUTHOR *author, *last_author;
X
X    last_author = Null(AUTHOR*);
X    for( author = author_root; author; author = author->link ) {
X#ifndef DONT_COMPARE_AUTHORS	/* might like to define this to save time */
X	if( strEQ( author->name, author_str ) ) {
X	    author->count++;
X	    return author;				/* RETURN */
X	}
X#endif
X	last_author = author;
X    }
X
X    author = (AUTHOR *)safemalloc( sizeof (AUTHOR) );
X    total.author++;
X    author->count = 1;
X    author->link = Null(AUTHOR*);
X    author->name = safemalloc( len );
X    total.string1 += len;
X    bcopy( author_str, author->name, len );
X
X    if( last_author ) {
X	last_author->link = author;
X    } else {
X	author_root = author;
X    }
X    return author;
X}
X
X/* Insert all of root2 into root1, setting the proper root values and
X** updating subject counts.
X*/
Xvoid
Xmerge_roots( root1, root2 )
XROOT *root1, *root2;
X{
X    register ARTICLE *node, *next;
X    register SUBJECT *subject;
X
X    /* Remember whoever's root num is lower.  This could screw up a
X    ** newsreader's kill-thread code if someone already saw the roots as
X    ** being separate, but it must be done.  The newsreader code will have
X    ** to handle this as best as it can.
X    */
X    if( root1->root_num > root2->root_num ) {
X	root1->root_num = root2->root_num;
X    }
X
X    for( node = root2->articles; node; node = next ) {
X	/* For each article attached to root2, detach them, set the
X	** branch's root pointers to root1, and then attach it to root1.
X	*/
X	next = node->siblings;
X	unlink_child( node );
X	node->siblings = Nullart;
X	set_root( node, root1 );		/* sets children too */
X	/* Link_child() depends on node->parent being null and node->root
X	** being set.
X	*/
X	link_child( node );
X    }
X    root1->subject_cnt += root2->subject_cnt;
X    if( !(subject = root1->subjects) ) {
X	root1->subjects = root2->subjects;
X    } else {
X	while( subject->link ) {
X	    subject = subject->link;
X	}
X	subject->link = root2->subjects;
X    }
X    unlink_root( root2 );
X    free_root( root2 );
X}
X
X/* When merging roots, we need to reset all the root pointers.
X*/
Xvoid
Xset_root( node, root )
XARTICLE *node;
XROOT *root;
X{
X    do {
X	node->root = root;
X	if( node->children ) {
X	    set_root( node->children, root );
X	}
X    } while( node = node->siblings );
X}
X
X/* Unlink a root from its neighbors. */
Xvoid
Xunlink_root( root )
Xregister ROOT *root;
X{
X    register ROOT *node;
X
X    if( (node = root_root) == root ) {
X	root_root = root->link;
X    } else {
X	while( node->link != root ) {
X	    node = node->link;
X	}
X	node->link = root->link;
X    }
X}
X
X/* Free an article and its message-id string.  All other resources must
X** already be free, and it must not be attached to any threads.
X*/
Xvoid
Xfree_article( this )
XARTICLE *this;
X{
X    register ARTICLE *art;
X
X    if( (art = this->domain->ids) == this ) {
X	if( !(this->domain->ids = this->id_link) ) {
X	    free_domain( this->domain );
X	}
X    } else {
X	while( this != art->id_link ) {
X	    art = art->id_link;
X	}
X	art->id_link = this->id_link;
X    }
X    total.string2 -= strlen( this->id ) + 1;
X    free( this->id );
X    free( this );
X    total.article--;
X}
X
X/* Free the domain only when its last unique id has been freed. */
Xvoid
Xfree_domain( this )
XDOMAIN *this;
X{
X    register DOMAIN *domain;
X
X    if( this == (domain = &unk_domain) ) {
X	return;
X    }
X    if( this == next_domain ) {	/* help expire routine skip freed domains */
X	next_domain = next_domain->link;
X    }
X    while( this != domain->link ) {
X	domain = domain->link;
X    }
X    domain->link = this->link;
X    total.string2 -= strlen( this->name ) + 1;
X    free( this->name );
X    free( this );
X    total.domain--;
X}
X
X/* Free the subject structure and its string. */
Xvoid
Xfree_subject( this )
XSUBJECT *this;
X{
X    total.string1 -= strlen( this->str ) + 1;
X    free( this->str );
X    free( this );
X    total.subject--;
X}
X
X/* Free a root.  It must already be unlinked. */
Xvoid
Xfree_root( this )
XROOT *this;
X{
X    free( this );
X    total.root--;
X}
X
X/* Free the author structure when it's not needed any more. */
Xvoid
Xfree_author( this )
XAUTHOR *this;
X{
X    total.string1 -= strlen( this->name ) + 1;
X    free( this->name );
X    free( this );
X    total.author--;
X}
END_OF_FILE
  if test 34673 -ne `wc -c <'mt-process.c'`; then
    echo shar: \"'mt-process.c'\" unpacked with wrong size!
  fi
  # end of 'mt-process.c'
fi
if test -f 'rt-select.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'rt-select.c'\"
else
  echo shar: Extracting \"'rt-select.c'\" \(22272 characters\)
  sed "s/^X//" >'rt-select.c' <<'END_OF_FILE'
X/* $Header: rt-select.c,v 4.3.3.2 90/08/20 18:32:33 davison Trn $
X**
X** $Log:	rt-select.c,v $
X** Revision 4.3.3.2  90/08/20  18:32:33  davison
X** Reset scan_all_roots while selecting.
X** 
X** Revision 4.3.3.1  90/07/24  22:04:07  davison
X** Initial Trn Release
X** 
X*/
X
X#include "EXTERN.h"
X#include "common.h"
X#include "rn.h"
X#include "rcstuff.h"
X#include "term.h"
X#include "final.h"
X#include "util.h"
X#include "help.h"
X#include "bits.h"
X#include "artsrch.h"
X#include "ng.h"
X#include "ngstuff.h"
X#include "rthreads.h"
X
X#ifdef USETHREADS
X
Xstatic int count_subj_lines();
Xstatic void display_subj();
X
X/* When display mode is 'l', each author gets a separate line; when 'm', up to
X** three authors share a line; when 's', no authors are displayed.
X*/
Xstatic char *display_mode = select_order;
Xstatic ART_NUM article_count;
Xstatic int author_line;
Xstatic char first_two_chars[3] = { ' ', ' ', '\0' }, mask = 1;
X
X#define MAX_SEL 64
X
X/* Display a menu of roots for the user to choose from.  If cmd is '+'
X** we display all the unread roots and allow the user to mark roots as
X** selected and perform various commands upon the articles.  If cmd is
X** 'U' we display all the previously read roots and allow the user to
X** select which ones should be marked as unread.
X*/
Xchar
Xselect_thread( cmd )
Xchar cmd;
X{
X    register int i, j, cnt;
X    ART_NUM art_hold = art;
X    int line_cnt, screen_line, subj_line_cnt;
X    int cur_root, page_root, last_root = -1;
X    ART_LINE running_total, last_running;
X    int last_line, got_dash;
X    int max_root;
X    int first, last;
X    int root_line[MAX_SEL], root_hold[MAX_SEL];
X    int ch, action;
X    char page_char, end_char;
X    char promptbuf[80];
X    bool etc, clean_screen, empty_ok, displayed_status;
X    char oldmode = mode;
X#ifndef CONDSUB
X    char tmpbuf[2];
X#endif
X    char *select_chars, *in_select;
X    int max_cnt;
X
X    mode = 't';
X    unread_selector = (cmd == 'U');
X    clear_on_stop = TRUE;
X    empty_ok = FALSE;
X
X  select_threads:
X    /* Setup for selecting articles to read or set unread */
X    scan_all_roots = FALSE;
X    if( unread_selector ) {
X	page_char = '>';
X	end_char = 'Z';
X	page_root = 0;
X	last_root = -1;
X	cmd = 0;
X    } else {
X	page_char = page_select;
X	end_char = end_select;
X	page_root = select_page;
X	if( curr_p_art ) {
X	    last_root = curr_p_art->root;
X	}
X    }
X    mask = unread_selector+1;
X
X    /* Leave empty roots selected for a short time to give them a chance
X    ** to 'q' out of the selector if they got here by mistake.
X    */
X    max_root = count_roots( FALSE );
X
X    /* If nothing to display, we're done. */
X    if( !article_count && !empty_ok ) {
X     all_empty:
X	clear_on_stop = FALSE;
X	mode = oldmode;
X	putchar( '\n' );
X	if( unread_selector ) {
X#ifdef VERBOSE
X	    IF(verbose)
X		fputs( "\nNo articles to set unread.\n", stdout );
X	    ELSE
X#endif
X#ifdef TERSE
X		fputs( "\nNo articles.\n", stdout ) FLUSH;
X#endif
X	    unread_selector = 0;
X	    mask = 1;
X	} else {
X#ifdef VERBOSE
X	    IF(verbose)
X		fputs( "\nNo unread articles to select.\n", stdout );
X	    ELSE
X#endif
X#ifdef TERSE
X		fputs( "\nNo articles.\n", stdout );	/* let "them" FLUSH */
X#endif
X	}
X	(void) count_roots( TRUE );
X	art = art_hold;
X	p_art = curr_p_art;
X	return 'q';
X    }
X    if( unread_selector ) {
X	for( j = 0; j < total.root; j++ ) {
X	    selected_roots[j] |= 4;
X	}
X    }
X    if( page_root >= max_root ) {
X	ch = '<';
X    } else {
X	ch = '>';
X    }
X    cur_root = 0;
X    running_total = 0;
X    for( i = 0; i < page_root; i++ ) {
X	running_total += root_article_cnts[i];
X    }
X    do {
X	select_chars = getval( "SELECTCHARS", SELECTCHARS );
X	max_cnt = strlen( select_chars );
X	if( max_cnt > MAX_SEL ) {
X	    max_cnt = MAX_SEL;
X	}
X	if( ch == '<' && i ) {
X	    screen_line = 2;
X	    cnt = 0;
X	    /* Scan the roots in reverse to go back a page */
X	    do {
X		if( !root_article_cnts[--i] ) {
X		    continue;
X		}
X		first = root_subjects[i];
X		last = first + p_roots[i].subject_cnt;
X		line_cnt = 0;
X		for( j = first; j < last; j++ ) {
X		    line_cnt += count_subj_lines( i, j );
X		}
X		if( line_cnt > LINES - 5 ) {
X		    line_cnt = LINES - 5;
X		}
X		screen_line += line_cnt;
X		if( screen_line > LINES - 3 ) {
X		    i++;
X		    break;
X		}
X		running_total -= root_article_cnts[i];
X		cnt++;
X	    } while( i > 0 && cnt < max_cnt );
X	}
X
X	/* Present a page of subjects to the user */
X#ifndef CLEAREOL
X	clear();
X#else
X	if( can_home_clear ) {
X	    home_cursor();
X	    maybe_eol();
X	} else {
X	    clear();
X	}
X#endif
X	carriage_return();
X	page_root = i;
X	last_running = running_total;
X#ifdef NOFIREWORKS
X	no_sofire();
X#endif
X	standout();
X	fputs( ngname, stdout );
X	un_standout();
X	printf( "\t\t\t\t%ld %sarticle%s\n", (long)article_count,
X	    unread_selector? "read " : nullstr,
X	    article_count == 1 ? nullstr : "s" );
X#ifdef CLEAREOL
X	maybe_eol();
X#endif
X	putchar( '\n' ) FLUSH;
X	screen_line = 2;
X	for( cnt = 0; i < max_root && cnt < max_cnt; i++ ) {
X	    if( last_root == i ) {
X		cur_root = cnt;
X	    }
X	    /* Check each root for articles to list */
X	    if( !root_article_cnts[i] ) {
X		continue;
X	    }
X	    first = root_subjects[i];
X	    last = first + p_roots[i].subject_cnt;
X
X	    /* Compute how many lines we need to display the subjects/authors */
X	    etc = FALSE;
X	    line_cnt = 0;
X	    for( j = first; j < last; j++ ) {
X		subj_line_cnt = count_subj_lines( i, j );
X		line_cnt += subj_line_cnt;
X		/* If this root is too long to fit on the screen all by
X		** itself, trim it to fit and set the "etc" flag.
X		*/
X		if( line_cnt > LINES - 5 ) {
X		    last = j;
X		    line_cnt -= subj_line_cnt;
X		    if( line_cnt != LINES - 5 ) {
X			last++;
X			line_cnt = LINES - 5;
X		    }
X		    if( screen_line == 2 ) {
X			etc = TRUE;
X		    }
X		    break;
X		}
X	    }
X	    /* If it doesn't fit, save it for the next page */
X	    if( screen_line + line_cnt > LINES - 3 ) {
X		break;
X	    }
X	    /* Output the subjects, with optional authors */
X	    root_line[cnt] = screen_line;
X	    running_total += root_article_cnts[i];
X	    first_two_chars[0] = select_chars[cnt];
X	    first_two_chars[1] = (selected_roots[i] & 4) ? '-' :
X				 (selected_roots[i] & mask) ? '+' : ' ';
X	    author_line = screen_line;
X	    for( j = first; j < last; j++ ) {
X		display_subj( i, j );
X	    }
X	    screen_line += line_cnt;
X	    root_hold[cnt++] = i;
X	    if( etc ) {
X		fputs( "      ...etc.", stdout );
X		i++;
X		break;
X	    }
X	}/* for */
X	last_root = -1;
X	if( cur_root && cur_root >= cnt ) {
X	    cur_root = cnt - 1;
X	}
X
X	/* Check if there is really anything left to display. */
X	if( !running_total && !empty_ok ) {
X	    goto all_empty;
X	}
X	empty_ok = FALSE;
X
X	last_line = screen_line+1;
X#ifdef CLEAREOL
X	maybe_eol();
X#endif
X	putchar( '\n' ) FLUSH;
X	/* Prompt the user */
X	strcpy( promptbuf, "-- Select threads -- " );
X	if( i != max_root ) {
X	    sprintf( promptbuf+21, "%s%d%% [%c%c] --",
X		(!page_root? "Top " : nullstr),
X		running_total*100 / article_count, page_char, end_char );
X	} else {
X	    sprintf( promptbuf+21, "%s [%c%c] --",
X		(!page_root? "All" : "Bot"), end_char, page_char );
X	}
X	if( cur_root > cnt ) {
X	    cur_root = 0;
X	}
X	screen_line = root_line[cur_root];
X#ifdef CLEAREOL
X	if( erase_screen && can_home_clear ) {
X	    clear_rest();
X	}
X#endif
X	displayed_status = FALSE;
X      prompt_select:
X	standout();
X	fputs( promptbuf, stdout );
X	un_standout();
X	if( can_home ) {
X	    carriage_return();
X	    goto_line( last_line, screen_line );
X	}
X	got_dash = 0;
X	/* Grab some commands from the user */
X	for( ;; ) {
X	    fflush(stdout);
X	    eat_typeahead();
X#ifdef CONDSUB
X	    getcmd( buf );
X	    ch = *buf;
X#else
X	    getcmd( tmpbuf );	/* If no conditionals, don't allow macros */ 
X	    ch = *tmpbuf;
X	    buf[0] = ch;
X	    buf[1] = FINISHCMD;
X#endif
X	    in_select = index( select_chars, ch );
X	    /* Plaster any inherited empty roots on first command, if not q. */
X	    if( cmd && (in_select || (ch != '\033' && ch != 'q')) ) {
X		max_root = count_roots( TRUE );
X		cmd = 0;
X	    }
X	    if( displayed_status && can_home ) {
X		goto_line( screen_line, last_line+1 );
X		erase_eol();
X		screen_line = last_line+1;
X		displayed_status = FALSE;
X	    }
X	    if( ch == '-' ) {
X		got_dash = 1;
X		if( !can_home ) {
X		    putchar( '-' );
X		    fflush( stdout );
X		}
X		continue;
X	    }
X	    if( ch == ' ' ) {
X		if( i == max_root ) {
X		    ch = end_char;
X		} else {
X		    ch = page_char;
X		}
X	    }
X	    if( !in_select && (index( "<>^$!?&:/hDJLNqQUXZ\n\r\t\033", ch )
X	     || ch == Ctl('l') || ch == Ctl('r') || ch == Ctl('k')) ) {
X		break;
X	    }
X	    if( in_select ) {
X		j = in_select - select_chars;
X		if( j >= cnt ) {
X		    dingaling();
X		    j = -1;
X		} else if( got_dash ) {
X		    ;
X		} else if( selected_roots[root_hold[j]] & mask ) {
X		    action = (unread_selector ? 'k' : '-');
X		} else {
X		    action = '+';
X		}
X	    } else if( ch == 'y' || ch == '.' ) {
X		j = cur_root;
X		if( selected_roots[root_hold[j]] & mask ) {
X		    action = (unread_selector ? 'k' : '-');
X		} else {
X		    action = '+';
X		}
X	    } else if( ch == 'k' || ch == 'j' || ch == ',' ) {
X		j = cur_root;
X		action = 'k';
X	    } else if( ch == 'm' || ch == '\\' ) {
X		j = cur_root;
X		action = 'm';
X	    } else if( ch == '@' ) {
X		cur_root = 0;
X		j = cnt-1;
X		got_dash = 1;
X		action = '@';
X	    } else if( ch == '[' || ch == 'p' ) {
X		if( --cur_root < 0 ) {
X		    cur_root = cnt ? cnt-1 : 0;
X		}
X		j = -1;
X	    } else if( ch == ']' || ch == 'n' ) {
X		if( ++cur_root >= cnt ) {
X		    cur_root = 0;
X		}
X		j = -1;
X	    } else {
X		if( can_home ) {
X		    goto_line( screen_line, last_line+1 );
X		    screen_line = last_line+1;
X		} else {
X		    putchar( '\n' );
X		}
X		printf( "Type ? for help." );
X		settle_down();
X		displayed_status = TRUE;
X
X		if( can_home ) {
X		    carriage_return();
X		} else {
X		    putchar( '\n' );
X		}
X		j = -1;
X	    }
X	    if( j >= 0 ) {
X		if( !got_dash ) {
X		    cur_root = j;
X		} else {
X		    got_dash = 0;
X		    if( j < cur_root ) {
X			ch = cur_root-1;
X			cur_root = j;
X			j = ch;
X		    }
X		}
X		if( ++j == cnt ) {
X		    j = 0;
X		}
X		do {
X		    if( can_home ) {
X			goto_line( screen_line, root_line[cur_root] );
X			screen_line = root_line[cur_root];
X		    }
X		    putchar( select_chars[cur_root] );
X		    if( action == '@' ) {
X			if( selected_roots[root_hold[cur_root]] & 4 ) {
X			    ch = (unread_selector ? '+' : ' ');
X			} else if( unread_selector ) {
X			    ch = 'k';
X			} else
X			if( selected_roots[root_hold[cur_root]] & mask ) {
X			    ch = '-';
X			} else {
X			    ch = '+';
X			}
X		    } else {
X			ch = action;
X		    }
X		    switch( ch ) {
X		    case '+':
X			if( !(selected_roots[root_hold[cur_root]] & mask) ) {
X			    selected_roots[root_hold[cur_root]] |= mask;
X			    selected_root_cnt++;
X			    selected_count
X				+= root_article_cnts[root_hold[cur_root]];
X			    putchar( '+' );
X			}
X			/* FALL THROUGH */
X		    case 'm':
X			if( selected_roots[root_hold[cur_root]] & 4 ) {
X			    selected_roots[root_hold[cur_root]] &= ~4;
X			    if( ch == 'm' ) {
X				putchar( ' ' );
X			    }
X			} else if( ch == 'm' ) {
X			    goto unsel;
X			}
X			break;
X		    case 'k':
X			if( !(selected_roots[root_hold[cur_root]] & 4) ) {
X			    selected_roots[root_hold[cur_root]] |= 4;
X			    putchar( '-' );
X			}
X			/* FALL THROUGH */
X		    case '-':
X		    unsel:
X			if( selected_roots[root_hold[cur_root]] & mask ) {
X			    selected_roots[root_hold[cur_root]] &= ~mask;
X			    selected_root_cnt--;
X			    selected_count
X				-= root_article_cnts[root_hold[cur_root]];
X			    if( ch != 'k' ) {
X				putchar( ' ' );
X			    }
X			}
X			break;
X		    }
X		    fflush( stdout );
X		    if( ++cur_root == cnt ) {
X			cur_root = 0;
X		    }
X		    if( can_home ) {
X			carriage_return();
X		    }
X		} while( cur_root != j );
X	    } else {
X		got_dash = FALSE;
X	    }
X	    if( can_home ) {
X		goto_line( screen_line, root_line[cur_root] );
X		screen_line = root_line[cur_root];
X	    }
X	}/* for */
X	if( can_home) {
X	    goto_line( screen_line, last_line );
X	}
X	clean_screen = TRUE;
X      do_command:
X	if( ch == 'L' ) {
X	    if( !*++display_mode ) {
X		display_mode = select_order;
X	    }
X	    ch = Ctl('l');
X	    cur_root = 0;
X	} else if( ch == '$' ) {
X	    ch = '<';
X	    page_root = max_root;
X	    last_running = article_count;
X	    cur_root = 0;
X	} else if( ch == '^' || ch == Ctl('r') ) {
X	    ch = '>';
X	    i = 0;
X	    running_total = 0;
X	    cur_root = 0;
X	} else if( ch == 'h' || ch == '?' ) {
X	    putchar( '\n' );
X	    if( (ch = help_select()) || (ch = pause_getcmd()) ) {
X		goto got_cmd;
X	    }
X	    ch = Ctl('l');
X	} else if( index( ":/&!", ch ) ) {
X	    erase_eol();		/* erase the prompt */
X	    if( !finish_command( TRUE ) ) {	/* get rest of command */
X		if( clean_screen ) {
X		    screen_line = root_line[cur_root];
X		    goto prompt_select;
X		}
X		goto extend_done;
X	    }
X	    if( ch == '&' || ch == '!' ) {
X		one_command = TRUE;
X		perform( buf, FALSE );
X		one_command = FALSE;
X
X		putchar( '\n' ) FLUSH;
X		clean_screen = FALSE;
X	    } else {
X		int selected_save = selected_root_cnt;
X
X		if( ch == ':' ) {
X		    clean_screen = (use_selected() == 2) && clean_screen;
X		} else {
X		    /* Force the search to begin at absfirst or firstart,
X		    ** depending upon whether they specified the 'r' option.
X		    */
X		    art = lastart+1;
X		    page_line = 1;
X		    switch( art_search( buf, sizeof buf, FALSE ) ) {
X		    case SRCH_ERROR:
X		    case SRCH_ABORT:
X		    case SRCH_INTR:
X			fputs( "\nInterrupted\n", stdout ) FLUSH;
X			break;
X		    case SRCH_DONE:
X		    case SRCH_SUBJDONE:
X			fputs( "Done\n", stdout ) FLUSH;
X			break;
X		    case SRCH_NOTFOUND:
X			fputs( "\nNot found.\n", stdout ) FLUSH;
X			break;
X		    case SRCH_FOUND:
X			break;
X		    }
X		    clean_screen = FALSE;
X		}
X		/* Recount, in case something has changed. */
X		max_root = count_roots( !unread_selector );
X
X		if( (selected_save -= selected_root_cnt) != 0 ) {
X		    putchar( '\n' );
X		    if( selected_save < 0 ) {
X			fputs( "S", stdout );
X			selected_save *= -1;
X		    } else {
X			fputs( "Des", stdout );
X		    }
X		    printf( "elected %d thread%s.", selected_save,
X			selected_save == 1 ? nullstr : "s" );
X		    clean_screen = FALSE;
X		}
X		if( !clean_screen ) {
X		    putchar('\n') FLUSH;
X		}
X	    }/* if !& or :/ */
X
X	    if( clean_screen ) {
X		carriage_return();
X		up_line();
X		erase_eol();
X		screen_line = root_line[cur_root];
X		goto prompt_select;
X	    }
X	  extend_done:
X	    if( (ch = pause_getcmd()) ) {
X	      got_cmd:
X		if( ch > 0 ) {
X		    /* try to optimize the screen update for some commands. */
X		    if( !index( select_chars, ch )
X		     && (index( "<>^$!?&:/hDJLNqQUXZ\n\r\t\033", ch )
X		      || ch == Ctl('k')) ) {
X			buf[0] = ch;
X			buf[1] = FINISHCMD;
X			goto do_command;
X		    }
X		    pushchar( ch | 0200 );
X		}
X	    }
X	    ch = Ctl('l');
X	} else if( ch == Ctl('k') ) {
X	    edit_kfile();
X	    ch = Ctl('l');
X	} else if( !unread_selector && (ch == 'X' || ch == 'D' || ch == 'J') ) {
X	    if( ch == 'D' ) {
X		j = page_root;
X		last = i;
X	    } else {
X		j = 0;
X		last = max_root;
X	    }
X	    for( ; j < last; j++ ) {
X		if( (!(selected_roots[j] & 1) ^ (ch == 'J'))
X		 && (cnt = root_article_cnts[j]) ) {
X		    p_art = p_articles + p_roots[j].articles;
X		    art = 0;
X		    follow_thread( 'J' );
X		}
X	    }
X	    max_root = count_roots( TRUE );
X	    if( article_count
X	     && (ch == 'J' || (ch == 'D' && !selected_root_cnt)) ) {
X		ch = Ctl('l');
X		cur_root = 0;
X	    } else {
X		break;
X	    }
X	} else if( ch == 'J' ) {
X	    for( j = 0; j < max_root; j++ ) {
X		selected_roots[j] = (selected_roots[j] & ~2) | 4;
X	    }
X	    selected_root_cnt = selected_count = 0;
X	    ch = Ctl('l');
X	}
X	if( ch == '>' ) {
X	    cur_root = 0;
X	} else if( ch == '<' || (page_root && page_root >= max_root) ) {
X	    cur_root = 0;
X	    running_total = last_running;
X	    if( !(i = page_root) || !max_root ) {
X		ch = '>';
X	    } else {
X		ch = '<';
X	    }
X	} else if( ch == Ctl('l') ) {
X	    i = page_root;
X	    running_total = last_running;
X	    ch = '>';
X	} else if( ch == '\r' || ch == '\n' ) {
X	    if( !selected_root_cnt ) {
X		selected_roots[root_hold[cur_root]] = mask;
X		selected_root_cnt++;
X		selected_count += root_article_cnts[root_hold[cur_root]];
X	    }
X	}
X    } while( (ch == '>' && i < max_root) || ch == '<' );
X    putchar( '\n' ) FLUSH;
X
X    if( unread_selector ) {
X	/* Turn selections into unread selected roots.  Let count_roots()
X	** fix the counts after we're through.
X	*/
X	last_root = -1;
X	for( j = 0; j < total.root; j++ ) {
X	    if( !(selected_roots[j] & 4) ) {
X		if( selected_roots[j] & 2 ) {
X		    selected_roots[j] = 1;
X		}
X		p_art = p_articles + p_roots[j].articles;
X		art = 0;
X		follow_thread( 'u' );
X	    } else {
X		selected_roots[j] &= ~4;
X	    }
X	}
X    } else {
X	select_page = page_root;
X	for( j = 0; j < total.root; j++ ) {
X	    if( selected_roots[j] & 4 ) {
X		selected_roots[j] = 0;
X		p_art = p_articles + p_roots[j].articles;
X		art = 0;
X		follow_thread( 'J' );
X	    }
X	}
X    }
X    if( ch == 'U' ) {
X	unread_selector = !unread_selector;
X	empty_ok = TRUE;
X	goto select_threads;
X    }
X
X    if( unread_selector ) {
X	unread_selector = 0;
X	mask = 1;
X	(void) count_roots( FALSE );
X    }
X    if( ch == '\033' || Ctl(ch) == Ctl('q') ) {
X	ch = (ch == 'Q' ? 'Q' : 'q');
X	art = art_hold;
X	p_art = curr_p_art;
X    } else if( ch == 'N' ) {
X	art = art_hold;
X	p_art = curr_p_art;
X    } else {
X	first_art();
X    }
X    clear_on_stop = FALSE;
X    mode = oldmode;
X    return ch;
X}
X
Xstatic int author_cnt, first_author;
X
X/* Counts the number of lines needed to output a subject, including optional
X** authors.
X*/
Xstatic int
Xcount_subj_lines( root, subj )
Xint root;
Xint subj;
X{
X    PACKED_ARTICLE *artp, *root_limit;
X    int author_subj;
X
X    author_cnt = 0;
X    author_subj = subj;
X    first_author = -1;
X
X    if( !subject_cnts[subj] ) {
X	return 0;
X    }
X    if( *display_mode == 's' ) {	/* no-author mode takes one line */
X	return ++author_cnt;
X    }
X    bzero( author_cnts, total.author * sizeof (WORD) );
X
X    /* Count authors associated with this subject.  Increments author_cnts. */
X    artp = p_articles + p_roots[root].articles;
X    root_limit = upper_limit( artp, FALSE );
X    for( ; artp != root_limit; artp++ ) {
X	if( artp->subject == author_subj
X	 && (!was_read( artp->num ) ^ unread_selector) ) {
X	    if( artp->author < 0 || artp->author >= total.author ) {
X		printf( "\
XFound invalid author (%d) with valid subject (%d)! [%ld]\n",
X			artp->author, artp->subject, artp->num );
X		artp->author = 0;
X	    } else {
X		if( first_author < 0 ) {
X		    first_author = artp->author;
X		}
X		if( !author_cnts[artp->author]++ ) {
X		    author_cnt++;
X		}
X	    }
X	}
X    }
X
X    if( *display_mode == 'm' ) {
X	return (author_cnt+4)/3;
X    } else {
X	return author_cnt;
X    }
X}
X
Xstatic void
Xdisplay_subj( root, subj )
Xint root;
Xint subj;
X{
X    PACKED_ARTICLE *artp, *root_limit;
X    char *str;
X
X    count_subj_lines( root, subj );
X    if( !author_cnt ) {
X	return;
X    }
X    artp = p_articles + p_roots[root].articles;
X    if( artp->subject != -1 && (artp->flags & ROOT_ARTICLE)
X     && (!was_read(artp->num) ^ unread_selector) ) {
X	str = nullstr;
X    } else {
X	str = ">";
X    }
X#ifdef CLEAREOL
X    maybe_eol();
X#endif
X    if( *display_mode == 's' ) {
X	printf( "%s%3d  %s%.71s\n", first_two_chars,
X		subject_cnts[subj], str, subject_ptrs[subj] ) FLUSH;
X    } else {
X	printf( "%s%-16.16s%3d  %s%.55s", first_two_chars,
X		author_ptrs[first_author],
X		subject_cnts[subj], str, subject_ptrs[subj] );
X	if( author_cnt > 1 ) {
X	    author_cnts[first_author] = 0;
X	    author_cnt = 0;
X	    root_limit = upper_limit( artp, FALSE );
X	    for( ; artp != root_limit; artp++ ) {
X		if( artp->author >= 0 && author_cnts[artp->author] ) {
X		    switch( author_cnt % 3 ) {
X		    case 0:
X			putchar( '\n' ) FLUSH;
X			if( ++author_line >= LINES - 3 ) {
X			    return;
X			}
X#ifdef CLEAREOL
X			maybe_eol();
X#endif
X			putchar( ' ' );
X			putchar( ' ' );
X			break;
X		    case 1:
X			putchar( '\t' );
X			putchar( '\t' );
X			break;
X		    case 2:
X			putchar( '\t' );
X			break;
X		    }
X		    author_cnt += (*display_mode == 'm');
X		    printf( "%-16.16s", author_ptrs[artp->author] );
X		    author_cnts[artp->author] = 0;
X		}/* if */
X	    }/* for */
X	}/* if */
X	putchar( '\n' ) FLUSH;
X	author_line++;
X    }/* if */
X    first_two_chars[0] = first_two_chars[1] = ' ';
X}
X
X/* Get each root's article count, and subject count(s); count total
X** articles and selected articles (use unread_selector to determine
X** whether to count read or unread articles); deselect any roots we
X** find that are empty (if do_unselect is TRUE); find the last non-
X** empty root, and return its count (the index+1).
X*/
Xint
Xcount_roots( do_unselect )
Xbool do_unselect;
X{
X    register int count;
X    register PACKED_ARTICLE *artp, *root_limit, *art_limit;
X    int last_root = -1;
X
X    article_count = selected_count = selected_root_cnt = 0;
X
X    if( !(artp = p_articles) ) {
X	return 0;
X    }
X    art_limit = artp + total.article;
X    root_limit = upper_limit( artp, 0 );
X
X    bzero( subject_cnts, total.subject * sizeof (WORD) );
X    count = 0;
X
X    for( ;; ) {
X	if( artp->subject == -1 ) {
X	    if( !was_read( artp->num ) ) {
X		oneless( artp->num );
X	    }
X	} else if( (!was_read( artp->num ) ^ unread_selector) ) {
X	    count++;
X	    subject_cnts[artp->subject]++;
X	}
X	if( ++artp == root_limit ) {
X	    int root_num = artp[-1].root;
X
X	    root_article_cnts[root_num] = count;
X	    if( count ) {
X		article_count += count;
X		if( selected_roots[root_num] & mask ) {
X		    selected_roots[root_num] &= ~4;
X		    selected_root_cnt++;
X		    selected_count += count;
X		}
X		last_root = root_num;
X	    } else if( do_unselect ) {
X		selected_roots[root_num] &= ~mask;
X	    } else if( selected_roots[root_num] & mask ) {
X		selected_roots[root_num] &= ~4;
X		selected_root_cnt++;
X	    }
X	    if( artp == art_limit ) {
X		break;
X	    }
X	    root_limit = upper_limit( artp, 0 );
X	    count = 0;
X	}
X    }
X    if( do_unselect ) {
X	scan_all_roots = !article_count;
X    }
X    unthreaded = toread[ng] - article_count;
X
X    return last_root+1;
X}
X
X/* Count the unread articles attached to the given root number.
X*/
Xint
Xcount_one_root( root_num )
Xint root_num;
X{
X    int last = (root_num == total.root-1 ? total.article
X					 : p_roots[root_num+1].articles);
X    register int count = 0, i;
X
X    for( i = p_roots[root_num].articles; i < last; i++ ) {
X	if( p_articles[i].subject != -1 && !was_read( p_articles[i].num ) ) {
X	    count++;
X	}
X    }
X    root_article_cnts[root_num] = count;
X
X    return count;
X}
X
X#endif /* USETHREADS */
END_OF_FILE
  if test 22272 -ne `wc -c <'rt-select.c'`; then
    echo shar: \"'rt-select.c'\" unpacked with wrong size!
  fi
  # end of 'rt-select.c'
fi
if test -f 'threads.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'threads.h'\"
else
  echo shar: Extracting \"'threads.h'\" \(1989 characters\)
  sed "s/^X//" >'threads.h' <<'END_OF_FILE'
X/* $Header: threads.h,v 4.3.3.2 90/08/20 16:50:09 davison Trn $
X**
X** $Log:	threads.h,v $
X** Revision 4.3.3.2  90/08/20  16:50:09  davison
X** Padded odd-boundaried arrays.  Upgraded database version #.
X** 
X** Revision 4.3.3.1  90/06/20  22:56:18  davison
X** Initial Trn Release
X** 
X*/
X
X#define DB_VERSION	2
X
Xtypedef char		BYTE;
Xtypedef short		WORD;
Xtypedef long		LONG;
X
X#define ROOT_ARTICLE	0x0001		/* article flag definitions */
X#define NEW_ARTICLE	0x0002		/* to avoid stat'ing new articles */
X
Xtypedef struct Article {
X    ART_NUM num;
X    char *id;
X    struct Domain *domain;
X    struct Subject *subject;
X    struct Author *author;
X    struct Article *parent, *children, *siblings;
X    struct Root *root;
X    struct Article *id_link;
X    time_t date;
X    WORD child_cnt;
X    WORD flags;
X    WORD seq;
X} ARTICLE;
X
Xtypedef struct Domain {
X    char *name;
X    ARTICLE *ids;
X    struct Domain *link;
X} DOMAIN;
X
Xtypedef struct Author {
X    struct Author *link;		/* this link MUST be first */
X    char *name;
X    WORD seq;
X    WORD count;
X} AUTHOR;
X
Xtypedef struct Subject {
X    struct Subject *link;
X    char *str;
X    WORD seq;
X    WORD count;
X} SUBJECT;
X
Xtypedef struct Root {
X    struct Root *link;			/* this link MUST be first */
X    ARTICLE *articles;
X    SUBJECT *subjects;
X    ART_NUM root_num;
X    WORD thread_cnt;
X    WORD subject_cnt;
X    WORD seq;
X} ROOT;
X
Xtypedef struct {
X    LONG root_num;
X    WORD articles;
X    WORD thread_cnt;
X    WORD subject_cnt;
X    WORD pad_hack;
X} PACKED_ROOT;
X
Xtypedef struct {
X    LONG num;
X    LONG date;
X    WORD subject, author;
X    WORD flags;
X    WORD child_cnt;
X    WORD parent, children, siblings;
X    WORD root;
X} PACKED_ARTICLE;
X
Xtypedef struct Total {
X    LONG first, last;
X    LONG string1;
X    LONG string2;
X    WORD root;
X    WORD article;
X    WORD subject;
X    WORD author;
X    WORD domain;
X    WORD pad_hack;
X} TOTAL;
X
Xtypedef struct {
X    BYTE l[sizeof (LONG)];
X    BYTE w[sizeof (WORD)];
X    BYTE version;
X    BYTE pad_hack;
X} BMAP;
END_OF_FILE
  if test 1989 -ne `wc -c <'threads.h'`; then
    echo shar: \"'threads.h'\" unpacked with wrong size!
  fi
  # end of 'threads.h'
fi
echo shar: End of archive 4 \(of 14\).
cp /dev/null ark4isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 14 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0
exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.



More information about the Comp.sources.unix mailing list