home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume23 / trn / part06 / mthreads.c < prev   
C/C++ Source or Header  |  1991-08-22  |  30KB  |  1,186 lines

  1. /* $Header: mthreads.c,v 4.3.3.3 91/01/18 19:05:00 davison Trn $
  2. **
  3. ** $Log:    mthreads.c,v $
  4. ** Revision 4.3.3.3  91/01/18  19:05:00  davison
  5. ** Modified the way signals are handled to avoid endless loops.  Added -s & -z
  6. ** options.  Fixed a truncate bug and a problem with new groups not processing.
  7. ** 
  8. ** Revision 4.3.3.2  90/08/20  16:43:19  davison
  9. ** Implemented new command-line interface and database upgrading.
  10. ** 
  11. ** Revision 4.3.3.1  90/07/24  22:24:17  davison
  12. ** Initial Trn Release
  13. ** 
  14. */
  15.  
  16. /* mthreads.c -- for making and updating a discussion-thread database
  17. **
  18. ** We use the active file as our high/low counts for each group, and create
  19. ** an active2 file of our own to keep track of the high/lows of the database.
  20. ** When fully updated, the two files should be identical.  This gives us
  21. ** quick access to the last processed high/low counts without opening
  22. ** each data file, PLUS it allows trn to use the fake active file as if it
  23. ** were the real thing to keep it from seeing articles before they are
  24. ** processed.  If the active2 file is removed or corrupted, it will be
  25. ** automatically repaired in the normal course of operation.  We update
  26. ** the file IN PLACE so that trn can keep it open all the time.  Normally
  27. ** the size of the file does not change, so it is easy to do.  In those
  28. ** rare instances where a news admin shuffles the real active file, we
  29. ** take it all in stride by throwing a little memory at the problem.
  30. **
  31. ** Usage:  mthreads [-d[MM]] [-e[HHMM]] [-aDfknv] [hierarchy_list]
  32. */
  33.  
  34. #include "EXTERN.h"
  35. #include "common.h"
  36. #ifdef SERVER
  37. #include "server.h"
  38. #endif
  39. #include "INTERN.h"
  40. #include "mthreads.h"
  41.  
  42. #ifdef TZSET
  43. #include <time.h>
  44. #else
  45. #include <sys/time.h>
  46. #include <sys/timeb.h>
  47. #endif
  48.  
  49. FILE *fp_lock, *fp_log;
  50.  
  51. struct stat filestat;
  52.  
  53. static char line[256];
  54. static char line2[256];
  55.  
  56. /* If you want to change the field size, do it once and then leave it alone. */
  57. char fmt_active2[] = "%s %06ld %06ld %c\n";
  58.  
  59. char *filename;
  60.  
  61. typedef struct _active_line {
  62.     struct _active_line *link;
  63.     char *name;
  64.     long last;
  65.     long first;
  66.     char type;
  67. } ACTIVE_LINE;
  68.  
  69. #define Nullact Null(ACTIVE_LINE*)
  70.  
  71. ACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
  72.  
  73. bool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
  74. bool add_new = FALSE, rebuild = FALSE, zap_thread = FALSE, grevious_error;
  75. int daemon_delay = 0, log_verbosity = 0, debug = 0, slow_down = 0;
  76. long expire_time = 0;
  77. char *hierarchy_list = NULL;
  78. long truncate_len = -1;
  79.  
  80. char nullstr[] = "";
  81.  
  82. BMAP my_bmap, mt_bmap;
  83.  
  84. #ifdef TZSET
  85. time_t tnow;
  86. #else
  87. struct timeb ftnow;
  88. #endif
  89.  
  90. #define TIMER_FIRST 1
  91. #define TIMER_DEFAULT (10 * 60)
  92.  
  93. int added_groups, removed_groups, action;
  94.  
  95. #define NG_DEFAULT    0
  96. #define NG_MATCH    1
  97. #define NG_SKIP        2
  98.  
  99. #ifdef SERVER
  100. char *server;
  101. #else
  102. time_t last_modified;
  103. #endif
  104.  
  105. SIGRET alarm_handler(), int_handler(), severe_handler();
  106. void makethreads(), wrap_it_up();
  107.  
  108. main( argc, argv )
  109. int  argc;
  110. char *argv[];
  111. {
  112.     int fd;
  113.     long pid;
  114.  
  115.     while( --argc ) {
  116.     if( **++argv == '-' ) {
  117.         while( *++*argv ) {
  118.         switch( **argv ) {
  119.         case 'a':        /* automatically thread new groups */
  120.             add_new = TRUE;
  121.             break;
  122.         case 'D':
  123.             debug++;
  124.             break;
  125.         case 'd':
  126.             if( *++*argv <= '9' && **argv >= '0' ) {
  127.             daemon_delay = atoi( *argv ) * 60;
  128.             while( *++*argv <= '9' && **argv >= '0' ) {
  129.                 ;
  130.             }
  131.             } else {
  132.             daemon_delay = TIMER_DEFAULT;
  133.             }
  134.             --*argv;
  135.             break;
  136.         case 'e': {
  137.             struct tm *ts;
  138.             long desired;
  139.  
  140.             (void) time( &expire_time );
  141.             ts = localtime( &expire_time );
  142.  
  143.             if( *++*argv <= '9' && **argv >= '0' ) {
  144.             desired = atol( *argv );
  145.             if( desired/100 > 23 || desired%100 > 59 ) {
  146.                 fprintf( stderr, "Illegal expire time: '%04d'\n",
  147.                 desired );
  148.                 exit( 1 );
  149.             }
  150.             desired = (desired/100)*60 + desired%100;
  151.             while( *++*argv <= '9' && **argv >= '0' ) {
  152.                 ;
  153.             }
  154.             } else {
  155.             desired = 30;            /* 0030 = 12:30am */
  156.             }
  157.             --*argv;
  158.             desired -= ts->tm_hour * 60 + ts->tm_min;
  159.             if( desired < 0 ) {
  160.             desired += 24 * 60;
  161.             }
  162.             expire_time += desired * 60 - ts->tm_sec;
  163.             break;
  164.          }
  165.         case 'f':
  166.             force_flag = TRUE;
  167.             break;
  168.         case 'k':
  169.             kill_mthreads = TRUE;
  170.             break;
  171.         case 'n':
  172.             no_processing = TRUE;
  173.             break;
  174.         case 's':
  175.             slow_down++;
  176.             break;
  177.         case 'v':
  178.             log_verbosity++;
  179.             break;
  180.         case 'z':
  181.             zap_thread = TRUE;
  182.             break;
  183.         default:
  184.             fprintf( stderr, "Unknown option: '%c'\n", **argv );
  185.             exit( 1 );
  186.         }
  187.         }
  188.     } else {
  189.         if( hierarchy_list ) {
  190.         fprintf( stderr, "Specify the newsgroups in one comma-separated list.\n" );
  191.         exit( 1 );
  192.         }
  193.         hierarchy_list = *argv;
  194.     }
  195.     }
  196.  
  197.     /* Set up a nice friendly umask. */
  198.     umask( 002 );
  199.  
  200.     /* What time is it? */
  201. #ifdef TZSET
  202.     (void) time( &tnow );
  203.     (void) tzset();
  204. #else
  205.     (void) ftime( &ftnow );
  206. #endif
  207.  
  208.     /* Make sure we're not already running by creating a lock file.
  209.     ** (I snagged this method from C news.)
  210.     */
  211.     sprintf( line, "%s.%d", file_exp( "%X/LOCK" ), getpid() );
  212.     if( (fp_lock = fopen( line, "w" )) == Nullfp ) {
  213.     fprintf( stderr, "Unable to create lock temporary `%s'.\n", line );
  214.     exit( 1 );
  215.     }
  216.     fprintf( fp_lock, "%d\n", getpid() );
  217.     fclose( fp_lock );
  218.  
  219.     /* Try to link to lock file. */
  220.     filename = file_exp( "%X/LOCKmthreads" );
  221.   dolink:
  222.     if( link( line, filename ) < 0 ) {
  223.       long otherpid;
  224.     /* Try to avoid possible race with daemon starting up. */
  225.     sleep (5);
  226.     if( (fp_lock = fopen( filename, "r")) == Nullfp ) {
  227.         fprintf( stderr, "unable to open %s\n", filename );
  228.         unlink( line );
  229.         exit( 1 );
  230.     }
  231.     if( fscanf( fp_lock, "%ld", &otherpid ) != 1) { 
  232.         fprintf( stderr, "unable to read pid from %s\n", filename );
  233.         unlink( line );
  234.         fclose( fp_lock );
  235.         exit( 1 );
  236.     }
  237.     fclose( fp_lock );
  238.     if( kill( otherpid, kill_mthreads ? SIGTERM : 0 ) == -1
  239.      && errno == ESRCH ) {
  240.         if( unlink( filename ) == -1 ) {
  241.         fprintf( stderr, "unable to unlink lockfile %s\n", filename );
  242.         unlink( line );
  243.         exit( 1 );
  244.         }
  245.         if( !kill_mthreads ) {
  246.         goto dolink;
  247.         }
  248.     }
  249.     unlink( line );
  250.     if( kill_mthreads ) {
  251.         fprintf( stderr, "killing currently running mthreads.\n" );
  252.         exit( 0 );
  253.     } else {
  254.         fprintf( stderr, "mthreads is already running.\n" );
  255.         exit( 1 );
  256.     }
  257.     }
  258.  
  259.     unlink( line );            /* remove temporary LOCK.<pid> file */
  260.  
  261.     if( kill_mthreads ) {
  262.     fprintf( stderr, "mthreads is not running.\n" );
  263.     exit( 1 );
  264.     }
  265.  
  266.     /* Open our log file */
  267.     filename = file_exp( "%X/mt.log" );
  268.     if( (fp_log = fopen( filename, "a" )) == Nullfp ) {
  269.     fprintf( stderr, "Unable to open `%s'.\n", filename );
  270.     exit( 1 );
  271.     }
  272.  
  273. #ifdef SIGHUP
  274.     if( sigset( SIGHUP, SIG_IGN ) != SIG_IGN ) {
  275.     sigset( SIGHUP, int_handler );
  276.     }
  277. #endif
  278.     if( sigset( SIGINT, SIG_IGN ) != SIG_IGN ) {
  279.     sigset( SIGINT, int_handler );
  280.     }
  281. #ifdef SIGQUIT
  282.     if( sigset( SIGQUIT, SIG_IGN ) != SIG_IGN ) {
  283.     sigset( SIGQUIT, int_handler );
  284.     }
  285. #endif
  286.     sigset( SIGTERM, int_handler );
  287. #ifdef SIGBUS
  288.     sigset( SIGBUS, severe_handler );
  289. #endif
  290.     sigset( SIGSEGV, severe_handler );
  291. #ifdef SIGTTIN
  292.     sigset( SIGTTIN, SIG_IGN );
  293.     sigset( SIGTTOU, SIG_IGN );
  294. #endif
  295.     sigset( SIGALRM, SIG_IGN );
  296. #ifdef lint
  297.     alarm_handler();            /* foolishness for lint's sake */
  298.     int_handler( SIGINT );
  299.     severe_handler( SIGSEGV );
  300. #endif
  301.  
  302.     /* Ensure this machine has the right byte-order for the database */
  303.     filename = file_exp( "%X/db.init" );
  304.     if( (fp_lock = fopen( filename, FOPEN_RB )) == Nullfp
  305.      || fread( &mt_bmap, 1, sizeof (BMAP), fp_lock ) < sizeof (BMAP)-1 ) {
  306.     if( fp_lock != Nullfp ) {
  307.         fclose( fp_lock );
  308.     }
  309.      write_db_init:
  310.     mybytemap( &mt_bmap );
  311.     if( (fp_lock = fopen( filename, FOPEN_WB )) == Nullfp ) {
  312.         log_entry( "Unable to create file: `%s'.\n", filename );
  313.         exit( 1 );
  314.     }
  315.     mt_bmap.version = DB_VERSION;
  316.     fwrite( &mt_bmap, 1, sizeof (BMAP), fp_lock );
  317.     fclose( fp_lock );
  318.     } else {
  319.     int i;
  320.  
  321.     fclose( fp_lock );
  322.     if( mt_bmap.version != DB_VERSION ) {
  323.         if( mt_bmap.version == DB_VERSION-1 ) {
  324.         rebuild = TRUE;
  325.         log_entry( "Upgrading database to version %d.\n", DB_VERSION );
  326.         goto write_db_init;
  327.         }
  328.         log_entry( "** Database is not the right version (%d instead of %d) **\n",
  329.         mt_bmap.version, DB_VERSION );
  330.         exit( 1 );
  331.     }
  332.     mybytemap( &my_bmap );
  333.     for( i = 0; i < sizeof (LONG); i++ ) {
  334.         if( my_bmap.l[i] != mt_bmap.l[i]
  335.          || (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i]) ) {
  336.         log_entry( "\
  337. ** Byte-order conflict -- re-run from a compatible machine **\n\
  338. \t\tor remove the current thread files, including db.init **\n" );
  339.         exit( 1 );
  340.         }
  341.     }
  342.     }
  343.  
  344. #ifdef SERVER
  345.     server = getserverbyfile( SERVER_FILE );
  346.     if( server == NULL ) {
  347.     log_entry( "Couldn't find name of news server.\n" );
  348.     exit( 1 );
  349.     }
  350. #endif
  351.  
  352.     /* If we're not in daemon mode, run through once and quit. */
  353.     if( !daemon_delay ) {
  354.     log_entry( "mthreads single pass started.\n" );
  355.     setbuf( stdout, Nullch );
  356.     extra_expire = (expire_time != 0);
  357.     makethreads();
  358.     } else {
  359.     /* For daemon mode, we cut ourself off from anything tty-related and
  360.     ** run in the background (involves forks, but no knives).
  361.     */
  362.     close( 0 );
  363.     if( open( "/dev/null", 2 ) != 0 ) {
  364.         fprintf( stderr, "unable to open /dev/null!\n" );
  365.         exit( 1 );
  366.     }
  367.     close( 1 );
  368.     close( 2 );
  369.     dup( 0 );
  370.     dup( 0 );
  371.     while( (pid = fork()) < 0 ) {
  372.         sleep( 2 );
  373.     }
  374.     if( pid ) {
  375.         exit( 0 );
  376.     }
  377. #ifdef TIOCNOTTY
  378.     if( (fd = open( "/dev/tty", 1 )) >= 0 ) {
  379.         ioctl( fd, TIOCNOTTY, (int*)0 );
  380.         close( fd );
  381.     }
  382. #else
  383.     (void) setpgrp();
  384.     while( (pid = fork()) < 0 ) {
  385.         sleep( 2 );
  386.     }
  387.     if( pid ) {
  388.         exit( 0 );
  389.     }
  390. #endif
  391.     /* Put our pid in the lock file for death detection */
  392.     if( (fp_lock = fopen( file_exp( "%X/LOCKmthreads" ), "w" )) != Nullfp ) {
  393.         fprintf( fp_lock, "%d\n", getpid() );
  394.         fclose( fp_lock );
  395.     }
  396.  
  397.     log_entry( "mthreads daemon started.\n" );
  398.  
  399. #ifndef SERVER
  400.     last_modified = 0;
  401. #endif
  402.     sigset( SIGALRM, alarm_handler );
  403.  
  404.     /* Start timer -- first interval is shorter than all others */
  405.     alarm( TIMER_FIRST );
  406.     for( ;; ) {
  407.         if( caught_interrupt ) {
  408.         wrap_it_up( 0 );
  409.         /* NORETURN */
  410.         }
  411.         pause();        /* let alarm go off */
  412.         if( caught_interrupt ) {
  413.         wrap_it_up( 0 );
  414.         /* NORETURN */
  415.         }
  416.         alarm( 0 );
  417.  
  418.         /* Re-open our log file, if needed */
  419.         if( !fp_log && !(fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
  420.         wrap_it_up( 1 );
  421.         }
  422. #ifndef SERVER
  423.         if( stat( file_exp( ACTIVE ), &filestat ) < 0 ) {
  424.         log_entry( "Unable to stat active file -- quitting.\n" );
  425.         wrap_it_up( 1 );
  426.         }
  427. #endif
  428.         if( expire_time && time( 0L ) > expire_time ) {
  429.         expire_time += 24L * 60 * 60;
  430.         extra_expire = TRUE;
  431.         }
  432. #ifdef SERVER
  433.         if( 1 ) {        /* always compare files */
  434. #else
  435.         if( extra_expire || filestat.st_mtime != last_modified ) {
  436.         last_modified = filestat.st_mtime;
  437. #endif
  438.         makethreads();
  439.         }
  440.         alarm( daemon_delay );
  441.         fclose( fp_log );    /* close the log file while we sleep */
  442.         fp_log = Nullfp;
  443.     } /* for */
  444.     }/* if */
  445.  
  446.     wrap_it_up( 0 );
  447. }
  448.  
  449. SIGRET
  450. alarm_handler()
  451. {
  452.     sigset( SIGALRM, alarm_handler );
  453. }
  454.  
  455. SIGRET
  456. int_handler( sig )
  457. int sig;
  458. {
  459.     /* Flag interrupt occurred -- main loop attempts an orderly retreat. */
  460.     if( ++caught_interrupt >= 3 ) {
  461.     wrap_it_up( 1 );
  462.     }
  463.     /* Re-open our log file, if needed */
  464.     if( fp_log || (fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
  465.     if( sig == SIGTERM ) {
  466.         log_entry( "mthreads halted.\n", sig);
  467.     } else {
  468.         log_entry( "Interrupt %d received.\n", sig);
  469.     }
  470.     }
  471.     if( !daemon_delay ) {
  472.     printf( "interrupt %d!\n", sig );
  473.     }
  474. }
  475.  
  476. /* Severe interrupts require severe action -- abort immediately, possibly
  477. ** removing the thread file on the way.
  478. */
  479. SIGRET
  480. severe_handler( sig )
  481. int sig;
  482. {
  483.     /* Let's be a bit paranoid here -- avoid any possibility of looping. */
  484.     if( caught_interrupt >= 10 ) {
  485.     wrap_it_up( 1 );
  486.     }
  487.     caught_interrupt = 10;
  488.  
  489.     /* Destroy offending thread file if requested to do so. */
  490.     if( zap_thread ) {
  491.     unlink( thread_name( line ) );
  492.     }
  493.     /* Re-open our log file, if needed */
  494.     if( fp_log || (fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
  495.     log_error( "** Severe signal: %d **\n", sig );
  496.     if( zap_thread ) {
  497.         log_entry( "Destroyed thread file for %s\n", line );
  498.     }
  499.     }
  500.     if( !daemon_delay ) {
  501.     printf( "Severe signal: %d!\n", sig);
  502.     if( zap_thread ) {
  503.         printf( "Destroyed thread file for %s\n", line );
  504.     }
  505.     }
  506.     wrap_it_up( 1 );
  507. }
  508.  
  509. void
  510. wrap_it_up( ret )
  511. int ret;
  512. {
  513.     unlink( file_exp( "%X/LOCKmthreads" ) );        /* remove lock */
  514.  
  515.     exit( ret );
  516. }
  517.  
  518. /* Process the active file, creating/modifying the active2 file and
  519. ** creating/modifying the thread data files.
  520. */
  521. void
  522. makethreads()
  523. {
  524.     register char *cp, *cp2;
  525.     FILE *fp_active, *fp_active2, *fp_active3;
  526.     long first, last, first2, last2;
  527.     char ch, ch2;
  528.     char data_file_open;
  529.     bool update_successful;
  530.     bool eof_active = FALSE, eof_active2 = FALSE;
  531.  
  532. #ifdef SERVER
  533.     switch( server_init( server ) ) {
  534.     case OK_NOPOST:
  535.     case OK_CANPOST:
  536.     break;
  537.     case ERR_ACCESS:
  538.     log_entry( "Server %s rejected connection -- quitting.\n", server );
  539.     wrap_it_up( 1 );
  540.     default:
  541.     log_entry( "Couldn't connect with server %s -- sleeping.\n", server );
  542.     return;
  543.     }
  544.     put_server( "LIST" );    /* ask server for the active file */
  545.     get_server( line, sizeof line );
  546.     if( *line != CHAR_OK ) {
  547.     log_entry( "Unable to get active file from server -- sleeping.\n" );
  548.     close_server();
  549.     return;
  550.     }
  551.     if( (fp_active = fopen( file_exp( ACTIVE1 ), "w+" )) == Nullfp ) {
  552.     log_entry( "Unable to write the active1 file.\n" );
  553.     wrap_it_up( 1 );
  554.     }
  555.     while( 1 ) {
  556.     if( caught_interrupt ) {
  557.         wrap_it_up( 0 );
  558.         /* NORETURN */
  559.     }
  560.     if( get_server( line, sizeof line ) < 0 ) {
  561.         log_entry( "Server failed to send entire active file -- sleeping.\n" );
  562.         fclose( fp_active );
  563.         close_server();
  564.         return;
  565.     }
  566.     if( *line == '.' ) {
  567.         break;
  568.     }
  569.     fputs( line, fp_active );
  570.     putc( '\n', fp_active );
  571.     }
  572.     fseek( fp_active, 0L, 0 );        /* rewind for read */
  573. #else
  574.     if( (fp_active = fopen( file_exp( ACTIVE ), "r" )) == Nullfp ) {
  575.     log_entry( "Unable to open the active file.\n" );
  576.     wrap_it_up( 1 );
  577.     }
  578. #endif
  579.     filename = file_exp( ACTIVE2 );
  580.     if( (fp_active3 = fopen( filename, "r+" )) == Nullfp ) {
  581.     if( (fp_active3 = fopen( filename, "w" )) == Nullfp ) {
  582.         log_entry( "Unable to open the active2 file for update.\n" );
  583.         wrap_it_up( 1 );
  584.     }
  585.     }
  586.     if( (fp_active2 = fopen( filename, "r" )) == Nullfp ) {
  587.     log_entry( "Unable to open the active2 file.\n" );
  588.     wrap_it_up( 1 );
  589.     }
  590.     if( caught_interrupt ) {
  591.     wrap_it_up( 0 );
  592.     /* NORETURN */
  593.     }
  594.     if( extra_expire && log_verbosity ) {
  595.     log_entry( "Using enhanced expiration for this pass.\n" );
  596.     }
  597.  
  598.     processed_groups = added_groups = removed_groups = 0;
  599.     added_articles = expired_articles = 0;
  600.  
  601.     /* Loop through entire active file. */
  602.     for( ;; ) {
  603.     if( eof_active || !fgets( line, sizeof line, fp_active ) ) {
  604.         if( eof_active2 && !line_root ) {
  605.         break;
  606.         }
  607.         eof_active = TRUE;
  608.         ch = 'x';
  609.     } else {
  610.         if( !(cp = index( line, ' ' )) ) {
  611.         log_entry( "active line has no space: %s\n", line );
  612.         continue;
  613.         }
  614.         *cp = '\0';
  615.         if( sscanf( cp+1, "%ld %ld %c", &last, &first, &ch ) != 3 ) {
  616.         log_entry( "active digits corrupted: %s %s\n", line, cp+1 );
  617.         continue;
  618.         }
  619.     }
  620.     if( debug || log_verbosity > 3 ) {
  621.         log_entry( "Processing %s:\n", line );
  622.     }
  623.     data_file_open = 0;
  624.     /* If we've allocated some lines in memory while searching for
  625.     ** newsgroups (they've scrambled the active file on us), check
  626.     ** them first.
  627.     */
  628.     last_line = Nullact;
  629.     for( pline = line_root; pline; pline = pline->link ) {
  630.         if( eof_active || strEQ( line, pline->name ) ) {
  631.         strcpy( line2, pline->name );
  632.         free( pline->name );
  633.         first2 = pline->first;
  634.         last2 = pline->last;
  635.         ch2 = pline->type;
  636.         if( last_line ) {
  637.             last_line->link = pline->link;
  638.         } else {
  639.             line_root = pline->link;
  640.         }
  641.         free( pline );
  642.         break;
  643.         }
  644.         last_line = pline;
  645.     }/* for */
  646.     /* If not found yet, check the active2 file. */
  647.     if( !pline ) {
  648.         for( ;; ) {
  649.         if( eof_active2 || !fgets( line2, sizeof line2, fp_active2 ) ) {
  650.             /* At end of file, check if the thread data file exists.
  651.             ** If so, use its high/low values.  Else, default to
  652.             ** some initial values.
  653.             */
  654.             eof_active2 = TRUE;
  655.             if( eof_active ) {
  656.             break;
  657.             }
  658.             strcpy( line2, line );
  659.             if( (data_file_open = init_data( thread_name( line ) )) ) {
  660.             last2 = total.last;
  661.             first2 = total.first;
  662.             ch2 = 'y';
  663.             } else {
  664.             total.first = first2 = first;
  665.             if( add_new ) {
  666.                 total.last = last2 = first - 1;
  667.                 ch2 = (ch == '=' ? 'x' : ch);
  668.                 added_groups++;
  669.             } else {
  670.                 total.last = last2 = last;
  671.                 ch2 = (ch == '=' ? 'X' : toupper( ch ));
  672.             }
  673.             }
  674.             data_file_open++;        /* (1 == empty, 2 == open) */
  675.             break;
  676.         }
  677.         if( !(cp2 = index( line2, ' ' )) ) {
  678.             log_entry( "active2 line has no space: %s\n", line2 );
  679.             continue;
  680.         }
  681.         *cp2 = '\0';
  682.         if( sscanf( cp2+1,"%ld %ld %c",&last2,&first2,&ch2 ) != 3 ) {
  683.             log_entry( "active2 digits corrupted: %s %s\n",
  684.             line2, cp2+1 );
  685.             continue;
  686.         }
  687.         /* Check if we're still in-sync */
  688.         if( eof_active || strEQ( line, line2 ) ) {
  689.             break;
  690.         }
  691.         /* Nope, we've got to go looking for this line somewhere
  692.         ** down in the file.  Save each non-matching line in memory
  693.         ** as we go.
  694.         */
  695.         pline = (ACTIVE_LINE*)safemalloc( sizeof (ACTIVE_LINE) );
  696.         pline->name = savestr( line2 );
  697.         pline->last = last2;
  698.         pline->first = first2;
  699.         pline->type = ch2;
  700.         pline->link = Nullact;
  701.         if( !last_line ) {
  702.             line_root = pline;
  703.         } else {
  704.             last_line->link = pline;
  705.         }
  706.         last_line = pline;
  707.         }/* for */
  708.         if( eof_active && eof_active2 ) {
  709.         break;
  710.         }
  711.     }/* if !pline */
  712.     if( eof_active ) {
  713.         strcpy( line, line2 );
  714.         if( truncate_len < 0 ) {
  715.         truncate_len = ftell( fp_active3 );
  716.         }
  717.     }
  718.     if( rebuild ) {
  719.         unlink( thread_name( line ) );
  720.     }
  721.     update_successful = FALSE;
  722.     if( hierarchy_list ) {
  723.         action = ngmatch( hierarchy_list, line );
  724.     } else {
  725.         action = NG_DEFAULT;
  726.     }
  727.     switch( action ) {
  728.     case NG_DEFAULT:
  729.         if( ch2 < 'a' ) {
  730.         action = NG_SKIP;
  731.         } else {
  732.         action = NG_MATCH;
  733.         }
  734.         break;
  735.     case NG_MATCH:                /* add if unthreaded */
  736.         if( ch2 < 'a' ) {
  737.         total.last = last2 = first2 - 1;
  738.         added_groups++;
  739.         }
  740.         break;
  741.     case NG_SKIP:                /* remove if threaded */
  742.         if( ch2 >= 'a' && !debug ) {
  743.         unlink( thread_name( line ) );
  744.         removed_groups++;
  745.         }
  746.         break;
  747.     }
  748.     if( caught_interrupt || (debug && action != NG_MATCH) ) {
  749.         dont_read_data( data_file_open );    /* skip silently */
  750.     } else if( ch == 'x' || ch == '=' ) {
  751.         if( !daemon_delay ) {        /* skip 'x'ed groups */
  752.         putchar( 'x' );
  753.         }
  754.         ch = (action == NG_SKIP ? 'X' : 'x');
  755.         if( (ch2 >= 'a' && ch2 != 'x') || force_flag ) {
  756.         /* Remove thread file if group is newly 'x'ed out */
  757.         unlink( thread_name( line ) );
  758.         }
  759.         update_successful = TRUE;
  760.         dont_read_data( data_file_open );
  761.     } else if( action == NG_SKIP ) {    /* skip excluded groups */
  762.         if( !daemon_delay ) {
  763.         putchar( 'X' );
  764.         }
  765.         ch = toupper( ch );
  766.         if( force_flag ) {
  767.         unlink( thread_name( line ) );
  768.         }
  769.         update_successful = TRUE;
  770.         dont_read_data( data_file_open );
  771.     } else if( no_processing ) {
  772.         if( !daemon_delay ) {
  773.         putchar( ',' );
  774.         }
  775.         ch2 = ch;
  776.         dont_read_data( data_file_open );
  777.     } else if( !force_flag && !extra_expire && !rebuild
  778.      && first == first2 && last == last2 ) {
  779.         /* We're up-to-date here.  Skip it. */
  780.         if( !daemon_delay ) {
  781.         putchar( '.' );
  782.         }
  783.         update_successful = TRUE;
  784.         dont_read_data( data_file_open );
  785.     } else {
  786.         /* Looks like we need to process something. */
  787. #ifdef SERVER
  788.         sprintf( line2, "GROUP %s", line );
  789.         put_server( line2 );        /* go to next group */
  790.         if( get_server( line2, sizeof line2 ) < 0 || *line2 != CHAR_OK ) {
  791.         log_entry( "NNTP failure on group `%s'.\n", line );
  792. #else
  793.         cp = line2;
  794.         while( (cp = index( cp, '.' )) ) {
  795.         *cp = '/';
  796.         }
  797.         filename = file_exp( line2 );    /* relative to spool dir */
  798.         if( chdir( filename ) < 0 ) {
  799.         if (errno != ENOENT) {
  800.             log_entry( "Unable to chdir to `%s'.\n", filename );
  801.         }
  802. #endif
  803.         if( !daemon_delay ) {
  804.             putchar( '*' );
  805.         }
  806.         dont_read_data( data_file_open );
  807.         } else {
  808.         filename = thread_name( line );
  809.         /* Try to open the data file only if we didn't try it
  810.         ** in the name matching code above.
  811.         */
  812.         if( !data_file_open-- ) {    /* (0 == haven't tried yet) */
  813.             if( !(data_file_open = init_data( filename )) ) {
  814.             total.last = first - 1;
  815.             total.first = first;
  816.             }
  817.         }
  818.         strcpy( line2, filename );
  819.         cp = rindex( line2, '/' ) + 1;
  820.  
  821.         if( data_file_open ) {        /* (0 == empty, 1 == open) */
  822.             if( !read_data() ) {    /* did read fail? */
  823. #ifndef DEBUG
  824.             unlink( filename );    /* trash input file */
  825. #else
  826.             strcpy( cp, "bad.read" );
  827.             rename( filename, line2 );
  828. #endif
  829.             data_file_open = init_data( filename );
  830.             total.last = first - 1;
  831.             total.first = first;
  832.             }
  833.         }
  834.         grevious_error = FALSE;
  835.         process_articles( first, last );
  836.         processed_groups++;
  837.         if( caught_interrupt ) {
  838.             processed_groups--;    /* save nothing -- no update */
  839.         } else if( !added_count && !expired_count && last == last2 ) {
  840.             (void) write_data( Nullch );
  841.             if( !daemon_delay ) {
  842.             putchar( ':' );
  843.             }
  844.             update_successful = TRUE;
  845.         } else if( !total.root ) {
  846.             /* When the data file goes empty, remove it. */
  847.             unlink( filename );
  848.             expired_articles += expired_count;
  849.             if( !daemon_delay ) {
  850.             putchar( '-' );
  851.             }
  852.             update_successful = TRUE;
  853.         } else {
  854.             strcpy( cp, NEW_THREAD );    /* write data as .new */
  855.             if( write_data( line2 ) && !grevious_error ) {
  856.             rename( line2, filename );
  857.             added_articles += added_count;
  858.             expired_articles += expired_count;
  859.             if( !daemon_delay ) {
  860.                 putchar( '#' );
  861.             }
  862.             update_successful = TRUE;
  863.             } else {
  864. #ifndef DEBUG
  865.             unlink( line2 );    /* blow-away bad write */
  866. #else
  867.             cp = rindex( filename, '/' ) + 1;
  868.             strcpy( cp, "bad.write" );
  869.             rename( line2, filename );
  870. #endif
  871.             if( !daemon_delay ) {
  872.                 putchar( '!' );
  873.             }
  874.             }/* if */
  875.         }/* if */
  876.         }/* if */
  877.     }/* if */
  878.     /* Finally, update the active2 entry for this newsgroup. */
  879.     if( update_successful ) {
  880.         fprintf( fp_active3, fmt_active2, line, last, first, ch );
  881.     } else {
  882.         fprintf( fp_active3, fmt_active2, line, last2, first2, ch2 );
  883.     }
  884.     /* If we're not out of sync, keep active2 file flushed. */
  885.     if( !line_root ) {
  886.         fflush( fp_active3 );
  887.     }
  888.     }/* for */
  889.  
  890. #ifdef SERVER
  891.     close_server();
  892. #endif
  893.     fclose( fp_active );
  894.     fclose( fp_active2 );
  895.     fclose( fp_active3 );
  896.  
  897.     if( truncate_len >= 0 ) {
  898. #ifdef TRUNCATE
  899.     if( truncate( file_exp( ACTIVE2 ), truncate_len ) == -1 )
  900.         log_entry( "Unable to truncate the active2 file.\n" );
  901. #else
  902. #ifdef CHSIZE
  903.     int fd;
  904.     if( (fd = open( file_exp( ACTIVE2 ), O_RDWR )) == -1 )
  905.         log_entry( "Unable to open the active2 file for truncation.\n" );
  906.     else {
  907.         if( chsize( fd, truncate_len ) == -1 )
  908.         log_entry( "Unable to truncate the active2 file.\n" );
  909.         close( fd );
  910.     }
  911. #else
  912.     filename = file_exp( ACTIVE2 );
  913.     sprintf( line, "%s.new", filename );
  914.     if( (fp_active3 = fopen( line, "w" )) == Nullfp ) {
  915.         log_entry( "Unable to create the active2.new file.\n" );
  916.     } else if( (fp_active2 = fopen( filename, "r" )) == Nullfp ) {
  917.         fclose( fp_active3 );
  918.         unlink( line );
  919.         log_entry( "Unable to open the active2 file.\n" );
  920.     } else {
  921.         while( ftell( fp_active3 ) < truncate_len ) {
  922.         if( !fgets( line2, sizeof line2, fp_active2 ) ) {
  923.             break;
  924.         }
  925.         fputs( line2, fp_active3 );
  926.         }
  927.         sprintf( line2, "%s.old", filename );
  928.         rename( filename, line2 );
  929.         rename( line, filename );
  930.         fclose( fp_active2 );
  931.         fclose( fp_active3 );
  932.     }
  933. #endif /* not XENIX */
  934. #endif /* not TRUNCATE */
  935.     truncate_len = -1;
  936.     }
  937.  
  938.     sprintf( line, "Processed %d group%s:  added %d article%s, expired %d.\n",
  939.     processed_groups, processed_groups == 1 ? nullstr : "s",
  940.     added_articles, added_articles == 1 ? nullstr : "s",
  941.     expired_articles );
  942.  
  943.     if( processed_groups ) {
  944.     log_entry( line );
  945.     }
  946.  
  947.     if( !daemon_delay ) {
  948.     putchar( '\n' );
  949.     fputs( line, stdout );
  950.     }
  951.     if( added_groups ) {
  952.     sprintf( line, "Turned %d group%s on.\n", added_groups,
  953.         added_groups == 1 ? nullstr : "s" );
  954.     log_entry( line );
  955.     if( !daemon_delay ) {
  956.         fputs( line, stdout );
  957.     }
  958.     }
  959.     if( removed_groups ) {
  960.     sprintf( line, "Turned %d group%s off.\n", removed_groups,
  961.         removed_groups == 1 ? nullstr : "s" );
  962.     log_entry( line );
  963.     if( !daemon_delay ) {
  964.         fputs( line, stdout );
  965.     }
  966.     }
  967.     extra_expire = FALSE;
  968.     rebuild = FALSE;
  969. }
  970.  
  971. /*
  972. ** ngmatch - newsgroup name matching
  973. **
  974. ** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
  975. ** and NG_DEFAULT if the group doesn't match at all.
  976. **
  977. ** "all" in a pattern is a wildcard that matches exactly one word;
  978. ** it does not cross "." (NGDELIM) delimiters.
  979. **
  980. ** This matching code was borrowed from C news.
  981. */
  982.  
  983. #define ALL "all"            /* word wildcard */
  984.  
  985. #define NGNEG '!'
  986. #define NGSEP ','
  987. #define NGDELIM '.'
  988.  
  989. int
  990. ngmatch( ngpat, grp )
  991. char *ngpat, *grp;
  992. {
  993.     register char *patp;        /* point at current pattern */
  994.     register char *patcomma;
  995.     register int depth;
  996.     register int faildeepest = 0, hitdeepest = 0;    /* in case no match */
  997.     register bool negation;
  998.  
  999.     for( patp = ngpat; patp != Nullch; patp = patcomma ) {
  1000.     negation = FALSE;
  1001.     patcomma = index( patp, NGSEP );
  1002.     if( patcomma != Nullch ) {
  1003.         *patcomma = '\0';    /* will be restored below */
  1004.     }
  1005.     if( *patp == NGNEG ) {
  1006.         ++patp;
  1007.         negation = TRUE;
  1008.     }
  1009.     depth = onepatmatch( patp, grp ); /* try 1 pattern, 1 group */
  1010.     if( patcomma != Nullch ) {
  1011.         *patcomma++ = NGSEP;    /* point after the comma */
  1012.     }
  1013.     if( depth == 0 ) {        /* mis-match */
  1014.         ;                /* ignore it */
  1015.     } else if( negation ) {
  1016.         /* record depth of deepest negated matched word */
  1017.         if( depth > faildeepest ) {
  1018.         faildeepest = depth;
  1019.         }
  1020.     } else {
  1021.         /* record depth of deepest plain matched word */
  1022.         if( depth > hitdeepest ) {
  1023.         hitdeepest = depth;
  1024.         }
  1025.     }
  1026.     }
  1027.     if( hitdeepest > faildeepest ) {
  1028.     return NG_MATCH;
  1029.     } else if( faildeepest ) {
  1030.     return NG_SKIP;
  1031.     } else {
  1032.     return NG_DEFAULT;
  1033.     }
  1034. }
  1035.  
  1036. /*
  1037. ** Match a pattern against a group by looking at each word of pattern in turn.
  1038. **
  1039. ** On a match, return the depth (roughly, ordinal number * k) of the rightmost
  1040. ** word that matches.  If group runs out first, the match fails; if pattern
  1041. ** runs out first, it succeeds.  On a failure, return zero.
  1042. */
  1043. int
  1044. onepatmatch( patp, grp )
  1045. char *patp, *grp;
  1046. {
  1047.     register char *rpatwd;        /* used by word match (inner loop) */
  1048.     register char *patdot, *grdot;    /* point at dots after words */
  1049.     register char *patwd, *grwd;    /* point at current words */
  1050.     register int depth = 0;
  1051.  
  1052.     for( patwd = patp, grwd = grp;
  1053.      patwd != Nullch && grwd != Nullch;
  1054.      patwd = patdot, grwd = grdot
  1055.     ) {
  1056.     register bool match = FALSE;
  1057.     register int incr = 20;
  1058.  
  1059.     /* null-terminate words */
  1060.     patdot = index(patwd, NGDELIM);
  1061.     if( patdot != Nullch ) {
  1062.         *patdot = '\0';        /* will be restored below */
  1063.     }
  1064.     grdot = index( grwd, NGDELIM );
  1065.     if( grdot != Nullch ) {
  1066.         *grdot = '\0';        /* will be restored below */
  1067.     }
  1068.     /*
  1069.      * Match one word of pattern with one word of group.
  1070.      * A pattern word of "all" matches any group word,
  1071.      * but isn't worth as much.
  1072.      */
  1073. #ifdef FAST_STRCMP
  1074.     match = STREQ( patwd, grwd );
  1075.     if( !match && STREQ( patwd, ALL ) ) {
  1076.         match = TRUE;
  1077.         --incr;
  1078.     }
  1079. #else
  1080.     for( rpatwd = patwd; *rpatwd == *grwd++; ) {
  1081.         if( *rpatwd++ == '\0' ) {
  1082.         match = TRUE;        /* literal match */
  1083.         break;
  1084.         }
  1085.     }
  1086.     if( !match ) {
  1087.         /* ugly special case match for "all" */
  1088.         rpatwd = patwd;
  1089.         if( *rpatwd++ == 'a' && *rpatwd++ == 'l'
  1090.          && *rpatwd++ == 'l' && *rpatwd   == '\0' ) {
  1091.         match = TRUE;
  1092.          --incr;
  1093.         }
  1094.     }
  1095. #endif                /* FAST_STRCMP */
  1096.  
  1097.     if( patdot != Nullch ) {
  1098.         *patdot++ = NGDELIM;    /* point after the dot */
  1099.     }
  1100.     if( grdot != Nullch ) {
  1101.         *grdot++ = NGDELIM;
  1102.     }
  1103.     if( !match ) {
  1104.         depth = 0;        /* words differed - mismatch */
  1105.         break;
  1106.     }
  1107.     depth += incr;
  1108.     }
  1109.     /* if group name ran out before pattern, then match fails */
  1110.     if( grwd == Nullch && patwd != Nullch ) {
  1111.     depth = 0;
  1112.     }
  1113.     return depth;
  1114. }
  1115.  
  1116. /* Generate a log entry with timestamp.
  1117. */
  1118. /*VARARGS1*/
  1119. void
  1120. log_entry( fmt, arg1, arg2 )
  1121. char *fmt;
  1122. long arg1;
  1123. long arg2;
  1124. {
  1125.     time_t now;
  1126.     char *ctime();
  1127.  
  1128.     (void) time( &now );
  1129.     fprintf( fp_log, "%.12s ", ctime( &now )+4 );
  1130.     fprintf( fp_log, fmt, arg1, arg2 );
  1131.     fflush( fp_log );
  1132. }
  1133.  
  1134. /* Generate an log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
  1135. ** and newsgroup name.
  1136. */
  1137. /*VARARGS1*/
  1138. void
  1139. log_error( fmt, arg1, arg2, arg3 )
  1140. char *fmt;
  1141. long arg1;
  1142. long arg2;
  1143. long arg3;
  1144. {
  1145.     log_entry( "%s: ", line );
  1146.     fprintf( fp_log, fmt, arg1, arg2, arg3 );
  1147.     fflush( fp_log );
  1148.     if( *fmt == '*' ) {
  1149.     grevious_error = TRUE;
  1150.     if( !daemon_delay ) {
  1151.         putchar( 'E' );
  1152.     }
  1153.     }
  1154.     else {
  1155.     if( !daemon_delay ) {
  1156.         putchar( 'e' );
  1157.     }
  1158.     }
  1159. }
  1160.  
  1161. #ifndef RENAME
  1162. int
  1163. rename( old, new )
  1164. char    *old, *new;
  1165. {
  1166.     struct stat st;
  1167.  
  1168.     if( stat( old, &st ) == -1 ) {
  1169.     return -1;
  1170.     }
  1171.     if( unlink( new ) == -1 && errno != ENOENT ) {
  1172.     return -1;
  1173.     }
  1174.     if( link( old, new ) == -1 ) {
  1175.     return -1;
  1176.     }
  1177.     if( unlink( old ) == -1 ) {
  1178.     int e = errno;
  1179.     (void) unlink( new );
  1180.     errno = e;
  1181.     return -1;
  1182.     }
  1183.     return 0;
  1184. }
  1185. #endif /*RENAME*/
  1186.