TRN Fixup package

Gregory G. Woodbury ggw%wolves at cs.duke.edu
Thu Dec 20 15:53:13 AEST 1990


Submitted-by: ggw at wolves.UUCP
Archive-name: trnfix/part01

This shar contains the files that are missing from the c.s.u. posting of
trn.

The files here already have patch1 applied!

After unpacking all of the CSU parts and applying the patch (and letting
parts of the patch process fail), unpack this archive and then run Configure
and continue.

I think all the missing pieces are here, I can build trn so I generated
a differences list in the directories between the tar extract and the csu
extract and added the one file that was not covered.

					Enjoy
					Greg

---- Cut Here and feed the following to sh ----
#!/bin/sh
# This is trnfix, a shell archive (shar 3.47)
# made 12/20/1990 04:40 UTC by ggw at wolves.UUCP
# Source directory /news/Src/TRN/Src
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#   1276 -rwxr-x--- mt.check.SH
#   8949 -rw-r----- mthreads.1
#  28049 -rw-r----- mthreads.c
#   1279 -rw-r----- mthreads.h
#   2251 -rw-r----- ndir.c
#   1491 -rw-r----- ndir.h
#
# ============= mt.check.SH ==============
if test -f 'mt.check.SH' -a X"$1" != X"-c"; then
	echo 'x - skipping mt.check.SH (File already exists)'
else
echo 'x - extracting mt.check.SH (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'mt.check.SH' &&
Xcase $CONFIG in
X    '') . ./config.sh ;;
Xesac
Xecho "Extracting mt.check (with variable substitutions)"
X$spitshell >mt.check <<!GROK!THIS!
X$startsh
X# $Header: mt.check.SH,v 4.3.3.2 90/08/20 16:42:10 davison Trn $
X#
X# $Log:	mt.check.SH,v $
X# Revision 4.3.3.2  90/08/20  16:42:10  davison
X# Changed email address.
X# 
X# Revision 4.3.3.1  90/06/20  23:00:07  davison
X# Initial Trn Release
X# 
X# mt.check - daily maintenance for mt.log
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 at dri.com or ...!uunet!drivax!davison) for his
Xattention.  The affected newsgroups can't be updated until this bug is
Xfixed, or the 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
chmod 0750 mt.check.SH ||
echo 'restore of mt.check.SH failed'
Wc_c="`wc -c < 'mt.check.SH'`"
test 1276 -eq "$Wc_c" ||
	echo 'mt.check.SH: original size 1276, current size' "$Wc_c"
fi
# ============= mthreads.1 ==============
if test -f 'mthreads.1' -a X"$1" != X"-c"; then
	echo 'x - skipping mthreads.1 (File already exists)'
else
echo 'x - extracting mthreads.1 (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'mthreads.1' &&
X''' $Header: mthreads.1,v 4.3.3.2 90/08/20 16:42:32 davison Trn $
X''' 
X''' $Log:	mthreads.1,v $
X''' Revision 4.3.3.2  90/08/20  16:42:32  davison
X''' Document new command-line interface.
X''' 
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]] [-aDfknv] [hierarchy_list]
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 the boot script \*(-- to update the thread
Xinformation whenever new news is received.
XA site that gets its news feed during the night and doesn't need local
Xpostings processed throughout the day can run
X.I mthreads
Xin single-pass mode once a day.
XIf more processing is needed, either run
X.I mthreads
Xmore often or run it in daemon mode.
XIn daemon mode, a background process is forked off that wakes up every 10
Xminutes (by default) to check if the 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 the 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.
XIt is also used to choose which groups are to be processed into thread files.
XAll groups start out as \*(L"unthreaded\*(R" unless they are turned on with
Xa command like:
X.IP
Xmthreads all
X.PP
Xwhich would create thread file for all the groups.
XFor testing purposes it is a good idea to start out small with a command
Xlike:
X.IP
Xmthreads news
X.PP
Xwhich would thread only the news hierarchy.
XThread processing can be turned on or off for individual groups or entire
Xhierarchies by specifying the groups in a syntax very similar to that used
Xin the sys file.
XFor example, to turn on all of soc and talk except for talk.politics, and
Xto turn off news.lists, use the following command once:
X.IP
Xmthreads soc,talk,!talk.politics,!news.lists
X.PP
XIf mthreads complains that another mthreads process is already running,
Xit can be killed with the command:
X.IP
Xmthreads -k
X.PP
Xand then repeat the prior command after giving it a little time to die.
X.PP
XOnce all the desired groups are turned on, it is not necessary to
Xspecify a hierarchy list for the normal functioning of mthreads.
XIt can be used, however, to customize which new groups get turned on
Xas they are created.
X.SH LOGGING
XAs mthreads executes, some status information (including error messages) 
Xis placed in
Xthe file mt.log in the RNLIB directory.
XThis file will 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 an mt.log maintainer that will send mail if it finds database errors.
X.SH OPTIONS
X.TP 5
X.B \-a
Xis used to automatically turn on thread processing for new news groups as
Xthey are created.
XThe default is to leave new groups unthreaded.
X.TP 5
X.B \-D
Xspecifies a debugging mode where only the groups mentioned on the
Xcommand-line are processed \*(-- all other groups are left unchanged.
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.
XIt will also remove any extraneous thread files from unthreaded groups
X(which should only occur if you manually change the active2 file).
XThis option should only be used when manipulating the thread files in
Xunorthodox ways.
X.TP 5
X.B -k
Xcan be used to terminate the currently running mthreads, just as if it
Xhad received a terminate signal.
XWhen this option is specified, no other activity is performed.
X.TP 5
X.B -n
Xtells
X.I mthreads
Xthat no actual processing of thread files is to be performed.
XThis can be used to just adjust which groups are to be processed, without
Xactually doing any of the processing right away.
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_list
XThe hierarchy list is used to turn thread processing on or off for the listed
Xgroups.
XThe groups are specified in a manner very similar to the news software's
Xsys file:  \*(L"news\*(R" matches all groups in news; \*(L"!news\*(R" excludes
Xall groups in news; \*(L"comp.all.ibm.pc,!comp.all.ibm.pc.all\*(L" matches both
Xcomp.sys.ibm.pc and comp.binaries.ibm.pc, but not comp.binaries.ibm.pc.d.
X.Sp
XThe hierarchy_list is also used in conjunction with the debug flag to only
Xprocess one or more groups for testing purposes.
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.
XAn easy way to get
X.I mthreads
Xto remove all the files except for db.init is to specify the command:
X.IP
Xmthreads !all
X.PP
XThis also turns off thread processing for all groups.
X.SH "ERROR HANDLING"
XIf the active2 file is removed or corrupted, it will
Xbe automatically rebuilt in the normal course of operation.
XThe record of which groups should be threaded will be lost, however.
XMissing/corrupted thread files are automatically re-built.
X.SH EXAMPLES
XRecommended commands to run on a regular basis are:
X.IP
Xmthreads -adve0630
X.PP
Xto start up an mthreads daemon in verbose logging mode that automatically
Xthreads new groups and performs an extended expire at 6:30 am, or:
X.IP
Xmthreads -e >/dev/null
X.PP
Xto run an mthreads single-pass with extended expire that leaves new groups
Xunthreaded.
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 dri.com> <uunet!drivax!davison>
SHAR_EOF
chmod 0640 mthreads.1 ||
echo 'restore of mthreads.1 failed'
Wc_c="`wc -c < 'mthreads.1'`"
test 8949 -eq "$Wc_c" ||
	echo 'mthreads.1: original size 8949, current size' "$Wc_c"
fi
# ============= mthreads.c ==============
if test -f 'mthreads.c' -a X"$1" != X"-c"; then
	echo 'x - skipping mthreads.c (File already exists)'
else
echo 'x - extracting mthreads.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'mthreads.c' &&
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*/
SHAR_EOF
chmod 0640 mthreads.c ||
echo 'restore of mthreads.c failed'
Wc_c="`wc -c < 'mthreads.c'`"
test 28049 -eq "$Wc_c" ||
	echo 'mthreads.c: original size 28049, current size' "$Wc_c"
fi
# ============= mthreads.h ==============
if test -f 'mthreads.h' -a X"$1" != X"-c"; then
	echo 'x - skipping mthreads.h (File already exists)'
else
echo 'x - extracting mthreads.h (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'mthreads.h' &&
X/* $Header: mthreads.h,v 4.3.3.2 90/08/20 16:44:29 davison Trn $
X**
X** $Log:	mthreads.h,v $
X** Revision 4.3.3.2  90/08/20  16:44:29  davison
X** New entries for new command-line interface.
X** 
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);
XEXT bool caught_interrupt 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
Xint ngmatch(), onepatmatch();
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 0640 mthreads.h ||
echo 'restore of mthreads.h failed'
Wc_c="`wc -c < 'mthreads.h'`"
test 1279 -eq "$Wc_c" ||
	echo 'mthreads.h: original size 1279, current size' "$Wc_c"
fi
# ============= ndir.c ==============
if test -f 'ndir.c' -a X"$1" != X"-c"; then
	echo 'x - skipping ndir.c (File already exists)'
else
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 0640 ndir.c ||
echo 'restore of ndir.c failed'
Wc_c="`wc -c < 'ndir.c'`"
test 2251 -eq "$Wc_c" ||
	echo 'ndir.c: original size 2251, current size' "$Wc_c"
fi
# ============= ndir.h ==============
if test -f 'ndir.h' -a X"$1" != X"-c"; then
	echo 'x - skipping ndir.h (File already exists)'
else
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 0640 ndir.h ||
echo 'restore of ndir.h failed'
Wc_c="`wc -c < 'ndir.h'`"
test 1491 -eq "$Wc_c" ||
	echo 'ndir.h: original size 1491, current size' "$Wc_c"
fi
exit 0
-- 
Gregory G. Woodbury @ The Wolves Den UNIX, Durham NC
UUCP: ...dukcds!wolves!ggw   ...mcnc!wolves!ggw           [use the maps!]
Domain: ggw at cds.duke.edu     ggw%wolves at mcnc.mcnc.org
[The line eater is a boojum snark! ]           <standard disclaimers apply>



More information about the Alt.sources mailing list