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