[comp.sources.unix] v19i072: NN, a Usenet news reader, Part11/15

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

Submitted-by: storm@texas.dk (Kim F. Storm)
Posting-number: Volume 19, Issue 72
Archive-name: nn/part11

#!/bin/sh
# this is part 11 of a multipart archive
# do not concatenate these parts, unpack them in order with /bin/sh
# file nngoback.1 continued
#
CurArch=11
if test ! -r s2_seq_.tmp
then echo "Please unpack part 1 first!"
     exit 1; fi
( read Scheck
  if test "$Scheck" != $CurArch
  then echo "Please unpack part $Scheck next!"
       exit 1;
  else exit 0; fi
) < s2_seq_.tmp || exit 1
echo "x - Continuing file nngoback.1"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' >> nngoback.1
X.SH SYNOPSIS
X.B nngoback 
X[ \-\fBh\fP ]
X.I days
X.SH DESCRIPTION
X.I nngoback
Xwill rewind the record file of \fBnn\fP(1) one or more days, i.e. mark
Xthe news articles which have arrived on the system during the last
X.I days
Xdays unread.
X.LP
XIf you are not up-to-date with your news reading, you can also use
X\fBnngoback\fP to catch up to only have the last few days of news
Xwaiting to be read in the following way:
X.br
X	nn \-a0
X.br
X	nngoback 3
X.br
XThe \fBnn\fP command will mark all articles in all groups as read (answer
X.I all
Xto the catch-up question.)  The following \fBnngoback\fP will then make
Xthe last three days of news unread again.
X.LP
XExamples:
X.TP
Xnngoback 0
XMark the articles which have arrived today as unread.
X.TP
Xnngoback 1
XMark the articles which have arrived yesterday and today as unread.
X.TP
Xnngoback 6
XMark the articles which have arrived during the last week as unread.
X.LP
X\fBnngoback\fP have two methods to rewind the rc file:
X.br
X\- scan the history file for the articles (option \-\fBh\fP), or
X.br
X\- use a copy of the active file with the `right' age (default).
X.LP
XNormally, \fInngoback\fP will run \fInntidy\fP on your rc file using
Xa copy of the active file saved on the requested day.  Using this
Xmethod, you cannot go more than 14 days back.
X.LP
XIf the \-\fBh\fP option is specified, the history file will be scanned
Xto locate articles which have been received after the specified day
Xand mark those as unread in the rc file.  When this method is used,
X\fBnngoback\fP can be quite slow depending on the size of the history
Xfile in your news system; it uses \fBegrep\fP(1) to scan the file, so
Xit must be read the entire file although the interesting data are
Xfound towards the end of the file.  This method does not normally work
Xwith NNTP or in a network environment.
X.LP
XIt is a prerequisite that the script \fBback_act\fP is executed at an
Xappropriate time once (and only once) every day.  Preferably this is
Xdone by \fBcron\fP right before the bacth of news for `today' is received.
X\fBback_act\fP will maintain copies of the active file for the last 14
Xdays, which can be used directly to rewind the rc file.
X.SH FILES
X.DT
X.ta \w'$lib/date_regexp'u+3m
X~/.nn/rc	The record of read articles.
X.br
X~/.nn/rc.bak1	The original rc file before goback.
X.br
X$db/active.\fIN\fP	The \fIN\fP days `old' active file.
X.br
X$lib/back_act	Script run by cron to keep the `old' active files.
X.br
X$lib/date_regexp	Program to build an egrep(1) pattern.
X.br
X$newslib/history	The list of all articles in the system.
X.DT
X.LP
XThe \fIdate_regexp\fP program builds an egrep pattern to extract the
Xhistory file entries for a specified number of days.
X.LP
XOn some systems the history file is split into smaller files in a
Xsubdirectory called history.d; \fBnngoback\fP tries to locate the
Xright history file to use.
X.SH SEE ALSO
Xnn(1), nncheck(1), nngrep(1), nntidy(1)
X.br
Xnnadmin(1M), nnquery(1M), nnusage(1M), nnmaster(8)
X.SH NOTES
XThe `old' active files method also works with NNTP and with a shared
Xdatabase (that is why the files are stored in the database directory). 
X.LP
X\fBnngoback\fP does not check the age of the `old' active files; it
Xwill blindly believe that active.0 was created today, and that
Xactive.7 is really seven days old!  Therefore, the \fIback_act\fP
Xscript should be run once and only once every day for \fInngoback\fP
Xto work properly.
X.LP
XWhen the history file is used, days are known to split at 12 midnight.
XWhen old active files are used, days are counted relative to the time
Xthe files were saved.
X.SH AUTHOR
XKim F. Storm, Texas Instruments A/S, Denmark
X.br
XE-mail: storm@texas.dk
NO_NEWS_IS_GOOD_NEWS
echo "File nngoback.1 is complete"
chmod 0644 nngoback.1 || echo "restore of nngoback.1 fails"
set `wc -c nngoback.1`;Sum=$1
if test "$Sum" != "3723"
then echo original size 3723, current size $Sum;fi
echo "x - extracting nngoback.sh (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nngoback.sh &&
X# prefix is inserted above by make
X
X# go $1 days back in the news feed and adjust .nn/rc
X# accordingly.
X
XUSE_HIST=false
Xif [ "$1" = "-h" ]
Xthen
X	USE_HIST=true
X	shift
Xfi
X 
Xif [ $# -ne 1 ]
Xthen
X	echo "Usage: $0 [-h] days"
X	exit 1
Xfi
X
Xif [ $USE_HIST = false ]
Xthen
X	if [ -s $DB/active.$1 ]
X	then
X	  nntidy - $DB/active.$1
X	else
X	  echo $0: No suitable copy of the active file exists -- no update
X	fi
X	exit 0
Xfi
X
XHISTORY="`expr $ACTIVE : '\(/.*/\)'`"history
X
Xif [ -s "${HISTORY}" ]
Xthen
X	:
Xelif [ -s "${HISTORY}.d/0" ]
Xthen
X	HISTORY="${HISTORY}.d/*"
Xelse
X	if $NNTP
X	then
X		echo "$0 is not setup to run with NNTP"
X		exit 2
X	fi
X	echo "Cannot find the history file -- no update"
X	exit 1
Xfi
X
Xecho Working on your nn record file ... do not run nn until completed
X
Xecho "NOTICE:  this operation is SLOOOOOOOOWWWWWWWW....."
X
Xcd
Xcd .nn
Xrm -f rc.bak1
X
Xtrap "mv rc.bak1 rc ; echo No changes ; exit 0" 1 2 3 13 14 15
X
Xmv rc rc.bak1
X
X{
Xecho PHASE_1
X# news.group last.art.no first.art.no mod
Xcat $ACTIVE
X
Xecho PHASE_2
X# + last.read.art.no news.group
Xcat rc.bak1
X
Xecho PHASE_3
X# from date time news.group/art.no ...
Xcat  $HISTORY | egrep "`$LIB/date_regexp $1`"
X} |
Xawk '
XBEGIN{
X	p=0
X}
X/^PHASE_/ {
X	p++
X	next
X}
Xp == 1 {
X	last[$1]=$2+1
X	next
X}
Xp == 2 {
X	if (last[$3] > 0) {
X		subscr[$3] = $1
X		if ($1 == "!") last[$3] = $2 + 1;
X	}
X	next
X}
Xp == 3 {
X	for (i = 4; i <= NF; i++) {
X		if (split($i, x, "/") != 2) continue;
X		g=x[1]; n=x[2]
X		if (subscr[g] == "+" && n < last[g]) last[g] = n
X	}
X}
XEND {
X	for (g in subscr) printf("%s %06d %s\n", subscr[g], last[g]-1, g)
X}' | sort +2 > rc
X
Xecho "nngoback $1 finished"
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nngoback.sh || echo "restore of nngoback.sh fails"
set `wc -c nngoback.sh`;Sum=$1
if test "$Sum" != "1591"
then echo original size 1591, current size $Sum;fi
echo "x - extracting nngrep.1 (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nngrep.1 &&
X.TH NNGREP 1 "Release 6.3"
X.UC 4
X.SH NAME
Xnngrep \- grep for news group names
X.SH SYNOPSIS
X.B nngrep
X[ \-\fBa\fP ]
X.I pattern
X.SH DESCRIPTION
X.B nngrep
Xscan \fInn\fP's rc file for news group names that matches the
X.I pattern 
Xand print their names on the standard output.
X.LP
XFor example, to get the names of all "source" groups, you can use the
Xcommand 
X.br
X	nngrep source
X.LP
XNormally, \fInngrep\fP will only print names of groups to which you subscribe.
X.LP
XIf you specify the \-a option, it will print all matching groups in
Xthe rc file.
X.LP
XYou can use this to read a specific subset of news groups with
X\fInn\fP; for example
X.br
X	nn -x -snn `nngrep source`
X.SH FILES
X.DT
X.ta \w'~/.nn/rc.bak'u+3m
X~/.nn/rc	The record of read articles
X.DT
X.SH SEE ALSO
Xnn(1), nncheck(1), nngoback(1), nngrep(1)
X.br
Xnnadmin(1M), nnquery(1M), nnusage(1M), nnmaster(8)
X.SH BUGS
XThe rc file only contains entries for the groups you have actually
Xread, i.e. new groups which you have not yet read are not printed.
XTherefore, groups in which no articles have ever been posted will not
Xshow up either.
X.LP
XThis is actually a feature of \fInn\fP:  it will
Xdelay the entry of new groups into the rc file, so it can tell you
Xthat it is a new group the first time you read it!
X.SH AUTHOR
XKim F. Storm, Texas Instruments A/S, Denmark
X.br
XE-mail: storm@texas.dk
X
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nngrep.1 || echo "restore of nngrep.1 fails"
set `wc -c nngrep.1`;Sum=$1
if test "$Sum" != "1337"
then echo original size 1337, current size $Sum;fi
echo "x - extracting nngrep.sh (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nngrep.sh &&
Xcd
X
Xif [ $# -eq 0 ] ; then
X	echo "usage: nngrep -a pattern"
X	exit 1
Xfi
X
Xif [ "$1" = "-a" ] ; then
X	grep "$2" .nn/rc
Xelse
X	grep "^+ .* .*$1" .nn/rc
Xfi |
Xawk '{print $3}'
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nngrep.sh || echo "restore of nngrep.sh fails"
set `wc -c nngrep.sh`;Sum=$1
if test "$Sum" != "169"
then echo original size 169, current size $Sum;fi
echo "x - extracting nnmail.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nnmail.c &&
X/*
X * 	nnmail - a mailer that understands @ addressing
X *		 when you don't have sendmail or smail
X */
X
X#include "config.h"
X
X#include "options.h"
X
Xchar * MAILER = MAILX;
Xstatic int print_vers, test_mode;
X
XOption_Description( mail_options ) {
X
X    'v', Bool_Option( print_vers ),
X    'm', String_Option( MAILER ),
X    't', Bool_Option( test_mode ),
X
X    '\0',
X};
X
X
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X{
X    int i, n;
X    char route[512];
X    char *getenv(), *envmail;
X    extern char **environ;
X
X    if (envmail = getenv("NNMAILER"))
X	MAILER = envmail;
X
X    n = parse_options(argc, argv, (char *)NULL, 
X		      mail_options, (char **)NULL, (int (*)())NULL);
X    
X    if (print_vers) {
X	print_version("Release %R.%V.%P #%U\n");
X	nn_exit(0);
X    }
X
X#ifndef HAVE_ROUTING    
X    if (test_mode) {
X	extern FILE *route_trace;
X	route_trace = stdout;
X    }
X#endif    
X	
X    argv[0] = MAILER;
X
X#ifndef HAVE_ROUTING    
X    for (i = 1; i <= n; i++)
X	if (reroute(route, argv[i])) {
X	    if (test_mode) {
X		printf("%s \t-->  %s\n", argv[i], route);
X		continue;
X	    }
X	    argv[i] = malloc(strlen(route)+1);
X	    strcpy(argv[i], route);
X	} else
X	    if (test_mode)
X		printf("%s \t***  no route found\n", argv[i]);
X#endif
X
X    if (test_mode) nn_exit(0);
X    
X    execve(MAILER, argv, environ);
X    fprintf(stderr, "Mailer '%s' not found\n", MAILER);
X    nn_exit(7);
X}
X
X
X/*VARARGS*/
Xuser_error()
X{
X}
X
Xnn_exit(n)
X{
X    exit(n);
X}
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nnmail.c || echo "restore of nnmail.c fails"
set `wc -c nnmail.c`;Sum=$1
if test "$Sum" != "1417"
then echo original size 1417, current size $Sum;fi
echo "x - extracting nnmaster.1m (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nnmaster.1m &&
X.TH NNMASTER 1M "Release 6.3"
X.\" (c) Copyright 1988, Kim F. Storm
X.UC 4
X.SH NAME
Xnnmaster \- nn database manager
X.SH SYNOPSIS
X.B nnmaster
X[ \-\fBI\fP ]
X.br
X.B nnmaster
X[ \-\fBw\fP ]
X.br
X.B nnmaster
X[ \-\fBv\fP ]
X.br
X.B nnmaster
X[ \-\fBr [ \fP\fIN\fP ] ]
X[ \-\fBe\fP\fIN\fP ] 
X[ \-\fBC\fP ]
X[ \-\fBE\fP ]
X[ \-\fBu\fP ]
X[ \-\fBt\fP ]
X.SH DESCRIPTION
X.I nnmaster
Xis the daemon which is responsible for building and maintaining the
Xdatabase used by the \fInn\fP(1) news reader.
X.LP
XNormally, 
X.I nnmaster
Xis started when the system enters multi-user mode, and runs until 
Xsystem shutdown.  To facilitate this, you should place the following
Xcall in /etc/rc (or similar) to invoke the
X.I nnmaster
Xdaemon:
X.sp 0.5v
X	$lib/nnmaster -r -C
X.sp 0.5v
Xwhere $lib is the LIB_DIRECTORY defined during configuration of \fInn\fP.
X.LP
XWhen
X.I nnmaster
Xis started as specified above, it will first perform a thorough consistency
Xcheck on the database (option \-C).
X.LP
XThen, every 10 minutes (option \-r), it will look at the time-stamp
Xof the news active file to see whether new articles have arrived on
Xthe system (or whether articles have been expired).
X.LP
XIf the active file has been modified,
X.I nnmaster
Xwill collect the header information from the new articles and enter
Xthem into the database (or remove the headers of the expired articles
Xfrom the database).
X.LP
XIf it detects that some articles have been expired, it will
Xautomatically remove the header information of the expired articles
Xfrom the database.
X.SH OPTIONS
XThe following options control the behaviour of the \fInnmaster\fP.
X.TP
X\-\fBr\fP [ \fImin\fP ]
X.br
XDaemon mode.  The \fInnmaster\fP will put itself in the background,
Xand will checks for arrival of new articles and expired articles every
X.I min
Xminutes (and update the database accordingly).  If 
X.I min
Xis omitted, the default is to check every 10 minutes.
X.sp 0.5v
XWithout the \-r option, the \fInnmaster\fP will just perform a single
Xcollection of new articles (if any) and then exit.  This can be used
Xto have the \fInnmaster\fP started by
X.IR cron (8)
Xat regular intervals instead of having it as a daemon which sleeps
Xbetween checking for new articles.  Since the \fInnmaster\fP is a bit
Xexpensive to start up (it has to read a few files), it is up to you to
Xdeside which mode is best on your system.  (I have also heard that it
Xworks to call \fInnmaster\fP without \-r from 
X.IR inews (1).
XI cannot recommend this unless you receive batched news; invoking
X\fInnmaster\fP for every received article sounds too expensive to me.)
X.TP
X\-\fBe\fP \fIart\fP
X.br
XRun internal
X.I expire
Xon a group in the database when
X.I art
Xarticles has been expired by
X.IR expire (1M).
XThe default is to expire a group whenever a single article in the
Xgroup has been expired.  Using this option, you can delay the expiring
Xof the header information from the database until the specified number
Xof articles are gone.  
X.sp 0.5v
XUsing a 
X.I art
Xvalue of 0 disables the automatic expire completely.
XThis may be used to keep old article
X.I headers
Xonline after the articles have been removed (useful if you backup
Xexpired articles on tape.)  You can then use the Expire command in
X\fInnadmin\fP(1M) to instruct the \fInnmaster\fP to expire individual
Xor all groups.
X.TP
X.B \-C
XPerform a consistency check on the database on start-up, and rebuild
Xcorrupted database files.  This operation can be quite time-consuming
Xsince it reads through all the database files.
X.TP
X.B \-E
XExpire groups by recollecting all available articles.  
X.sp 0.5v
XThe default method to expire a group is to simply rewrite the database
Xfiles for that group eliminating the entries for the non-existing
Xarticles.  The problem with the default approach is that it will only
Xremove the entries for the articles with article number less than the
Xlowest existing article number (found in the active file).  This may
Xcause problems if there is a single old article with a "distant"
Xexpire date.
X.sp 0.5v
XSince recollecting all articles in a group may be expensive if you
Xkeep news for a long time, you should not use the \-E option
Xwithout a little thought. 
X.TP
X.B \-I
XInitialize database.  This option will erase an existing database, and
Xcreate an empty database containing entries for the currently known
Xgroups.  Since groups are sometimes identified by a number across
Xinvokations of \fInn\fP (saved article selections), \fInnmaster\fP
Xwill offer you to use the same numbering of the news groups as in the
Xold database (it will ask whether you want to use the old group file).
X.TP
X.B \-t
XTrace the collection process.  This will place a lot of information
Xinto the log file (T: entries).  Otherwise, the log file will only
Xcontain entries reporting the total number of articles collected or
Xexpired during the collection process.
X.TP
X.B \-v
XPrint the release and version identification for \fInnmaster\fP, and exit.
X.TP
X.B \-u
XNormally, \fInnmaster\fP will just compare the time-stamp on the
Xactive file with a time-stamp saved in the database to see if new
Xarticles have arrived.  The \-u option forces the \fInnmaster\fP to 
X.I read
Xthe active file on start-up to see if new articles have arrived; the
Xfollowing checks (in daemon mode) will still be based on the
Xtime-stamp of the active file.
X.TP
X.B \-w
XWakeup the real \fInnmaster\fP.  Send a signal to the \fInnmaster\fP
Xdaemon to have it check for new articles immediately.
X.SH FILES
XThe $db, $lib, and $news used below are synonyms for the DB_DIRECTORY,
XLIB_DIRECTORY, and the news system's lib directories respectively.
X.LP
X.DT
X.ta \w'$db/DATA/\fInnn\fP.[dx]'u+3m
X$db/MASTER	Database master index
X.br
X$db/GROUPS	News group names in MASTER file order
X.br
X$db/DATA/\fInnn\fP.[dx]	Database files for group number \fInnn\fP
X.br
X$lib/GATE	Message channel from \fInnadmin\fP to \fInnmaster\fP
X.br
X$lib/MPID	The process id of the \fInnmaster\fP daemon.
X.br
X$lib/Log	The log file
X.br
X$news/active	Existing articles and groups
X.DT
X.LP
XThe MASTER file contains a record for each news group, occurring in
Xthe same sequence as the group names in the GROUPS file.  The sequence
Xalso defines the group numbers used to identify the files in the
Xdatabase and in a few other places.
X.LP
XThe GATE file will be created by \fInnadmin\fP when needed, and
Xremoved by \fInnmaster\fP when it has read it.  Therefore, to send a
Xmessage to the \fInnmaster\fP requires that you are allowed to write
Xin the $lib directory.
X.LP
XThe contents of the Log file are described in the \fInnadmin\fP manual.
X.SH SEE ALSO
Xnn(1), nncheck(1), nngrep(1), nntidy(1)
X.br
Xnnadmin(1M), nnquery(1M), nnusage(1M), nnmaster(8)
X.SH NOTES
XThe functionality of the \-e options should be to specify how many
Xexpired articles should remain in the database.  With the current
Xfunctionality, all "old" headers will be removed from the database
Xwhen \fInnmaster\fP performs the expire.
X.LP
XThe message "Article .... not found" is displayed by \fInn\fP if the
Xuser has selected an article which is still in the database but has
Xbeen expired in the news system.
X.LP
X\fInnmaster\fP uses the "lowest article number" field in the active
Xfile to detect that 
X.I expire 
Xhas been run on the group.  This "quick check" will not trigger an
Xinternal expire if the "lowest numbered article" was not removed by
Xexpire.  To expire such a group, you must explicitly request it using
Xthe Group-Expire command in \fInnadmin\fP(1M).
X.SH AUTHOR
XKim F. Storm, Texas Instruments A/S, Denmark
X.br
XE-mail: storm@texas.dk
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nnmaster.1m || echo "restore of nnmaster.1m fails"
set `wc -c nnmaster.1m`;Sum=$1
if test "$Sum" != "7469"
then echo original size 7469, current size $Sum;fi
echo "x - extracting nnquery.sh (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nnquery.sh &&
X# CONFIG file is insert above this line by make
X
Xif [ -f ${ACTIVE} ] ; then
X	echo "Cannot locate active file ${ACTIVE}"
X	exit 3
Xfi
X
XOUTAGED=30
XREADERS=/tmp/nnq$$q
XDIRS=/tmp/nnq$$d
Xecho > $DIRS
X
X{
Xsort ${ACTIVE}
X
Xawk -F: '{print "%s %s\n", $1, $6}' /etc/passwd |
Xwhile read user homedir
Xdo
X  if [ -f $homedir/.nn/rc ] ; then
X    if [ -n "`grep '^'$homedir'$' $DIRS`" ] ; then
X      :
X    else
X      echo $homedir >> $DIRS
X      if [ -n "`find $homedir/.nn -name rc -mtime -$OUTAGED -print`" ] ; then
X        echo $user >> $READERS
X        echo USER $user
X        cat $homedir/.nn/rc
X      fi
X    fi
X  fi
Xdone
X} |
X
Xawk '
XBEGIN {
X	a=1
X	g=0
X}
X
X$1 == "USER" {
X	a = 0
X	user = " " $2
X	next
X}
X
Xa == 1 {
X	g++
X	group[$1] = g
X	name[g] = $1
X#	if ($2 == $3) last[g] = 0; else last[g] = $2+0
X	sub[g] = 0
X	users[g] = ""
X	next
X}		
X
X$1 == "!" {
X	next
X}
X
X$1 == "+" {
X	n = group[$3]
X#	if ( last[n] == 0 ) next
X#	if ( ($2+200) < last[n] ) next
X	sub[n]++;
X	users[n] = users[n] user
X	next
X}
X
X{
X	next
X}
X
XEND {
X	for (n = 1; n <= g; n++) {
X		printf "%5d %s\t%s\n", sub[n], name[n], users[n]
X	}
X}' |
X
Xif [ "$1" = "-s" ] ; then
X	grep -v "^[ 	]*0"
Xelif [ "$1" = "-u" ] ; then
X	grep "^[ 	]*0"
Xelse
X	cat
Xfi
X
Xrm -f $READERS $DIRS
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nnquery.sh || echo "restore of nnquery.sh fails"
set `wc -c nnquery.sh`;Sum=$1
if test "$Sum" != "1199"
then echo original size 1199, current size $Sum;fi
echo "x - extracting nntidy.1 (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nntidy.1 &&
X.TH NNTIDY 1 "Release 6.3"
X.UC 4
X.SH NAME
Xnntidy \- tidy your personal \fInn\fP(1) rc file
X.SH SYNOPSIS
X.B nntidy
X.SH DESCRIPTION
X.B nntidy
Xwill clean out non-existing groups, adjust obviously wrong article
Xnumbers, and remove badly formed lines from your rc file.
X.LP
XIt will also sort the lines in the rc file in alphabetical order.
X.LP
XYou should run 
X.B nntidy
Xif your rc file has been corrupted for some reason.
X.SH FILES
X.DT
X.ta \w'~/.nn/rc.bak'u+3m
X~/.nn/rc	The record of read articles
X.br
X~/.nn/rc.bak	The original rc file before tidy
X.DT
X.SH SEE ALSO
Xnn(1), nncheck(1), nngoback(1), nngrep(1)
X.br
Xnnadmin(1M), nnquery(1M), nnusage(1M), nnmaster(8)
X.SH AUTHOR
XKim F. Storm, Texas Instruments A/S, Denmark
X.br
XE-mail: storm@texas.dk
X
X
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nntidy.1 || echo "restore of nntidy.1 fails"
set `wc -c nntidy.1`;Sum=$1
if test "$Sum" != "742"
then echo original size 742, current size $Sum;fi
echo "x - extracting nntidy.sh (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nntidy.sh &&
X# CONFIG file is inserted above during installation
X
X# clean out non-existing groups, badly formed lines etc.
X
Xif [ ! -s ${2-$ACTIVE} ] ; then
X	echo "$0: Cannot locate active file ${2-$ACTIVE}"
X	exit 3
Xfi
X
Xecho Working on your nn record file ... do not run nn until completed
X
Xcd
Xcd .nn
Xrm -f rc.bak1
X
Xtrap 'mv rc.bak1 rc ; echo "No changes" ; exit 0' 1 2 3 13 14 15
X
Xmv rc rc.bak1
X
X{
X	cat ${ACTIVE}
X	echo EOA
X	cat rc.bak1
X
X} |
Xawk '
XBEGIN{
X	act=1
X}
X$1 == "EOA" {
X	act=0
X	next
X}
XNF==4 {
X	if (act) { 
X		X[$1] = 1
X		L[$1] = $2+0 
X		if (L[$1] == 0) F[$1] = 0; else F[$1] = $3+0
X	}
X	next
X}
X$1 ~ /^#/ {
X	print $0
X	next
X}
XNF == 3 && ($1 == "!" || $1 == "+") {
X	if (X[$3] != 1) next
X	S[$3] = $1
X	if (L[$3] >= $2+0)
X		N[$3] = $2+0
X	else
X		N[$3] = F[$3]
X	next
X}
XEND {
X	for (g in N) printf("%s %06d %s\n", S[g], N[g], g)
X}
X' |
Xsort +2 > rc
X
Xexit 0
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nntidy.sh || echo "restore of nntidy.sh fails"
set `wc -c nntidy.sh`;Sum=$1
if test "$Sum" != "838"
then echo original size 838, current size $Sum;fi
echo "x - extracting nntp.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nntp.c &&
X/* 
X * nntp module for nn.
X *
X * The original taken from the nntp 1.5 clientlib.c
X * Modified heavily for nn.
X *
X * Rene' Seindal (seindal@diku.dk) Thu Dec  1 18:41:23 1988
X *
X * Last change: Tue May 23 18:38:49 1989
X */
X
X
X#include "config.h"
X
X/* 
X * 	nn maintains a cache of recently used articles to improve efficiency.
X * 	To change the size of the cache, define NNTPCACHE in config.h to be
X *	the new size of this cache.
X */
X
X#ifndef NNTPCACHE
X#define NNTPCACHE	10
X#endif
X
X#undef DEBUG
X
X#ifdef NNTP
X#include <stdio.h>
X#include "nntp.h"
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <netdb.h>
X
X/* This is necessary due to the definitions in m-XXX.h */
X#ifdef NETWORK_BYTE_ORDER
X#include <netinet/in.h>
X#endif
X
Xexport char nntp_server[256];	/* name of nntp server */
Xexport int use_nntp = 0;	/* bool: t iff we use nntp */
Xexport int nntp_failed = 0;	/* bool: t iff connection is broken */
X				/*  in nntp_get_article() or nntp_get_active() */
Ximport int silent;
Ximport char news_active[];
X
Xextern int errno, sys_nerr;
Xextern char *sys_errlist[];
X
X#define syserr() (errno >= 0 && errno < sys_nerr ? \
X		  sys_errlist[errno] : "Illegal error.")
X
Xextern char *mktemp();
X
Xstatic FILE *nntp_in = NULL;		/* fp for reading from server */
Xstatic FILE *nntp_out = NULL;		/* fp for writing to server */
Xstatic int is_connected = 0;		/* bool: t iff we are connected */
Xstatic group_header *group_hd;		/* ptr to servers current group */
Xstatic int is_set = 0;			/* bool: t iff group_hd is set */
X
X#define ERROR_LEVEL	400		/* All codes below are not errors */
X
X
X/* 
X * nntp_find_server: Find out which host to use as NNTP server.
X *
X * 	This is done by consulting the file NNTP_SERVER (defined in config.h).
X * 	Set nntp_server[] to the host's name.
X */
X
Xvoid nntp_find_server()
X{
X    char *cp, *name, *getenv();
X    char buf[BUFSIZ];
X    FILE *fp;
X    
X    /* 
X     * This feature cannot normally be enabled, because the data base
X     * and the users rc file contains references to articles by number,
X     * which is not unique across NNTP servers.
X     */
X#ifdef DEBUG 
X    if ((cp = getenv("NNTPSERVER")) != NULL) {
X	strncpy(nntp_server, cp, sizeof nntp_server);
X	return;
X    }
X#endif /* DEBUG */
X
X    name = NNTP_SERVER;
X    if (*name != '/')
X	name = relative(lib_directory, name);
X
X    if ((fp = open_file(name, OPEN_READ)) != NULL) {
X	while (fgets(buf, sizeof buf, fp) != 0) {
X	    if (*buf == '#' || *buf == '\n')
X		continue;
X	    if ((cp = strchr(buf, '\n')) != 0)
X		*cp = '\0';
X	    strncpy(nntp_server, buf, sizeof nntp_server);
X	    fclose(fp);
X	    return;
X	}
X	fclose(fp);
X    }
X    
X    if (!is_master)
X	printf("\nCannot find name of NNTP server.\nCheck %s\n", name);
X
X    sys_error("Failed to find name of NNTP server!");
X}
X
X/* 
X * nntp_check: Find out whether we need to use NNTP.
X *
X * 	This is done by comparing the NNTP servers name with whatever
X * 	gethostname() returns.  
X *	use_nntp and news_active[] are initialised as a side effect.
X */
X
Xvoid nntp_check()
X{
X    char host[128];
X    char *cp;
X
X    nntp_find_server();
X    gethostname(host, sizeof host);
X    use_nntp = strcmp(host, nntp_server) != 0; /* too simplistic ??? */
X
X    if (use_nntp) 
X	strcpy(news_active, relative(db_directory, "ACTIVE"));
X    else 
X	strcpy(news_active, NEWS_ACTIVE);
X}
X
X/* 
X * nntp_server_init: initialise a connection to the nntp server.
X *
X * 	It expects nntp_server[] to be set previously, by a call to
X * 	nntp_check.  It is called from nntp_get_article() and 
X *	nntp_get_active() if there is no established connection.
X */
X
Xnntp_server_init()
X{
X    int sockt_rd, sockt_wr;
X    int response;
X    char line[NNTP_STRLEN];
X
X    if (!is_master && !silent) {
X	printf("\rConnecting to NNTP server %s ... ", nntp_server);
X	fl;
X    }
X    nntp_failed = 1;
X
X    sockt_rd = nntp_get_socket();
X    if ((nntp_in = fdopen(sockt_rd, "r")) == NULL)
X        return -1;
X 
X    sockt_wr = dup(sockt_rd);
X    if ((nntp_out = fdopen(sockt_wr, "w")) == NULL) {
X        nntp_in = NULL;               /* from above */
X        return -1;
X    }
X
X    /* Now get the server's signon message */
X    response = nntp_get_server(line, sizeof(line));
X    if (response < 0 || response >= ERROR_LEVEL) {
X	if (!is_master) {
X	    user_error("Failed to connect to NNTP server.\n%s",
X		        response >= ERROR_LEVEL ? line : syserr());
X	    /* NOTREACHED */
X	} else {
X	    log_entry('N', "Failed to connect to NNTP server.\n%s",
X		      response >= ERROR_LEVEL ? line : syserr());
X	    fclose(nntp_out);
X	    fclose(nntp_in);
X	    return -1;
X	}
X    }
X    if (!is_master && !silent) {
X	fputs("ok.\r", stdout);
X	fl;
X    }
X    is_connected = 1;
X    nntp_failed = 0;
X    return 0;
X}
X
X/* 
X * nntp_get_socket:  get a connection to the nntp server.
X *
X * 	Doesn't return in case of errors.
X */
X
Xnntp_get_socket()
X{
X    int s;
X    struct sockaddr_in sin;
X    struct servent *getservbyname(), *sp;
X    struct hostent *gethostbyname(), *hp;
X#ifdef h_addr
X    int     x = 0;
X    register char **cp;
X#endif
X    int (*errfct)() = is_master ? sys_error : user_error;
X
X    if ((sp = getservbyname("nntp", "tcp")) ==  NULL)
X	(*errfct)("nntp/tcp: Unknown service.\n");
X
X    if ((hp = gethostbyname(nntp_server)) == NULL)
X	(*errfct)("NNTP server %s unknown.\n", nntp_server);
X
X    bzero((char *) &sin, sizeof(sin));
X    sin.sin_family = hp->h_addrtype;
X    sin.sin_port = sp->s_port;
X
X#ifdef  h_addr
X    /* get a socket and initiate connection -- use multiple addresses */
X
X    for (cp = hp->h_addr_list; cp && *cp; cp++) {
X	s = socket(hp->h_addrtype, SOCK_STREAM, 0);
X	if (s < 0)
X	    (*errfct)("Can't get NNTP socket: %s\n", syserr());
X	bcopy(*cp, (char *)&sin.sin_addr, hp->h_length);
X
X	x = connect(s, (struct sockaddr *)&sin, sizeof (sin));
X	if (x == 0)
X	    break;
X	if (!is_master)
X	    msg("Connecting to %s: %s", nntp_server, syserr());
X	(void) close(s);
X    }
X    if (x < 0 && !is_master)
X	(*errfct)("Giving up on NNTP server %s!\n", nntp_server);
X#else					/* no name server */
X    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
X	(*errfct)("Can't get NNTP socket: %s\n", syserr());
X
X    /* And then connect */
X    bcopy(hp->h_addr, (char *) &sin.sin_addr, hp->h_length);
X    if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
X	if (is_master)
X	    (*errfct)("Connecting to %s: %s", nntp_server, syserr());
X    }
X    
X#endif
X    return s;
X}
X
X/* 
X * nntp_put_server:  send a line to the nntp server.
X *
X * 	Expects to be connected to the server.  
X */
X
Xnntp_put_server(string)
Xchar *string;
X{
X#ifdef DEBUG
X    msg(">>> %s", string);
X#endif
X    fprintf(nntp_out, "%s\r\n", string);
X    if (fflush(nntp_out) == EOF) {
X	nntp_error();
X    }
X}
X
X/* 
X * nntp_get_server_line: get a line from the server.
X *
X * 	Expects to be connected to the server.  
X * 	The line can be any kind of line i.e., either response or text.
X */
X
Xnntp_get_server_line(string, size)
Xchar *string;
Xint size;
X{
X    register char *cp, *nl;
X
X    errno = 0;
X    if (fgets(string, size, nntp_in) == NULL) {
X	nntp_error();
X	return -1;
X    }
X    for (cp = string, nl = NULL; *cp != NUL; cp++) {
X	if (*cp == CR) {
X	    nl = cp;
X	    break;
X	}
X	if (nl == NULL && *cp == NL) 
X	    nl = cp;
X    }    
X    if (nl != NULL) *nl = NUL;
X
X    return 0;
X}
X
X/* 
X * nntp_get_server: get a response line from the server.
X *
X * 	Expects to be connected to the server.  
X * 	The numerical value of the response is returned.
X */
X
Xnntp_get_server(string, size)
Xchar *string;
Xint size;
X{
X    if (nntp_get_server_line(string, size) < 0)
X	return -1;
X    return isdigit(*string) ? atoi(string) : 0;
X}
X
X/* 
X * nntp_close_server: close the connection to the server.
X */
X
Xnntp_close_server()
X{
X    if (!is_connected)
X	return;
X
X    if (!nntp_failed) {			/* avoid infinite recursion */
X	char line[NNTP_STRLEN];
X
X	nntp_put_server("QUIT");
X	(void) nntp_get_server(line, sizeof line);
X    }
X
X    (void) fclose(nntp_out);
X    (void) fclose(nntp_in);
X
X    is_connected = 0;
X}
X
X/* 
X * nntp_ask_server:  ask the server a question and return the answer.
X *
X *	Expects to be connected to the server.  
X *	Returns the numerical value of the reponse, or -1 in case of errors.
X */
X
Xnntp_ask_server(string, size)
Xchar *string;
Xint size;
X{
X    (void)nntp_put_server(string);
X
X    if (nntp_get_server(string, size) < 0)
X	return -1;
X#ifdef DEBUG
X    msg("<<< %.75s", string);
X#endif
X    return isdigit(*string) ? atoi(string) : 0;
X}
X
X/* 
X * nntp_error: signal an error in talking to the server.
X *
X * 	An nn client terminates a session with the user.  
X *	The master simply closes the connection, which is probably 
X *	inadequate.  nntp_failed is set, for use by the master to
X *	terminate collection.
X */
X
Xnntp_error()
X{
X    nntp_failed = 1;
X    if (is_master) {
X	log_entry('N', "Lost connection to server %s: %s", nntp_server, syserr());
X	if (is_connected)
X	    nntp_close_server();
X    } else {
X#ifdef DEBUG
X	printf("Can't talk to NNTP server %s: %s", nntp_server, syserr());
X	abort();
X#else
X	user_error("Can't talk to NNTP server %s: %s", nntp_server, syserr());
X#endif
X    }
X}
X
X/* 
X * nntp_copy_text: copy text response into file.
X *
X * 	Sends COMMAND to the server, and copies the following text response
X * 	into an open file.
X *	Return -1 on error, 0 otherwise.
X */
X
Xnntp_copy_text(fp, command)
XFILE *fp;
Xchar *command;
X{
X    char buf[NNTP_STRLEN];
X    char *cp;
X    int n, nlines;
X
X    strcpy(buf, command);
X    if ((n = nntp_ask_server(buf, sizeof buf)) < 0) {
X	return -1;
X    }
X    if (n >= ERROR_LEVEL)
X	return -1;
X
X    nlines = 0;
X    while ((n = nntp_get_server_line(buf, sizeof buf)) >= 0) {
X	cp = buf;
X	if (*cp == '.')
X	    if (*++cp == '\0')
X		return nlines > 0 ? 0 : -1;
X	fputs(cp, fp);
X	putc('\n', fp);
X	nlines++;
X    }
X    return -1;
X}
X
X/* 
X * nntp_get_active:  get a copy of the active file.
X *
X * 	If we are the master get a copy of the file from the nntp server.
X * 	nnadmin just uses the one we already got.  In this way the master 
X *	can maintain a remote copy of the servers active file.  
X *	We try to be a little smart, if not inefficient, about the 
X *	modification times on the local active file.
X *	Even when the master is running on the nntp server, a separate
X *	copy of the active file will be made for access via NFS.
X */
X
Xnntp_get_active()
X{
X    FILE *old, *new;
X    char bufo[NNTP_STRLEN], bufn[NNTP_STRLEN];
X    char *new_name;
X    int same;
X
X    if (!is_master) 
X	return access(news_active, 4);
X	
X    if (!is_connected && nntp_server_init() < 0)
X	    return -1;
X
X    new_name = mktemp(relative(db_directory, ".actXXXXXX"));
X
X    if ((new = fopen(new_name, "w+")) == NULL)
X	return -1;
X    if (nntp_copy_text(new, "list") < 0) {
X	fclose(new);
X	unlink(new_name);
X	return -1;
X    }
X    rewind(new);
X    same = 0;
X    if ((old = open_file(news_active, OPEN_READ)) != NULL) {
X	do {
X	    fgets(bufo, sizeof bufo, old);
X	    fgets(bufn, sizeof bufn, new);
X	} while (!feof(old) && !feof(new) && strcmp(bufo, bufn) == 0);
X	same = feof(old) && feof(new);
X	fclose(old);
X    }
X    fclose(new);
X
X    if (same) 
X	unlink(new_name);
X    else
X	if (rename(new_name, news_active) != 0)
X	    sys_error("Cannot rename %s to %s", new_name, news_active);
X
X    return 0;
X}
X
X/* 
X * The following functions implements a simple lru cache of recently
X * accessed articles.  It is a simple way to improve effeciency.  Files
X * must be kept by name, because the rest of the code expects to be able
X * to open an article multiple times, and get separate file pointers.
X */
X
Xstruct cache {
X    char		*file_name;	/* file name */
X    article_number	art;		/* article stored in file */
X    group_header	*grp;		/* from this group */
X    unsigned		time;		/* time last accessed */
X} cache[NNTPCACHE];
X
Xstatic unsigned cur_time = 1;		/* virtual time */
X
X/* 
X * nntp_search_cache: search the cache for an (article, group) pair.
X *
X * 	Returns a pointer to the slot where it is, null otherwise 
X */
X
Xstruct cache *nntp_search_cache(art, gh)
Xarticle_number art;
Xgroup_header *gh;
X{
X    struct cache *cptr = cache;
X    int i;
X
X    for (i = 0; i < NNTPCACHE; i++, cptr++)
X	if (cptr->art == art && cptr->grp == gh) {
X	    cptr->time = cur_time++;
X	    return cptr;
X	}
X    return NULL;
X}
X
X/* 
X * nntp_new_slot: get a free cache slot.
X *
X * 	Returns a pointer to the allocated slot.  
X * 	Frees the old filename, and allocates a new, unused filename.
X *	The user's files are in ~/.nn, and the master's are in LIB_DIRECTORY.
X */
X
Xstruct cache *nntp_new_slot()
X{
X    struct cache *cptr = cache;
X    int i, lru;
X    unsigned min_time = cur_time;
X    char name[24];
X	
X    for (i = 0; i < NNTPCACHE; i++, cptr++)
X	if (min_time > cptr->time) {
X	    min_time = cptr->time;
X	    lru = i;
X	}
X    cptr = &cache[lru];
X
X    if (cptr->file_name) {
X	unlink(cptr->file_name);
X	free(cptr->file_name);
X    }
X    sprintf(name, "nn%02d-XXXXXX", lru);
X    cptr->file_name =
X	copy_str(relative(is_master ? lib_directory : nn_directory,
X			  mktemp(name)));
X    cptr->time = cur_time++;
X#ifdef DEBUG
X    msg("cache: select slot %d, file name %s", lru, cptr->file_name);
X#endif
X    return cptr;
X}
X
X/* 
X * nntp_clean_cache: clean up the cache.
X *
X * 	Removes all allocated files.
X */
X
Xvoid nntp_clean_cache()
X{
X    struct cache *cptr = cache;
X    int i;
X
X    for (i = 0; i < NNTPCACHE; i++, cptr++)
X	if (cptr->file_name)
X	    unlink(cptr->file_name);
X}
X
X/* 
X * nntp_set_group: set the server's current group.
X *
X * 	Actual communication is delayed until an article is accessed, to
X * 	avoid unnecessary traffic.
X */
X
Xnntp_set_group(gh)
Xgroup_header *gh;
X{
X    group_hd = gh;
X    is_set = 0;
X    return 0;
X}
X
X/* 
X * nntp_get_article: get an article from the server.
X *
X * 	Returns a FILE pointer.
X *	If necessary the server's current group is set.
X *	The article (header and body) are copied into a file, so they
X *	are seekable (nn likes that).
X */
X
XFILE *nntp_get_article(article)
Xarticle_number article;
X{
X    char buf[NNTP_STRLEN];
X    FILE *tmp;
X    struct cache *cptr;
X    int n;
X    
X    if (!is_connected && nntp_server_init() < 0) {
X	return NULL;
X    }
X    
X    /* 
X     * Set the server group to the current group
X     */
X    if (is_set == 0) {
X	sprintf(buf, "group %s", group_hd->group_name);
X	if ((n = nntp_ask_server(buf, sizeof buf)) < 0){
X	    return NULL;
X	}
X	if (n >= ERROR_LEVEL)
X	    return NULL;
X	is_set = 1;
X    }
X    /* 
X     * Search the cache for the requested article,and allocate a new
X     * slot if necessary.
X     */
X    cptr = nntp_search_cache(article, group_hd);
X    if (cptr != 0) {
X	return open_file(cptr->file_name, OPEN_READ);
X    } 
X    cptr = nntp_new_slot();
X
X    if ((tmp = open_file(cptr->file_name, OPEN_CREATE)) == NULL)
X	return NULL;
X
X    /* 
X     * Copy the article.
X     */
X    sprintf(buf, "article %d", article);
X    if (nntp_copy_text(tmp, buf) < 0) {
X	fclose(tmp);
X	return NULL;
X    }
X    fclose(tmp);
X    if ((tmp = open_file(cptr->file_name, OPEN_READ)) != NULL) {
X	cptr->art = article;
X	cptr->grp = group_hd;
X    }
X    return tmp;
X}
X
X/* 
X * nntp_cleanup:  clean up after an nntp session.
X *
X *	Called from nn_exit().
X */
X
Xnntp_cleanup()
X{
X    if (is_connected)
X	nntp_close_server();
X    nntp_clean_cache();
X}
X#endif /* NNTP */
X
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nntp.c || echo "restore of nntp.c fails"
set `wc -c nntp.c`;Sum=$1
if test "$Sum" != "15036"
then echo original size 15036, current size $Sum;fi
echo "x - extracting nntp.h (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nntp.h &&
X/*
X * Response codes for NNTP server
X *
X * @(#)nntp.h	1.7	(Berkeley) 1/11/88
X *
X * First digit:
X *
X *	1xx	Informative message
X *	2xx	Command ok
X *	3xx	Command ok so far, continue
X *	4xx	Command was correct, but couldn't be performed
X *		for some specified reason.
X *	5xx	Command unimplemented, incorrect, or a
X *		program error has occured.
X *
X * Second digit:
X *
X *	x0x	Connection, setup, miscellaneous
X *	x1x	Newsgroup selection
X *	x2x	Article selection
X *	x3x	Distribution
X *	x4x	Posting
X */
X
X#define	CHAR_INF	'1'
X#define	CHAR_OK		'2'
X#define	CHAR_CONT	'3'
X#define	CHAR_ERR	'4'
X#define	CHAR_FATAL	'5'
X
X#define	INF_HELP	100	/* Help text on way */
X#define	INF_DEBUG	199	/* Debug output */
X
X#define	OK_CANPOST	200	/* Hello; you can post */
X#define	OK_NOPOST	201	/* Hello; you can't post */
X#define	OK_SLAVE	202	/* Slave status noted */
X#define	OK_GOODBYE	205	/* Closing connection */
X#define	OK_GROUP	211	/* Group selected */
X#define	OK_GROUPS	215	/* Newsgroups follow */
X#define	OK_ARTICLE	220	/* Article (head & body) follows */
X#define	OK_HEAD		221	/* Head follows */
X#define	OK_BODY		222	/* Body follows */
X#define	OK_NOTEXT	223	/* No text sent -- stat, next, last */
X#define	OK_NEWNEWS	230	/* New articles by message-id follow */
X#define	OK_NEWGROUPS	231	/* New newsgroups follow */
X#define	OK_XFERED	235	/* Article transferred successfully */
X#define	OK_POSTED	240	/* Article posted successfully */
X
X#define CONT_XFER	335	/* Continue to send article */
X#define	CONT_POST	340	/* Continue to post article */
X
X#define	ERR_GOODBYE	400	/* Have to hang up for some reason */
X#define	ERR_NOGROUP	411	/* No such newsgroup */
X#define	ERR_NCING	412	/* Not currently in newsgroup */
X#define	ERR_NOCRNT	420	/* No current article selected */
X#define	ERR_NONEXT	421	/* No next article in this group */
X#define	ERR_NOPREV	422	/* No previous article in this group */
X#define	ERR_NOARTIG	423	/* No such article in this group */
X#define ERR_NOART	430	/* No such article at all */
X#define ERR_GOTIT	435	/* Already got that article, don't send */
X#define ERR_XFERFAIL	436	/* Transfer failed */
X#define	ERR_XFERRJCT	437	/* Article rejected, don't resend */
X#define	ERR_NOPOST	440	/* Posting not allowed */
X#define	ERR_POSTFAIL	441	/* Posting failed */
X
X#define	ERR_COMMAND	500	/* Command not recognized */
X#define	ERR_CMDSYN	501	/* Command syntax error */
X#define	ERR_ACCESS	502	/* Access to server denied */
X#define ERR_FAULT	503	/* Program fault, command not performed */
X
X/* RFC 977 defines this; don't change it. */
X
X#define	NNTP_STRLEN	512
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nntp.h || echo "restore of nntp.h fails"
set `wc -c nntp.h`;Sum=$1
if test "$Sum" != "2530"
then echo original size 2530, current size $Sum;fi
echo "x - extracting nnusage.1m (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nnusage.1m &&
X.TH NNUSAGE 1M "Release 6.1"
X.UC 4
X.SH NAME
Xnnusage \- display \fInn\fP usage statistics
X.SH SYNOPSIS
X.B nnusage
X[ -t ]
X.SH DESCRIPTION
X.B nnusage
Xwill extract the usage entries from the log file and calculate the
Xtotal usage time for each \fInn\fP user.
X.LP
XWithout options, the output will be sorted according to user names.
X.LP
XWith the \-t option, \fInnusage\fP will list the users ordered after
Xthe total usage time.
X.LP
XSince it is possible to
Xsuspend
X\fInn\fP, or leave the terminal while \fInn\fP is active, \fInn\fP
Xtries to be intelligent when it calculates the usage time so it will
Xtruly report the actual time spent on news reading.
X.SH FILES
X.DT
X.ta \w'$lib/Log'u+3m
X$lib/Log	The log file
X.DT
X.SH SEE ALSO
Xnn(1), nncheck(1), nngoback(1), nngrep(1), nntidy(1)
X.br
Xnnadmin(1M), nnquery(1M), nnmaster(8)
X.SH NOTES
XThe \fInn\fP package must have been compiled with the STATISTICS
Xoption turned on to produce the usage entries in the log file.
X.LP
XOnly \fInn\fP sessions longer than 5 minutes are registered in the log file.
X.SH AUTHOR
XKim F. Storm, Texas Instruments A/S, Denmark
X.br
XE-mail: storm@texas.dk
X
X
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nnusage.1m || echo "restore of nnusage.1m fails"
set `wc -c nnusage.1m`;Sum=$1
if test "$Sum" != "1119"
then echo original size 1119, current size $Sum;fi
echo "x - extracting nnusage.sh (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > nnusage.sh &&
X# CONFIG file is inserted here
X
Xif [ x"$1" = x'-t' ] ; then
X	SORTMODE="+1r"
Xelse
X	SORTMODE=""
Xfi
X
Xgrep '^U:' $LIB/Log |
X
Xawk ' 
XNF == 7 { 
X	if (split($7, t, ".") == 2) u[$5] += t[1] * 60 + t[2]
X}
XEND {
X	for (n in u) {
X		name=substr(n, 2, length(n)-3)
X		printf("%s%16d.%02d\n", name, u[n]/60, u[n]%60);
X	}
X}' |
X
Xsort $SORTMODE
NO_NEWS_IS_GOOD_NEWS
chmod 0644 nnusage.sh || echo "restore of nnusage.sh fails"
set `wc -c nnusage.sh`;Sum=$1
if test "$Sum" != "326"
then echo original size 326, current size $Sum;fi
echo "x - extracting options.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > options.c &&
X/*
X * generic option parsing 
X *
X * (c) Copyright 1988, Kim F. Storm, storm@texas.dk
X */
X
X#include "config.h"
X#include "options.h"
X
Xstatic char **save_argv, *usage_mesg;
Xstatic struct option_descr *save_optd;
X
Xchar *program_name(av)
Xchar **av;
X{
X    char *cp;
X    
X    /* skip "/path/" part of program name */
X    if (cp = strrchr(*av, '/')) 
X	return cp + 1;
X    else
X	return *av;
X}
X
X    
Xparse_options(ac, av, envname, options, usage)
Xint ac;
Xchar **av, *envname;
Xstruct option_descr options[];
Xchar *usage;
X{
X    register char *cp, opt;
X    register struct option_descr *od;
X    extern int atoi();
X    int files;
X    char **names;
X    char *envinit;
X
X    save_argv = av;
X    save_optd = options;
X    
X    if (options == NULL) return 0;
X
X    usage_mesg = usage;
X    
X    --ac;
X    names = ++av;    
X    files = 0;
X    
X    envinit = envname ? getenv(envname) : NULL;
X    cp = envinit;
X
X next_option:
X    
X    if (envinit) {
X	while (*cp && isspace(*cp)) cp++;
X	if (*cp == '-') {
X	    cp++;
X	    goto next_option;
X	}
X	if (*cp == NUL) {
X	    envinit = NULL;
X	    goto next_option;
X	}
X    } else
X	if (cp == NULL || *cp == NUL) {
X	    if ((cp = *av++) == NULL) {
X		*names = NULL;
X		return files;
X	    }
X	    ac--;
X	    
X	    if (*cp != '-') {
X		*names++ = cp;
X		cp = NULL;
X		files++;
X		goto next_option;
X	    }
X	    
X	    cp++; /* skip - */
X	}
X    
X    opt = *cp++;
X    
X    for (od = options; od->option_letter; od++) {
X	if (od->option_letter != opt) continue;
X	
X	switch (od->option_type) {
X	    
X	 case 1:	/* BOOL_OPTION */
X	    
X	    *((int *)(od->option_address)) = !*((int *)(od->option_address));
X	    goto next_option;
X	    
X	 case 2:	/* STRING_OPTION */
X	 case 3:	/* OPTIONAL_STRING */
X	    
X	    /* -oSTR or -o STR */
X	    
X	    while (*cp && isspace(*cp)) cp++;
X	    
X	    if (*cp == NUL) {
X		if (envinit || ac == 0) {
X		    if (od->option_type == 3) goto opt_str;
X		    error("missing string argumet to -%c", opt);
X		}
X		cp = *av++;
X		ac--;
X	    }
X	    
X	    if (od->option_type == 3 && *cp == '-') goto opt_str;
X	    
X	    *(od->option_address) = cp;
X	    
X	    if (envinit) {
X		while (*cp && !isspace(*cp)) cp++;
X		if (*cp) *cp++ = NUL;
X	    } else
X		cp = NULL;
X	    
X	    goto next_option;
X	    
X	 opt_str:
X	    *(od->option_address) = od->option_default;
X	    goto next_option;
X	    
X	 case 4:
X	 case 5:
X	    
X	    /* -oN or -o N */
X	    
X	    while (*cp && isspace(*cp)) cp++;
X	    
X	    if (*cp) {
X		if (!isdigit(*cp)) {
X		    if (od->option_type == 5) goto opt_int;
X		    error("non-numeric argument to -%c", opt);
X		}
X	    } else {
X		if (envinit || ac == 0 || !isdigit(**av)) {
X		    if (od->option_type == 5) goto opt_int;
X		    error("missing argument to -%c", opt);
X		}
X		
X		cp = *av++;
X		ac--;
X	    }
X	    *((int *)(od->option_address)) = atoi(cp);
X	    while (isdigit(*cp)) cp++;
X	    goto next_option;
X	    
X	 opt_int:
X	    *((int *)(od->option_address)) = (int)(od->option_default);
X	    goto next_option;
X	}
X    }
X    
X    error("unknown option '-%c'", opt);
X    /*NOTREACHED*/
X}
X
X
Xstatic error(message, option_letter)
Xchar *message, option_letter;
X{
X    char *prog_name = program_name(save_argv);
X    
X    fprintf(stderr, "%s: ", prog_name);
X    fprintf(stderr, message, option_letter);
X    fputc('\n', stderr);
X
X    fprintf(stderr, "usage: %s", prog_name);
X    
X    dump_options(1, "");
X    dump_options(2, " STR");
X    dump_options(3, " [STR]");
X    dump_options(4, " NUM");
X    dump_options(5, " [NUM]");
X        
X    if (usage_mesg) fprintf(stderr, usage_mesg);
X    fputc(NL, stderr);
X
X    nn_exit(9);
X}
X
Xstatic dump_options(type, tail)
Xint type;
Xchar *tail;
X{
X    register struct option_descr *od;
X    int any = 0;
X    
X    for (od = save_optd; od->option_letter; od++) {
X	if (od->option_type != type) continue;
X	fprintf(stderr, any ? "%c" : " -%c", od->option_letter );
X	any++;
X    }
X    
X    if (any && tail && tail[0]) {
X	fprintf(stderr, "%s", tail);
X    }
X}
NO_NEWS_IS_GOOD_NEWS
chmod 0644 options.c || echo "restore of options.c fails"
set `wc -c options.c`;Sum=$1
if test "$Sum" != "3891"
then echo original size 3891, current size $Sum;fi
echo "x - extracting options.h (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > options.h &&
X/*
X * options.h - include file for generic option parsing 
X *
X * (c) Copyright 1988, Kim F. Storm, storm@texas.dk
X */
X
X/*
X * To use this routine, you must a table called an Option_Description.
X * Each element in this table describes one possible option: 
X *	Its option letter
X *	Its argument type (if any)
X *	Whether an argument is mandatory or optional 
X *	The address of the variable holding the option value
X *	The defualt value if argument is optional
X *
X * Example:
X *
X *	A program accepts the following options:
X *		-a	[no value]
X *		-b N	[a numeric value]
X *		-p [N]	[an optional numeric value]
X *		-t S	[a string value]
X *
X * The corresponding option description table would then look like:
X * 
X *	#include <options.h>
X *	int a_flg = 1, b_value = 0, p_value = 0;
X *	char *t_string = "default";
X *
X *	Option_Description( options ) {
X *	    'a', Bool_Option(a_flg),
X *	    'b', Int_Option(b_value),
X *	    'p', Int_Option_Optional(p_value, -1),
X *	    't', String_Option(t_string),
X *	    '\0',
X *	 }
X * To parse the argument list - and the contents of the environment variable 
X * XXINIT, all that has to be done is to issue the following call:
X *
X *	files = parse_options(argc, argv, "XXINIT", options, NULL);
X *
X * If no environment variable is associated with the program, use NULL as
X * the third parameter.
X *
X * Upon return, the elements argv[1] .. argv[files] will contain
X * the file names (and other 'non-options') that occur in the argument list.
X *
X * The last NULL argument may be replaced by your own 'usage routine'
X * which will be called in the following way:
X *
X *	usage(pname)
X *	char *pname; /+ argv[0] without path +/
X *
X * 
X * char *program_name(argv)
X *
X * return a pointer to the last component of argv[0] (the program name with
X * with the path deleted).
X *
X
X */
X
X
Xstruct option_descr {
X    char	option_letter;
X    char	option_type;
X    char **	option_address;
X    char *	option_default;
X} ;
X
X
X#define	Option_Description(name) \
X    struct option_descr name[] =
X
X#define	Bool_Option(addr) \
X    1, (char **)(&addr), (char *)0
X
X#define	String_Option(addr) \
X    2, &addr, (char *)0
X
X#define String_Option_Optional(addr, default) \
X    3, &addr, default
X
X#define	Int_Option(addr) \
X    4, (char **)(&addr), (char *)0
X
X#define	Int_Option_Optional(addr, default) \
X    5, (char **)(&addr), (char *)default
NO_NEWS_IS_GOOD_NEWS
chmod 0644 options.h || echo "restore of options.h fails"
set `wc -c options.h`;Sum=$1
if test "$Sum" != "2333"
then echo original size 2333, current size $Sum;fi
echo "x - extracting pack_date.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > pack_date.c &&
X#include "config.h"
X
X/* #define DATE_TEST /* never define this !! */
X
X/*
X *	Calculate an approximate "time_stamp" value for a date
X *	string.  The actual value is not at all critical, 
NO_NEWS_IS_GOOD_NEWS
echo "End of part 11"
echo "File pack_date.c is continued in part 12"
echo "12" > s2_seq_.tmp
exit 0
---
Kim F. Storm        storm@texas.dk        Tel +45 429 174 00
Texas Instruments, Marielundvej 46E, DK-2730 Herlev, Denmark
	  No news is good news, but nn is better!

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