home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
unix
/
volume23
/
trn
/
part06
/
mthreads.c
< prev
Wrap
C/C++ Source or Header
|
1991-08-22
|
30KB
|
1,186 lines
/* $Header: mthreads.c,v 4.3.3.3 91/01/18 19:05:00 davison Trn $
**
** $Log: mthreads.c,v $
** Revision 4.3.3.3 91/01/18 19:05:00 davison
** Modified the way signals are handled to avoid endless loops. Added -s & -z
** options. Fixed a truncate bug and a problem with new groups not processing.
**
** Revision 4.3.3.2 90/08/20 16:43:19 davison
** Implemented new command-line interface and database upgrading.
**
** Revision 4.3.3.1 90/07/24 22:24:17 davison
** Initial Trn Release
**
*/
/* mthreads.c -- for making and updating a discussion-thread database
**
** We use the active file as our high/low counts for each group, and create
** an active2 file of our own to keep track of the high/lows of the database.
** When fully updated, the two files should be identical. This gives us
** quick access to the last processed high/low counts without opening
** each data file, PLUS it allows trn to use the fake active file as if it
** were the real thing to keep it from seeing articles before they are
** processed. If the active2 file is removed or corrupted, it will be
** automatically repaired in the normal course of operation. We update
** the file IN PLACE so that trn can keep it open all the time. Normally
** the size of the file does not change, so it is easy to do. In those
** rare instances where a news admin shuffles the real active file, we
** take it all in stride by throwing a little memory at the problem.
**
** Usage: mthreads [-d[MM]] [-e[HHMM]] [-aDfknv] [hierarchy_list]
*/
#include "EXTERN.h"
#include "common.h"
#ifdef SERVER
#include "server.h"
#endif
#include "INTERN.h"
#include "mthreads.h"
#ifdef TZSET
#include <time.h>
#else
#include <sys/time.h>
#include <sys/timeb.h>
#endif
FILE *fp_lock, *fp_log;
struct stat filestat;
static char line[256];
static char line2[256];
/* If you want to change the field size, do it once and then leave it alone. */
char fmt_active2[] = "%s %06ld %06ld %c\n";
char *filename;
typedef struct _active_line {
struct _active_line *link;
char *name;
long last;
long first;
char type;
} ACTIVE_LINE;
#define Nullact Null(ACTIVE_LINE*)
ACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
bool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
bool add_new = FALSE, rebuild = FALSE, zap_thread = FALSE, grevious_error;
int daemon_delay = 0, log_verbosity = 0, debug = 0, slow_down = 0;
long expire_time = 0;
char *hierarchy_list = NULL;
long truncate_len = -1;
char nullstr[] = "";
BMAP my_bmap, mt_bmap;
#ifdef TZSET
time_t tnow;
#else
struct timeb ftnow;
#endif
#define TIMER_FIRST 1
#define TIMER_DEFAULT (10 * 60)
int added_groups, removed_groups, action;
#define NG_DEFAULT 0
#define NG_MATCH 1
#define NG_SKIP 2
#ifdef SERVER
char *server;
#else
time_t last_modified;
#endif
SIGRET alarm_handler(), int_handler(), severe_handler();
void makethreads(), wrap_it_up();
main( argc, argv )
int argc;
char *argv[];
{
int fd;
long pid;
while( --argc ) {
if( **++argv == '-' ) {
while( *++*argv ) {
switch( **argv ) {
case 'a': /* automatically thread new groups */
add_new = TRUE;
break;
case 'D':
debug++;
break;
case 'd':
if( *++*argv <= '9' && **argv >= '0' ) {
daemon_delay = atoi( *argv ) * 60;
while( *++*argv <= '9' && **argv >= '0' ) {
;
}
} else {
daemon_delay = TIMER_DEFAULT;
}
--*argv;
break;
case 'e': {
struct tm *ts;
long desired;
(void) time( &expire_time );
ts = localtime( &expire_time );
if( *++*argv <= '9' && **argv >= '0' ) {
desired = atol( *argv );
if( desired/100 > 23 || desired%100 > 59 ) {
fprintf( stderr, "Illegal expire time: '%04d'\n",
desired );
exit( 1 );
}
desired = (desired/100)*60 + desired%100;
while( *++*argv <= '9' && **argv >= '0' ) {
;
}
} else {
desired = 30; /* 0030 = 12:30am */
}
--*argv;
desired -= ts->tm_hour * 60 + ts->tm_min;
if( desired < 0 ) {
desired += 24 * 60;
}
expire_time += desired * 60 - ts->tm_sec;
break;
}
case 'f':
force_flag = TRUE;
break;
case 'k':
kill_mthreads = TRUE;
break;
case 'n':
no_processing = TRUE;
break;
case 's':
slow_down++;
break;
case 'v':
log_verbosity++;
break;
case 'z':
zap_thread = TRUE;
break;
default:
fprintf( stderr, "Unknown option: '%c'\n", **argv );
exit( 1 );
}
}
} else {
if( hierarchy_list ) {
fprintf( stderr, "Specify the newsgroups in one comma-separated list.\n" );
exit( 1 );
}
hierarchy_list = *argv;
}
}
/* Set up a nice friendly umask. */
umask( 002 );
/* What time is it? */
#ifdef TZSET
(void) time( &tnow );
(void) tzset();
#else
(void) ftime( &ftnow );
#endif
/* Make sure we're not already running by creating a lock file.
** (I snagged this method from C news.)
*/
sprintf( line, "%s.%d", file_exp( "%X/LOCK" ), getpid() );
if( (fp_lock = fopen( line, "w" )) == Nullfp ) {
fprintf( stderr, "Unable to create lock temporary `%s'.\n", line );
exit( 1 );
}
fprintf( fp_lock, "%d\n", getpid() );
fclose( fp_lock );
/* Try to link to lock file. */
filename = file_exp( "%X/LOCKmthreads" );
dolink:
if( link( line, filename ) < 0 ) {
long otherpid;
/* Try to avoid possible race with daemon starting up. */
sleep (5);
if( (fp_lock = fopen( filename, "r")) == Nullfp ) {
fprintf( stderr, "unable to open %s\n", filename );
unlink( line );
exit( 1 );
}
if( fscanf( fp_lock, "%ld", &otherpid ) != 1) {
fprintf( stderr, "unable to read pid from %s\n", filename );
unlink( line );
fclose( fp_lock );
exit( 1 );
}
fclose( fp_lock );
if( kill( otherpid, kill_mthreads ? SIGTERM : 0 ) == -1
&& errno == ESRCH ) {
if( unlink( filename ) == -1 ) {
fprintf( stderr, "unable to unlink lockfile %s\n", filename );
unlink( line );
exit( 1 );
}
if( !kill_mthreads ) {
goto dolink;
}
}
unlink( line );
if( kill_mthreads ) {
fprintf( stderr, "killing currently running mthreads.\n" );
exit( 0 );
} else {
fprintf( stderr, "mthreads is already running.\n" );
exit( 1 );
}
}
unlink( line ); /* remove temporary LOCK.<pid> file */
if( kill_mthreads ) {
fprintf( stderr, "mthreads is not running.\n" );
exit( 1 );
}
/* Open our log file */
filename = file_exp( "%X/mt.log" );
if( (fp_log = fopen( filename, "a" )) == Nullfp ) {
fprintf( stderr, "Unable to open `%s'.\n", filename );
exit( 1 );
}
#ifdef SIGHUP
if( sigset( SIGHUP, SIG_IGN ) != SIG_IGN ) {
sigset( SIGHUP, int_handler );
}
#endif
if( sigset( SIGINT, SIG_IGN ) != SIG_IGN ) {
sigset( SIGINT, int_handler );
}
#ifdef SIGQUIT
if( sigset( SIGQUIT, SIG_IGN ) != SIG_IGN ) {
sigset( SIGQUIT, int_handler );
}
#endif
sigset( SIGTERM, int_handler );
#ifdef SIGBUS
sigset( SIGBUS, severe_handler );
#endif
sigset( SIGSEGV, severe_handler );
#ifdef SIGTTIN
sigset( SIGTTIN, SIG_IGN );
sigset( SIGTTOU, SIG_IGN );
#endif
sigset( SIGALRM, SIG_IGN );
#ifdef lint
alarm_handler(); /* foolishness for lint's sake */
int_handler( SIGINT );
severe_handler( SIGSEGV );
#endif
/* Ensure this machine has the right byte-order for the database */
filename = file_exp( "%X/db.init" );
if( (fp_lock = fopen( filename, FOPEN_RB )) == Nullfp
|| fread( &mt_bmap, 1, sizeof (BMAP), fp_lock ) < sizeof (BMAP)-1 ) {
if( fp_lock != Nullfp ) {
fclose( fp_lock );
}
write_db_init:
mybytemap( &mt_bmap );
if( (fp_lock = fopen( filename, FOPEN_WB )) == Nullfp ) {
log_entry( "Unable to create file: `%s'.\n", filename );
exit( 1 );
}
mt_bmap.version = DB_VERSION;
fwrite( &mt_bmap, 1, sizeof (BMAP), fp_lock );
fclose( fp_lock );
} else {
int i;
fclose( fp_lock );
if( mt_bmap.version != DB_VERSION ) {
if( mt_bmap.version == DB_VERSION-1 ) {
rebuild = TRUE;
log_entry( "Upgrading database to version %d.\n", DB_VERSION );
goto write_db_init;
}
log_entry( "** Database is not the right version (%d instead of %d) **\n",
mt_bmap.version, DB_VERSION );
exit( 1 );
}
mybytemap( &my_bmap );
for( i = 0; i < sizeof (LONG); i++ ) {
if( my_bmap.l[i] != mt_bmap.l[i]
|| (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i]) ) {
log_entry( "\
** Byte-order conflict -- re-run from a compatible machine **\n\
\t\tor remove the current thread files, including db.init **\n" );
exit( 1 );
}
}
}
#ifdef SERVER
server = getserverbyfile( SERVER_FILE );
if( server == NULL ) {
log_entry( "Couldn't find name of news server.\n" );
exit( 1 );
}
#endif
/* If we're not in daemon mode, run through once and quit. */
if( !daemon_delay ) {
log_entry( "mthreads single pass started.\n" );
setbuf( stdout, Nullch );
extra_expire = (expire_time != 0);
makethreads();
} else {
/* For daemon mode, we cut ourself off from anything tty-related and
** run in the background (involves forks, but no knives).
*/
close( 0 );
if( open( "/dev/null", 2 ) != 0 ) {
fprintf( stderr, "unable to open /dev/null!\n" );
exit( 1 );
}
close( 1 );
close( 2 );
dup( 0 );
dup( 0 );
while( (pid = fork()) < 0 ) {
sleep( 2 );
}
if( pid ) {
exit( 0 );
}
#ifdef TIOCNOTTY
if( (fd = open( "/dev/tty", 1 )) >= 0 ) {
ioctl( fd, TIOCNOTTY, (int*)0 );
close( fd );
}
#else
(void) setpgrp();
while( (pid = fork()) < 0 ) {
sleep( 2 );
}
if( pid ) {
exit( 0 );
}
#endif
/* Put our pid in the lock file for death detection */
if( (fp_lock = fopen( file_exp( "%X/LOCKmthreads" ), "w" )) != Nullfp ) {
fprintf( fp_lock, "%d\n", getpid() );
fclose( fp_lock );
}
log_entry( "mthreads daemon started.\n" );
#ifndef SERVER
last_modified = 0;
#endif
sigset( SIGALRM, alarm_handler );
/* Start timer -- first interval is shorter than all others */
alarm( TIMER_FIRST );
for( ;; ) {
if( caught_interrupt ) {
wrap_it_up( 0 );
/* NORETURN */
}
pause(); /* let alarm go off */
if( caught_interrupt ) {
wrap_it_up( 0 );
/* NORETURN */
}
alarm( 0 );
/* Re-open our log file, if needed */
if( !fp_log && !(fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
wrap_it_up( 1 );
}
#ifndef SERVER
if( stat( file_exp( ACTIVE ), &filestat ) < 0 ) {
log_entry( "Unable to stat active file -- quitting.\n" );
wrap_it_up( 1 );
}
#endif
if( expire_time && time( 0L ) > expire_time ) {
expire_time += 24L * 60 * 60;
extra_expire = TRUE;
}
#ifdef SERVER
if( 1 ) { /* always compare files */
#else
if( extra_expire || filestat.st_mtime != last_modified ) {
last_modified = filestat.st_mtime;
#endif
makethreads();
}
alarm( daemon_delay );
fclose( fp_log ); /* close the log file while we sleep */
fp_log = Nullfp;
} /* for */
}/* if */
wrap_it_up( 0 );
}
SIGRET
alarm_handler()
{
sigset( SIGALRM, alarm_handler );
}
SIGRET
int_handler( sig )
int sig;
{
/* Flag interrupt occurred -- main loop attempts an orderly retreat. */
if( ++caught_interrupt >= 3 ) {
wrap_it_up( 1 );
}
/* Re-open our log file, if needed */
if( fp_log || (fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
if( sig == SIGTERM ) {
log_entry( "mthreads halted.\n", sig);
} else {
log_entry( "Interrupt %d received.\n", sig);
}
}
if( !daemon_delay ) {
printf( "interrupt %d!\n", sig );
}
}
/* Severe interrupts require severe action -- abort immediately, possibly
** removing the thread file on the way.
*/
SIGRET
severe_handler( sig )
int sig;
{
/* Let's be a bit paranoid here -- avoid any possibility of looping. */
if( caught_interrupt >= 10 ) {
wrap_it_up( 1 );
}
caught_interrupt = 10;
/* Destroy offending thread file if requested to do so. */
if( zap_thread ) {
unlink( thread_name( line ) );
}
/* Re-open our log file, if needed */
if( fp_log || (fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
log_error( "** Severe signal: %d **\n", sig );
if( zap_thread ) {
log_entry( "Destroyed thread file for %s\n", line );
}
}
if( !daemon_delay ) {
printf( "Severe signal: %d!\n", sig);
if( zap_thread ) {
printf( "Destroyed thread file for %s\n", line );
}
}
wrap_it_up( 1 );
}
void
wrap_it_up( ret )
int ret;
{
unlink( file_exp( "%X/LOCKmthreads" ) ); /* remove lock */
exit( ret );
}
/* Process the active file, creating/modifying the active2 file and
** creating/modifying the thread data files.
*/
void
makethreads()
{
register char *cp, *cp2;
FILE *fp_active, *fp_active2, *fp_active3;
long first, last, first2, last2;
char ch, ch2;
char data_file_open;
bool update_successful;
bool eof_active = FALSE, eof_active2 = FALSE;
#ifdef SERVER
switch( server_init( server ) ) {
case OK_NOPOST:
case OK_CANPOST:
break;
case ERR_ACCESS:
log_entry( "Server %s rejected connection -- quitting.\n", server );
wrap_it_up( 1 );
default:
log_entry( "Couldn't connect with server %s -- sleeping.\n", server );
return;
}
put_server( "LIST" ); /* ask server for the active file */
get_server( line, sizeof line );
if( *line != CHAR_OK ) {
log_entry( "Unable to get active file from server -- sleeping.\n" );
close_server();
return;
}
if( (fp_active = fopen( file_exp( ACTIVE1 ), "w+" )) == Nullfp ) {
log_entry( "Unable to write the active1 file.\n" );
wrap_it_up( 1 );
}
while( 1 ) {
if( caught_interrupt ) {
wrap_it_up( 0 );
/* NORETURN */
}
if( get_server( line, sizeof line ) < 0 ) {
log_entry( "Server failed to send entire active file -- sleeping.\n" );
fclose( fp_active );
close_server();
return;
}
if( *line == '.' ) {
break;
}
fputs( line, fp_active );
putc( '\n', fp_active );
}
fseek( fp_active, 0L, 0 ); /* rewind for read */
#else
if( (fp_active = fopen( file_exp( ACTIVE ), "r" )) == Nullfp ) {
log_entry( "Unable to open the active file.\n" );
wrap_it_up( 1 );
}
#endif
filename = file_exp( ACTIVE2 );
if( (fp_active3 = fopen( filename, "r+" )) == Nullfp ) {
if( (fp_active3 = fopen( filename, "w" )) == Nullfp ) {
log_entry( "Unable to open the active2 file for update.\n" );
wrap_it_up( 1 );
}
}
if( (fp_active2 = fopen( filename, "r" )) == Nullfp ) {
log_entry( "Unable to open the active2 file.\n" );
wrap_it_up( 1 );
}
if( caught_interrupt ) {
wrap_it_up( 0 );
/* NORETURN */
}
if( extra_expire && log_verbosity ) {
log_entry( "Using enhanced expiration for this pass.\n" );
}
processed_groups = added_groups = removed_groups = 0;
added_articles = expired_articles = 0;
/* Loop through entire active file. */
for( ;; ) {
if( eof_active || !fgets( line, sizeof line, fp_active ) ) {
if( eof_active2 && !line_root ) {
break;
}
eof_active = TRUE;
ch = 'x';
} else {
if( !(cp = index( line, ' ' )) ) {
log_entry( "active line has no space: %s\n", line );
continue;
}
*cp = '\0';
if( sscanf( cp+1, "%ld %ld %c", &last, &first, &ch ) != 3 ) {
log_entry( "active digits corrupted: %s %s\n", line, cp+1 );
continue;
}
}
if( debug || log_verbosity > 3 ) {
log_entry( "Processing %s:\n", line );
}
data_file_open = 0;
/* If we've allocated some lines in memory while searching for
** newsgroups (they've scrambled the active file on us), check
** them first.
*/
last_line = Nullact;
for( pline = line_root; pline; pline = pline->link ) {
if( eof_active || strEQ( line, pline->name ) ) {
strcpy( line2, pline->name );
free( pline->name );
first2 = pline->first;
last2 = pline->last;
ch2 = pline->type;
if( last_line ) {
last_line->link = pline->link;
} else {
line_root = pline->link;
}
free( pline );
break;
}
last_line = pline;
}/* for */
/* If not found yet, check the active2 file. */
if( !pline ) {
for( ;; ) {
if( eof_active2 || !fgets( line2, sizeof line2, fp_active2 ) ) {
/* At end of file, check if the thread data file exists.
** If so, use its high/low values. Else, default to
** some initial values.
*/
eof_active2 = TRUE;
if( eof_active ) {
break;
}
strcpy( line2, line );
if( (data_file_open = init_data( thread_name( line ) )) ) {
last2 = total.last;
first2 = total.first;
ch2 = 'y';
} else {
total.first = first2 = first;
if( add_new ) {
total.last = last2 = first - 1;
ch2 = (ch == '=' ? 'x' : ch);
added_groups++;
} else {
total.last = last2 = last;
ch2 = (ch == '=' ? 'X' : toupper( ch ));
}
}
data_file_open++; /* (1 == empty, 2 == open) */
break;
}
if( !(cp2 = index( line2, ' ' )) ) {
log_entry( "active2 line has no space: %s\n", line2 );
continue;
}
*cp2 = '\0';
if( sscanf( cp2+1,"%ld %ld %c",&last2,&first2,&ch2 ) != 3 ) {
log_entry( "active2 digits corrupted: %s %s\n",
line2, cp2+1 );
continue;
}
/* Check if we're still in-sync */
if( eof_active || strEQ( line, line2 ) ) {
break;
}
/* Nope, we've got to go looking for this line somewhere
** down in the file. Save each non-matching line in memory
** as we go.
*/
pline = (ACTIVE_LINE*)safemalloc( sizeof (ACTIVE_LINE) );
pline->name = savestr( line2 );
pline->last = last2;
pline->first = first2;
pline->type = ch2;
pline->link = Nullact;
if( !last_line ) {
line_root = pline;
} else {
last_line->link = pline;
}
last_line = pline;
}/* for */
if( eof_active && eof_active2 ) {
break;
}
}/* if !pline */
if( eof_active ) {
strcpy( line, line2 );
if( truncate_len < 0 ) {
truncate_len = ftell( fp_active3 );
}
}
if( rebuild ) {
unlink( thread_name( line ) );
}
update_successful = FALSE;
if( hierarchy_list ) {
action = ngmatch( hierarchy_list, line );
} else {
action = NG_DEFAULT;
}
switch( action ) {
case NG_DEFAULT:
if( ch2 < 'a' ) {
action = NG_SKIP;
} else {
action = NG_MATCH;
}
break;
case NG_MATCH: /* add if unthreaded */
if( ch2 < 'a' ) {
total.last = last2 = first2 - 1;
added_groups++;
}
break;
case NG_SKIP: /* remove if threaded */
if( ch2 >= 'a' && !debug ) {
unlink( thread_name( line ) );
removed_groups++;
}
break;
}
if( caught_interrupt || (debug && action != NG_MATCH) ) {
dont_read_data( data_file_open ); /* skip silently */
} else if( ch == 'x' || ch == '=' ) {
if( !daemon_delay ) { /* skip 'x'ed groups */
putchar( 'x' );
}
ch = (action == NG_SKIP ? 'X' : 'x');
if( (ch2 >= 'a' && ch2 != 'x') || force_flag ) {
/* Remove thread file if group is newly 'x'ed out */
unlink( thread_name( line ) );
}
update_successful = TRUE;
dont_read_data( data_file_open );
} else if( action == NG_SKIP ) { /* skip excluded groups */
if( !daemon_delay ) {
putchar( 'X' );
}
ch = toupper( ch );
if( force_flag ) {
unlink( thread_name( line ) );
}
update_successful = TRUE;
dont_read_data( data_file_open );
} else if( no_processing ) {
if( !daemon_delay ) {
putchar( ',' );
}
ch2 = ch;
dont_read_data( data_file_open );
} else if( !force_flag && !extra_expire && !rebuild
&& first == first2 && last == last2 ) {
/* We're up-to-date here. Skip it. */
if( !daemon_delay ) {
putchar( '.' );
}
update_successful = TRUE;
dont_read_data( data_file_open );
} else {
/* Looks like we need to process something. */
#ifdef SERVER
sprintf( line2, "GROUP %s", line );
put_server( line2 ); /* go to next group */
if( get_server( line2, sizeof line2 ) < 0 || *line2 != CHAR_OK ) {
log_entry( "NNTP failure on group `%s'.\n", line );
#else
cp = line2;
while( (cp = index( cp, '.' )) ) {
*cp = '/';
}
filename = file_exp( line2 ); /* relative to spool dir */
if( chdir( filename ) < 0 ) {
if (errno != ENOENT) {
log_entry( "Unable to chdir to `%s'.\n", filename );
}
#endif
if( !daemon_delay ) {
putchar( '*' );
}
dont_read_data( data_file_open );
} else {
filename = thread_name( line );
/* Try to open the data file only if we didn't try it
** in the name matching code above.
*/
if( !data_file_open-- ) { /* (0 == haven't tried yet) */
if( !(data_file_open = init_data( filename )) ) {
total.last = first - 1;
total.first = first;
}
}
strcpy( line2, filename );
cp = rindex( line2, '/' ) + 1;
if( data_file_open ) { /* (0 == empty, 1 == open) */
if( !read_data() ) { /* did read fail? */
#ifndef DEBUG
unlink( filename ); /* trash input file */
#else
strcpy( cp, "bad.read" );
rename( filename, line2 );
#endif
data_file_open = init_data( filename );
total.last = first - 1;
total.first = first;
}
}
grevious_error = FALSE;
process_articles( first, last );
processed_groups++;
if( caught_interrupt ) {
processed_groups--; /* save nothing -- no update */
} else if( !added_count && !expired_count && last == last2 ) {
(void) write_data( Nullch );
if( !daemon_delay ) {
putchar( ':' );
}
update_successful = TRUE;
} else if( !total.root ) {
/* When the data file goes empty, remove it. */
unlink( filename );
expired_articles += expired_count;
if( !daemon_delay ) {
putchar( '-' );
}
update_successful = TRUE;
} else {
strcpy( cp, NEW_THREAD ); /* write data as .new */
if( write_data( line2 ) && !grevious_error ) {
rename( line2, filename );
added_articles += added_count;
expired_articles += expired_count;
if( !daemon_delay ) {
putchar( '#' );
}
update_successful = TRUE;
} else {
#ifndef DEBUG
unlink( line2 ); /* blow-away bad write */
#else
cp = rindex( filename, '/' ) + 1;
strcpy( cp, "bad.write" );
rename( line2, filename );
#endif
if( !daemon_delay ) {
putchar( '!' );
}
}/* if */
}/* if */
}/* if */
}/* if */
/* Finally, update the active2 entry for this newsgroup. */
if( update_successful ) {
fprintf( fp_active3, fmt_active2, line, last, first, ch );
} else {
fprintf( fp_active3, fmt_active2, line, last2, first2, ch2 );
}
/* If we're not out of sync, keep active2 file flushed. */
if( !line_root ) {
fflush( fp_active3 );
}
}/* for */
#ifdef SERVER
close_server();
#endif
fclose( fp_active );
fclose( fp_active2 );
fclose( fp_active3 );
if( truncate_len >= 0 ) {
#ifdef TRUNCATE
if( truncate( file_exp( ACTIVE2 ), truncate_len ) == -1 )
log_entry( "Unable to truncate the active2 file.\n" );
#else
#ifdef CHSIZE
int fd;
if( (fd = open( file_exp( ACTIVE2 ), O_RDWR )) == -1 )
log_entry( "Unable to open the active2 file for truncation.\n" );
else {
if( chsize( fd, truncate_len ) == -1 )
log_entry( "Unable to truncate the active2 file.\n" );
close( fd );
}
#else
filename = file_exp( ACTIVE2 );
sprintf( line, "%s.new", filename );
if( (fp_active3 = fopen( line, "w" )) == Nullfp ) {
log_entry( "Unable to create the active2.new file.\n" );
} else if( (fp_active2 = fopen( filename, "r" )) == Nullfp ) {
fclose( fp_active3 );
unlink( line );
log_entry( "Unable to open the active2 file.\n" );
} else {
while( ftell( fp_active3 ) < truncate_len ) {
if( !fgets( line2, sizeof line2, fp_active2 ) ) {
break;
}
fputs( line2, fp_active3 );
}
sprintf( line2, "%s.old", filename );
rename( filename, line2 );
rename( line, filename );
fclose( fp_active2 );
fclose( fp_active3 );
}
#endif /* not XENIX */
#endif /* not TRUNCATE */
truncate_len = -1;
}
sprintf( line, "Processed %d group%s: added %d article%s, expired %d.\n",
processed_groups, processed_groups == 1 ? nullstr : "s",
added_articles, added_articles == 1 ? nullstr : "s",
expired_articles );
if( processed_groups ) {
log_entry( line );
}
if( !daemon_delay ) {
putchar( '\n' );
fputs( line, stdout );
}
if( added_groups ) {
sprintf( line, "Turned %d group%s on.\n", added_groups,
added_groups == 1 ? nullstr : "s" );
log_entry( line );
if( !daemon_delay ) {
fputs( line, stdout );
}
}
if( removed_groups ) {
sprintf( line, "Turned %d group%s off.\n", removed_groups,
removed_groups == 1 ? nullstr : "s" );
log_entry( line );
if( !daemon_delay ) {
fputs( line, stdout );
}
}
extra_expire = FALSE;
rebuild = FALSE;
}
/*
** ngmatch - newsgroup name matching
**
** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
** and NG_DEFAULT if the group doesn't match at all.
**
** "all" in a pattern is a wildcard that matches exactly one word;
** it does not cross "." (NGDELIM) delimiters.
**
** This matching code was borrowed from C news.
*/
#define ALL "all" /* word wildcard */
#define NGNEG '!'
#define NGSEP ','
#define NGDELIM '.'
int
ngmatch( ngpat, grp )
char *ngpat, *grp;
{
register char *patp; /* point at current pattern */
register char *patcomma;
register int depth;
register int faildeepest = 0, hitdeepest = 0; /* in case no match */
register bool negation;
for( patp = ngpat; patp != Nullch; patp = patcomma ) {
negation = FALSE;
patcomma = index( patp, NGSEP );
if( patcomma != Nullch ) {
*patcomma = '\0'; /* will be restored below */
}
if( *patp == NGNEG ) {
++patp;
negation = TRUE;
}
depth = onepatmatch( patp, grp ); /* try 1 pattern, 1 group */
if( patcomma != Nullch ) {
*patcomma++ = NGSEP; /* point after the comma */
}
if( depth == 0 ) { /* mis-match */
; /* ignore it */
} else if( negation ) {
/* record depth of deepest negated matched word */
if( depth > faildeepest ) {
faildeepest = depth;
}
} else {
/* record depth of deepest plain matched word */
if( depth > hitdeepest ) {
hitdeepest = depth;
}
}
}
if( hitdeepest > faildeepest ) {
return NG_MATCH;
} else if( faildeepest ) {
return NG_SKIP;
} else {
return NG_DEFAULT;
}
}
/*
** Match a pattern against a group by looking at each word of pattern in turn.
**
** On a match, return the depth (roughly, ordinal number * k) of the rightmost
** word that matches. If group runs out first, the match fails; if pattern
** runs out first, it succeeds. On a failure, return zero.
*/
int
onepatmatch( patp, grp )
char *patp, *grp;
{
register char *rpatwd; /* used by word match (inner loop) */
register char *patdot, *grdot; /* point at dots after words */
register char *patwd, *grwd; /* point at current words */
register int depth = 0;
for( patwd = patp, grwd = grp;
patwd != Nullch && grwd != Nullch;
patwd = patdot, grwd = grdot
) {
register bool match = FALSE;
register int incr = 20;
/* null-terminate words */
patdot = index(patwd, NGDELIM);
if( patdot != Nullch ) {
*patdot = '\0'; /* will be restored below */
}
grdot = index( grwd, NGDELIM );
if( grdot != Nullch ) {
*grdot = '\0'; /* will be restored below */
}
/*
* Match one word of pattern with one word of group.
* A pattern word of "all" matches any group word,
* but isn't worth as much.
*/
#ifdef FAST_STRCMP
match = STREQ( patwd, grwd );
if( !match && STREQ( patwd, ALL ) ) {
match = TRUE;
--incr;
}
#else
for( rpatwd = patwd; *rpatwd == *grwd++; ) {
if( *rpatwd++ == '\0' ) {
match = TRUE; /* literal match */
break;
}
}
if( !match ) {
/* ugly special case match for "all" */
rpatwd = patwd;
if( *rpatwd++ == 'a' && *rpatwd++ == 'l'
&& *rpatwd++ == 'l' && *rpatwd == '\0' ) {
match = TRUE;
--incr;
}
}
#endif /* FAST_STRCMP */
if( patdot != Nullch ) {
*patdot++ = NGDELIM; /* point after the dot */
}
if( grdot != Nullch ) {
*grdot++ = NGDELIM;
}
if( !match ) {
depth = 0; /* words differed - mismatch */
break;
}
depth += incr;
}
/* if group name ran out before pattern, then match fails */
if( grwd == Nullch && patwd != Nullch ) {
depth = 0;
}
return depth;
}
/* Generate a log entry with timestamp.
*/
/*VARARGS1*/
void
log_entry( fmt, arg1, arg2 )
char *fmt;
long arg1;
long arg2;
{
time_t now;
char *ctime();
(void) time( &now );
fprintf( fp_log, "%.12s ", ctime( &now )+4 );
fprintf( fp_log, fmt, arg1, arg2 );
fflush( fp_log );
}
/* Generate an log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
** and newsgroup name.
*/
/*VARARGS1*/
void
log_error( fmt, arg1, arg2, arg3 )
char *fmt;
long arg1;
long arg2;
long arg3;
{
log_entry( "%s: ", line );
fprintf( fp_log, fmt, arg1, arg2, arg3 );
fflush( fp_log );
if( *fmt == '*' ) {
grevious_error = TRUE;
if( !daemon_delay ) {
putchar( 'E' );
}
}
else {
if( !daemon_delay ) {
putchar( 'e' );
}
}
}
#ifndef RENAME
int
rename( old, new )
char *old, *new;
{
struct stat st;
if( stat( old, &st ) == -1 ) {
return -1;
}
if( unlink( new ) == -1 && errno != ENOENT ) {
return -1;
}
if( link( old, new ) == -1 ) {
return -1;
}
if( unlink( old ) == -1 ) {
int e = errno;
(void) unlink( new );
errno = e;
return -1;
}
return 0;
}
#endif /*RENAME*/