home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / nn.tar / nn-6.5.1 / master.c < prev    next >
C/C++ Source or Header  |  1995-04-29  |  22KB  |  1,006 lines

  1. /*
  2.  *    (c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
  3.  *
  4.  *    nn database daemon (nnmaster)
  5.  *
  6.  *    maintains the article header database.
  7.  */
  8.  
  9. #include <signal.h>
  10. #include <errno.h>
  11. #include "config.h"
  12. #include "db.h"
  13. #include "proto.h"
  14.  
  15. /* master.c */
  16.  
  17. static void clean_group_internal __APROTO((register group_header *gh));
  18. static void group_restriction __APROTO((register group_header *gh));
  19. static void set_group_restrictions __APROTO((char **restr, int n));
  20. static void visit_active_file __APROTO((void));
  21. static void build_master __APROTO((void));
  22. static void set_lock_message __APROTO((void));
  23. static void do_reread_groups __APROTO((void));
  24.  
  25.  
  26. import char *bin_directory;
  27. extern char proto_host[];
  28.  
  29. /*
  30.  * nnmaster options:
  31.  *
  32.  *    -e [N]    expire a group if more than N articles are gone
  33.  *    -r N    repeat every N minutes
  34.  *    -h N    don't update if active file is less than N *seconds* old
  35.  *
  36.  *    -f    foreground execution (use with -r)
  37.  *    -y N    retry N times on error
  38.  *
  39.  *    -E [N]    expire mode (see expire.c) [N==1 if omitted]
  40.  *    -F    Run ONLY expire ONCE and exit.
  41.  *    -R N    auto-recollect mode (see expire.c)
  42.  *
  43.  *    -C    check consistency of database on start-up
  44.  *    -b    include 'bad' articles (disables -B)
  45.  *    -B    remove 'bad' articles (just unlink the files)
  46.  *    -O N    Consider articles older than N days as bad articles.
  47.  *
  48.  *    -I [N]    initialize [ limit to N articles in each group ]
  49.  *    -G    reread groups file.
  50.  *    -X    clean ignored groups.
  51.  *
  52.  *    -l"MSG"    lock database with message MSG
  53.  *    -l    unlock database
  54.  *    -i    ignore lock (run collection on locked database)
  55.  *    -k    kill the running master and take over.
  56.  *
  57.  *    -Q    quiet: don't write fatal errors to /dev/console (if no syslog).
  58.  *    -t    trace collection of each group
  59.  *    -v    print version and exit
  60.  *    -u    update even if active is not modified
  61.  *    -w    send wakeup to real master
  62.  *    -Ltypes    exclude 'types' entries from the log
  63.  *    -D [N]    debug, N = +(1 => verbose, 2 => nntp trace)
  64.  *
  65.  *    [master group]...    Collect these groups only.
  66.  */
  67.  
  68. #include "options.h"
  69.  
  70.  
  71. import int
  72.     dont_write_console,
  73.     expire_method,
  74.     expire_level,
  75.     mail_errors_mode,
  76.     recollect_method,
  77.     reread_groups_file,
  78.     ignore_bad_articles,
  79. #ifdef NNTP
  80.     nntp_local_server,
  81.     nntp_debug,
  82. #endif
  83.     remove_bad_articles,
  84.     retry_on_error;
  85.  
  86. import time_t
  87.     max_article_age;
  88.  
  89. import char
  90.     *log_entry_filter;
  91.  
  92. export int
  93.     trace = 0,
  94.     debug_mode = 0,
  95. #ifdef NNTP
  96.     silent = 1,
  97.     no_update = 0,
  98. #endif
  99.     Debug = 0;
  100.  
  101. static int
  102.     check_on_startup = 0,
  103.     clean_ignored = 0,
  104.     expire_once = 0,
  105.     foreground = 0,
  106.     ignore_lock = 0,
  107.     initialize = -1,
  108.     kill_running = 0,
  109.     max_age_days = 0,
  110.     prt_vers = 0,
  111.     unconditional = 0,
  112.     Unconditional = 0,
  113.     wakeup_master = 0;
  114.  
  115. static unsigned
  116.     hold_updates = 0,
  117.     repeat_delay = 0;
  118.  
  119.  
  120. static char
  121.     *lock_message = NULL;
  122.  
  123.  
  124. Option_Description(master_options) {
  125.  
  126.     'b', Bool_Option( ignore_bad_articles ),
  127.     'B', Bool_Option( remove_bad_articles ),
  128.     'C', Bool_Option( check_on_startup ),
  129.     'D', Int_Option_Optional( debug_mode, 1 ),
  130.     'e', Int_Option_Optional( expire_level, 1 ),
  131.     'E', Int_Option_Optional( expire_method, 1 ),
  132.     'f', Bool_Option( foreground ),
  133.     'F', Bool_Option( expire_once ),
  134.     'G', Bool_Option( reread_groups_file ),
  135.     'h', Int_Option_Optional( hold_updates, 60 ),
  136. #ifdef NNTP
  137.     'H', Bool_Option( nntp_local_server ),
  138. #endif
  139.     'i', Bool_Option( ignore_lock ),
  140.     'I', Int_Option_Optional( initialize, 0 ),
  141.     'k', Bool_Option( kill_running ),
  142.     'l', String_Option_Optional( lock_message, "" ),
  143.     'L', String_Option( log_entry_filter ),
  144.     'M', Int_Option( mail_errors_mode ),
  145.     'O', Int_Option( max_age_days ),
  146.     'Q', Bool_Option( dont_write_console ),
  147.     'r', Int_Option_Optional( repeat_delay, 10 ),
  148.     'R', Int_Option( recollect_method ),
  149.     't', Bool_Option( trace ),
  150.     'u', Bool_Option( unconditional ),
  151.     'U', Bool_Option( Unconditional ),
  152.     'v', Bool_Option( prt_vers ),
  153.     'w', Bool_Option( wakeup_master ),
  154.     'X', Bool_Option( clean_ignored ),
  155.     'y', Int_Option( retry_on_error ),
  156.     '\0',
  157. };
  158.  
  159. import char *master_directory, *db_directory, *news_active;
  160.  
  161. static int unlock_on_exit = 0;
  162.  
  163. /*
  164.  * nn_exit() --- called whenever a program exits.
  165.  */
  166.  
  167. void
  168. nn_exit(n)
  169. int n;
  170. {
  171. #ifdef NNTP
  172.     if (use_nntp) nntp_cleanup();
  173. #endif /* NNTP */
  174.     close_master();
  175.  
  176.     if (unlock_on_exit)
  177.     proto_lock(I_AM_MASTER, PL_CLEAR);
  178.  
  179.     if (n)
  180.     log_entry('E', "Abnormal termination, exit=%d", n);
  181.     else
  182.     if (unlock_on_exit)
  183.     log_entry('M', "Master terminated%s", s_hangup ? " (hangup)" : "");
  184.  
  185.     exit(n);
  186. }
  187.  
  188.  
  189. static void
  190. clean_group_internal(gh)    /* no write */
  191. register group_header *gh;
  192. {
  193.     gh->first_db_article = 0;
  194.     gh->last_db_article = 0;
  195.  
  196.     gh->data_write_offset = (off_t)0;
  197.     gh->index_write_offset = (off_t)0;
  198.  
  199.     if (init_group(gh)) {
  200.     (void)open_data_file(gh, 'd', -1);
  201.     (void)open_data_file(gh, 'x', -1);
  202.     }
  203.     
  204.     gh->master_flag &= ~(M_EXPIRE | M_BLOCKED);
  205.     if ((gh->master_flag & M_IGNORE_GROUP) == 0)
  206.     gh->master_flag |= M_BLOCKED;
  207.  
  208. }
  209.  
  210. void
  211. clean_group(gh)    /* does write */
  212. group_header *gh;
  213. {
  214.     if (trace)
  215.     log_entry('T', "CLEAN %s", gh->group_name);
  216.     if (debug_mode)
  217.     printf("CLEAN %s\n", gh->group_name);
  218.  
  219.     clean_group_internal(gh);
  220.  
  221.     db_write_group(gh);
  222. }
  223.  
  224. extern long collect_group();
  225. extern long expire_group();
  226.  
  227. static char **restrictions = NULL;
  228. static int *restr_len, *restr_excl;
  229.  
  230. static void
  231. group_restriction(gh)
  232. register group_header *gh;
  233. {
  234.     register char **rp;
  235.     register int *lp, *xp;
  236.  
  237.     if (restrictions == NULL) return;
  238.  
  239.     for (rp = restrictions, lp = restr_len, xp = restr_excl; *lp > 0; rp++, lp++, xp++)
  240.     if (strncmp(gh->group_name, *rp, *lp) == 0) {
  241.         if (*xp) break;
  242.         return;
  243.     }
  244.  
  245.     if (*lp == 0) return;
  246.  
  247.     gh->master_flag |= M_IGNORE_G;
  248. }
  249.  
  250. static void
  251. set_group_restrictions(restr, n)
  252. char **restr;
  253. int n;
  254. {
  255.     register group_header *gh;
  256.     register int i;
  257.  
  258.     restrictions = restr;
  259.     restr_len = newobj(int, n + 1);
  260.     restr_excl = newobj(int, n);
  261.  
  262.     for (i = 0; i < n; i++) {
  263.     if (restrictions[i][0] == '!') {
  264.         restr_excl[i] = 1;
  265.         restrictions[i]++;
  266.     } else
  267.         restr_excl[i] = 0;
  268.     restr_len[i] = strlen(restrictions[i]);
  269.     }
  270.  
  271.     restr_len[n] = -1;
  272.  
  273.     Loop_Groups_Header(gh) {
  274.     if (gh->master_flag & M_IGNORE_GROUP) continue;
  275.     group_restriction(gh);
  276.     if (clean_ignored && (gh->master_flag & M_IGNORE_G)) {
  277.         log_entry('X', "Group %s ignored", gh->group_name);
  278.         clean_group(gh);
  279.     }
  280.     }
  281. }
  282.  
  283. /*
  284.  * add new group to master file
  285.  */
  286.  
  287. group_header *add_new_group(name)
  288. char *name;
  289. {
  290.     register group_header *gh;
  291.  
  292.     if (master.free_groups <= 0)
  293.     db_expand_master();
  294.  
  295.     master.free_groups--;
  296.  
  297. #ifdef MALLOC_64K_LIMITATION
  298.     gh = newobj(group_header, 1);
  299.     active_groups[master.number_of_groups] = gh;
  300. #else
  301.     gh = &active_groups[master.number_of_groups];
  302. #endif
  303.  
  304.     gh->group_name_length = strlen(name);
  305.     gh->group_name = copy_str(name);
  306.  
  307.     gh->group_num = master.number_of_groups++;
  308.     gh->creation_time = cur_time();
  309.  
  310.     db_rewrite_groups(1, (group_header *)NULL);
  311.  
  312.     group_restriction(gh);    /* done after append to avoid setting ! */
  313.     clean_group(gh);
  314.  
  315.     db_write_master();
  316.  
  317.     sort_groups();
  318.  
  319.     log_entry('C', "new group: %s (%d)", gh->group_name, gh->group_num);
  320.  
  321.     return gh;
  322. }
  323.  
  324.  
  325. static void
  326. visit_active_file()
  327. {
  328.     FILE *act;
  329.     FILE *nntp_act = NULL;
  330.  
  331. #ifdef NNTP
  332.     if (!use_nntp)        /* copy 'active' to DB/ACTIVE */
  333.     nntp_act = open_file(relative(db_directory, "ACTIVE"), OPEN_CREATE | MUST_EXIST);
  334. #endif
  335.  
  336.     act = open_file(news_active, OPEN_READ|MUST_EXIST);
  337.  
  338.     read_active_file(act, nntp_act);
  339.  
  340.     master.last_size = ftell(act);
  341.  
  342.     fclose(act);
  343.  
  344. #ifdef NNTP
  345.     if (nntp_act != NULL) fclose(nntp_act);
  346. #endif
  347. }
  348.  
  349.  
  350. /*
  351.  *    Build initial master file.
  352.  */
  353.  
  354. static void
  355. build_master()
  356. {
  357.     char command[512];
  358.     char groupname[512];
  359.     group_header *groups, *next_g, *gh;
  360.     group_header *dupg;
  361.     FILE *src;
  362.     int lcount, use_group_file, found_nn_group = 0;
  363.  
  364.     printf("Confirm initialization by typing 'OK': ");
  365.     fl;
  366.     gets(command);
  367.     if (strcmp(command, "OK")) {
  368.     printf("No initialization\n");
  369.     nn_exit(0);
  370.     }
  371.  
  372.     if (chdir(master_directory) < 0)    /* so we can use open_file (?) */
  373.     sys_error("master");
  374.  
  375. #ifdef NNTP
  376.     if (use_nntp && nntp_get_active() < 0)
  377.         sys_error("Can't get active file");
  378. #endif
  379.     /* check active file for duplicates */
  380.  
  381.     sprintf(command, "awk 'NF>0{print $1}' %s | sort | uniq -d", news_active);
  382.  
  383.     src = popen(command, "r");
  384.     if (src == NULL)
  385.     sys_error("popen(%s) failed", command);
  386.     
  387.     for (lcount = 0; fgets(groupname, 512, src); lcount++) {
  388.     if (lcount == 0)
  389.         printf("\n%s contains duplicate entries for the following groups:",
  390.            news_active);
  391.  
  392.     fputs(groupname, stdout);
  393.     }
  394.  
  395.     pclose(src);
  396.  
  397.     if (lcount > 0) {
  398.     printf("Do you want to repair this file before continuing ? (y)");
  399.     gets(command);
  400.     if (s_hangup ||
  401.         command[0] == NUL || command[0] == 'y' || command[0] == 'Y') {
  402.         printf("Initialization stopped.\n");
  403.         printf("Redo ./inst INIT after repairing active file\n");
  404.         nn_exit(0);
  405.     }
  406.     }
  407.  
  408.     /* if a "GROUPS" file exist offer to use that, else */
  409.     /* read group names from active file */
  410.  
  411.     use_group_file = 0;
  412.  
  413.     if (open_groups(OPEN_READ)) {
  414.     printf("\nA GROUPS file already exist -- reuse it? (y)");
  415.     fl;
  416.     gets(command);
  417.     if (command[0] == NUL || command[0] == 'y' || command[0] == 'Y') {
  418.         use_group_file = 1;
  419.     } else
  420.         close_groups();
  421.     if (s_hangup) return;
  422.     }
  423.  
  424.     printf("\nBuilding %s/MASTER file\n", db_directory);
  425.     fl;
  426.  
  427.     if (!use_group_file) {
  428.     sprintf(command, "awk 'NF>0{print $1}' %s | sort -u", news_active);
  429.  
  430.     src = popen(command, "r");
  431.     if (src == NULL)
  432.         sys_error("popen(%s) failed", command);
  433.     }
  434.  
  435.     open_master(OPEN_CREATE);
  436.  
  437.     master.db_magic = NNDB_MAGIC;
  438.     master.last_scan = 0;
  439.     master.number_of_groups = 0;
  440.     strcpy(master.db_lock, "Initializing database");
  441.  
  442.     db_write_master();
  443.  
  444.     groups = next_g = newobj(group_header, 1);
  445.     next_g->next_group = NULL;
  446.  
  447.     for (;;) {
  448.     if (s_hangup) goto intr;
  449.     gh = newobj(group_header, 1);
  450.  
  451.     gh->master_flag = 0;
  452.  
  453.     if (use_group_file) {
  454.         gh->group_name_length = 0;
  455.         if (db_parse_group(gh, 0) <= 0) break;
  456.     } else {
  457.         if (fgets(groupname, 512, src) == NULL) break;
  458.  
  459.         gh->group_name_length = strlen(groupname) - 1;    /* strip NL */
  460.         groupname[gh->group_name_length] = NUL;
  461.         gh->creation_time = 0;
  462.         gh->group_name = copy_str(groupname);
  463.         gh->archive_file = NULL;
  464.     }
  465.  
  466.     for (dupg = groups->next_group; dupg != NULL; dupg = dupg->next_group)
  467.         if (strcmp(dupg->group_name, gh->group_name) == 0) break;
  468.     if (dupg != NULL) {
  469.         printf("Ignored duplicate of group %s\n", dupg->group_name);
  470.         continue;
  471.     }
  472.  
  473.     gh->group_num = master.number_of_groups++;
  474.  
  475.     if (trace || debug_mode)
  476.         printf("%4d '%s' (%d)\n", gh->group_num, gh->group_name,
  477.            gh->group_name_length);
  478.  
  479.     next_g->next_group = gh;
  480.     next_g = gh;
  481.     gh->next_group = NULL;
  482.  
  483.     /* moderation flag will be set by first visit_active_file call */
  484.  
  485.     /* might have the INN control.newgrp, control.cancel, etc */
  486.     if (strncmp(gh->group_name, "control", 7) == 0)
  487.         gh->master_flag |= M_CONTROL;
  488.  
  489.     if (strcmp(gh->group_name, "news.software.nn") == 0)
  490.         found_nn_group++;
  491.  
  492.     gh->master_flag &= ~M_MUST_CLEAN;
  493.     clean_group_internal(gh);
  494.     gh->master_flag |= M_VALID;    /* better than the reverse */
  495.     db_write_group(gh);
  496.     }
  497.  
  498.     if (use_group_file)
  499.     close_groups();
  500.     else
  501.     pclose(src);
  502.  
  503.     printf("%s %s/GROUPS file\n",
  504.        use_group_file ? "Updating" : "Building", db_directory);
  505.  
  506.     db_rewrite_groups(use_group_file, groups);
  507.  
  508.     if (initialize > 0) {
  509.     printf("Setting articles per group limit to %d...\n", initialize);
  510.     db_write_master();
  511.     open_master(OPEN_READ);
  512.     open_master(OPEN_UPDATE);
  513.     visit_active_file();
  514.     Loop_Groups_Header(gh) {
  515.         gh->first_db_article = gh->last_a_article - initialize + 1;
  516.         if (gh->first_db_article <= gh->first_a_article) continue;
  517.         gh->last_db_article = gh->first_db_article - 1;
  518.         if (gh->last_db_article < 0) gh->last_db_article = 0;
  519.         db_write_group(gh);
  520.     }
  521.     }
  522.     
  523.     master.db_lock[0] = NUL;
  524.     master.db_created = cur_time();
  525.  
  526.     db_write_master();
  527.  
  528.     close_master();
  529.  
  530.     printf("Done\n");
  531.     fl;
  532.  
  533.     log_entry('M', "Master data base initialized");
  534.  
  535.     if (!found_nn_group)
  536.     printf("\nNotice: nn's own news group `news.software.nn' was not found\n");
  537.  
  538.     return;
  539.  
  540.  intr:
  541.     printf("\nINTERRUPT\n\nDatabase NOT completed\n");
  542.     log_entry('M', "Master data base initialization not completed (INTERRUPTED)");
  543. }
  544.  
  545. static void
  546. set_lock_message()
  547. {
  548.     if (lock_message[0] || master.db_lock[0]) {
  549.     open_master(OPEN_UPDATE);
  550.     strncpy(master.db_lock, lock_message, DB_LOCK_MESSAGE);
  551.     master.db_lock[DB_LOCK_MESSAGE-1] = NUL;
  552.     db_write_master();
  553.     printf("DATABASE %sLOCKED\n", lock_message[0] ? "" : "UN");
  554.     }
  555. }
  556.  
  557. static void
  558. do_reread_groups()
  559. {
  560.     register group_header *gh;
  561.  
  562.     open_master(OPEN_UPDATE);
  563.     Loop_Groups_Header(gh)
  564.     if (gh->master_flag & M_MUST_CLEAN) {
  565.         gh->master_flag &= ~M_MUST_CLEAN;
  566.         clean_group(gh);
  567.     } else
  568.         db_write_group(gh);
  569.     master.last_scan--;    /* force update */
  570.     db_write_master();
  571.     close_master();
  572.     log_entry('M', "Reread GROUPS file");
  573. }
  574.  
  575. #ifdef HAVE_HARD_SLEEP
  576. /*
  577.  * Hard sleeper...  The normal sleep is not terminated by SIGALRM or
  578.  * other signals which nnadmin may send to wake it up.
  579.  */
  580.  
  581. static int got_alarm;
  582. static sig_type catch_alarm()
  583. {
  584.     signal(SIGALRM, SIG_IGN);
  585.     got_alarm = 1;
  586. }
  587.     
  588. static take_a_nap(t)
  589. int t;
  590. {
  591.     got_alarm = 0;
  592.     signal(SIGALRM, catch_alarm);
  593.     alarm(repeat_delay);
  594.     /* the timeout is at least 1 minute, so we ignore racing condition here */
  595.     pause();
  596.     if (!got_alarm) {
  597.     signal(SIGALRM, SIG_IGN);
  598.     alarm(0);
  599.     }
  600. }
  601. #else
  602. #define take_a_nap(t) sleep(t)
  603. #endif
  604.  
  605. int
  606. main(argc, argv)
  607. int argc;
  608. char **argv;
  609. {
  610.     register group_header *gh;
  611.     time_t age_active;
  612.     int group_selection;
  613.     int temp;
  614.     int skip_pass;
  615.     long pass_no;
  616.  
  617.     umask(002);            /* avoid paranoia */
  618.  
  619.     who_am_i = I_AM_MASTER;
  620.  
  621.     init_global();
  622.  
  623.     group_selection =
  624.     parse_options(argc, argv, (char *)NULL, master_options, (char *)NULL);
  625.  
  626.     if (debug_mode) {
  627. #ifdef NNTP
  628.     nntp_debug = debug_mode & 2;
  629. #endif
  630.     debug_mode = debug_mode & 1;
  631.     }
  632.  
  633.     if (debug_mode) {
  634.     signal(SIGINT, catch_hangup);
  635.     }
  636.  
  637.     if (wakeup_master) {
  638.     if (proto_lock(I_AM_MASTER, PL_WAKEUP) < 0)
  639.         printf("master is not running\n");
  640.     exit(0);
  641.     }
  642.  
  643.     if (prt_vers) {
  644.     printf("nnmaster release %s\n", version_id);
  645.     exit(0);
  646.     }
  647.  
  648.     if (kill_running) {
  649.     if (proto_lock(I_AM_MASTER, PL_TERMINATE) < 0)
  650.         temp = 0;
  651.     else {
  652.         int mpid;
  653.  
  654.         if (proto_host[0]) {
  655.         printf("Can't kill master on another host (%s)\n", proto_host);
  656.         log_entry('R', "Attempt to kill master on host %s", proto_host);
  657.         exit(1);
  658.         }
  659.  
  660.         for (temp = 10; --temp >= 0; sleep(3)) {
  661.         sleep(3);
  662.         mpid = proto_lock(I_AM_MASTER, PL_CHECK);
  663.         if (mpid < 0) break;
  664.         sleep(1);
  665.         kill(mpid, SIGTERM); /* SIGHUP lost??? */
  666.         }
  667.     }
  668.  
  669.     if (temp < 0) {
  670.         printf("The running master will not die....!\n");
  671.         log_entry('E', "Could not kill running master");
  672.         exit(1);
  673.     }
  674.  
  675.     if (repeat_delay == 0 && !foreground &&
  676.         !reread_groups_file && lock_message == NULL &&
  677.         group_selection == 0 && initialize < 0)
  678.         exit(0);
  679.     }
  680.  
  681.     if (!file_exist(db_directory, "drwx")) {
  682.     fprintf(stderr, "%s invoked with wrong user privileges\n", argv[0]);
  683.     exit(9);
  684.     }
  685.  
  686.     if (proto_lock(I_AM_MASTER, PL_SET) != 0) {
  687.     if (proto_host[0])
  688.         printf("The master is running on another host (%s)\n", proto_host);
  689.     else
  690.     printf("The master is already running\n");
  691.     exit(0);
  692.     }
  693.     unlock_on_exit = 1;
  694.  
  695. #ifdef NNTP
  696.     nntp_check();
  697. #endif
  698.  
  699.     if (initialize >= 0) {
  700.     build_master();
  701.     nn_exit(0);
  702.     }
  703.  
  704.     open_master(OPEN_READ);
  705.  
  706.     if (lock_message != NULL) {
  707.     set_lock_message();
  708.     if (repeat_delay == 0 && !foreground &&
  709.         !reread_groups_file && group_selection == 0)
  710.         nn_exit(0);
  711.     }
  712.  
  713.     if (!ignore_lock && master.db_lock[0]) {
  714.     printf("Database locked (unlock with -l or ignore with -i)\n");
  715.     nn_exit(88);
  716.     }
  717.  
  718.     if (reread_groups_file) {
  719.     do_reread_groups();
  720.     nn_exit(0);
  721.     }
  722.  
  723.     if (!debug_mode) {
  724.     close(0);
  725.     close(1);
  726.     close(2);
  727.     if (open("/dev/null", 2) == 0) dup(0), dup(0);
  728.     }
  729.  
  730.     if (repeat_delay && !debug_mode && !foreground) {
  731.     while ((temp = fork()) < 0) sleep(1);
  732.     if (temp) exit(0);    /* not nn_exit() !!! */
  733.  
  734.     process_id = getpid();    /* init_global saved parent's pid */
  735.  
  736.     proto_lock(I_AM_MASTER, PL_TRANSFER);
  737.  
  738. #ifdef DETATCH_TERMINAL
  739.     DETATCH_TERMINAL
  740. #endif
  741.     }
  742.  
  743.     log_entry('M', "Master started -r%d -e%d %s-E%d",
  744.           repeat_delay, expire_level,
  745.           expire_once ? "-F " : "", expire_method);
  746.  
  747.     if (check_on_startup) {
  748.     char cmd[FILENAME];
  749.     sprintf(cmd, "%s/nnadmin Z", bin_directory);
  750.     system(cmd);
  751.     log_entry('M', "Database validation completed");
  752.     }
  753.  
  754.     repeat_delay *= 60;
  755.  
  756.     init_digest_parsing();
  757.  
  758.     open_master(OPEN_UPDATE);
  759.  
  760.     if (group_selection)
  761.     set_group_restrictions(argv + 1, group_selection);
  762.  
  763.     if (max_age_days && !use_nntp) /* we have to stat spool files */
  764.     max_article_age = cur_time() - (time_t)max_age_days * 24 * 60 * 60;
  765.     else
  766.     max_article_age = 0;
  767.  
  768.     if (expire_once) {
  769.     if (group_selection)    /* mark selected groups for expire */
  770.         Loop_Groups_Header(gh) {
  771.         if (gh->master_flag & M_IGNORE_GROUP) continue;
  772.         gh->master_flag |= M_EXPIRE;
  773.         }
  774.     unconditional = 1;
  775.     }
  776.  
  777.     for (pass_no = 0; !s_hangup; pass_no++) {
  778.     if (pass_no > 0) {
  779.         take_a_nap(repeat_delay);
  780.         if (s_hangup) break;
  781.     }
  782.  
  783. #ifdef NNTP
  784.     if (use_nntp && nntp_get_active() < 0) {
  785.         nntp_close_server();
  786.         current_group = NULL; /* for init_group */
  787.         log_entry('N', "Can't access active file --- %s",
  788.               repeat_delay ? "sleeping" : "terminating");
  789.         if (repeat_delay == 0)
  790.         nn_exit(1);
  791.         continue;
  792.     }
  793. #endif
  794.     temp=2;
  795.     while ((age_active = file_exist(news_active, "fr")) == (time_t)0) {
  796.         if (use_nntp) break;
  797.         if (--temp < 0)
  798.         sys_error("Cannot access active file");
  799.         sleep(5);    /* maybe a temporary glitch ? */
  800.     }
  801.  
  802.     skip_pass = 0;
  803.  
  804.     if (Unconditional)
  805.         unconditional = 1;
  806.     
  807.     if (unconditional) {
  808.         unconditional = 0;
  809.     } else
  810.     if (age_active <= master.last_scan ||
  811.         (hold_updates && (cur_time() - age_active) < hold_updates))
  812.         skip_pass = 1;
  813.  
  814.     if (receive_admin()) skip_pass = 0;
  815.  
  816.     if (skip_pass) {
  817.         if (repeat_delay == 0) break;
  818.         if (s_hangup) break;
  819. #ifdef NNTP
  820.         if (use_nntp) nntp_cleanup();
  821. #endif
  822.         if (debug_mode) {
  823.         printf("NONE (*** SLEEP ***)\n");
  824.         }
  825.  
  826.         if (trace) log_entry('T', "none");
  827.         continue;
  828.     }
  829.  
  830.     visit_active_file();
  831.  
  832.     if (do_expire())
  833.         if (!expire_once && do_collect())
  834.         if (Unconditional)
  835.             master.last_scan = cur_time();
  836.         else
  837.             master.last_scan = age_active;
  838.  
  839.     db_write_master();
  840.  
  841.     if (expire_once || s_hangup) break;
  842.     if (repeat_delay == 0) break;
  843.  
  844. #ifdef NNTP
  845.     if (use_nntp) nntp_cleanup();
  846. #endif
  847.     }
  848.  
  849.     nn_exit(0);
  850.     /*NOTREACHED*/
  851.     return 0;
  852. }
  853.  
  854.  
  855.  
  856. /*
  857.  * receive commands from administrator
  858.  */
  859.  
  860. int
  861. receive_admin()
  862. {
  863.     FILE *gate;
  864.     char buffer[128], *bp;
  865.     char command, opt, *user_date;
  866.     int32 arg;
  867.     int must_collect;
  868.     register group_header *gh;
  869.  
  870.     gate = open_gate_file(OPEN_READ);
  871.     if (gate == NULL) return 0;
  872.  
  873.     sleep(2);    /* give administrator time to flush buffers */
  874.  
  875.     must_collect = 0;
  876.  
  877.     while (fgets(buffer, 128, gate)) {
  878.     bp = buffer;
  879.  
  880.     command = *bp++;
  881.     if (*bp++ != ';') continue;
  882.  
  883.     arg = atol(bp);
  884.     if (arg >= master.number_of_groups) continue;
  885.     gh = (arg >= 0) ? ACTIVE_GROUP(arg) : NULL;
  886.     if ((bp = strchr(bp, ';')) == NULL) continue;
  887.     bp++;
  888.  
  889.     opt = *bp++;
  890.     if (*bp++ != ';') continue;
  891.  
  892.     arg = atol(bp);
  893.     if ((bp = strchr(bp, ';')) == NULL) continue;
  894.  
  895.     user_date = ++bp;
  896.     if ((bp = strchr(bp, ';')) == NULL) continue;
  897.     *bp++ = NUL;
  898.     if (*bp != NL) continue;
  899.  
  900.     log_entry('A', "RECV %c %s %c %ld (%s)",
  901.           command, gh == NULL ? "(all)" : gh->group_name, opt, arg, user_date);
  902.  
  903.     switch (command) {
  904.  
  905.      case SM_SET_OPTION:
  906.         switch (opt){
  907.          case 'r':
  908.         repeat_delay = arg;
  909.         continue;
  910.          case 'e':
  911.         expire_level = arg;
  912.         continue;
  913.          case 't':
  914.         trace = (arg < 0) ? !trace : arg;
  915.         continue;
  916.         }
  917.         continue;        /* XXX: Is this the right thing to do? */
  918.  
  919.         
  920.      case SM_EXPIRE:
  921.         if (gh) {
  922.         gh->master_flag |= M_EXPIRE | M_BLOCKED;
  923.         db_write_group(gh);
  924.         break;
  925.         }
  926.         Loop_Groups_Header(gh) {
  927.         if (gh->master_flag & M_IGNORE_GROUP) continue;
  928.         if (gh->index_write_offset == 0) continue;
  929.         if (gh->master_flag & M_EXPIRE) continue;
  930.         gh->master_flag |= M_EXPIRE;
  931.         db_write_group(gh);
  932.         }
  933.         break;
  934.  
  935.      case SM_SET_FLAG:
  936.         if (opt == 's')
  937.         gh->master_flag |= (flag_type)arg;
  938.         else
  939.         gh->master_flag &= ~(flag_type)arg;
  940.         db_write_group(gh);
  941.         continue;
  942.  
  943.      case SM_RECOLLECT:    /* recollect */
  944.         if (gh) {
  945.         if ((gh->master_flag & M_IGNORE_GROUP) == 0)
  946.             clean_group(gh);
  947.         } else
  948.         Loop_Groups_Header(gh)
  949.             if ((gh->master_flag & M_IGNORE_GROUP) == 0)
  950.             clean_group(gh);
  951.         break;
  952.  
  953.      case SM_SCAN_ONCE:    /* unconditional pass */
  954.         unconditional++;
  955.         break;
  956.  
  957.      default:
  958.         continue;
  959.     }
  960.     must_collect = 1;
  961.     }
  962.  
  963.     fclose(gate);
  964.  
  965.     return must_collect;
  966. }
  967.  
  968.  
  969. void
  970. write_error()
  971. {
  972.     /*
  973.      * should wait for problems to clear out rather than die...
  974.      */
  975.     sys_error("DISK WRITE ERROR");
  976. }
  977.  
  978. /*
  979.  * dummy routines - should never be called by master
  980.  */
  981.  
  982. /*VARARGS*/
  983. void nn_exitmsg()
  984. {
  985.     dummy_error("nn_exitmsg");
  986. }
  987.  
  988. void dummy_error(name)
  989. char *name;
  990. {
  991.     sys_error("Dummy routine called by master: %s", name);
  992. }
  993.  
  994. #ifdef HAVE_JOBCONTROL
  995. int suspend_nn()
  996. {
  997.   return 0;
  998. }
  999. #endif
  1000.  
  1001. #ifdef NNTP /* XXX */
  1002. /*VARARGS*/
  1003. void msg() {}
  1004. void user_delay(n) {}
  1005. #endif /* NNTP Bogus */
  1006.