v23i064: Complete reposting of TRN at patchlevel 1, Part05/14

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


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

#! /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:  mbox.saver.SH mthreads.c trn.1.02
# Wrapped by rsalz at litchi.bbn.com on Thu Dec 27 11:34:04 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 5 (of 14)."'
if test -f 'mbox.saver.SH' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'mbox.saver.SH'\"
else
  echo shar: Extracting \"'mbox.saver.SH'\" \(1720 characters\)
  sed "s/^X//" >'mbox.saver.SH' <<'END_OF_FILE'
Xcase $CONFIG in
X    '') . ./config.sh ;;
Xesac
Xecho "Extracting mbox.saver (with variable substitutions)"
X$spitshell >mbox.saver <<!GROK!THIS!
X$startsh
X# $Header: mbox.saver.SH,v 4.3.3.1 90/08/20 16:30:28 davison Trn $
X# 
X# $Log:	mbox.saver.SH,v $
X# Revision 4.3.3.1  90/08/20  16:30:28  davison
X# Added support for MMDF format mailboxes.
X# 
X# Revision 4.3.2.2  90/03/17  20:44:54  sob
X# Modify Article header to place the colon after Article.
X# 
X# Revision 4.3.2.1  89/11/28  00:05:47  sob
X# Branch for RN/RRN combo patches
X# 
X# Revision 4.3.1.2  85/05/20  15:55:37  lwall
X# Turned $5 into \$5.
X# 
X# Revision 4.3.1.1  85/05/10  11:35:30  lwall
X# Branch for patches.
X# 
X# Revision 4.3  85/05/01  11:42:51  lwall
X# Baseline for release with 4.3bsd.
X# 
X# 
X#	Arguments:
X#	1 Full name of article (%A)
X#	2 Public news spool directory (%P)
X#	3 Directory of current newsgroup (%c)
X#	4 Article number (%a)
X#	5 Where in article to start (%B)
X#	6 Newsgroup name (%C)
X#	7 Save destination (%b)
X#	8 First line of message, normally From...
X#
Xexport PATH || (echo "OOPS, this isn't sh.  Desperation time.  I will feed myself to sh."; sh \$0; kill \$\$)
X
X!GROK!THIS!
Xcase $mboxchar in
X"$CTRLA")
X    $spitshell >>mbox.saver <<!GROK!THIS!
X( $echo "$CTRLA$CTRLA$CTRLA$CTRLA"
X  if $test "\$5" = 0 -a ! "\$4" = 0 ; then
X    $echo "Article: \$4 of \$6"
X  fi
X  $tail +\$5c \$1
X  $echo ""
X  $echo ""
X  $echo "$CTRLA$CTRLA$CTRLA$CTRLA" ) >> \$7
X!GROK!THIS!
X    ;;
X*)
X    $spitshell >>mbox.saver <<!GROK!THIS!
X( $echo "\$8"
X  if $test "\$5" = 0 -a ! "\$4" = 0 ; then
X    $echo "Article: \$4 of \$6"
X  fi
X  $tail +\$5c \$1 | $sed "s/^From />From /"
X  $echo ""
X  $echo "" ) >> \$7
X!GROK!THIS!
X    ;;
Xesac
X$eunicefix mbox.saver
Xchmod 755 mbox.saver
END_OF_FILE
  if test 1720 -ne `wc -c <'mbox.saver.SH'`; then
    echo shar: \"'mbox.saver.SH'\" unpacked with wrong size!
  fi
  chmod +x 'mbox.saver.SH'
  # end of 'mbox.saver.SH'
fi
if test -f 'mthreads.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'mthreads.c'\"
else
  echo shar: Extracting \"'mthreads.c'\" \(28049 characters\)
  sed "s/^X//" >'mthreads.c' <<'END_OF_FILE'
X/* $Header: mthreads.c,v 4.3.3.2 90/08/20 16:43:19 davison Trn $
X**
X** $Log:	mthreads.c,v $
X** Revision 4.3.3.2  90/08/20  16:43:19  davison
X** Implemented new command-line interface and database upgrading.
X** 
X** Revision 4.3.3.1  90/07/24  22:24:17  davison
X** Initial Trn Release
X** 
X*/
X
X/* mthreads.c -- for making and updating a discussion-thread database
X**
X** We use the active file as our high/low counts for each group, and create
X** an active2 file of our own to keep track of the high/lows of the database.
X** When fully updated, the two files should be identical.  This gives us
X** quick access to the last processed high/low counts without opening
X** each data file, PLUS it allows trn to use the fake active file as if it
X** were the real thing to keep it from seeing articles before they are
X** processed.  If the active2 file is removed or corrupted, it will be
X** automatically repaired in the normal course of operation.  We update
X** the file IN PLACE so that trn can keep it open all the time.  Normally
X** the size of the file does not change, so it is easy to do.  In those
X** rare instances where a news admin shuffles the real active file, we
X** take it all in stride by throwing a little memory at the problem.
X**
X** Usage:  mthreads [-d[MM]] [-e[HHMM]] [-aDfknv] [hierarchy_list]
X*/
X
X#include "EXTERN.h"
X#include "common.h"
X#ifdef SERVER
X#include "server.h"
X#endif
X#include "INTERN.h"
X#include "mthreads.h"
X
X#ifdef TZSET
X#include <time.h>
X#else
X#include <sys/time.h>
X#include <sys/timeb.h>
X#endif
X
XFILE *fp_lock, *fp_log;
X
Xstruct stat filestat;
X
Xstatic char line[256];
Xstatic char line2[256];
X
X/* If you want to change the field size, do it once and then leave it alone. */
Xchar fmt_active2[] = "%s %06ld %06ld %c\n";
X
Xchar *filename;
X
Xtypedef struct _active_line {
X    struct _active_line *link;
X    char *name;
X    long last;
X    long first;
X    char type;
X} ACTIVE_LINE;
X
X#define Nullact Null(ACTIVE_LINE*)
X
XACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
X
Xbool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
Xbool add_new = FALSE, rebuild = FALSE, grevious_error;
Xint daemon_delay = 0, log_verbosity = 0, debug = 0;
Xlong expire_time = 0;
Xchar *hierarchy_list = NULL;
Xlong truncate_len = -1;
X
Xchar nullstr[] = "";
X
XBMAP my_bmap, mt_bmap;
X
X#ifdef TZSET
Xtime_t tnow;
X#else
Xstruct timeb ftnow;
X#endif
X
X#define TIMER_FIRST 1
X#define TIMER_DEFAULT (10 * 60)
X
Xint added_groups, removed_groups, action;
X
X#define NG_DEFAULT	0
X#define NG_MATCH	1
X#define NG_SKIP		2
X
X#ifdef SERVER
Xchar *server;
X#else
Xtime_t last_modified;
X#endif
X
Xchar *thread_name(), *file_exp();
Xvoid makethreads(), interrupt(), alarm_handler(), wrap_it_up();
X
Xmain( argc, argv )
Xint  argc;
Xchar *argv[];
X{
X    int fd;
X    long pid;
X
X    while( --argc ) {
X	if( **++argv == '-' ) {
X	    while( *++*argv ) {
X		switch( **argv ) {
X		case 'a':		/* automatically thread new groups */
X		    add_new = TRUE;
X		    break;
X		case 'D':
X		    debug++;
X		    break;
X		case 'd':
X		    if( *++*argv <= '9' && **argv >= '0' ) {
X			daemon_delay = atoi( *argv ) * 60;
X			while( *++*argv <= '9' && **argv >= '0' ) {
X			    ;
X			}
X		    } else {
X			daemon_delay = TIMER_DEFAULT;
X		    }
X		    --*argv;
X		    break;
X		case 'e': {
X		    struct tm *ts;
X		    long desired;
X
X		    (void) time( &expire_time );
X		    ts = localtime( &expire_time );
X
X		    if( *++*argv <= '9' && **argv >= '0' ) {
X			desired = atol( *argv );
X			if( desired/100 > 23 || desired%100 > 59 ) {
X			    fprintf( stderr, "Illegal expire time: '%04d'\n",
X				desired );
X			    exit( 1 );
X			}
X			desired = (desired/100)*60 + desired%100;
X			while( *++*argv <= '9' && **argv >= '0' ) {
X			    ;
X			}
X		    } else {
X			desired = 30;			/* 0030 = 12:30am */
X		    }
X		    --*argv;
X		    desired -= ts->tm_hour * 60 + ts->tm_min;
X		    if( desired < 0 ) {
X			desired += 24 * 60;
X		    }
X		    expire_time += desired * 60 - ts->tm_sec;
X		    break;
X		 }
X		case 'f':
X		    force_flag = TRUE;
X		    break;
X		case 'k':
X		    kill_mthreads = TRUE;
X		    break;
X		case 'n':
X		    no_processing = TRUE;
X		    break;
X		case 'v':
X		    log_verbosity++;
X		    break;
X		default:
X		    fprintf( stderr, "Unknown option: '%c'\n", **argv );
X		    exit( 1 );
X		}
X	    }
X	} else {
X	    if( hierarchy_list ) {
X		fprintf( stderr, "Specify the newsgroups in one comma-separated list.\n" );
X		exit( 1 );
X	    }
X	    hierarchy_list = *argv;
X	}
X    }
X
X    /* Set up a nice friendly umask. */
X    umask( 002 );
X
X    /* What time is it? */
X#ifdef TZSET
X    (void) time( &tnow );
X    (void) tzset();
X#else
X    (void) ftime( &ftnow );
X#endif
X
X    /* Make sure we're not already running by creating a lock file.
X    ** (I snagged this method from C news.)
X    */
X    sprintf( line, "%s.%d", file_exp( "%X/LOCK" ), getpid() );
X    if( (fp_lock = fopen( line, "w" )) == Nullfp ) {
X	fprintf( stderr, "Unable to create lock temporary `%s'.\n", line );
X	exit( 1 );
X    }
X    fprintf( fp_lock, "%d\n", getpid() );
X    fclose( fp_lock );
X
X    /* Try to link to lock file. */
X    filename = file_exp( "%X/LOCKmthreads" );
X  dolink:
X    if( link( line, filename ) < 0 ) {
X      long otherpid;
X	/* Try to avoid possible race with daemon starting up. */
X	sleep (5);
X	if( (fp_lock = fopen( filename, "r")) == Nullfp ) {
X	    fprintf( stderr, "unable to open %s\n", filename );
X	    unlink( line );
X	    exit( 1 );
X	}
X	if( fscanf( fp_lock, "%ld", &otherpid ) != 1) { 
X	    fprintf( stderr, "unable to read pid from %s\n", filename );
X	    unlink( line );
X	    fclose( fp_lock );
X	    exit( 1 );
X	}
X	fclose( fp_lock );
X	if( kill( otherpid, kill_mthreads ? SIGTERM : 0 ) == -1 ) {
X	    if( unlink( filename ) == -1 ) {
X		fprintf( stderr, "unable to unlink lockfile %s\n", filename );
X		unlink( line );
X		exit( 1 );
X	    }
X	    if( !kill_mthreads ) {
X		goto dolink;
X	    }
X	}
X	unlink( line );
X	if( kill_mthreads ) {
X	    fprintf( stderr, "killing currently running mthreads.\n" );
X	    exit( 0 );
X	} else {
X	    fprintf( stderr, "mthreads is already running.\n" );
X	    exit( 1 );
X	}
X    }
X
X    unlink( line );			/* remove temporary LOCK.<pid> file */
X
X    if( kill_mthreads ) {
X	fprintf( stderr, "mthreads is not running.\n" );
X	exit( 1 );
X    }
X
X    /* Open our log file */
X    filename = file_exp( "%X/mt.log" );
X    if( (fp_log = fopen( filename, "a" )) == Nullfp ) {
X	fprintf( stderr, "Unable to open `%s'.\n", filename );
X	exit( 1 );
X    }
X
X    if( sigset( SIGHUP, SIG_IGN ) != SIG_IGN ) {
X	sigset( SIGHUP, interrupt );
X    }
X    if( sigset( SIGINT, SIG_IGN ) != SIG_IGN ) {
X	sigset( SIGINT, interrupt );
X    }
X    if( sigset( SIGQUIT, SIG_IGN ) != SIG_IGN ) {
X	sigset( SIGQUIT, interrupt );
X    }
X    sigset( SIGTERM, interrupt );
X    sigset( SIGBUS, interrupt );
X    sigset( SIGSEGV, interrupt );
X#ifdef SIGTTIN
X    sigset( SIGTTIN, SIG_IGN );
X    sigset( SIGTTOU, SIG_IGN );
X#endif
X    sigset( SIGALRM, SIG_IGN );
X#ifdef lint
X    alarm_handler();			/* foolishness for lint's sake */
X    interrupt( 14 );
X#endif
X
X    /* Ensure this machine has the right byte-order for the database */
X    filename = file_exp( "%X/db.init" );
X    if( (fp_lock = fopen( filename, "r")) == Nullfp
X     || fread( &mt_bmap, 1, sizeof (BMAP), fp_lock ) < sizeof (BMAP)-1 ) {
X	if( fp_lock != Nullfp ) {
X	    fclose( fp_lock );
X	}
X     write_db_init:
X	mybytemap( &mt_bmap );
X	if( (fp_lock = fopen( filename, "w" )) == Nullfp ) {
X	    log_entry( "Unable to create file: `%s'.\n", filename );
X	    exit( 1 );
X	}
X	mt_bmap.version = DB_VERSION;
X	fwrite( &mt_bmap, 1, sizeof (BMAP), fp_lock );
X	fclose( fp_lock );
X    } else {
X	int i;
X
X	fclose( fp_lock );
X	if( mt_bmap.version != DB_VERSION ) {
X	    if( mt_bmap.version == DB_VERSION-1 ) {
X		rebuild = TRUE;
X		log_entry( "Upgrading database to version %d.\n", DB_VERSION );
X		goto write_db_init;
X	    }
X	    log_entry( "** Database is not the right version (%d instead of %d) **\n",
X		mt_bmap.version, DB_VERSION );
X	    exit( 1 );
X	}
X	mybytemap( &my_bmap );
X	for( i = 0; i < sizeof (LONG); i++ ) {
X	    if( my_bmap.l[i] != mt_bmap.l[i]
X	     || (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i]) ) {
X		log_entry( "\
X** Byte-order conflict -- re-run from a compatible machine **\n\
X\t\tor remove the current thread files, including db.init **\n" );
X		exit( 1 );
X	    }
X	}
X    }
X
X#ifdef SERVER
X    server = getserverbyfile( SERVER_FILE );
X    if( server == NULL ) {
X	log_entry( "Couldn't find name of news server.\n" );
X	exit( 1 );
X    }
X#endif
X
X    /* If we're not in daemon mode, run through once and quit. */
X    if( !daemon_delay ) {
X	log_entry( "mthreads single pass started.\n" );
X	setbuf( stdout, Nullch );
X	extra_expire = (expire_time != 0);
X	makethreads();
X    } else {
X	/* For daemon mode, we cut ourself off from anything tty-related and
X	** run in the background (involves forks, but no knives).
X	*/
X	close( 0 );
X	if( open( "/dev/null", 2 ) != 0 ) {
X	    fprintf( stderr, "unable to open /dev/null!\n" );
X	    exit( 1 );
X	}
X	close( 1 );
X	close( 2 );
X	dup( 0 );
X	dup( 0 );
X	while( (pid = fork()) < 0 ) {
X	    sleep( 2 );
X	}
X	if( pid ) {
X	    exit( 0 );
X	}
X#ifdef TIOCNOTTY
X	if( (fd = open( "/dev/tty", 1 )) >= 0 ) {
X	    ioctl( fd, TIOCNOTTY, (int*)0 );
X	    close( fd );
X	}
X#else
X	(void) setpgrp();
X	while( (pid = fork()) < 0 ) {
X	    sleep( 2 );
X	}
X	if( pid ) {
X	    exit( 0 );
X	}
X#endif
X	/* Put our pid in the lock file for death detection */
X	if( (fp_lock = fopen( file_exp( "%X/LOCKmthreads" ), "w" )) != Nullfp ) {
X	    fprintf( fp_lock, "%d\n", getpid() );
X	    fclose( fp_lock );
X	}
X
X	log_entry( "mthreads daemon started.\n" );
X
X#ifndef SERVER
X	last_modified = 0;
X#endif
X	sigset( SIGALRM, alarm_handler );
X
X	/* Start timer -- first interval is shorter than all others */
X	alarm( TIMER_FIRST );
X	for( ;; ) {
X	    if( caught_interrupt ) {
X		wrap_it_up();
X		/* NORETURN */
X	    }
X	    pause();		/* let alarm go off */
X	    if( caught_interrupt ) {
X		wrap_it_up();
X		/* NORETURN */
X	    }
X	    alarm( 0 );
X
X	    /* Re-open our log file, if needed */
X	    if( !fp_log && !(fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
X		exit( 1 );
X	    }
X#ifndef SERVER
X	    if( stat( file_exp( ACTIVE ), &filestat ) < 0 ) {
X		log_entry( "Unable to stat active file -- quitting.\n" );
X		exit( 1 );
X	    }
X#endif
X	    if( expire_time && time( 0L ) > expire_time ) {
X		expire_time += 24L * 60 * 60;
X		extra_expire = TRUE;
X	    }
X#ifdef SERVER
X	    if( 1 ) {		/* always compare files */
X#else
X	    if( extra_expire || filestat.st_mtime != last_modified ) {
X		last_modified = filestat.st_mtime;
X#endif
X		makethreads();
X	    }
X	    alarm( daemon_delay );
X	    fclose( fp_log );	/* close the log file while we sleep */
X	    fp_log = Nullfp;
X	} /* for */
X    }/* if */
X
X    wrap_it_up();
X}
X
Xvoid
Xalarm_handler()
X{
X    sigset( SIGALRM, alarm_handler );
X}
X
Xvoid
Xinterrupt( sig )
Xint sig;
X{
X    /* Re-open our log file, if needed */
X    if( fp_log || (fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
X	if( sig == SIGTERM ) {
X	    log_entry( "mthreads halted.\n", sig);
X	} else {
X	    log_entry( "** interrupt %d **\n", sig);
X	}
X    }
X    if( !daemon_delay ) {
X	printf( "interrupt %d!\n", sig );
X    }
X    /* Flag interrupt occurred -- main loop attempts an orderly retreat. */
X    caught_interrupt = TRUE;
X}
X
Xvoid
Xwrap_it_up()
X{
X    unlink( file_exp( "%X/LOCKmthreads" ) );		/* remove lock */
X
X    exit( 0 );
X}
X
X/* Process the active file, creating/modifying the active2 file and
X** creating/modifying the thread data files.
X*/
Xvoid
Xmakethreads()
X{
X    register char *cp, *cp2;
X    FILE *fp_active, *fp_active2, *fp_active3;
X    long first, last, first2, last2;
X    char ch, ch2;
X    char data_file_open;
X    bool update_successful;
X    bool eof_active = FALSE, eof_active2 = FALSE;
X
X#ifdef SERVER
X    switch( server_init( server ) ) {
X    case OK_NOPOST:
X    case OK_CANPOST:
X	break;
X    case ERR_ACCESS:
X	log_entry( "Server %s rejected connection -- quitting.\n", server );
X	exit( 1 );
X    default:
X	log_entry( "Couldn't connect with server %s -- sleeping.\n", server );
X	return;
X    }
X    put_server( "LIST" );	/* ask server for the active file */
X    get_server( line, sizeof line );
X    if( *line != CHAR_OK ) {
X	log_entry( "Unable to get active file from server -- sleeping.\n" );
X	close_server();
X	return;
X    }
X    if( (fp_active = fopen( file_exp( ACTIVE1 ), "w+" )) == Nullfp ) {
X	log_entry( "Unable to write the active1 file.\n" );
X	exit( 1 );
X    }
X    while( 1 ) {
X	if( caught_interrupt ) {
X	    wrap_it_up();
X	    /* NORETURN */
X	}
X	if( get_server( line, sizeof line ) < 0 ) {
X	    log_entry( "Server failed to send entire active file -- sleeping.\n" );
X	    fclose( fp_active );
X	    close_server();
X	    return;
X	}
X	if( *line == '.' ) {
X	    break;
X	}
X	fputs( line, fp_active );
X	putc( '\n', fp_active );
X    }
X    fseek( fp_active, 0L, 0 );		/* rewind for read */
X#else
X    if( (fp_active = fopen( file_exp( ACTIVE ), "r" )) == Nullfp ) {
X	log_entry( "Unable to open the active file.\n" );
X	exit( 1 );
X    }
X#endif
X    filename = file_exp( ACTIVE2 );
X    if( (fp_active3 = fopen( filename, "r+" )) == Nullfp ) {
X	if( (fp_active3 = fopen( filename, "w" )) == Nullfp ) {
X	    log_entry( "Unable to open the active2 file for update.\n" );
X	    exit( 1 );
X	}
X    }
X    if( (fp_active2 = fopen( filename, "r" )) == Nullfp ) {
X	log_entry( "Unable to open the active2 file.\n" );
X	exit( 1 );
X    }
X    if( caught_interrupt ) {
X	wrap_it_up();
X	/* NORETURN */
X    }
X    if( extra_expire && log_verbosity ) {
X	log_entry( "Using enhanced expiration for this pass.\n" );
X    }
X
X    processed_groups = added_groups = removed_groups = 0;
X    added_articles = expired_articles = 0;
X
X    /* Loop through entire active file. */
X    for( ;; ) {
X	if( eof_active || !fgets( line, sizeof line, fp_active ) ) {
X	    if( eof_active2 && !line_root ) {
X		break;
X	    }
X	    eof_active = TRUE;
X	    ch = 'x';
X	} else {
X	    if( !(cp = index( line, ' ' )) ) {
X		log_entry( "active line has no space: %s\n", line );
X		continue;
X	    }
X	    *cp = '\0';
X	    if( sscanf( cp+1, "%ld %ld %c", &last, &first, &ch ) != 3 ) {
X		log_entry( "active digits corrupted: %s %s\n", line, cp+1 );
X		continue;
X	    }
X	}
X	data_file_open = 0;
X	/* If we've allocated some lines in memory while searching for
X	** newsgroups (they've scrambled the active file on us), check
X	** them first.
X	*/
X	last_line = Nullact;
X	for( pline = line_root; pline; pline = pline->link ) {
X	    if( eof_active || strEQ( line, pline->name ) ) {
X		strcpy( line2, pline->name );
X		free( pline->name );
X		first2 = pline->first;
X		last2 = pline->last;
X		ch2 = pline->type;
X		if( last_line ) {
X		    last_line->link = pline->link;
X		} else {
X		    line_root = pline->link;
X		}
X		free( pline );
X		break;
X	    }
X	    last_line = pline;
X	}/* for */
X	/* If not found yet, check the active2 file. */
X	if( !pline ) {
X	    for( ;; ) {
X		if( eof_active2 || !fgets( line2, sizeof line2, fp_active2 ) ) {
X		    /* At end of file, check if the thread data file exists.
X		    ** If so, use its high/low values.  Else, default to
X		    ** some initial values.
X		    */
X		    eof_active2 = TRUE;
X		    if( eof_active ) {
X			break;
X		    }
X		    strcpy( line2, line );
X		    if( (data_file_open = init_data( thread_name( line ) )) ) {
X			last2 = total.last;
X			first2 = total.first;
X			ch2 = 'y';
X		    } else {
X			total.last = last2 = last;
X			total.first = first2 = first;
X			if( add_new ) {
X			    ch2 = (ch == '=' ? 'x' : ch);
X			    added_groups++;
X			} else {
X			    ch2 = (ch == '=' ? 'X' : toupper( ch ));
X			}
X		    }
X		    data_file_open++;		/* (1 == empty, 2 == open) */
X		    break;
X		}
X		if( !(cp2 = index( line2, ' ' )) ) {
X		    log_entry( "active2 line has no space: %s\n", line2 );
X		    continue;
X		}
X		*cp2 = '\0';
X		if( sscanf( cp2+1,"%ld %ld %c",&last2,&first2,&ch2 ) != 3 ) {
X		    log_entry( "active2 digits corrupted: %s %s\n",
X			line2, cp2+1 );
X		    continue;
X		}
X		/* Check if we're still in-sync */
X		if( eof_active || strEQ( line, line2 ) ) {
X		    break;
X		}
X		/* Nope, we've got to go looking for this line somewhere
X		** down in the file.  Save each non-matching line in memory
X		** as we go.
X		*/
X		pline = (ACTIVE_LINE*)safemalloc( sizeof (ACTIVE_LINE) );
X		pline->name = savestr( line2 );
X		pline->last = last2;
X		pline->first = first2;
X		pline->type = ch2;
X		pline->link = Nullact;
X		if( !last_line ) {
X		    line_root = pline;
X		} else {
X		    last_line->link = pline;
X		}
X		last_line = pline;
X	    }/* for */
X	    if( eof_active && eof_active2 ) {
X		break;
X	    }
X	}/* if !pline */
X	if( eof_active ) {
X	    strcpy( line, line2 );
X	    if( truncate_len < 0 ) {
X		truncate_len = ftell( fp_active3 );
X	    }
X	}
X	if( rebuild ) {
X	    unlink( thread_name( line ) );
X	}
X	update_successful = FALSE;
X	if( hierarchy_list ) {
X	    action = ngmatch( hierarchy_list, line );
X	} else {
X	    action = NG_DEFAULT;
X	}
X	switch( action ) {
X	case NG_DEFAULT:
X	    if( ch2 < 'a' ) {
X		action = NG_SKIP;
X	    } else {
X		action = NG_MATCH;
X	    }
X	    break;
X	case NG_MATCH:				/* add if unthreaded */
X	    if( ch2 < 'a' ) {
X		last2 = first2 - 1;
X		added_groups++;
X	    }
X	    break;
X	case NG_SKIP:				/* remove if threaded */
X	    if( ch2 >= 'a' && !debug ) {
X		unlink( thread_name( line ) );
X		removed_groups++;
X	    }
X	    break;
X	}
X	if( caught_interrupt || (debug && action != NG_MATCH) ) {
X	    dont_read_data( data_file_open );	/* skip silently */
X	} else if( ch == 'x' || ch == '=' ) {
X	    if( !daemon_delay ) {		/* skip 'x'ed groups */
X		putchar( 'x' );
X	    }
X	    ch = (action == NG_SKIP ? 'X' : 'x');
X	    if( (ch2 >= 'a' && ch2 != 'x') || force_flag ) {
X		/* Remove thread file if group is newly 'x'ed out */
X		unlink( thread_name( line ) );
X	    }
X	    update_successful = TRUE;
X	    dont_read_data( data_file_open );
X	} else if( action == NG_SKIP ) {	/* skip excluded groups */
X	    if( !daemon_delay ) {
X		putchar( 'X' );
X	    }
X	    ch = toupper( ch );
X	    if( force_flag ) {
X		unlink( thread_name( line ) );
X	    }
X	    update_successful = TRUE;
X	    dont_read_data( data_file_open );
X	} else if( no_processing ) {
X	    if( !daemon_delay ) {
X		putchar( ',' );
X	    }
X	    ch2 = ch;
X	    dont_read_data( data_file_open );
X	} else if( !force_flag && !extra_expire && !rebuild
X	 && first == first2 && last == last2 ) {
X	    /* We're up-to-date here.  Skip it. */
X	    if( !daemon_delay ) {
X		putchar( '.' );
X	    }
X	    update_successful = TRUE;
X	    dont_read_data( data_file_open );
X	} else {
X	    /* Looks like we need to process something. */
X#ifdef SERVER
X	    sprintf( line2, "GROUP %s", line );
X	    put_server( line2 );		/* go to next group */
X	    if( get_server( line2, sizeof line2 ) < 0 || *line2 != CHAR_OK ) {
X		log_entry( "NNTP failure on group `%s'.\n", line );
X#else
X	    cp = line2;
X	    while( (cp = index( cp, '.' )) ) {
X		*cp = '/';
X	    }
X	    filename = file_exp( line2 );	/* relative to spool dir */
X	    if( chdir( filename ) < 0 ) {
X		if (errno != ENOENT) {
X		    log_entry( "Unable to chdir to `%s'.\n", filename );
X		} else {
X		    update_successful = TRUE;
X		}
X#endif
X		if( !daemon_delay ) {
X		    putchar( '*' );
X		}
X		dont_read_data( data_file_open );
X	    } else {
X		filename = thread_name( line );
X		/* Try to open the data file only if we didn't try it
X		** in the name matching code above.
X		*/
X		if( !data_file_open-- ) {	/* (0 == haven't tried yet) */
X		    if( !(data_file_open = init_data( filename )) ) {
X			total.last = first - 1;
X			total.first = first;
X		    }
X		}
X		strcpy( line2, filename );
X		cp = rindex( line2, '/' ) + 1;
X
X		if( data_file_open ) {		/* (0 == empty, 1 == open) */
X		    if( !read_data() ) {	/* did read fail? */
X#ifndef DEBUG
X			unlink( filename );	/* trash input file */
X#else
X			strcpy( cp, "bad.read" );
X			rename( filename, line2 );
X#endif
X			data_file_open = init_data( filename );
X			total.last = first - 1;
X			total.first = first;
X		    }
X		}
X		grevious_error = FALSE;
X		process_articles( first, last );
X		processed_groups++;
X		if( caught_interrupt ) {
X		    processed_groups--;	/* save nothing -- no update */
X		} else if( !added_count && !expired_count && last == last2 ) {
X		    (void) write_data( Nullch );
X		    if( !daemon_delay ) {
X			putchar( ':' );
X		    }
X		    update_successful = TRUE;
X		} else if( !total.root ) {
X		    /* When the data file goes empty, remove it. */
X		    unlink( filename );
X		    expired_articles += expired_count;
X		    if( !daemon_delay ) {
X			putchar( '-' );
X		    }
X		    update_successful = TRUE;
X		} else {
X		    strcpy( cp, ".new" );	/* write data as .new */
X		    if( write_data( line2 ) && !grevious_error ) {
X			rename( line2, filename );
X			added_articles += added_count;
X			expired_articles += expired_count;
X			if( !daemon_delay ) {
X			    putchar( '#' );
X			}
X			update_successful = TRUE;
X		    } else {
X#ifndef DEBUG
X			unlink( line2 );	/* blow-away bad write */
X#else
X			cp = rindex( filename, '/' ) + 1;
X			strcpy( cp, "bad.write" );
X			rename( line2, filename );
X#endif
X			if( !daemon_delay ) {
X			    putchar( '!' );
X			}
X		    }/* if */
X		}/* if */
X	    }/* if */
X	}/* if */
X	/* Finally, update the active2 entry for this newsgroup. */
X	if( update_successful ) {
X	    fprintf( fp_active3, fmt_active2, line, last, first, ch );
X	} else {
X	    fprintf( fp_active3, fmt_active2, line, last2, first2, ch2 );
X	}
X    }/* for */
X
X#ifdef SERVER
X    close_server();
X#endif
X    fclose( fp_active );
X    fclose( fp_active2 );
X    fclose( fp_active3 );
X
X    if( truncate_len >= 0 ) {
X#ifdef TRUNCATE
X	if( truncate( file_exp( ACTIVE2 ), truncate_len ) == -1 )
X	    log_entry( "Unable to truncate the active2 file.\n" );
X#else
X#ifdef CHSIZE
X	int fd;
X	if( (fd = open( file_exp( ACTIVE2 ), O_RDWR )) == -1 )
X	    log_entry( "Unable to open the active2 file for truncation.\n" );
X	else {
X	    if( chsize( fd, truncate_len ) == -1 )
X		log_entry( "Unable to truncate the active2 file.\n" );
X	    close( fd );
X	}
X#else
X	filename = file_exp( ACTIVE2 );
X	sprintf( line, "%s.new", filename );
X	if( (fp_active3 = fopen( line, "w" )) == Nullfp ) {
X	    log_entry( "Unable to create the active2.new file.\n" );
X	} else if( (fp_active2 = fopen( filename, "r" )) == Nullfp ) {
X	    fclose( fp_active3 );
X	    unlink( line );
X	    log_entry( "Unable to open the active2 file.\n" );
X	} else {
X	    while( ftell( fp_active3 ) < truncate_len ) {
X		if( !fgets( line2, sizeof line2, fp_active2 ) ) {
X		    break;
X		}
X		fputs( line2, fp_active3 );
X	    }
X	    sprintf( line2, "%s.old", filename );
X	    rename( filename, line2 );
X	    rename( line, filename );
X	    fclose( fp_active2 );
X	    fclose( fp_active3 );
X	}
X#endif /* not XENIX */
X#endif /* not TRUNCATE */
X    }
X
X    sprintf( line, "Processed %d group%s:  added %d article%s, expired %d.\n",
X	processed_groups, processed_groups == 1 ? nullstr : "s",
X	added_articles, added_articles == 1 ? nullstr : "s",
X	expired_articles );
X
X    if( processed_groups ) {
X	log_entry( line );
X    }
X
X    if( !daemon_delay ) {
X	putchar( '\n' );
X	fputs( line, stdout );
X    }
X    if( added_groups ) {
X	sprintf( line, "Turned %d group%s on.\n", added_groups,
X		added_groups == 1 ? nullstr : "s" );
X	log_entry( line );
X	if( !daemon_delay ) {
X	    fputs( line, stdout );
X	}
X    }
X    if( removed_groups ) {
X	sprintf( line, "Turned %d group%s off.\n", removed_groups,
X		removed_groups == 1 ? nullstr : "s" );
X	log_entry( line );
X	if( !daemon_delay ) {
X	    fputs( line, stdout );
X	}
X    }
X    extra_expire = FALSE;
X    rebuild = FALSE;
X}
X
X/*
X** ngmatch - newsgroup name matching
X**
X** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
X** and NG_DEFAULT if the group doesn't match at all.
X**
X** "all" in a pattern is a wildcard that matches exactly one word;
X** it does not cross "." (NGDELIM) delimiters.
X**
X** This matching code was borrowed from C news.
X*/
X
X#define ALL "all"			/* word wildcard */
X
X#define NGNEG '!'
X#define NGSEP ','
X#define NGDELIM '.'
X
Xint
Xngmatch( ngpat, grp )
Xchar *ngpat, *grp;
X{
X    register char *patp;		/* point at current pattern */
X    register char *patcomma;
X    register int depth;
X    register int faildeepest = 0, hitdeepest = 0;	/* in case no match */
X    register bool negation;
X
X    for( patp = ngpat; patp != Nullch; patp = patcomma ) {
X	negation = FALSE;
X	patcomma = index( patp, NGSEP );
X	if( patcomma != Nullch ) {
X	    *patcomma = '\0';	/* will be restored below */
X	}
X	if( *patp == NGNEG ) {
X	    ++patp;
X	    negation = TRUE;
X	}
X	depth = onepatmatch( patp, grp ); /* try 1 pattern, 1 group */
X	if( patcomma != Nullch ) {
X	    *patcomma++ = NGSEP;	/* point after the comma */
X	}
X	if( depth == 0 ) {		/* mis-match */
X	    ;				/* ignore it */
X	} else if( negation ) {
X	    /* record depth of deepest negated matched word */
X	    if( depth > faildeepest ) {
X		faildeepest = depth;
X	    }
X	} else {
X	    /* record depth of deepest plain matched word */
X	    if( depth > hitdeepest ) {
X		hitdeepest = depth;
X	    }
X	}
X    }
X    if( hitdeepest > faildeepest ) {
X	return NG_MATCH;
X    } else if( faildeepest ) {
X	return NG_SKIP;
X    } else {
X	return NG_DEFAULT;
X    }
X}
X
X/*
X** Match a pattern against a group by looking at each word of pattern in turn.
X**
X** On a match, return the depth (roughly, ordinal number * k) of the rightmost
X** word that matches.  If group runs out first, the match fails; if pattern
X** runs out first, it succeeds.  On a failure, return zero.
X*/
Xint
Xonepatmatch( patp, grp )
Xchar *patp, *grp;
X{
X    register char *rpatwd;		/* used by word match (inner loop) */
X    register char *patdot, *grdot;	/* point at dots after words */
X    register char *patwd, *grwd;	/* point at current words */
X    register int depth = 0;
X
X    for( patwd = patp, grwd = grp;
X	 patwd != Nullch && grwd != Nullch;
X	 patwd = patdot, grwd = grdot
X    ) {
X	register bool match = FALSE;
X	register int incr = 20;
X
X	/* null-terminate words */
X	patdot = index(patwd, NGDELIM);
X	if( patdot != Nullch ) {
X	    *patdot = '\0';		/* will be restored below */
X	}
X	grdot = index( grwd, NGDELIM );
X	if( grdot != Nullch ) {
X	    *grdot = '\0';		/* will be restored below */
X	}
X	/*
X	 * Match one word of pattern with one word of group.
X	 * A pattern word of "all" matches any group word,
X	 * but isn't worth as much.
X	 */
X#ifdef FAST_STRCMP
X	match = STREQ( patwd, grwd );
X	if( !match && STREQ( patwd, ALL ) ) {
X	    match = TRUE;
X	    --incr;
X	}
X#else
X	for( rpatwd = patwd; *rpatwd == *grwd++; ) {
X	    if( *rpatwd++ == '\0' ) {
X		match = TRUE;		/* literal match */
X		break;
X	    }
X	}
X	if( !match ) {
X	    /* ugly special case match for "all" */
X	    rpatwd = patwd;
X	    if( *rpatwd++ == 'a' && *rpatwd++ == 'l'
X	     && *rpatwd++ == 'l' && *rpatwd   == '\0' ) {
X		match = TRUE;
X		 --incr;
X	    }
X	}
X#endif				/* FAST_STRCMP */
X
X	if( patdot != Nullch ) {
X	    *patdot++ = NGDELIM;	/* point after the dot */
X	}
X	if( grdot != Nullch ) {
X	    *grdot++ = NGDELIM;
X	}
X	if( !match ) {
X	    depth = 0;		/* words differed - mismatch */
X	    break;
X	}
X	depth += incr;
X    }
X    /* if group name ran out before pattern, then match fails */
X    if( grwd == Nullch && patwd != Nullch ) {
X	depth = 0;
X    }
X    return depth;
X}
X
X/* Generate a log entry with timestamp.
X*/
X/*VARARGS1*/
Xvoid
Xlog_entry( fmt, arg1, arg2 )
Xchar *fmt;
Xlong arg1;
Xlong arg2;
X{
X    time_t now;
X
X    (void) time( &now );
X    fprintf( fp_log, "%.12s ", ctime( &now )+4 );
X    fprintf( fp_log, fmt, arg1, arg2 );
X    fflush( fp_log );
X}
X
X/* Generate an log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
X** and newsgroup name.
X*/
X/*VARARGS1*/
Xvoid
Xlog_error( fmt, arg1, arg2, arg3 )
Xchar *fmt;
Xlong arg1;
Xlong arg2;
Xlong arg3;
X{
X    log_entry( "%s: ", line );
X    fprintf( fp_log, fmt, arg1, arg2, arg3 );
X    fflush( fp_log );
X    if( *fmt == '*' ) {
X	grevious_error = TRUE;
X	if( !daemon_delay ) {
X	    putchar( 'E' );
X	}
X    }
X    else {
X	if( !daemon_delay ) {
X	    putchar( 'e' );
X	}
X    }
X}
X
X#ifndef RENAME
Xint
Xrename( old, new )
Xchar	*old, *new;
X{
X    struct stat st;
X
X    if( stat( old, &st ) == -1 ) {
X	return -1;
X    }
X    if( unlink( new ) == -1 && errno != ENOENT ) {
X	return -1;
X    }
X    if( link( old, new ) == -1 ) {
X	return -1;
X    }
X    if( unlink( old ) == -1 ) {
X	int e = errno;
X	(void) unlink( new );
X	errno = e;
X	return -1;
X    }
X    return 0;
X}
X#endif /*RENAME*/
END_OF_FILE
  if test 28049 -ne `wc -c <'mthreads.c'`; then
    echo shar: \"'mthreads.c'\" unpacked with wrong size!
  fi
  # end of 'mthreads.c'
fi
if test -f 'trn.1.02' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'trn.1.02'\"
else
  echo shar: Extracting \"'trn.1.02'\" \(29757 characters\)
  sed "s/^X//" >'trn.1.02' <<'END_OF_FILE'
XOrdinarily rn will eat typeahead to prevent your autorepeating space bar from
Xdoing a very frustrating thing when you accidentally hold it down.
XIf you don't have a repeating space bar, or you are working at low baud
Xrate, you can set this switch to prevent this behavior.
XYou may wish to use the baud-rate switch modifier below to disable typeahead
Xonly at lower baud rates.
X.TP 5
X.B \-v
Xsets verification mode for commands.
XWhen set, the command being executed is displayed to give some feedback that
Xthe key has actually been typed.
XUseful when the system is heavily loaded and you give a command that takes
Xa while to start up.
X.TP 5
X.B \-x<number><list>
XEnable the extended (threaded) features of
X.I trn
Xbeyond the traditional
X.I rn
Xcompatibility mode.
X(This may be the default on your system, use +x if you yearn for the good
Xol' days.)
XThe <number> is the maximum number of article-tree lines (from 0 to 11)
Xyou want displayed in your header.
XUse the <list> to choose which thread selector modes you like (s \- short,
Xm \- medium, or l \- long), and in what order they are selected with the
X\&\*(L'L\*(R' command.
XFor example, use
X.B \-xls
Xto start with the long display mode and only switch between it and
Xthe short mode.
XYou can omit either or both of the parameters, in which case a default of
X.B \-x6lsm
Xis assumed.
X.TP 5
X.B \-X<number><commands>
XIf you like using the thread selector, you'll probably want to use this
Xoption to make the thread selector command (+) the default when a newsgroup
Xis started up with at least <number> unread articles.
X(Your installer may have chosen to make -X1 the default on your system.)
XIt is also used to select which commands you want to be the defaults while
Xusing the thread selector.
XFor example,
X.B \-X2XD
Xwill make the thread selector the default command for entering a newsgroup
Xwith at least 2 unread articles, and set the default command for the LAST
Xpage of the thread selector to be the
X.B X
Xcommand and the default command for all other pages to be the
X.B D
Xcommand.
XEither or both parameters can be omitted, as well as the second default
Xcommand (e.g.
X.B \-XX
Xwould change the default newsgroup entry to use the selector and the default
Xcommand for the last page of the selector to be \*(L'X\*(R').
XThe default is
X.B \-X1Z>
Xif just
X.B \-X
Xis specified.
XTo set the default selector commands without affecting the default entry
Xinto a newsgroup, specify a high number, like 9999.
X.TP 5
X.B \-/
Xsets SAVEDIR to \*(L"%p/%c\*(R" and SAVENAME to \*(L"%a\*(R", which means
Xthat by default articles are saved in a subdirectory of your private news
Xdirectory corresponding to the name of the the current newsgroup, with the
Xfilename being the article number.
X.B +/
Xsets SAVEDIR to \*(L"%p\*(R" and SAVENAME to \*(L"%^C\*(R", which by
Xdefault saves articles directly to your private news directory, with the
Xfilename being the name of the current newsgroup, first letter capitalized.
X(Either
X.B +/
Xor
X.B \-/
Xmay be default on your system, depending on the feelings of your news
Xadministrator when he, she or it installed
X.IR trn .)
XYou may, of course, explicitly set SAVEDIR and SAVENAME to other values\*(--see
Xdiscussion in the environment section.
X.PP
XAny switch may be selectively applied according to the current baud-rate.
XSimply prefix the switch with +speed to apply the switch at that speed or
Xgreater, and \%\-speed to apply the switch at that speed or less.
XExamples: \%\-1200\-hposted suppresses the Posted line at 1200 baud or less;
X\%+9600\-m enables marking at 9600 baud or more.
XYou can apply the modifier recursively to itself also: \%+300\-1200\-t sets
Xterse mode from 300 to 1200 baud.
X.PP
XSimilarly, switches may be selected based on terminal type:
X.Sp
X	\-=vt100+T		set +T on vt100
X.br
X	\-=tvi920\-ETERM=mytvi	get a special termcap entry
X.br
X	\-=tvi920\-ERNMACRO=%./.rnmac.tvi
X.br
X				set up special keymappings
X.br
X	+=paper\-v		set verify mode if not hardcopy
X.PP
XSome switch arguments, such as environment variable values, may require
Xspaces in them.
XSuch spaces should be quoted via ", ', or \e in the conventional fashion,
Xeven when passed via RNINIT or the & command.
X.Sh "Regular Expressions"
XThe patterns used in article searching are regular expressions such as
Xthose used by
X.IR ed (1).
XIn addition, \ew matches an alphanumeric character and \eW a nonalphanumeric.
XWord boundaries may be matched by \eb, and non-boundaries by \eB.
XThe bracketing construct \e(\ ...\ \e) may also be used, and \edigit matches
Xthe digit'th substring, where digit can range from 1 to 9.
X\e0 matches whatever the last bracket match matched.
XUp to 10 alternatives may given in a pattern, separated by \e|, with the
Xcaveat that \e(\ ...\ \e|\ ...\ \e) is illegal.
X.Sh "Interpretation and Interpolation"
XMany of the strings that
X.I trn
Xhandles are subject to interpretations of several types.
XUnder filename expansion, an initial \*(L"~/\*(R" is translated to the name
Xof your home directory, and \*(L"~name\*(R" is translated to the login
Xdirectory for the user specified.
XFilename expansion will also expand an initial environment variable, and
Xalso does the backslash, uparrow and percent expansion mentioned below.
X.PP
XAll interpreted strings go through backslash, uparrow and percent
Xinterpretation.
XThe backslash escapes are the normal ones (such as \en, \et, \ennn, etc.).
XThe uparrow escapes indicate control codes in the normal fashion.
XBackslashes or uparrows to be passed through should be escaped with backslash.
XThe special percent escapes are similar to printf percent escapes.
XThese cause the substitution of various run-time values into the string.
XThe following are currently recognized:
X.Ip %a 8
XCurrent article number.
X.Ip %A 8
XFull name of current article (%P/%c/%a).
X(On a Eunice system with the LINKART option, %P/%c/%a returns the name of
Xthe article in the current newsgroup, while %A returns the real name of
Xthe article, which may be different if the current article was posted to
Xmultiple newsgroups.)
X.Ip %b 8
XDestination of last save command, often a mailbox.
X.Ip %B 8
XThe byte offset to the beginning of the part of the article to be saved,
Xset by the save command.
XThe \*(L's\*(R' and \*(L'S\*(R' commands set it to 0, and the \*(L'w\*(R'
Xand \*(L'W\*(R' commands set it to the byte offset of the body of the article.
X.Ip %c 8
XCurrent newsgroup, directory form.
X.Ip %C 8
XCurrent newsgroup, dot form.
X.Ip %d 8
XFull name of newsgroup directory (%P/%c).
X.Ip %D 8
X\*(L"Distribution:\*(R" line from the current article.
X.Ip %e 8
XThe last command executed to extract data from an article.
X.Ip %E 8
XThe number of extra (unselected) articles, not counting the current article
Xif it is unselected.
X.Ip %f 8
X\*(L"From:\*(R" line from the current article, or the \*(L"Reply-To:\*(R"
Xline if there is one.
XThis differs from %t in that comments (such as the full name) are not
Xstripped out with %f.
X.Ip %F 8
X\*(L"Newsgroups:\*(R" line for a new article, constructed from
X\*(L"Newsgroups:\*(R" and \*(L"Followup-To:\*(R" lines of current article.
X.Ip %h 8
XName of the header file to pass to the mail or news poster,
Xcontaining all the information that the poster program needs in the
Xform of a message header.
XIt may also contain a copy of the current article.
XThe format of the header file is controlled by the MAILHEADER and NEWSHEADER
Xenvironment variables.
X.Ip %H 8
XHost name (your machine's name).
X.Ip %i 8
X\*(L"Message-I.D.:\*(R" line from the current article, with <> guaranteed.
X.Ip %I 8
XThe reference indication mark (see the
X.B \-F
Xswitch.)
X.Ip %l 8
XThe news administrator's login name, if any.
X.Ip %L 8
XLogin name (yours).
X.Ip %m 8
XThe current mode of
X.I trn,
Xfor use in conditional macros.
X.Sp
X.nf
X	i	Initializing.
X	n	Newsgroup selection level.
X	t	Thread selection level.
X	a	Article selection level (What next?).
X	p	Pager level (MORE prompt).
X	u	Set unread? prompt.
X	A	Add this newsgroup?
X	C	Catchup confirmation.
X	D	Delete bogus newsgroups?
X	M	Use mailbox format?
X	R	Resubscribe to this newsgroup?
X.fi
X.Sp
XNote that yes/no questions are all upper-case modes.
XIf, for example, you wanted to disallow defaults on all yes/no questions,
Xyou could define the following macro:
X.Sp
X.nf
X\e040	%(%m=[A-Z]?h: )
X.fi
X.Ip %M 8
XThe number of articles marked to return via the \*(L'M\*(R' command.
XIf the same article is Marked multiple times, \*(L"%M\*(R" counts it
Xmultiple times in the current implementation.
X.Ip %n 8
X\*(L"Newsgroups:\*(R" line from the current article.
X.Ip %N 8
XFull name (yours).
X.Ip %o 8
XOrganization (yours).
X.Ip %O 8
XOriginal working directory (where you ran rn from).
X.Ip %p 8
XYour private news directory, normally ~/News.
X.Ip %P 8
XPublic news spool directory, normally /usr/spool/news on systems that don't use NNTP.
X.Ip %r 8
XLast reference on references line of current article (parent article id).
X.Ip %R 8
XReferences list for a new article, constructed from the references and article
XID of the current article.
X.Ip %s 8
XSubject, with all Re's and (nf)'s stripped off.
X.Ip %S 8
XSubject, with one \*(L"Re:\*(R" stripped off.
X.Ip %t 8
X\*(L"To:\*(R" line derived from the \*(L"From:\*(R" and \*(L"Reply-To:\*(R"
Xlines of the current article.
XThis always returns an Internet format address.
X.Ip %T 8
X\*(L"To:\*(R" line derived from the \*(L"Path:\*(R" line of the
Xcurrent article to produce a uucp path.
X.Ip %u 8
XThe number of unread articles in the current newsgroup.
X.Ip %U 8
XThe number of unread articles in the current newsgroup, not counting the
Xthe current article.
XWhen threads are selected, this count reflects only selected articles.
X.Ip %x 8
XThe news library directory.
X.Ip %X 8
XThe rn library directory.
X.Ip %z 8
XThe length of the current article in bytes.
X.Ip %Z 8
XThe number of selected threads.
X.Ip %~ 8
XYour home directory.
X.Ip %. 8
XThe directory containing your dot files, which is your home directory unless
Xthe environment variable DOTDIR is defined when rn is invoked.
X.Ip %# 8
XA counter incremented during multiple-article commands.
XPrior to executing any such commands, it will return 0.
XDuring a multi-article command, it returns the current count starting from 1.
XAfterward, it retains its final value.
XFor example, the command
X.Sp
X	1003-1008 s my.%#
X.Sp
Xwould save articles 1003 through 1008 in the files my.1 through my.6.
X.Ip %$ 8
XCurrent process number.
X.Ip %/ 8
XLast search string.
X.Ip %% 8
XA percent sign.
X.Ip "%{name} or %{name\-default}" 8
XThe environment variable \*(L"name\*(R".
X.Ip %[name] 8
XThe value of header line \*(L"Name:\*(R" from the current article.
XThe \*(L"Name:\ \*(R" is not included.
XFor example \*(L"%D\*(R" and \*(L"%[distribution]\*(R" are equivalent.
XThe name must be spelled out in full.
X.Ip %`command` 8
XInserts the output of the command, with any embedded newlines translated
Xto space.
X.Ip %""prompt"" 8
XPrints prompt on the terminal, then inputs one string, and inserts it.
X.Ip "%(test_text=pattern?then_text:else_text)" 8
XIf
X.I test_text
Xmatches
X.IR pattern ,
Xhas the value
X.IR then_text ,
Xotherwise
X.IR else_text .
XThe \*(L":else_text\*(R" is optional, and if absent, interpolates the null string.
XThe = may be replaced with != to negate the test.
XTo quote any of the metacharacters
X(\*(L'=\*(R', \*(L'?\*(R', \*(L':\*(R', or \*(L')\*(R'),
Xprecede with a backslash.
X.Ip %digit 8
XThe digits 1 through 9 interpolate the string matched by the nth bracket
Xin the last pattern match that had brackets.
XIf the last pattern had alternatives, you may not know the number of the
Xbracket you want\*(--%0 will give you the last bracket matched.
X.PP
XModifiers: to capitalize the first letter, insert \*(L'^\*(R':
X\*(L"%^C\*(R" produces something like \*(L"Net.jokes\*(R".
XInserting \*(L'_\*(R' causes the first letter following the last
X\&\*(L'/\*(R' to be capitalized: \*(L"%_c\*(R" produces \*(L"net/Jokes\*(R".
X.SH ENVIRONMENT
XThe following environment variables are paid attention to by
X.IR trn .
XIn general the default values assumed for these variables by
X.I trn
Xare reasonable, so if you are using
X.I trn
Xfor the first time, you can safely ignore this section.
XNote that the defaults below may not correspond precisely to the defaults
Xon your system.
XTo find the actual defaults you would need to look in config.h and common.h
Xin the trn source directory, and the file INIT in the trn library.
X.PP
XThose variables marked (%) are subject to % interpolation, and those marked
X(~) are subject to both % interpolation and ~ interpretation.
X.Ip "ATTRIBUTION (%)" 8
XGives the format of the attribution line in front of the quoted article
Xincluded by an F command.
X.Sp
XDefault: In article %i %f writes:
X.Ip "CANCEL (~)" 8
XThe shell command used to cancel an article.
X.Sp
XDefault: inews \-h < %h
X.Ip "CANCELHEADER (%)" 8 13v
XThe format of the file to pass to the CANCEL command in order to cancel
Xan article.
X.Sp
XDefault:
X.br
XNewsgroups: %n
X.br
XSubject: cmsg cancel %i
X.br
XReferences: %R
X.br
XReply-To: %L@%H (%N)
X.br
XDistribution: %D
X.br
XOrganization: %o
X.sp 1
X%i cancelled from rn.
X.Ip DOTDIR 8
XWhere to find your dot files, if they aren't in your home directory.
XCan be interpolated using \*(L"%.\*(R".
X.Sp
XDefault: $HOME
X.Ip "EDITOR (~)" 8
XThe name of your editor, if VISUAL is undefined.
X.Sp
XDefault: whatever your news administrator compiled in, usually vi.
X.Ip "EXSAVER (%)" 8
XThe shell command to execute in order to extract data to either /bin/sh
Xor a user-specified command.
X.Sp
XDefault: tail +%Bc %A | %e
X.Ip "FIRSTLINE (%)" 8
XControls the format of the line displayed at the top of an article.
XWarning: this may go away.
X.Sp
XThe default in an unthreaded group is approximately:
X.Sp
XArticle %a %(%U%M!=^00$?(%U more%(%M!=^0$? + %M Marked to return)\e) )in %C:
X.Sp
XWhile the default in a threaded group is almost:
X.Sp
X%C #%a%(%Z=^0$?%(%U!=^0$? (%U more\e)): (%U + %E more\e))
X.Ip HIDELINE 8
XIf defined, contains a regular expression which matches article lines to
Xbe hidden, in order, for instance, to suppress quoted material.
XA recommended string for this purpose is \*(L"^>...\*(R", which \fIdoesn't\fR
Xhide lines with only \*(L'>\*(R', to give some indication that quoted
Xmaterial is being skipped.
XIf you want to hide more than one pattern, you can use \*(L"\||\|\*(R" to
Xseparate the alternatives.
XYou can view the hidden lines by restarting the article with the \*(L'v\*(R'
Xcommand.
X.Sp
XThere is some overhead involved in matching each line of the article against
Xa regular expression.
XYou might wish to use a baud-rate modifier to enable this feature only at
Xlow baud rates.
X.Sp
XDefault: undefined
X.Ip HOME 8
XYour home directory.
XAffects ~ interpretation, and the location of your
Xdot files if DOTDIR is not defined.
X.Sp
XDefault: $LOGDIR
X.Ip "KILLGLOBAL (~)" 8
XWhere to find the KILL file to apply to every newsgroup.
XSee the \*(L'^K\*(R' command at the newsgroup selection level.
X.Sp
XDefault: %p/KILL
X.Ip "KILLLOCAL (~)" 8
XWhere to find the KILL file for the current newsgroup.
XSee the commands \*(L'K\*(R' and \*(L'^K\*(R' at the article selection level,
Xand the search modifier \*(L'K\*(R'.
X.Sp
XDefault: %p/%c/KILL
X.Ip LOGDIR 8
XYour home directory if HOME is undefined.
XAffects ~ interpretation, and the location of your
Xdot files if DOTDIR is not defined.
X.Sp
XDefault: none.
X.Sp
XExplanation: you must have either $HOME or $LOGDIR.
X.Ip LOGNAME 8
XYour login name, if USER is undefined.
XMay be interpolated using \*(L"%L\*(R".
X.Sp
XDefault: value of getlogin().
X.Ip "MAILCALL (~)" 8
XWhat to say when there is new mail.
X.Sp
XDefault: (Mail)
X.Ip "MAILFILE (~)" 8
XWhere to check for mail.
X.Sp
XDefault: /usr/spool/mail/%L
X.Ip "MAILHEADER (%)" 8
XThe format of the header file for replies.
XSee also MAILPOSTER.
X.Sp
XDefault:
X.Sp
XTo: %T
X.br
XSubject: %(%i=^$?:Re: %S
X.br
XNewsgroups: %n
X.br
XIn-Reply-To: %i)
X.br
X%(%[references]!=^$?References\\: %[references]
X.br
X)Organization: %o
X.br
XCc: 
X.br
XBcc: \en\en
X.Ip "MAILPOSTER (~)" 8
XThe shell command to be used by the reply commands (r and R)
Xin order to allow you to enter and deliver the response.
X.I trn
Xwill not itself call upon an editor for replies\*(--this
Xis a function of the program called by
X.IR trn .
XSee also MAILHEADER.
X.Sp
XDefault: Rnmail \-h %h
X.Ip "MBOXSAVER (~)" 8
XThe shell command to save an article in mailbox format.
X.Sp
XDefault: %X/mbox.saver %A %P %c %a %B %C "%b" \e
X.br
X"From: %T %`date`"
X.Sp
XExplanation: the first seven arguments are the same as for NORMSAVER.
XThe eighth argument to the shell script is the new From: line
Xfor the article, including the posting date,
Xderived either directly from the Posted: line, or not-so-directly from
Xthe Date: line.
XHeader munging at its finest.
X.Ip MODSTRING 8
XThe string to insert in the group summary line, which heads each article,
Xfor a moderated group.  See also NOPOSTRING.
X.Sp
XDefault: " (moderated)"
X.Ip NAME 8
XYour full name.
XMay be interpolated using \*(L"%N\*(R".
X.Sp
XDefault: name from /etc/passwd, or ~/.fullname.
X.Ip "NEWSHEADER (%)" 8 16v
XThe format of the header file for followups.
XSee also NEWSPOSTER.
X.Sp
XDefault:
X.Sp
XNewsgroups: %(%F=^$?%C:%F)
X.br
XSubject: %(%S=^$?%"\en\enSubject: ":Re: %S)
X.br
XSummary:
X.br
XExpires: 
X.br
X%(%R=^$?:References: %R
X.br
X)Sender: 
X.br
XReply-To: %L@%H (%N)
X.br
XFollowup-To: 
X.br
XDistribution: %(%i=^$?%"\enDistribution: ":%D)
X.br
XOrganization: %o
X.br
XKeywords: \en\en
X.Ip "NEWSPOSTER (~)" 8
XThe shell command to be used by the followup commands (f and F)
Xin order to allow you to enter and post a followup news article.
X.I trn
Xwill not itself call upon an editor for followups\*(--this
Xis a function of the program called by
X.IR trn .
XSee also NEWSHEADER.
X.Sp
XDefault: Pnews \-h %h
X.Ip NOPOSTRING 8
XThe string to insert in the group summary line, which heads each article,
Xfor a group to which local posting is not allowed.  See also MODSTRING.
X.Sp
XDefault: " (no posting)"
X.Ip "NORMSAVER (~)" 8
XThe shell command to save an article in the normal (non-mailbox) format.
X.Sp
XDefault: %X/norm.saver %A %P %c %a %B %C "%b"
X.Ip ORGANIZATION 8
XEither the name of your organization, or the name of a file containing the
Xname of your organization.
XMay be interpolated using \*(L"%o\*(R".
X.Sp
XDefault: whatever your news administrator compiled in.
X.Ip PAGESTOP 8
XIf defined, contains a regular expression which matches article lines to
Xbe treated as form-feeds.
XThere are at least two things you might want to do with this.
XTo cause page breaks between articles in a digest, you might define it
Xas \*(L"^--------\*(R".
XTo force a page break before a signature, you could define it
Xas \*(L"^-- $\*(R".
X(Then, when you see \*(L"--\*(R" at the bottom of the page, you can skip
Xthe signature if you so desire by typing \*(L'n\*(R' instead of space.)
XTo do both, you could use \*(L"^--\*(R".
XIf you want to break on more than one pattern, you can use \*(L"\||\|\*(R" to
Xseparate the alternatives.
X.Sp
XThere is some overhead involved in matching each line of the article against
Xa regular expression.
XYou might wish to use a baud-rate modifier to enable this feature only at
Xlow baud rates.
X.Sp
XDefault: undefined
X.Ip "PIPESAVER (%)" 8
XThe shell command to execute in order to accomplish a save to a pipe
X(\*(L"s\ |\ command\*(R" or \*(L"w\ |\ command\*(R").
XThe command typed by the user is substituted in as %b.
X.Sp
XDefault: %(%B=^0$?<%A:tail +%Bc %A |) %b
X.Sp
XExplanation: if %B is 0, the command is \*(L"<%A %b\*(R", otherwise
Xthe command is \*(L"tail +%Bc %A | %b\*(R".
X.Ip RNINIT 8
XDefault values for switches may be passed to
X.I trn
Xby placing them in RNINIT.
XAny switch that is set in RNINIT may be overruled 
Xon the command line, or via the \*(L'&\*(R' command from within
X.IR trn .
XBinary-valued switches that are set with \*(L"\-switch\*(R" may be unset
Xusing \*(L"+switch\*(R".
X.Sp
XIf RNINIT begins with a \*(L'/\*(R' it is assumed to be the name of a file
Xcontaining switches.
XIf you want to set many environment variables but don't want to keep
Xthem all in your environment, or if the use of any of these variables
Xconflicts with other programs, you can use this feature along with the
X.B \-E
Xswitch to set the environment variables upon startup.
X.Sp
XDefault: \*(L" \*(R".
X.Ip "RNMACRO (~)" 8
XThe name of the file containing macros and key mappings.
XSee the MACROS section.
X.Sp
XDefault: %./.rnmac
X.Ip "SAVEDIR (~)" 8
XThe name of the directory to save to, if the save command does not specify
Xa directory name.
X.Sp
XDefault:
X.br
X   If
X.B \-/
Xis set: %p/%c
X.br
X   If
X.B +/
Xis set: %p
X.Ip "SAVENAME (%)" 8
XThe name of the file to save to, if the save command contains only a
Xdirectory name.
X.Sp
XDefault:
X.br
X   If
X.B \-/
Xis set: %a
X.br
X   If
X.B +/
Xis set: %^C
X.Ip "SELECTCHARS" 8
XThe characters used by the thread selector to select the associated thread
Xof discussion.
XYou can specify up to 64 visible characters, including upper- and lower-case
Xletters, numbers, and many punctuation characters.
XSelection characters override command characters in the selector, but are
Xnot excluded from macro expansion, so be careful.
X.br
XDefault: abcdefgijlorstuvwxz1234567890
X that can be typed
X.Ip SHELL 8
XThe name of your preferred shell.
XIt will be used by the \*(L'!\*(R', \*(L'S\*(R' and \*(L'W\*(R' commands.
X.Sp
XDefault: whatever your news administrator compiled in.
X.Ip "SUBJLINE (%)" 8
XControls the format of the lines displayed by the \*(L'=\*(R' command at
Xthe article selection level.
X.Sp
XDefault: %s
X.Ip TERM 8
XDetermines which termcap entry to use, unless TERMCAP contains the entry.
X.Ip TERMCAP 8
XHolds either the name of your termcap file, or a termcap entry.
X.Sp
XDefault: /etc/termcap, normally.
X.Ip "UNSHAR (~)" 8
XThe shell command to execute in order to accomplish the unshar'ing of a
Xshell archive.
X.Sp
XDefault: /bin/sh
X.Ip USER 8
XYour login name.
XMay be interpolated using \*(L"%L\*(R".
X.Sp
XDefault: $LOGNAME
X.Ip "VISUAL (~)" 8
XThe name of your editor.
X.Sp
XDefault: $EDITOR
X.Ip "YOUSAID (%)" 8
XGives the format of the attribution line in front of the quoted article
Xincluded by an R command.
X.Sp
XDefault: In article %i you write:
X.SH MACROS
XWhen
X.I trn
Xstarts up, it looks for a file containing macro definitions (see environment
Xvariable RNMACRO).
XAny sequence of commands may be bound to any sequence of keys, so you
Xcould remap your entire keyboard if you desire.
XBlank lines or lines beginning with # in the macro file are considered
Xcomments; otherwise
X.I trn
Xlooks for two fields separated by white space.
XThe first field gives the sequence of keystrokes that trigger the macro,
Xand the second field gives the sequence of commands to execute.
XBoth fields are subject to % interpolation, which will also translate
Xbackslash and uparrow sequences.
X(The keystroke field is interpreted at startup time, but the command field
Xis interpreted at macro execution time so that you may refer to % values
Xin a macro.)
XFor example, if you want to reverse the roles of carriage return and
Xspace in
X.I trn
X.Sp
X^J	\e040
X.br
X^M	\e040
X.br
X\e040	^J
X.Sp
Xwill do just that.
XBy default, all characters in the command field are interpreted as the
Xcanonical
X.I trn
Xcharacters, i.e. no macro expansion is done.
XOtherwise the above pair of macros would cause an infinite loop.
XTo force macro expansion in the command field, enclose the
Xmacro call with ^( ... ^) thusly:
X.Sp
X at s	|mysavescript
X.br
X at w	w^(@s^)
X.Sp
XYou can use the %() conditional construct to construct macros that work
Xdifferently under different circumstances.
XIn particular, the current mode (%m) of
X.I trn
Xcould be used to make a command that only works at a particular level.
XThis is particularly vital for the thread selector, which uses most of
Xthe lower-case letters to select the associated thread of discussion.
XFor example,
X.Sp
Xa	%(%m=t?a:s art.hold\en)
X.Sp
Xwill return the original letter (a) in the thread selector, and the command
X\*(L"s art.hold\en\*(R" everywhere else.
X.Sp
X%(%{TERM}=vt100?^[[O)	/^J
X.Sp
Xwill do the binding only if the terminal type is vt100,
Xthough if you have many of these it would be better to have separate
Xfiles for each terminal.
X.Sp
XIf you want to bind a macro to a function key that puts a common garbage character
Xafter the sequence (such as the carriage return on the end of Televideo 920
Xfunction sequences), DO NOT put the carriage return
Xinto all the sequences or you will waste a CONSIDERABLE amount of internal
Xstorage.
XInstead of \*(L"^AF^M\*(R", put \*(L"^AF+1\*(R", which indicates to
X.I trn
Xthat it should gobble up one character after the F.
X.SH "WHAT'S NEW?"
XHere's a quick run-down of
X.IR trn 's
Xnew features and commands aimed at the knowledgeable
X.I rn
Xuser.
X.Sp
XOne of the biggest improvements is the Thread Selector, which is bound
Xto the \*(L'+\*(R' key.
XThe selector displays a list of subject threads and (by default) authors
Xto allow you to select the topics that interest you by typing their
Xassociated letter.
XThe thread selector can also be used to browse articles that have already
Xbeen read, to selectively re-read discussions (use the \*(L'U\*(R' command).
X.Sp
XAnother big improvement is the thread-ordered display of articles.
XWhile reading each topic,
X.I trn
Xdisplays each article and its replies in the order of their parent/child
Xrelationship.
XThis lets you follow the flow of the discussion better, instead of jumping
Xaround from idea to idea or even reading a reply before the original article.
X.Sp
XAlong this same line is the addition of the article-tree display in the
Xupper-right corner of the header.
XGlancing at the tree gives you a better feel for how the articles you are
Xreading relate to each other.
X.Sp
XThe header has also been modified to hide a few more header-lines by default
X(e.g. References), but, as always, you can override these with \-h.
XThere is also some new \*(L"magic\*(R" in the header: the From header is
Xtrimmed to be just the comment portion (if available), and the Date header
Xis displayed in local time (in threaded groups).
XOverride these defaults with +H.
X.Sp
XOnce you begin reading articles, use the regular movement commands (n, N,
Xp, P, etc.) as you normally would.
XThen, check out the [, ], {, and } commands to move around in the article
Xtree a bit more directly.
XFor example, the \*(L'[\*(R' command takes you to your parent article,
Xeven if it was already read, whichp is very useful for tracking down the
Xcited portion of the article in its original context.
X.Sp
XThere are additional kill commands for the entire thread (J) and the
Xcurrent article and all its replies (,).
XThe KILL files have been extended to allow killing by thread (T), which will
Xkill a topic even if people fine-tune the subject along the way.
X.Sp
XThere is also an easy way to skip around in the various threads with
Xthe < and > commands.
X.Sp
XThere is a new command, \*(L"e dir\*(R", that extracts a shell archive or
Xuuencoded file into the specified directory.
XIt is even possible to extract other data formats if you specify the
Xappropriate filter command (e.g. \*(L"e dir|cmd\*(R".
X.Sp
XAlso, if you plan to use macro definitions, it is good to keep in mind
Xthat the thread selector uses most of the lower-case letters for thread
Xselection, and thus it is a good idea to explicitly set the mode(s) in
Xwhich a macro applies.
XFor example, if you want to press 'f' from the article pager/selector to
Xforward the current article to the user \*(L"smith\*(R", you could define:
X.Sp
X.nf
X	f	%(%m=[pa]?|mail smith\en:f)
X.fi
X.Sp
XThis checks the current mode (%m) and if it is \*(L'p\*(R' or \*(L'a\*(R'
Xit expands it to the string \*(L"|mail smith\en\*(R", otherwise it returns
Xthe letter \*(L'f\*(R'.
XIn some cases, you may simply wish to exclude the thread selector from a
Xmacro with the conditional \*(L"%m!=t\*(R".
X.Sp
XFinally, you'll probably want to use the new options,
X.B \-x
Xand
X.B \-X
Xto ensure that all the newest features are available for use.
XThese options might be on by default, depending on how your administrator
Xdecided to install
X.IR trn .
X.SH AUTHORS
XRn was created by Larry Wall <lwall at jpl-devvax.jpl.nasa.gov>
X.br
Xand is now under the direction of Stan Barber <sob at bcm.tmc.edu>.
X.br
XThreaded version by Wayne Davison <davison at dri.com>
X.br
X(Mail all bug reports for trn to Wayne.)
X.br
XRegular expression routines are borrowed from emacs, by James Gosling.
X.SH FILES
X.Ip "%./.newsrc" 1.25i
Xstatus of your news reading
X.Ip "%./.oldnewsrc" 1.25i
Xbackup copy of your
X.I .newsrc
Xfrom start of session
X.Ip "%./.rnlock" 1.25i
Xlock file so you don't screw up your
X.I .newsrc
X.Ip "%./.rnlast" 1.25i
Xinfo from last run of rn
X.Ip "%./.rnsoft" 1.25i
Xsoft pointers into /usr/lib/news/active to speed startup, synchronous with
X.I .newsrc
X.Ip "%./.rnhead" 1.25i
Xtemporary header file to pass to a mailer or news poster
X.Ip "%./.rnmac" 1.25i
Xmacro and keymap definitions
X.Ip "%p" 1.25i
Xyour news save directory, usually ~/News
X.Ip "%x/active" 1.25i
Xthe list of active newsgroups, usually /usr/lib/news/active on systems that don't use NNTP
X.Ip "%X/active2" 1.25i
Xthe list of active newsgroups when running in threaded mode.
X.Ip "%P" 1.25i
Xthe public news spool directory, usually /usr/spool/news on systems that don't use NNTP
X.Ip "%X/INIT" 1.25i
Xsystem-wide default switches
X.SH SEE ALSO
Xnewsrc(5), more(1), readnews(1), Pnews(1), Rnmail(1)
X.SH DIAGNOSTICS
XGenerally self-documenting, as they say.
X.SH BUGS
XThe
X.B \-h
Xswitch can only hide header lines that
X.I trn
Xknows about.
X.PP
XThe \*(L'\-\*(R' command doesn't cross newsgroup boundaries, and only undoes
Xthe last article selection.
X.PP
XIf you edit your
X.I .newsrc
Xwhile
X.I trn
Xis running,
X.I trn
Xwill happily wipe out your changes when it decides to
Xwrite out the
X.I .newsrc
Xfile.
X.PP
XMarking of duplicate articles as read in cross-referenced newsgroups will
Xnot work unless the Xref patch is installed in inews.
X.PP
XIf you get carried away with % or escape substitutions, you can overflow
Xbuffers.
X.PP
XThere should be no fixed limit on the number of newsgroups.
X.PP
XSome of the more esoteric features may be missing on machines with limited
Xaddress space.
END_OF_FILE
  if test 29757 -ne `wc -c <'trn.1.02'`; then
    echo shar: \"'trn.1.02'\" unpacked with wrong size!
  fi
  # end of 'trn.1.02'
fi
echo shar: End of archive 5 \(of 14\).
cp /dev/null ark5isdone
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