home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume27 / mthreads / part02 / mthreads.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-11-20  |  32.5 KB  |  1,410 lines

  1. /* mthreads.c -- for making and updating a discussion-thread database
  2. **
  3. ** We use the active file to read the high/low counts for each newsgroup
  4. ** and compare them with the corresponding values in a file called active2
  5. ** (which we created to keep the database high/lows in one place).  If they
  6. ** don't match, we read/update/write the group's thread file and move on.
  7. ** If the active2 file is missing or corrupted, it will be repaired in the
  8. ** normal course of operation.
  9. **
  10. ** Usage:  mthreads [-d[MM]] [-e[HHMM]] [-s[hsec]] [-aDfknv] [hierarchy_list]
  11. */
  12. /* The authors make no claims as to the fitness or correctness of this software
  13.  * for any use whatsoever, and it is provided as is. Any use of this software
  14.  * is at the user's own risk. 
  15.  */
  16.  
  17. #include "patchlevel.h"
  18. static char mtid[] = "@(#)$Id: mthreads.c,v 3.0 1993/10/01 00:14:06 davison Trn $";
  19. static char patchlevel[] = PATCHLEVEL;
  20.  
  21. #include "EXTERN.h"
  22. #include "common.h"
  23. #include "thread.h"
  24. #include "nntpclient.h"
  25. #include "INTERN.h"
  26. #include "mthreads.h"
  27.  
  28. #if !defined(HAS_FTRUNCATE) && !defined(HAS_CHSIZE)
  29. # ifdef F_FREESP
  30. #  define MYCHSIZE
  31. # else
  32. #  define MVTRUNC
  33. # endif
  34. #endif
  35.  
  36. #ifdef USE_SYSLOG
  37. #include <syslog.h>
  38. #else
  39. FILE *fp_log;
  40. #endif
  41.  
  42. FILE *fp_tmp, *fp_active, *fp_active2, *fp_active2w = Nullfp;
  43. bool eof_active = FALSE, eof_active2 = FALSE;
  44. long first, last, first2, last2;
  45. char ch, ch2;
  46.  
  47. struct stat filestat;
  48.  
  49. char buf[LBUFLEN+1];
  50.  
  51. char line[256];
  52. static char line2[256];
  53.  
  54. char fmt_active2[] = "%s %010ld %07ld %c\n";
  55.  
  56. char *filename;
  57.  
  58. typedef struct _active_line {
  59.     struct _active_line *link;
  60.     char *name;
  61.     long last;
  62.     long first;
  63.     char type;
  64. } ACTIVE_LINE;
  65.  
  66. #define Nullact Null(ACTIVE_LINE*)
  67.  
  68. ACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
  69.  
  70. bool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
  71. bool add_new = FALSE, rebuild = FALSE, zap_thread = FALSE;
  72. bool acttimes_flag = FALSE, grevious_error;
  73. bool initializing = TRUE;
  74. int daemon_delay = 0, log_verbosity = 0, debug = 0, slow_down = 0;
  75. long expire_time = 0;
  76. char *hierarchy_list = NULL;
  77. long truncate_len = -1;
  78.  
  79. char nullstr[1] = "";
  80.  
  81. extern int locked, cron_locking;
  82.  
  83. #define TIMER_FIRST 1
  84. #define TIMER_DEFAULT (10 * 60)
  85.  
  86. int processed_groups = 0, added_groups = 0, removed_groups = 0;
  87. int action;
  88.  
  89. #define NG_DEFAULT    0
  90. #define NG_MATCH    1
  91. #define NG_SKIP        2
  92.  
  93. #ifndef USE_NNTP
  94. time_t last_modified = 0;
  95. #endif
  96.  
  97. Signal_t alarm_handler(), int_handler();
  98. bool makethreads _((void));
  99. void log_startup _((void));
  100. void log_stats _((void));
  101.  
  102. int
  103. main(argc, argv)
  104. int  argc;
  105. char *argv[];
  106. {
  107. #ifdef TIOCNOTTY
  108.     int fd;
  109. #endif
  110.     long pid;
  111.  
  112.     while (--argc) {
  113.     if (**++argv == '-') {
  114.         while (*++*argv) {
  115.         switch (**argv) {
  116.         case 'a':        /* automatically thread new groups */
  117.             add_new = TRUE;
  118.             break;
  119.         case 'c':        /* continue trying to lock */
  120.             cron_locking = TRUE;
  121.             break;
  122.         case 'D':        /* run in debug mode */
  123.             debug++;
  124.             break;
  125.         case 'd':        /* run in daemon mode */
  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': {        /* enhanced expire processing */
  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: '%04ld'\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':        /* force each group to process */
  166.             force_flag = TRUE;
  167.             break;
  168.         case 'k':        /* kill running mthreads */
  169.             kill_mthreads = TRUE;
  170.             break;
  171.         case 'n':        /* don't process anything */
  172.             no_processing = TRUE;
  173.             break;
  174.         case 's':        /* sleep between articles */
  175.             if (*++*argv <= '9' && **argv >= '0') {
  176.             slow_down = atoi(*argv);
  177.             while (*++*argv <= '9' && **argv >= '0') {
  178.                 ;
  179.             }
  180.             } else {
  181.             slow_down = 1L * 1000 * 1000;
  182.             }
  183.             --*argv;
  184.             break;
  185.         case 't':        /* maintain active.times file */
  186. #ifdef ACTIVE_TIMES
  187.             if (strEQ(ACTIVE_TIMES, "nntp")) {
  188.             fprintf(stderr, "Ignoring the -t option.\n");
  189.             } else {
  190.             acttimes_flag = TRUE;
  191.             }
  192. #else
  193.             fprintf(stderr, "Ignoring the -t option.\n");
  194. #endif
  195.             break;
  196.         case 'v':        /* get more verbose in the log file */
  197.             log_verbosity++;
  198.             break;
  199.         case 'V':
  200.             fprintf(stderr,"%s\n%s\n",mtid,patchlevel);
  201.             fprintf(stderr,"Send bug reports to davison@borland.com\n");
  202.             exit(0);
  203.         case 'z':        /* destroy .thread on severe signal */
  204.             zap_thread = TRUE;
  205.             break;
  206.         default:
  207.             fprintf(stderr, "Unknown option: '%c'\n", **argv);
  208.             exit(1);
  209.         }
  210.         }
  211.     } else {
  212.         if (hierarchy_list) {
  213.         fprintf(stderr, "Specify the newsgroups in one comma-separated list.\n");
  214.         exit(1);
  215.         }
  216.         hierarchy_list = *argv;
  217.     }
  218.     }
  219.  
  220.     /* Initialize umask(), file_exp(), etc. */
  221.     mt_init();
  222.  
  223.     /* If this is a kill request, look for the daemon lock file. */
  224.     if (kill_mthreads) {
  225.     if (!mt_lock(DAEMON_LOCK, SIGTERM)) {
  226.         fprintf(stderr, "No mthreads daemon is running.\n");
  227.         wrap_it_up(1);
  228.     }
  229.     fprintf(stderr, "Killed mthreads daemon.\n");
  230.     wrap_it_up(0);
  231.     }
  232.  
  233.     /* See if an mthreads pass is already going. */
  234.     if (mt_lock(PASS_LOCK, 0) != 0 && !daemon_delay) {
  235.     fprintf(stderr, "mthreads is already running.\n");
  236.     wrap_it_up(1);
  237.     }
  238.  
  239. #ifdef USE_SYSLOG
  240. # ifdef LOG_DAEMON
  241.     openlog("mthreads", LOG_PID, SYSLOG_PRIORITY);
  242. # else
  243.     openlog("mthreads", LOG_PID);
  244. # endif
  245. #else
  246.     /* Open our log file */
  247.     filename = file_exp(MTLOG);
  248.     if ((fp_log = fopen(filename, "a")) == Nullfp) {
  249.     fprintf(stderr, "Unable to open `%s'.\n", filename);
  250.     wrap_it_up(1);
  251.     }
  252. #endif
  253.  
  254. #ifdef SIGHUP
  255.     if (sigset(SIGHUP, SIG_IGN) != SIG_IGN) {
  256.     sigset(SIGHUP, int_handler);
  257.     }
  258. #endif
  259.     if (sigset(SIGINT, SIG_IGN) != SIG_IGN) {
  260.     sigset(SIGINT, int_handler);
  261.     }
  262. #ifdef SIGQUIT
  263.     if (sigset(SIGQUIT, SIG_IGN) != SIG_IGN) {
  264.     sigset(SIGQUIT, int_handler);
  265.     }
  266. #endif
  267.     sigset(SIGTERM, int_handler);
  268. #ifdef SIGBUS
  269.     sigset(SIGBUS, int_handler);
  270. #endif
  271.     sigset(SIGSEGV, int_handler);
  272. #ifdef SIGTTIN
  273.     sigset(SIGTTIN, SIG_IGN);
  274.     sigset(SIGTTOU, SIG_IGN);
  275. #endif
  276.     sigset(SIGALRM, SIG_IGN);
  277. #ifdef USE_NNTP
  278.     sigset(SIGPIPE, int_handler);
  279. #endif
  280.  
  281.     /* Ensure this machine has the right byte-order for the database */
  282.     filename = file_exp(DBINIT);
  283.     if ((fp_tmp = fopen(filename, FOPEN_RB)) == Nullfp
  284.      || fread((char*)&mt_bmap,1,sizeof (BMAP), fp_tmp) < sizeof (BMAP)-1) {
  285.     if (fp_tmp != Nullfp) {
  286.         fclose(fp_tmp);
  287.     }
  288.      write_db_init:
  289.     mybytemap(&mt_bmap);
  290.     if ((fp_tmp = fopen(filename, FOPEN_WB)) == Nullfp) {
  291.         log_entry("Unable to create file: `%s'.\n", filename);
  292.         wrap_it_up(1);
  293.     }
  294.     mt_bmap.version = DB_VERSION;
  295.     fwrite((char*)&mt_bmap, 1, sizeof (BMAP), fp_tmp);
  296.     fclose(fp_tmp);
  297.     } else {
  298.     int i;
  299.  
  300.     fclose(fp_tmp);
  301.     if (mt_bmap.version != DB_VERSION) {
  302.         if (mt_bmap.version == DB_VERSION-1) {
  303.         rebuild = TRUE;
  304.         log_entry("Upgrading database to version %d.\n", DB_VERSION);
  305.         goto write_db_init;
  306.         }
  307.         log_entry("** Database is not the right version (%d instead of %d) **\n",
  308.         mt_bmap.version, DB_VERSION);
  309.         wrap_it_up(1);
  310.     }
  311.     mybytemap(&my_bmap);
  312.     for (i = 0; i < sizeof (LONG); i++) {
  313.         if (my_bmap.l[i] != mt_bmap.l[i]
  314.          || (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i])) {
  315.         log_entry("\
  316. ** Byte-order conflict -- re-run from a compatible machine **\n\
  317. \t\tor remove the current thread files, including db.init **\n");
  318.         wrap_it_up(1);
  319.         }
  320.     }
  321.     }
  322.  
  323.     initializing = FALSE;
  324.  
  325.     /* If we're not in daemon mode, run through once and quit. */
  326.     if (!daemon_delay) {
  327.     log_startup();
  328.     setbuf(stdout, Nullch);
  329.     extra_expire = (expire_time != 0);
  330.     makethreads();
  331.     } else {
  332.     cron_locking = FALSE;
  333.     if (mt_lock(DAEMON_LOCK, 0) != 0) {
  334.         fprintf(stderr, "An mthreads daemon is already running.\n");
  335.         wrap_it_up(1);
  336.     }
  337.     /* For daemon mode, we cut ourself off from anything tty-related and
  338.     ** run in the background (involves forks, but no knives).
  339.     */
  340.     close(0);
  341.     if (open("/dev/null", 2) != 0) {
  342.         fprintf(stderr, "unable to open /dev/null!\n");
  343.         wrap_it_up(1);
  344.     }
  345.     close(1);
  346.     close(2);
  347.     dup(0);
  348.     dup(0);
  349.     while ((pid = fork()) < 0) {
  350.         sleep(2);
  351.     }
  352.     if (pid) {
  353.         exit(0);
  354.     }
  355. #ifdef TIOCNOTTY
  356.     if ((fd = open("/dev/tty", 1)) >= 0) {
  357.         ioctl(fd, TIOCNOTTY, (int*)0);
  358.         close(fd);
  359.     }
  360. #else
  361.     (void) setpgrp();
  362.     while ((pid = fork()) < 0) {
  363.         sleep(2);
  364.     }
  365.     if (pid) {
  366.         exit(0);
  367.     }
  368. #endif
  369.     /* Put our pid in the lock file for death detection */
  370.     if ((fp_tmp = fopen(file_exp(MTDLOCK), "w")) != Nullfp) {
  371.         fprintf(fp_tmp, "%ld\n", (long)getpid());
  372.         fclose(fp_tmp);
  373.     }
  374.     log_startup();
  375.  
  376.     sigset(SIGALRM, alarm_handler);
  377.  
  378.     /* Start timer -- first interval is shorter than all others */
  379.     alarm(TIMER_FIRST);
  380.     for (;;) {
  381.         pause();        /* let alarm go off */
  382.         alarm(0);
  383.  
  384. #ifndef USE_SYSLOG
  385.         /* Re-open our log file, if needed */
  386.         if (!fp_log && !(fp_log = fopen(file_exp(MTLOG), "a"))) {
  387.         wrap_it_up(1);
  388.         }
  389. #endif
  390. #ifndef USE_NNTP
  391.         if (stat(file_exp(ACTIVE), &filestat) < 0) {
  392.         log_entry("Unable to stat active file -- quitting.\n");
  393.         wrap_it_up(1);
  394.         }
  395. #endif
  396.         if (expire_time && time(Null(time_t*)) > expire_time) {
  397.         expire_time += 24L * 60 * 60;
  398.         extra_expire = TRUE;
  399.         }
  400. #ifdef USE_NNTP
  401.         makethreads();    /* NNTP version always compares files */
  402. #else
  403.         if (extra_expire || filestat.st_mtime != last_modified) {
  404.         last_modified = filestat.st_mtime;
  405.         if (!makethreads()) {
  406.             last_modified--;
  407.         }
  408.         }
  409. #endif
  410.         alarm(daemon_delay);
  411. #ifndef USE_SYSLOG
  412.         fclose(fp_log);    /* close the log file while we sleep */
  413.         fp_log = Nullfp;
  414. #endif
  415.     } /* for */
  416.     }/* if */
  417.  
  418.     wrap_it_up(0);
  419.     return 0;                /* NOTREACHED */
  420. }
  421.  
  422. Signal_t
  423. alarm_handler(dummy)
  424. int dummy;
  425. {
  426.     sigset(SIGALRM, alarm_handler);
  427. }
  428.  
  429. Signal_t
  430. int_handler(sig)
  431. int sig;
  432. {
  433.     static int visits = 0;
  434.     int ret = 0;
  435.  
  436.     if (++visits > 4) {
  437.     wrap_it_up(1);
  438.     }
  439.  
  440. #ifndef USE_SYSLOG
  441.     /* Re-open our log file, if needed */
  442.     if (fp_log || (fp_log = fopen(file_exp(MTLOG), "a")))
  443. #endif
  444.     {
  445.     switch (sig) {
  446.     case SIGTERM:
  447. #ifdef SIGHUP
  448.     case SIGHUP:
  449. #endif
  450. #ifdef SIGQUIT
  451.     case SIGQUIT:
  452. #endif
  453.         log_entry("halt requested.\n");
  454.         zap_thread = 0;
  455.         break;
  456. #ifdef USE_NNTP
  457.     case SIGPIPE:
  458.         log_entry("broken pipe -- trying to continue.\n");
  459.         sigset(SIGPIPE, int_handler);
  460.         return;
  461. #endif
  462. #ifdef SIGBUS
  463.         case SIGBUS:
  464. #endif
  465.         case SIGSEGV:
  466.         log_error("** Severe signal: %d **\n", sig);
  467.         /* Destroy offending thread file if requested to do so. */
  468.         if (zap_thread) {
  469.             unlink(thread_name(line));
  470.             log_entry("Destroyed thread file for %s\n", line);
  471.         }
  472.         break;
  473.     default:
  474.         log_entry("interrupt %d received.\n", sig);
  475.         zap_thread = 0;
  476.         ret = 1;
  477.         break;
  478.     }
  479.     }
  480.     if (!daemon_delay) {
  481.     printf("Interrupt %d!\n", sig);
  482.     if (zap_thread) {
  483.         printf("Destroyed thread file for %s\n", line);
  484.     }
  485.     }
  486.  
  487.     /* If we're in the middle of writing the new active2 file, finish it. */
  488.     if (fp_active2w) {
  489.     if (*line2) {
  490.         if (index(line2, ' ')) {
  491.         fputs(line2, fp_active2w);
  492.         } else {
  493.         fprintf(fp_active2w, fmt_active2, line2, last2, first2, ch2);
  494.         }
  495.     }
  496.     for (pline = line_root; pline; pline = pline->link) {
  497.         fprintf(fp_active2w, fmt_active2,
  498.             pline->name, pline->last, pline->first, pline->type);
  499.     }
  500.     if (!eof_active2) {
  501.         while (fgets(line2, sizeof line2, fp_active2)) {
  502.         fputs(line2, fp_active2w);
  503.         }
  504.     }
  505.     log_stats();
  506.     }
  507.     wrap_it_up(ret);
  508. }
  509.  
  510. void
  511. wrap_it_up(ret)
  512. int ret;
  513. {
  514.     mt_unlock(locked);
  515.     (void) chdir(PRIVLIB);        /* for *mon.out files, etc. */
  516.     exit(ret);
  517. }
  518.  
  519. /* Process the active file, creating/modifying the active2 file and
  520. ** creating/modifying the thread data files.
  521. */
  522. bool
  523. makethreads()
  524. {
  525.     register char *cp, *cp2;
  526.     char data_file_open;
  527.     bool update_successful, old_groups, touch_thread;
  528. #ifdef USE_NNTP
  529.     int server_failure = 0;
  530. #endif
  531.  
  532.     /* See if an mthreads pass is already going. */
  533.     if (!(locked & PASS_LOCK) && mt_lock(PASS_LOCK, 0) != 0) {
  534.     log_entry("unable to get a lock for this pass.\n");
  535.     return FALSE;
  536.     }
  537. #ifdef USE_NNTP
  538.     if (!nntp_connect()) {
  539.     return FALSE;
  540.     }
  541.     nntp_command("LIST");    /* ask server for the active file */
  542.     if (nntp_check(FALSE) != NNTP_CLASS_OK) {
  543.     log_entry("Unable to get active file from server.\n");
  544.     nntp_close();
  545.     return FALSE;
  546.     }
  547.     if ((fp_active = fopen(file_exp(ACTIVE1), "w+")) == Nullfp) {
  548.     log_entry("Unable to write the active1 file -- quitting.\n");
  549.     wrap_it_up(1);
  550.     }
  551.     while (1) {
  552.     if (nntp_gets(line, sizeof line) < 0) {
  553.         log_entry("Server failed to send entire active file.\n");
  554.         fclose(fp_active);
  555.         nntp_close();
  556.         return FALSE;
  557.     }
  558.     if (*line == '.') {
  559.         break;
  560.     }
  561.     fputs(line, fp_active);
  562.     putc('\n', fp_active);
  563.     }
  564.     if (ferror(fp_active)) {
  565.     log_entry("Error writing to active1 file.\n");
  566.     fclose(fp_active);
  567.     nntp_close();
  568.     return FALSE;
  569.     }
  570.     fseek(fp_active, 0L, 0);        /* rewind for read */
  571. #else /* !USE_NNTP */
  572.     if ((fp_active = fopen(file_exp(ACTIVE), "r")) == Nullfp) {
  573.     log_entry("Unable to open the active file.\n");
  574.     wrap_it_up(1);
  575.     }
  576. #endif
  577.     filename = file_exp(ACTIVE2);
  578.     if ((fp_active2w = fopen(filename, "r+")) == Nullfp) {
  579.     if ((fp_active2w = fopen(filename, "w")) == Nullfp) {
  580.         log_entry("Unable to open the active2 file for update.\n");
  581.         wrap_it_up(1);
  582.     }
  583.     /* Add existing groups to active.times file with ancient date. */
  584.     old_groups = TRUE;
  585.     } else {
  586.     old_groups = FALSE;
  587.     }
  588.     if ((fp_active2 = fopen(filename, "r")) == Nullfp) {
  589.     log_entry("Unable to open the active2 file.\n");
  590.     wrap_it_up(1);
  591.     }
  592.     if (extra_expire && log_verbosity) {
  593.     log_entry("Using enhanced expiration for this pass.\n");
  594.     }
  595.  
  596.     eof_active = eof_active2 = FALSE;
  597.     fp_tmp = Nullfp;
  598.  
  599.     /* Loop through entire active file. */
  600.     for (;;) {
  601.     if (eof_active || !fgets(line, sizeof line, fp_active)) {
  602.         if (eof_active2 && !line_root) {
  603.         break;
  604.         }
  605.         eof_active = TRUE;
  606.         ch = 'x';
  607.     } else {
  608.         cp = line + strlen(line) - 1;
  609.         if (*cp == '\n') {
  610.         *cp = '\0';
  611.         }
  612.         if (!(cp = index(line, ' '))) {
  613.         log_entry("** line in 'active' has no space: %s **\n", line);
  614.         continue;
  615.         }
  616.         *cp = '\0';
  617.         if (sscanf(cp+1, "%ld %ld %c", &last, &first, &ch) != 3) {
  618.         log_entry("** digits corrupted in 'active': %s %s **\n",
  619.             line, cp+1);
  620.         continue;
  621.         }
  622.         if (last < first - 1) {
  623.         log_entry("** bogus group values in 'active': %s %s **\n",
  624.             line, cp+1);
  625.         continue;
  626.         }
  627.     }
  628.     if (debug > 1 || log_verbosity > 3) {
  629.         log_entry("Processing %s:\n", line);
  630.     }
  631.     data_file_open = 0;
  632.     /* If we've allocated some lines in memory while searching for
  633.     ** newsgroups (they've scrambled the active file on us), check
  634.     ** them first.
  635.     */
  636.     last_line = Nullact;
  637.     for (pline = line_root; pline; pline = pline->link) {
  638.         if (eof_active || strEQ(line, pline->name)) {
  639.         strcpy(line2, pline->name);
  640.         free(pline->name);
  641.         first2 = pline->first;
  642.         last2 = pline->last;
  643.         ch2 = pline->type;
  644.         if (last_line) {
  645.             last_line->link = pline->link;
  646.         } else {
  647.             line_root = pline->link;
  648.         }
  649.         free(pline);
  650.         break;
  651.         }
  652.         last_line = pline;
  653.     }/* for */
  654.     touch_thread = FALSE;
  655.  
  656.     /* If not found yet, check the active2 file. */
  657.     if (!pline) {
  658.         for (;;) {
  659.         if (eof_active2 || !fgets(line2, sizeof line2, fp_active2)) {
  660.             /* At end of file, check if the thread data file exists.
  661.             ** If so, use its high/low values.  Else, default to
  662.             ** some initial values.
  663.             */
  664.             eof_active2 = TRUE;
  665.             if (eof_active) {
  666.             break;
  667.             }
  668.             strcpy(line2, line);
  669.             if ((data_file_open = init_data(thread_name(line)))) {
  670.             last2 = total.last;
  671.             first2 = total.first;
  672.             ch2 = 'y';
  673.             } else {
  674.             total.first = first2 = first;
  675.             if (add_new && (!hierarchy_list
  676.               || ngmatch(hierarchy_list, line) == NG_MATCH)) {
  677.                 total.last = last2 = first - 1;
  678.                 ch2 = (ch == '=' ? 'x' : ch);
  679.                 touch_thread = TRUE;
  680.                 added_groups += (ch2 != 'x');
  681.             } else {
  682.                 total.last = last2 = last;
  683.                 ch2 = (ch == '=' ? 'X' : toupper(ch));
  684.             }
  685.             }
  686.             data_file_open++;        /* (1 == empty, 2 == open) */
  687. #ifdef ACTIVE_TIMES
  688.             /* Found a new group -- see if we need to log it. */
  689.             if (acttimes_flag) {
  690.             if (!fp_tmp && !(fp_tmp = fopen(ACTIVE_TIMES, "a"))) {
  691.                 log_entry("unable to append to %s.\n",ACTIVE_TIMES);
  692.                 acttimes_flag = FALSE;
  693.             } else {
  694.                 fprintf(fp_tmp, "%s %ld mthreads\n", line,
  695.                 old_groups ? 30010440L : time(Null(time_t)));
  696.             }
  697.             }
  698. #endif
  699.             break;
  700.         }
  701.         if (!(cp2 = index(line2, ' '))) {
  702.             log_entry("active2 line has no space: %s\n", line2);
  703.             continue;
  704.         }
  705.         *cp2 = '\0';
  706.         if (sscanf(cp2+1,"%ld %ld %c",&last2,&first2,&ch2) != 3) {
  707.             log_entry("active2 digits corrupted: %s %s\n",
  708.             line2, cp2+1);
  709.             continue;
  710.         }
  711.         /* Check if we're still in-sync */
  712.         if (eof_active || strEQ(line, line2)) {
  713.             break;
  714.         }
  715.         /* Nope, we've got to go looking for this line somewhere
  716.         ** down in the file.  Save each non-matching line in memory
  717.         ** as we go.
  718.         */
  719.         pline = (ACTIVE_LINE*)safemalloc(sizeof (ACTIVE_LINE));
  720.         pline->name = savestr(line2);
  721.         pline->last = last2;
  722.         pline->first = first2;
  723.         pline->type = ch2;
  724.         pline->link = Nullact;
  725.         if (!last_line) {
  726.             line_root = pline;
  727.         } else {
  728.             last_line->link = pline;
  729.         }
  730.         *line2 = '\0';
  731.         last_line = pline;
  732.         }/* for */
  733.         if (eof_active && eof_active2) {
  734.         break;
  735.         }
  736.     }/* if !pline */
  737.     if (eof_active) {
  738.         strcpy(line, line2);
  739.         if (truncate_len < 0) {
  740.         truncate_len = ftell(fp_active2w);
  741.         }
  742.     }
  743.     if (rebuild) {
  744.         unlink(thread_name(line));
  745.     }
  746.     update_successful = FALSE;
  747.     if (hierarchy_list && !add_new) {
  748.         switch ((action = ngmatch(hierarchy_list, line))) {
  749.         case NG_MATCH:            /* if unthreaded, add it */
  750.         if (ch2 < 'a' && ch != 'x' && ch != '=') {
  751.             total.last = last2 = first2 - 1;
  752.             touch_thread = TRUE;
  753.             added_groups++;
  754.         }
  755.         break;
  756.         case NG_SKIP:            /* if threaded, remove it */
  757.         if (ch2 >= 'a') {
  758.             unlink(thread_name(line));
  759.             removed_groups++;
  760.         }
  761.         break;
  762.         }
  763.     } else {
  764.         action = (ch2 < 'a' ? NG_SKIP : NG_MATCH);
  765.     }
  766.     if (action == NG_DEFAULT || (debug && action == NG_SKIP)) {
  767.         dont_read_data(data_file_open);    /* skip silently */
  768.         if (touch_thread) {
  769.         (void) write_data(thread_name(line));
  770.         }
  771.     } else if (ch == 'x' || ch == '=') {
  772.         if (!daemon_delay) {        /* skip 'x'ed groups */
  773.         putchar('x');
  774.         }
  775.         ch = (action == NG_SKIP ? 'X' : 'x');
  776.         if ((ch2 >= 'a' && ch2 != 'x') || force_flag) {
  777.         /* Remove thread file if group is newly 'x'ed out */
  778.         unlink(thread_name(line));
  779.         }
  780.         update_successful = TRUE;
  781.         dont_read_data(data_file_open);
  782.     } else if (action == NG_SKIP) {    /* skip excluded groups */
  783.         if (!daemon_delay) {
  784.         putchar('X');
  785.         }
  786.         ch = toupper(ch);
  787.         if (force_flag) {
  788.         unlink(thread_name(line));
  789.         }
  790.         update_successful = TRUE;
  791.         dont_read_data(data_file_open);
  792.     } else if (no_processing) {
  793.         if (!daemon_delay) {
  794.         putchar(',');
  795.         }
  796.         ch2 = ch;
  797.         dont_read_data(data_file_open);
  798.         if (touch_thread) {
  799.         (void) write_data(thread_name(line));
  800.         }
  801.     } else if (!force_flag && !extra_expire && !rebuild
  802.      && first == first2 && last == last2) {
  803.         /* We're up-to-date here.  Skip it. */
  804.         if (!daemon_delay) {
  805.         putchar('.');
  806.         }
  807.         update_successful = TRUE;
  808.         dont_read_data(data_file_open);
  809.         if (touch_thread) {
  810.         (void) write_data(thread_name(line));
  811.         }
  812.     } else {
  813.         /* Looks like we need to process something. */
  814. #ifdef USE_NNTP
  815.         if (!server_failure) {
  816.         sprintf(buf, "GROUP %s", line);
  817.         nntp_command(buf);        /* go to next group */
  818.         if (nntp_check(FALSE) != NNTP_CLASS_OK) {
  819.             log_error("NNTP failure -- %s.\n", ser_line);
  820.             if (strnNE(ser_line, "400", 3)) {
  821.             nntp_close();
  822.             }
  823.             server_failure = 1;
  824.         }
  825.         }
  826.         if (server_failure) {
  827. #else
  828.         strcpy(cp = buf, line2);
  829.         while ((cp = index(cp, '.'))) {
  830.         *cp = '/';
  831.         }
  832.         filename = file_exp(buf);        /* relative to spool dir */
  833.         if (chdir(filename) < 0) {
  834.         if (errno != ENOENT) {
  835.             log_entry("Unable to chdir to `%s'.\n", filename);
  836.         }
  837. #endif
  838.         if (!daemon_delay) {
  839.             putchar('*');
  840.         }
  841.         dont_read_data(data_file_open);
  842.         } else {
  843.         filename = thread_name(line);
  844.         /* Try to open the data file only if we didn't try it
  845.         ** in the name matching code above.
  846.         */
  847.         if (!data_file_open--) {    /* (0 == haven't tried yet) */
  848.             if (!(data_file_open = init_data(filename))) {
  849.             total.last = first - 1;
  850.             total.first = first;
  851.             }
  852.         }
  853.  
  854.         if (data_file_open) {        /* (0 == empty, 1 == open) */
  855.             if (!read_data()) {    /* did read fail? */
  856.             if (debug) {
  857.                 strcpy(buf, filename);
  858.                 cp = rindex(buf, '/') + 1;
  859.                 strcpy(cp, "bad.read");
  860.                 rename(filename, buf);
  861.             }
  862.             data_file_open = init_data(Nullch);
  863.             total.last = first - 1;
  864.             total.first = first;
  865.             }
  866.         }
  867.         grevious_error = FALSE;
  868.         process_articles(first, last);
  869.         processed_groups++;
  870.         if (!added_count && !expired_count && !touch_thread
  871.          && last == last2) {
  872.             (void) write_data(Nullch);
  873.             if (!daemon_delay) {
  874.             putchar(':');
  875.             }
  876.             update_successful = TRUE;
  877.         } else {
  878.             int root_count = total.root;
  879.             strcpy(buf, filename);
  880.             cp = rindex(buf, '/') + 1;
  881.             strcpy(cp, NEW_THREAD);    /* write data as .new */
  882.             if (write_data(buf) && !grevious_error) {
  883.             rename(buf, filename);
  884.             added_articles += added_count;
  885.             expired_articles += expired_count;
  886.             if (!daemon_delay) {
  887.                 if (!root_count) {
  888.                 putchar('-');
  889.                 } else {
  890.                 putchar('#');
  891.                 }
  892.             }
  893.             update_successful = TRUE;
  894.             } else {
  895.             if (debug) {
  896.                 cp = rindex(filename, '/') + 1;
  897.                 strcpy(cp, "bad.write");
  898.                 rename(buf, filename);
  899.             } else {
  900.                 unlink(buf);    /* blow away bad write */
  901.                 if (grevious_error) {
  902.                 unlink(filename); /* blow away the .thread, */
  903.                 (void) init_data(Nullch); /* set totals */
  904.                 total.last = first-1;
  905.                 total.first = first;
  906.                 (void) write_data(filename); /* write it null */
  907.                 }
  908.             }
  909.             if (!daemon_delay) {
  910.                 putchar('!');
  911.             }
  912.             }/* if */
  913.         }/* if */
  914.         }/* if */
  915.     }/* if */
  916.     /* Finally, update the active2 entry for this newsgroup. */
  917.     if (!eof_active) {
  918.         if (update_successful) {
  919.         fprintf(fp_active2w, fmt_active2, line, last, first, ch);
  920.         } else {
  921.         fprintf(fp_active2w, fmt_active2, line, last2, first2, ch2);
  922.         }
  923.     }
  924.     *line2 = '\0';
  925.     /* If we're not out of sync, keep active2 file flushed. */
  926.     if (!line_root) {
  927.         fflush(fp_active2w);
  928.     }
  929. #ifdef CHECKLOAD
  930.     checkload();
  931. #endif
  932.     }/* for */
  933.  
  934. #ifdef USE_NNTP
  935.     if (!server_failure) {
  936.     nntp_close();
  937.     }
  938. #endif
  939.     fclose(fp_active);
  940.     fclose(fp_active2);
  941.  
  942.     if (truncate_len >= 0) {
  943. #ifdef HAS_FTRUNCATE
  944.     if (ftruncate(fileno(fp_active2w), truncate_len) == -1)
  945. #else
  946. #ifdef MVTRUNC
  947.     if (mvtrunc(file_exp(ACTIVE2), truncate_len) == -1)
  948. #else
  949.     if (chsize(fileno(fp_active2w), truncate_len) == -1)
  950. #endif
  951. #endif
  952.     {
  953.         log_entry("Unable to truncate the active2 file.\n");
  954.     }
  955.     truncate_len = -1;
  956.     }
  957.     fclose(fp_active2w);
  958.     fp_active2w = Nullfp;
  959.  
  960.     if (fp_tmp) {
  961.     fclose(fp_tmp);
  962.     }
  963.     log_stats();
  964.     processed_groups = added_groups = removed_groups = 0;
  965.     added_articles = expired_articles = 0;
  966.  
  967.     extra_expire = FALSE;
  968.     rebuild = FALSE;
  969.  
  970.     mt_unlock(PASS_LOCK);        /* remove single-pass lock */
  971.  
  972.     return TRUE;
  973. }
  974.  
  975. /*
  976. ** ngmatch - newsgroup name matching
  977. **
  978. ** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
  979. ** and NG_DEFAULT if the group doesn't match at all.
  980. **
  981. ** "all" in a pattern is a wildcard that matches exactly one word;
  982. ** it does not cross "." (NGDELIM) delimiters.
  983. **
  984. ** This matching code was borrowed from C news.
  985. */
  986.  
  987. #define ALL "all"            /* word wildcard */
  988.  
  989. #define NGNEG '!'
  990. #define NGSEP ','
  991. #define NGDELIM '.'
  992.  
  993. int
  994. ngmatch(ngpat, grp)
  995. char *ngpat, *grp;
  996. {
  997.     register char *patp;        /* point at current pattern */
  998.     register char *patcomma;
  999.     register int depth;
  1000.     register int faildeepest = 0, hitdeepest = 0;    /* in case no match */
  1001.     register bool negation;
  1002.  
  1003.     for (patp = ngpat; patp != Nullch; patp = patcomma) {
  1004.     negation = FALSE;
  1005.     patcomma = index(patp, NGSEP);
  1006.     if (patcomma != Nullch) {
  1007.         *patcomma = '\0';        /* will be restored below */
  1008.     }
  1009.     if (*patp == NGNEG) {
  1010.         ++patp;
  1011.         negation = TRUE;
  1012.     }
  1013.     depth = onepatmatch(patp, grp); /* try 1 pattern, 1 group */
  1014.     if (patcomma != Nullch) {
  1015.         *patcomma++ = NGSEP;    /* point after the comma */
  1016.     }
  1017.     if (depth == 0) {        /* mis-match */
  1018.         ;                /* ignore it */
  1019.     } else if (negation) {
  1020.         /* record depth of deepest negated matched word */
  1021.         if (depth > faildeepest) {
  1022.         faildeepest = depth;
  1023.         }
  1024.     } else {
  1025.         /* record depth of deepest plain matched word */
  1026.         if (depth > hitdeepest) {
  1027.         hitdeepest = depth;
  1028.         }
  1029.     }
  1030.     }
  1031.     if (hitdeepest > faildeepest) {
  1032.     return NG_MATCH;
  1033.     } else if (faildeepest) {
  1034.     return NG_SKIP;
  1035.     } else {
  1036.     return NG_DEFAULT;
  1037.     }
  1038. }
  1039.  
  1040. /*
  1041. ** Match a pattern against a group by looking at each word of pattern in turn.
  1042. **
  1043. ** On a match, return the depth (roughly, ordinal number * k) of the rightmost
  1044. ** word that matches.  If group runs out first, the match fails; if pattern
  1045. ** runs out first, it succeeds.  On a failure, return zero.
  1046. */
  1047. int
  1048. onepatmatch(patp, grp)
  1049. char *patp, *grp;
  1050. {
  1051.     register char *rpatwd;        /* used by word match (inner loop) */
  1052.     register char *patdot, *grdot;    /* point at dots after words */
  1053.     register char *patwd, *grwd;    /* point at current words */
  1054.     register int depth = 0;
  1055.  
  1056.     for (patwd = patp, grwd = grp;
  1057.      patwd != Nullch && grwd != Nullch;
  1058.      patwd = patdot, grwd = grdot
  1059.     ) {
  1060.     register bool match = FALSE;
  1061.     register int incr = 20;
  1062.  
  1063.     /* null-terminate words */
  1064.     patdot = index(patwd, NGDELIM);
  1065.     if (patdot != Nullch) {
  1066.         *patdot = '\0';        /* will be restored below */
  1067.     }
  1068.     grdot = index(grwd, NGDELIM);
  1069.     if (grdot != Nullch) {
  1070.         *grdot = '\0';        /* will be restored below */
  1071.     }
  1072.     /*
  1073.      * Match one word of pattern with one word of group.
  1074.      * A pattern word of "all" matches any group word,
  1075.      * but isn't worth as much.
  1076.      */
  1077. #ifdef FAST_STRCMP
  1078.     match = STREQ(patwd, grwd);
  1079.     if (!match && STREQ(patwd, ALL)) {
  1080.         match = TRUE;
  1081.         --incr;
  1082.     }
  1083. #else
  1084.     for (rpatwd = patwd; *rpatwd == *grwd++;) {
  1085.         if (*rpatwd++ == '\0') {
  1086.         match = TRUE;        /* literal match */
  1087.         break;
  1088.         }
  1089.     }
  1090.     if (!match) {
  1091.         /* ugly special case match for "all" */
  1092.         rpatwd = patwd;
  1093.         if (*rpatwd++ == 'a' && *rpatwd++ == 'l'
  1094.          && *rpatwd++ == 'l' && *rpatwd   == '\0') {
  1095.         match = TRUE;
  1096.          --incr;
  1097.         }
  1098.     }
  1099. #endif                /* FAST_STRCMP */
  1100.  
  1101.     if (patdot != Nullch) {
  1102.         *patdot++ = NGDELIM;    /* point after the dot */
  1103.     }
  1104.     if (grdot != Nullch) {
  1105.         *grdot++ = NGDELIM;
  1106.     }
  1107.     if (!match) {
  1108.         depth = 0;        /* words differed - mismatch */
  1109.         break;
  1110.     }
  1111.     depth += incr;
  1112.     }
  1113.     /* if group name ran out before pattern, then match fails */
  1114.     if (grwd == Nullch && patwd != Nullch) {
  1115.     depth = 0;
  1116.     }
  1117.     return depth;
  1118. }
  1119.  
  1120. /* Put our startup options into the log file.
  1121. */
  1122. void
  1123. log_startup()
  1124. {
  1125.     char tmpbuf[256];
  1126.  
  1127.     strcpy(tmpbuf, "Started mthreads");
  1128.     if (cron_locking) {
  1129.     strcat(tmpbuf, " -c");
  1130.     }
  1131.     if (debug) {
  1132.     strcat(tmpbuf, " -D");
  1133.     }
  1134.     if (force_flag) {
  1135.     strcat(tmpbuf, " -f");
  1136.     }
  1137.     if (daemon_delay) {
  1138.     sprintf(tmpbuf + strlen(tmpbuf), " -d%d", daemon_delay / 60);
  1139.     if (expire_time) {
  1140.         struct tm *ts;
  1141.  
  1142.         ts = localtime(&expire_time);
  1143.         sprintf(tmpbuf + strlen(tmpbuf), " -e%02d%02d",ts->tm_hour,ts->tm_min);
  1144.     }
  1145.     } else if (expire_time) {
  1146.     strcat(tmpbuf, " -e");
  1147.     }
  1148.     if (slow_down) {
  1149.     sprintf(tmpbuf + strlen(tmpbuf), " -s%d", slow_down);
  1150.     }
  1151.     if (no_processing) {
  1152.     strcat(tmpbuf, " -n");
  1153.     }
  1154.     if (log_verbosity) {
  1155.     sprintf(tmpbuf + strlen(tmpbuf), " -v%d", log_verbosity);
  1156.     }
  1157.     if (zap_thread) {
  1158.     strcat(tmpbuf, " -z");
  1159.     }
  1160.     if (add_new) {
  1161.     if (hierarchy_list) {
  1162.         sprintf(tmpbuf + strlen(tmpbuf), " -a %s", hierarchy_list);
  1163.     } else {
  1164.         strcat(tmpbuf, " -a all");
  1165.     }
  1166.     } else if (hierarchy_list) {
  1167.     sprintf(tmpbuf + strlen(tmpbuf), " %s (only)", hierarchy_list);
  1168.     }
  1169.     log_entry("%s\n", tmpbuf);
  1170. }
  1171.  
  1172. /* Put our statistics into the log file.
  1173. */
  1174. void
  1175. log_stats()
  1176. {
  1177.     sprintf(line, "Processed %d group%s:  added %d article%s, expired %d.\n",
  1178.     processed_groups, processed_groups == 1 ? nullstr : "s",
  1179.     added_articles, added_articles == 1 ? nullstr : "s",
  1180.     expired_articles);
  1181.  
  1182.     log_entry(line);
  1183.  
  1184.     if (!daemon_delay) {
  1185.     putchar('\n');
  1186.     fputs(line, stdout);
  1187.     }
  1188.     if (added_groups) {
  1189.     sprintf(line, "Turned %d group%s on.\n", added_groups,
  1190.         added_groups == 1 ? nullstr : "s");
  1191.     log_entry(line);
  1192.     if (!daemon_delay) {
  1193.         fputs(line, stdout);
  1194.     }
  1195.     }
  1196.     if (removed_groups) {
  1197.     sprintf(line, "Turned %d group%s off.\n", removed_groups,
  1198.         removed_groups == 1 ? nullstr : "s");
  1199.     log_entry(line);
  1200.     if (!daemon_delay) {
  1201.         fputs(line, stdout);
  1202.     }
  1203.     }
  1204. }
  1205. /* Generate a log entry with timestamp.
  1206. */
  1207. /*VARARGS1*/
  1208. void
  1209. log_entry(fmt, arg1, arg2, arg3)
  1210. char *fmt;
  1211. long arg1, arg2, arg3;
  1212. {
  1213. #ifndef USE_SYSLOG
  1214.     time_t now;
  1215.     char *ctime();
  1216. #endif
  1217.  
  1218.     if (initializing) {
  1219.     fprintf(stderr, fmt, arg1, arg2, arg3);
  1220.     return;
  1221.     }
  1222.  
  1223. #ifndef USE_SYSLOG
  1224.     (void) time(&now);
  1225.     fprintf(fp_log, "%.12s%c", ctime(&now)+4, daemon_delay ? ' ' : '+');
  1226.     fprintf(fp_log, fmt, arg1, arg2, arg3);
  1227.     fflush(fp_log);
  1228. #else
  1229.     syslog(LOG_INFO, fmt, arg1, arg2, arg3);
  1230. #endif
  1231. }
  1232.  
  1233. /* Generate a log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
  1234. ** and newsgroup name.
  1235. */
  1236. /*VARARGS1*/
  1237. void
  1238. log_error(fmt, arg1, arg2, arg3)
  1239. char *fmt;
  1240. long arg1;
  1241. long arg2;
  1242. long arg3;
  1243. {
  1244.     char fmtbuf[256];
  1245.  
  1246.     sprintf(fmtbuf, "%s: %s", line, fmt);
  1247. #ifndef USE_SYSLOG
  1248.     log_entry(fmtbuf, arg1, arg2, arg3);
  1249. #else
  1250.     syslog(LOG_NOTICE, fmtbuf, arg1, arg2, arg3);
  1251. #endif
  1252.     if (*fmt == '*') {
  1253.     grevious_error = TRUE;
  1254.     if (!daemon_delay) {
  1255.         putchar('E');
  1256.     }
  1257.     }
  1258.     else {
  1259.     if (!daemon_delay) {
  1260.         putchar('e');
  1261.     }
  1262.     }
  1263. }
  1264.  
  1265. #ifdef MYCHSIZE
  1266.     /* code courtesy of William Kucharski */
  1267.  
  1268. int
  1269. chsize(fd, length)
  1270. int fd;            /* file descriptor */
  1271. off_t length;        /* length to set file to */
  1272. {
  1273.     extern long lseek();
  1274.     struct flock fl;
  1275.  
  1276.     if (fstat(fd, &filestat) < 0) {
  1277.     return -1;
  1278.     }
  1279.     if (filestat.st_size < length) {    /* extend file length */
  1280.     /* Write a 0 byte at the end. */
  1281.     if (lseek(fd, length - 1, 0) < 0
  1282.      || write(fd, "", 1) != 1) {
  1283.         return -1;
  1284.     }
  1285.     } else {
  1286.     /* Truncate file at length. */
  1287.     fl.l_whence = 0;
  1288.     fl.l_len = 0;
  1289.     fl.l_start = length;
  1290.     fl.l_type = F_WRLCK;        /* write lock on file space */
  1291.  
  1292.     /*
  1293.     ** This relies on the UNDOCUMENTED F_FREESP argument to
  1294.     ** fcntl(2), which truncates the file so that it ends at the
  1295.     ** position indicated by fl.l_start.
  1296.     **
  1297.     ** Will minor miracles never cease?
  1298.     */
  1299.     if (fcntl(fd, F_FREESP, &fl) < 0) {
  1300.         return -1;
  1301.     }
  1302.     }
  1303.     return 0;
  1304. }
  1305. #endif
  1306.  
  1307. #ifdef MVTRUNC
  1308. int
  1309. mvtrunc(filename, truncate_len)
  1310. char *filename;
  1311. long truncate_len;
  1312. {
  1313.     FILE *fp_in, *fp_out;
  1314.  
  1315.     sprintf(line, "%s.new", filename);
  1316.     if ((fp_out = fopen(line, "w")) == Nullfp) {
  1317.     log_entry("Tried to create active2.new.\n");
  1318.     return -1;
  1319.     }
  1320.     if ((fp_in = fopen(filename, "r")) == Nullfp) {
  1321.     fclose(fp_out);
  1322.     unlink(line);
  1323.     log_entry("Tried to re-open the active2 file.\n");
  1324.     return -1;
  1325.     }
  1326.     while (ftell(fp_out) < truncate_len) {
  1327.     if (!fgets(buf, sizeof buf, fp_in)) {
  1328.         break;
  1329.     }
  1330.     fputs(buf, fp_out);
  1331.     }
  1332.     sprintf(buf, "%s.old", filename);
  1333.     rename(filename, buf);
  1334.     rename(line, filename);
  1335.     fclose(fp_in);
  1336.     fclose(fp_out);
  1337.  
  1338.     return 0;
  1339. }
  1340. #endif
  1341.  
  1342. #ifndef HAS_RENAME
  1343. int
  1344. rename(old, new)
  1345. char    *old, *new;
  1346. {
  1347.     struct stat st;
  1348.  
  1349.     if (stat(old, &st) == -1) {
  1350.     return -1;
  1351.     }
  1352.     if (unlink(new) == -1 && errno != ENOENT) {
  1353.     return -1;
  1354.     }
  1355.     if (link(old, new) == -1) {
  1356.     return -1;
  1357.     }
  1358.     if (unlink(old) == -1) {
  1359.     int e = errno;
  1360.     (void) unlink(new);
  1361.     errno = e;
  1362.     return -1;
  1363.     }
  1364.     return 0;
  1365. }
  1366. #endif /* HAS_RENAME */
  1367.  
  1368. #ifdef CHECKLOAD
  1369.  
  1370. #define FREQCHECK    300
  1371. #define SLPLOAD        300
  1372. #define UPTIME        "/usr/ucb/uptime"
  1373. #define TOOHIGH        5
  1374.  
  1375. checkload()
  1376. {
  1377.     static long lastcheck = 0;
  1378.     long time(), i;
  1379.     FILE *pp;
  1380.     char buf[BUFSIZ];
  1381.     register char *cp;
  1382.     char *strrchr();
  1383.  
  1384.     i = time(Null(time_t*));
  1385.     if ((i - lastcheck) < FREQCHECK) {
  1386.     return;
  1387.     }
  1388.     lastcheck = i;
  1389.  
  1390. again:
  1391.     if ((pp = popen(UPTIME, "r")) == NULL) {
  1392.     return;
  1393.     }
  1394.     if (fgets(buf, BUFSIZ, pp) == NULL) {
  1395.     pclose(pp);
  1396.     return;
  1397.     }
  1398.     pclose(pp);
  1399.     if ((cp = strrchr(buf, ':')) == NULL) {
  1400.     return;
  1401.     }
  1402.     if (atoi(cp + 2) >= TOOHIGH) {
  1403.     sleep(SLPLOAD);
  1404.     goto again;
  1405.     } else {
  1406.     return;
  1407.     }
  1408. }
  1409. #endif
  1410.