v23i068: TRN, version of RN that follows conversation threads, Part09/14

Rich Salz rsalz at bbn.com
Tue Dec 4 07:28:00 AEST 1990


Submitted-by: Wayne Davison <davison at dri.com>
Posting-number: Volume 23, Issue 68
Archive-name: trn/part09

---- Cut Here and unpack ----
#!/bin/sh
# this is part 9 of a multipart archive
# do not concatenate these parts, unpack them in order with /bin/sh
# file mt.check.SH continued
#
CurArch=9
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 mt.check.SH"
sed 's/^X//' << 'SHAR_EOF' >> mt.check.SH
X# 
X# Check mt.log for earth-shattering errors, and mail them to \$gurus if found.
X# Then move the mt.log file into a week-long history chain.
X#
X# Usage: mt.check
X#
X
Xgurus="$newsadmin"
Xtmp="/tmp/mt.c\$\$"
X
XPATH=/bin:/usr/bin
Xexport PATH
X
Xumask 002
X
Xtrap "rm -f \$tmp ; exit 0" 0 1 2 15
X
Xcd $rnlib
X
X$egrep " \\*\\*\$" mt.log >\$tmp
X
Xif test -s \$tmp ; then
X	(cat <<EOT
XTo: \$gurus
XSubject: mthreads error!
X
XThe following errors were reported in mt.log.  Please report this fact
Xto Wayne Davison (davison%drivax at uunet.uu.net) for him to fix.  All of
Xthe affected newsgroups can not be updated until this bug is fixed, or
Xthe offending articles are expired.
X
XEOT
X	cat \$tmp) | mail \$gurus
Xfi
X
Xmv mt.log.6 mt.log.7
Xmv mt.log.5 mt.log.6
Xmv mt.log.4 mt.log.5
Xmv mt.log.3 mt.log.4
Xmv mt.log.2 mt.log.3
Xmv mt.log   mt.log.2
Xtouch mt.log
X
Xexit 0
SHAR_EOF
echo "File mt.check.SH is complete"
chmod 0770 mt.check.SH || echo "restore of mt.check.SH fails"
echo "x - extracting mthreads.1 (Text)"
sed 's/^X//' << 'SHAR_EOF' > mthreads.1 &&
X''' $Header: mthreads.1,v 4.3.3.1 90/07/21 20:03:37 davison Trn $
X''' 
X''' $Log:	mthreads.1,v $
X''' Revision 4.3.3.1  90/07/21  20:03:37  davison
X''' Initial Trn Release
X''' 
X''' 
X.de Sh
X.br
X.ne 5
X.PP
X\fB\\$1\fR
X.PP
X..
X.de Sp
X.if t .sp .5v
X.if n .sp
X..
X.de Ip
X.br
X.ie \\n.$>=3 .ne \\$3
X.el .ne 3
X.IP "\\$1" \\$2
X..
X'''
X'''     Set up \*(-- to give an unbreakable dash;
X'''     string Tr holds user defined translation string.
X'''     Bell System Logo is used as a dummy character.
X'''
X.tr \(bs-|\(bv\*(Tr
X.ie n \{\
X.ds -- \(bs-
X.if (\n(.H=4u)&(1m=24u) .ds -- \(bs\h'-12u'\(bs\h'-12u'-\" diablo 10 pitch
X.if (\n(.H=4u)&(1m=20u) .ds -- \(bs\h'-12u'\(bs\h'-8u'-\" diablo 12 pitch
X.ds L" ""
X.ds R" ""
X.ds L' '
X.ds R' '
X'br\}
X.el\{\
X.ds -- \(em\|
X.tr \*(Tr
X.ds L" ``
X.ds R" ''
X.ds L' `
X.ds R' '
X'br\}
X.TH MTHREADS 1 LOCAL
X.UC 6
X.SH NAME
Xmthreads - threaded database manager for trn
X.SH SYNOPSIS
X.B mthreads [-d[MM]] [-e[HHMM]] [-f] [-v] [hierarchy]
X.SH DESCRIPTION
X.I Mthreads
Xmanages the thread files that are used by the
X.IR trn (1)
Xnewsreader.
X\*(L"Thread files\*(R" are used to store information about the news
Xarticles and how they are all related to one another.
X.PP
X.I Mthreads
Xneeds to be run
Xperiodically \*(-- either in single-pass mode out of cron,
Xor in daemon mode out of your boot script \*(-- to update the thread
Xinformation whenever new news is received.
XIf a site only gets one feed a day and doesn't need local postings processed
Xthroughout the day, running
X.I mthreads
Xin single-pass mode once a day may be all that is needed.
XOtherwise, daemon mode should be used, where mthreads forks off
Xa background process and wakes up every 10 minutes (by default) to check if
Xthe active file has been updated.
X.SH INSTALLATION
X.I Mthreads
Xin installed in the RNLIB directory chosen during configuration.
XWhen it is run for first time, it will automatically create a file called
X.I active2
Xin the same directory.
XThis file is essentially a copy of the active file that keeps the newsgroup
Xtotals from the last run in one place.
XThe active2 file is also used to limit the number of groups to be processed
Xinto thread files.
XIf groups need to be limited for testing purposes, use
Xa command like:
X.IP
Xmthreads news.software
X.PP
Xto process only the groups that match the given hierarchy name.
XIf mthreads is to be more selective or limit the groups on a more permanent
Xbasis, edit the fourth field of the
X.I active2
Xfile to change the single character (e.g. \*(L'y\*(R', \*(L'm\*(R',
Xetc). to upper-case.
XIf the active2 file hasn't been created yet, use the command:
X.IP
Xmthreads namethatwontmatch
X.PP
Xto create a valid active2 file without processing any groups.
XSee also the
X.B \-f
Xoption if an active2 file has invalid min/max
Xfields (such as if the active file has been directly copied to active2).
X.PP
XIf no further processing of a group is desired, change its entry in active2
Xto have the upper-case 4th field and then be sure to delete its thread file.
XThis is the only time that
X.I mthreads
Xwon't be able to handle the thread file upkeep automatically.
X(It knows to delete thread files when the group goes empty, when the group
Xgets removed, and when it gets x-ed out in the active file.)
X.SH LOGGING
XAs mthreads executes, some status information (including error messages) 
Xis placed in
Xthe file mt.log in your RNLIB directory.
XMt.log can grow without bounds, and should be scanned periodically for
Xerrors, and trimmed in size when it grows too large.
XSee the shell script
X.I mt.check
Xfor a simple example that can be run on a daily basis.
X.SH OPTIONS
X.TP 5
X.B \-d
Xis used to specify the daemon mode, where
X.I mthreads
Xforks a background task that periodically wakes up and checks for an updated
Xactive file.
XThe number of minutes to wait after the completion of the last pass can
Xbe specified after the '-d' option (e.g. -d20), otherwise it will default to
X10 minutes.
X.TP 5
X.B \-e
Xtells
X.I mthreads
Xto run an enhanced expiration check on the database.
XWithout this option, only articles below the minimum field in the active
Xfile are expired.
XWith this option, all unexpired articles in the database are stat()'ed to
Xsee if they actually exist.
XIn single-pass mode the
X.B -e
Xoption always affects the current pass \*(-- use it
Xonce a day after expire has run.
XIn daemon mode, the
X.B -e
Xoption will cause one pass a day to be the enhanced expire pass.
XBy default, this is the first time mthreads wakes up after 12:30 am.
XIf a different time is desired, it can be specified in the form HHMM 
X(e.g. -e2359).
X.TP 5
X.B -f
Xis used to force
X.I mthreads
Xto open each and every thread file to see which ones really need to be
Xupdated, not just the ones that differ in the active/active2 comparison.
XThis should only be used when setting up initially
Xor when you are hacking around with the thread files.
X.TP 5
X.B -v
Xselects additional levels of verbosity in the log file.
XThe default (without -v) is to log mthread's startup, the totals for each
Xpass, the inclusion of the enhanced expire option, and major database errors.
XAdd one
X.B -v
Xto get extra reference line problems logged into the file.
XAdd a second and a third for even more (useless?) information.
X.TP 5
X.B hierarchy
XThe scope of the groups processec can be limited
Xby specifying one hierarchy (e.g. \*(L"news\*(R",
X\*(L"comp.sys.ibm.pc.d\*(R").
XTo run mthreads on a limited set of newsgroups in normal operation
Xedit the active2 file (NOT the active file!) and change the fourth
Xfield to be upper-case for each group that should not be processed
X(e.g. \*(L'Y\*(R', \*(L'M\*(R', etc.)
X.SH OUTPUT
XWhen
X.I mthreads
Xis run in single-pass mode it generates a stream a status characters on
Xstdout that present a visual display of what is happening.  If 
Xsingle-pass mode is used for regular processing, this output can be
Xredirected to /dev/null.
X.Sp
XThe output definitions:
X.br
X	\&'.' = group's entry is up-to-date
X.br
X	\&':' = group processed -- no change
X.br
X	\&'+' = group processed
X.br
X	\&'-' = group processed -- is now empty
X.br
X	\&'x' = group excluded in active
X.br
X	\&'X' = group excluded in active2
X.br
X	\&'*' = chdir failed (might be ok)
X.br
X	\&'!' = write failed (is NOT ok)
X.br
X	\&'e' = informational error
X.br
X	\&'E' = database-affecting error -- please report!
X.SH CONFIGURATION
XDuring the configuration of
X.I trn
Xa choice was made about where to place the thread data files.
XThey either exist as a .thread file in each group's spool directory, or
Xthey are a group.th file in a one-off directory structure on another drive.
XSee the THREAD_DIR definition in config.h to review or change this definition.
X.SH REBUILDING
XIf the thread files are ever removed, also remove the file db.init in
Xthe RNLIB directory.
XThis file contains the byte-order of the machine that generated the database,
Xand needs to be removed to truly start from scratch.
X.SH ERROR HANDLING
XIf the active2 file is removed or corrupted, it will
Xbe automatically repaired in the normal course of operation.
XMissing thread files are automatically re-built.
X.SH FILES
X/usr/lib/news/active
X.br
X$RNLIB/active2
X.br
X$RNLIB/mt.log
X.br
X$RNLIB/db.init
X.br
X$RNLIB/LOCKmthreads
X.br
XLots of thread data files.
X.SH AUTHOR
XWayne Davison <davison at drivax.UUCP> <davison%drivax at uunet.uu.net>
SHAR_EOF
chmod 0660 mthreads.1 || echo "restore of mthreads.1 fails"
echo "x - extracting mthreads.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > mthreads.c &&
X/* $Header: mthreads.c,v 4.3.3.1 90/07/24 22:24:17 davison Trn $
X**
X** $Log:	mthreads.c,v $
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 tn 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 should
X** be automatically repaired in the normal course of operation.  We update
X** the file IN PLACE so that tn 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]] [-f] [-v] [hierarchy]
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
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, grevious_error;
Xint daemon_delay = 0;
Xlong expire_time = 0;
Xint log_verbosity = 0;
Xchar *hierarchy = "all";
Xint match_len = 0;
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
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 '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 '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( match_len ) {
X		fprintf( stderr, "Only one hierarchy name is currently allowed.\n" );
X		exit( 1 );
X	    }
X	    hierarchy = *argv;
X	    match_len = strlen( hierarchy );
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, 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	    goto dolink;
X	}
X	fprintf( stderr, "mthreads is already running.\n" );
X	unlink( line );
X	exit( 1 );
X    }
X
X    unlink( line );			/* remove temporary LOCK.<pid> file */
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) ) {
X	if( fp_lock != Nullfp ) {
X	    fclose( fp_lock );
X	}
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 = 1;
X	fwrite( &mt_bmap, 1, sizeof (BMAP), fp_lock );
X	fclose( fp_lock );
X    } else {
X	int i;
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	fclose( fp_lock );
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 for '%s'\n", hierarchy );
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 for '%s'\n", hierarchy );
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	    pause();		/* let alarm go off */
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	exit( 1 );
X    }
X    if( sig == SIGTERM ) {
X	log_entry( "interrupt %d\n", sig);
X    } else {
X	log_entry( "** interrupt %d **\n", sig);
X    }
X    if( !daemon_delay ) {
X	printf( "interrupt %d!\n", sig );
X    }
X    /* Handle interrupt by just bugging out */
X    wrap_it_up();
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( 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( extra_expire && log_verbosity ) {
X	log_entry( "Using enhanced expiration for this pass.\n" );
X    }
X
X    processed_groups = 0;
X    added_articles = 0;
X    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		    } else {
X			total.last = last2 = first - 1;
X			total.first = first2 = first;
X		    }
X		    ch2 = (ch == 'x' ? 'n' : ch);
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	update_successful = FALSE;
X	if( match_len && strnNE( line, hierarchy, match_len ) ) {
X	    dont_read_data( data_file_open );		/* skip non-matches */
X	} else if( ch == 'x' ) {
X	    if( !daemon_delay ) {			/* skip 'x'ed groups */
X		putchar( 'x' );
X	    }
X	    dont_read_data( data_file_open );
X	    if( ch2 == 'X' ) {
X		ch = ch2;
X	    } else if( ch2 != 'x' ) {
X		/* Remove thread file if group is newly 'x'ed out */
X		unlink( thread_name( line ) );
X	    }
X	    update_successful = TRUE;
X	} else if( ch2 < 'a' ) {
X	    /* This group is marked 'Y', 'M', 'N', or '=' in active2.
X	    ** Don't process it.
X	    */
X	    if( !daemon_delay ) {
X		putchar( 'X' );
X	    }
X	    dont_read_data( data_file_open );
X	    if( ch2 != '=' ) {
X		if( ch != '=' ) {
X		    ch = toupper( ch );
X		} else {
X		    ch = ch2;
X		}
X	    }
X	    update_successful = TRUE;
X	} else if( !force_flag && !extra_expire
X	 && first == first2 && last == last2 ) {
X	    /* We're up-to-date here.  Skip it. */
X	    if( !daemon_delay ) {
X		putchar( '.' );
X	    }
X	    dont_read_data( data_file_open );
X	    update_successful = TRUE;
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		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		    }
X		}
X	    }
X	}
X	/* Finally, update the active2 entry for this newsgroup. */
X	if( update_successful ) {
X	    fprintf( fp_active3, "%s %06ld %06ld %c\n",
X		line, last, first, ch );
X	} else {
X	    fprintf( fp_active3, "%s %06ld %06ld %c\n",
X		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    extra_expire = FALSE;
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*/
SHAR_EOF
chmod 0660 mthreads.c || echo "restore of mthreads.c fails"
echo "x - extracting mthreads.h (Text)"
sed 's/^X//' << 'SHAR_EOF' > mthreads.h &&
X/* $Header: mthreads.h,v 4.3.3.1 90/06/20 22:55:27 davison Trn $
X**
X** $Log:	mthreads.h,v $
X** Revision 4.3.3.1  90/06/20  22:55:27  davison
X** Initial Trn Release
X** 
X*/
X
X#ifdef lint
X#include "mt-lint.h"
X#endif
X#include "threads.h"
X
XEXT TOTAL total;
X
XEXT int processed_groups;
XEXT int added_articles, added_count;
XEXT int expired_articles, expired_count;
XEXT bool extra_expire INIT(FALSE);
X
XEXT char *strings INIT(0);
XEXT WORD *subject_cnts INIT(0);
XEXT WORD *author_cnts INIT(0);
XEXT WORD *ids INIT(0);
X
XEXT SUBJECT **subject_array;
XEXT ROOT **root_array;
XEXT AUTHOR **author_array;
XEXT ARTICLE **article_array;
X
XEXT PACKED_ROOT p_root;
XEXT PACKED_ARTICLE p_article;
X
XEXT ROOT *root_root;
XEXT AUTHOR *author_root;
X
X#ifndef DOINIT
XEXT DOMAIN unk_domain;
X#else
XDOMAIN unk_domain = {
X    ".unknown.", NULL, NULL
X};
X#endif
X
Xvoid log_entry(), log_error();
X
Xvoid mybytemap();
Xint read_data(), write_data();
Xvoid dont_read_data(), process_data();
X
Xvoid process_articles();
X
Xchar *thread_name(), *file_exp(), *savestr();
X
X#ifndef lint
Xchar *safemalloc();
Xvoid free(), Free();
X#endif
X
X#define Nullart Null(ARTICLE*)
SHAR_EOF
chmod 0660 mthreads.h || echo "restore of mthreads.h fails"
echo "x - extracting ndir.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > ndir.c &&
X/* $Header: ndir.c,v 4.3.3.1 90/06/20 22:38:20 davison Trn $
X *
X * $Log:	ndir.c,v $
X * Revision 4.3.3.1  90/06/20  22:38:20  davison
X * Initial Trn Release
X * 
X * Revision 4.3.1.3  85/05/23  11:19:24  lwall
X * Oops, shouldn't have included sys/types.h again.
X * 
X * Revision 4.3.1.2  85/05/15  14:46:00  lwall
X * Changed short to ino_t, which may be ushort on some systems.
X * 
X * Revision 4.3.1.1  85/05/10  11:35:34  lwall
X * Branch for patches.
X * 
X * Revision 4.3  85/05/01  11:42:55  lwall
X * Baseline for release with 4.3bsd.
X * 
X */
X
X#include "EXTERN.h"
X#include "common.h"
X#include "INTERN.h"
X#include "ndir.h"
X
X#ifdef USENDIR
X/*
X * support for Berkeley directory reading routine on a V7 file system
X */
X
X/*
X * open a directory.
X */
XDIR *
Xopendir(name)
Xchar *name;
X{
X	register DIR *dirp;
X	register int fd;
X	char *malloc();
X
X	if ((fd = open(name, 0)) == -1)
X		return NULL;
X	if ((dirp = (DIR *)malloc(sizeof(DIR))) == NULL) {
X		close (fd);
X		return NULL;
X	}
X	dirp->dd_fd = fd;
X	dirp->dd_loc = 0;
X	return dirp;
X}
X
X/*
X * read an old style directory entry and present it as a new one
X */
X#ifndef pyr
X#define	ODIRSIZ	14
X
Xstruct	olddirect {
X	ino_t	od_ino;
X	char	od_name[ODIRSIZ];
X};
X#else	an Pyramid in the ATT universe
X#define	ODIRSIZ	248
X
Xstruct	olddirect {
X	long	od_ino;
X	short	od_fill1, od_fill2;
X	char	od_name[ODIRSIZ];
X};
X#endif
X
X/*
X * get next entry in a directory.
X */
Xstruct direct *
Xreaddir(dirp)
Xregister DIR *dirp;
X{
X	register struct olddirect *dp;
X	static struct direct dir;
X
X	for (;;) {
X		if (dirp->dd_loc == 0) {
X			dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf,
X			    DIRBLKSIZ);
X			if (dirp->dd_size <= 0)
X				return NULL;
X		}
X		if (dirp->dd_loc >= dirp->dd_size) {
X			dirp->dd_loc = 0;
X			continue;
X		}
X		dp = (struct olddirect *)(dirp->dd_buf + dirp->dd_loc);
X		dirp->dd_loc += sizeof(struct olddirect);
X		if (dp->od_ino == 0)
X			continue;
X		dir.d_ino = dp->od_ino;
X		strncpy(dir.d_name, dp->od_name, ODIRSIZ);
X		dir.d_name[ODIRSIZ] = '\0'; /* insure null termination */
X		dir.d_namlen = strlen(dir.d_name);
X		dir.d_reclen = DIRSIZ(&dir);
X		return (&dir);
X	}
X}
X
X/*
X * close a directory.
X */
Xvoid
Xclosedir(dirp)
Xregister DIR *dirp;
X{
X	close(dirp->dd_fd);
X	dirp->dd_fd = -1;
X	dirp->dd_loc = 0;
X	free(dirp);
X}
X
X#endif /* USENDIR */
SHAR_EOF
chmod 0660 ndir.c || echo "restore of ndir.c fails"
echo "x - extracting ndir.h (Text)"
sed 's/^X//' << 'SHAR_EOF' > ndir.h &&
X/* $Header: ndir.h,v 4.3.2.1 90/04/17 15:28:13 sob Exp $
X *
X * $Log:	ndir.h,v $
X * Revision 4.3.2.1  90/04/17  15:28:13  sob
X * Altered to include correct directory include file.
X * 
X * Revision 4.3  85/05/01  11:43:00  lwall
X * Baseline for release with 4.3bsd.
X * 
X */
X
X#ifdef LIBNDIR
X#   include <ndir.h>
X#else
X#   ifndef USENDIR
X#	include DIRINC
X#   else
X
X#ifndef DEV_BSIZE
X#define	DEV_BSIZE	512
X#endif
X#define DIRBLKSIZ	DEV_BSIZE
X#define	MAXNAMLEN	255
X
Xstruct	direct {
X	long	d_ino;			/* inode number of entry */
X	short	d_reclen;		/* length of this record */
X	short	d_namlen;		/* length of string in d_name */
X	char	d_name[MAXNAMLEN + 1];	/* name must be no longer than this */
X};
X
X/*
X * The DIRSIZ macro gives the minimum record length which will hold
X * the directory entry.  This requires the amount of space in struct direct
X * without the d_name field, plus enough space for the name with a terminating
X * null byte (dp->d_namlen+1), rounded up to a 4 byte boundary.
X */
X#undef DIRSIZ
X#define DIRSIZ(dp) \
X    ((sizeof (struct direct) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3))
X
X/*
X * Definitions for library routines operating on directories.
X */
Xtypedef struct _dirdesc {
X	int	dd_fd;
X	long	dd_loc;
X	long	dd_size;
X	char	dd_buf[DIRBLKSIZ];
X} DIR;
X#ifndef NULL
X#define NULL 0
X#endif
Xextern	DIR *opendir();
Xextern	struct direct *readdir();
Xextern	long telldir();
Xextern	void seekdir();
X#define rewinddir(dirp)	seekdir((dirp), (long)0)
Xextern	void closedir();
X
X#   endif
X#endif
SHAR_EOF
chmod 0660 ndir.NOPSIS

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