home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / nn.tar / nn-6.5.1 / expire.c < prev    next >
C/C++ Source or Header  |  1996-08-14  |  15KB  |  614 lines

  1. /*
  2.  *    (c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
  3.  *
  4.  *    Expire will remove all entries in the index and data files
  5.  *    corresponding to the articles before the first article registered
  6.  *    in the active file.  No attempt is made to eliminate other
  7.  *    expired articles.
  8.  */
  9.  
  10. #include "config.h"
  11. #include "db.h"
  12. #include "dir.h"
  13.  
  14. /* expire.c */
  15.  
  16. static sort_art_list __APROTO((register article_number *f1, register article_number *f2));
  17. static article_number *get_article_list __APROTO((char *dir));
  18. static long expire_in_database __APROTO((register group_header *gh));
  19. static long expire_sliding __APROTO((register group_header *gh));
  20. static void block_group __APROTO((register group_header *gh));
  21. static void unblock_group __APROTO((register group_header *gh));
  22.  
  23.  
  24. import int trace, debug_mode;
  25. import int nntp_failed;
  26.  
  27. /*
  28.  *    Expire methods:
  29.  *    1: read directory and reuse database info.
  30.  *    2: "slide" index and datafiles (may leave unexpired art. in database)
  31.  *    3: recollect group to expire (also if "min" still exists)
  32.  */
  33.  
  34. export int expire_method = 1;    /* expire method */
  35. export int recollect_method = 1; /* recollection method -- see do_expire */
  36. export int expire_level = 0;    /* automatic expiration detection */
  37.  
  38. #ifdef HAVE_DIRECTORY
  39.  
  40. static article_number *article_list = NULL;
  41. static long art_list_length = 0;
  42.  
  43. static int
  44. sort_art_list(f1, f2)
  45. register article_number *f1, *f2;
  46. {
  47.     return (*f1 < *f2) ? -1 : (*f1 == *f2) ? 0 : 1;
  48. }
  49.  
  50. static article_number *get_article_list(dir)
  51. char *dir;
  52. {
  53.     DIR *dirp;
  54.     register Direntry *dp;
  55.     register char c, *pp, *cp;
  56.     register article_number *art;
  57.     register long count;    /* No. of completions plus one */
  58.  
  59.     if ((dirp = opendir(dir)) == NULL)
  60.     return NULL;            /* tough luck */
  61.  
  62.     art = article_list;
  63.     count = 0;
  64.  
  65.     while ((dp = readdir(dirp)) != NULL) {
  66.     cp = dp->d_name;
  67. #ifdef FAKED_DIRECTORY
  68.     if (dp->d_ino == 0) continue;
  69.     cp[14] = NUL;
  70. #endif
  71.     for (pp = cp; (c = *pp++); )
  72.         if (!isascii(c) || !isdigit(c)) break;
  73.     if (c) continue;
  74.  
  75.     if (count == art_list_length) {
  76.         art_list_length += 250;
  77.         article_list = resizeobj(article_list, article_number, art_list_length + 1);
  78.         art = article_list + count;
  79.     }
  80.     *art++ = atol(cp);
  81.     count++;
  82.     }
  83.     closedir(dirp);
  84.  
  85.     if (article_list != NULL) {
  86.     *art = 0;
  87.     if (count > 1)
  88.         quicksort(article_list, count, article_number, sort_art_list);
  89.     }
  90.  
  91.     return article_list;
  92. }
  93.  
  94. static long expire_in_database(gh)
  95. register group_header *gh;
  96. {
  97.     FILE *old, *data, *ix;
  98.     off_t old_max_offset;
  99.     register article_number *list;
  100.     article_number old_last_article;
  101.     long count;
  102.  
  103.     if (gh->first_db_article > gh->last_db_article) return 0;
  104.  
  105.     if (gh->last_db_article == 0) return 0;
  106.  
  107.     if (!init_group(gh)) return 0;
  108.  
  109.     if (debug_mode == 1) {
  110.     printf("\t\tExp %s (%ld..%ld)\r",
  111.            gh->group_name, gh->first_db_article, gh->last_db_article);
  112.     fl;
  113.     }
  114.  
  115.     count = 0;
  116.  
  117.     /* get list of currently available articles in the group */
  118. #ifdef NNTP
  119.     if (use_nntp)
  120.     list = nntp_get_article_list(gh);
  121.     else
  122. #endif
  123.     list = get_article_list(".");
  124.  
  125.     if (list == NULL || *list == 0) {
  126. #ifdef NNTP
  127.     if (nntp_failed == 2) {
  128.         log_entry('N', "NNTP server supports neither LISTGROUP nor XHDR");
  129.         sys_error("Cannot use specified expire method (NNTP limitations)");
  130.     }
  131.     if (nntp_failed) return -1;
  132. #endif
  133.     if (debug_mode == 1) { printf("\rempty"); fl; }
  134.     /* group is empty - clean it */
  135.     count = gh->last_db_article - gh->first_db_article + 1;
  136.     clean_group(gh);
  137.     gh->last_db_article = gh->last_a_article;
  138.     gh->first_db_article = gh->last_db_article + 1;
  139.     db_write_group(gh);
  140.     return count;
  141.     }
  142.  
  143.     /*
  144.      * Clean & block the group while expire is working on it.
  145.      */
  146.  
  147.     gh->first_db_article = 0;
  148.     old_last_article = gh->last_db_article;
  149.     gh->last_db_article = 0;
  150.  
  151.     gh->index_write_offset = (off_t)0;
  152.     old_max_offset = gh->data_write_offset;
  153.     gh->data_write_offset = (off_t)0;
  154.  
  155.     gh->master_flag &= ~M_EXPIRE;
  156.     gh->master_flag |= M_BLOCKED;
  157.  
  158.     db_write_group(gh);
  159.  
  160.     /*
  161.      * We ignore the old index file, and we unlink the data file
  162.      * immediately after open because we want to write a new.
  163.      */
  164.  
  165.     (void)open_data_file(gh, 'x', -1);
  166.     old = open_data_file(gh, 'd', OPEN_READ | OPEN_UNLINK);
  167.     if (old == NULL) goto out;
  168.  
  169.     data = open_data_file(gh, 'd', OPEN_CREATE | MUST_EXIST);
  170.     ix = open_data_file(gh, 'x', OPEN_CREATE | MUST_EXIST);
  171.  
  172.     while (ftell(old) < old_max_offset) {
  173.     if (s_hangup) {
  174.         /* ok, this is what we got -- let collect get the rest */
  175.         old_last_article = gh->last_db_article;
  176.         break;
  177.     }
  178.  
  179.     /*
  180.      * maybe not enough articles, or last one is incomplete
  181.      * we take what there is, and leave the rest to do_collect()
  182.      * It may actually be ok if the last articles have been expired/
  183.      * cancelled!
  184.      */
  185.  
  186.     if (db_read_art(old) <= 0) {
  187.         break;
  188.     }
  189.  
  190.     if (debug_mode == 1) { printf("\r%ld", (long)db_hdr.dh_number); fl; }
  191.  
  192.     /* check whether we want this article */
  193.  
  194.     while (*list && db_hdr.dh_number > *list) {
  195.         /* potentially, we have a problem here: there might be an */
  196.         /* article in the directory which is not in the database! */
  197.         /* For the moment we just ignore this - it might be a bad */
  198.         /* article which has been rejected!! */
  199.         list++;
  200.     }
  201.  
  202.     if (*list == 0) {
  203.         /* no more articles in the directory - the rest must be */
  204.         /* expired.  So we ignore the rest of the data file */
  205.         break;
  206.     }
  207.  
  208.     if (db_hdr.dh_number < *list) {
  209.         /* the current article from the data file isn't in the */
  210.         /* article list, so it must be expired! */
  211.         count++;
  212.         if (debug_mode == 1) { printf("\t%ld", count); fl; }
  213.         continue;
  214.     }
  215.  
  216.     if (gh->first_db_article == 0) {
  217.         gh->first_db_article = db_hdr.dh_number;
  218.         gh->last_db_article = db_hdr.dh_number - 1;
  219.     }
  220.  
  221.     if (gh->last_db_article < db_hdr.dh_number) {
  222.         gh->data_write_offset = ftell(data);
  223.  
  224.         /* must fill gab between last index and current article */
  225.         while (gh->last_db_article < db_hdr.dh_number) {
  226.         if (!db_write_offset(ix, &(gh->data_write_offset)))
  227.             write_error();
  228.         gh->last_db_article++;
  229.         }
  230.     }
  231.  
  232.     if (db_write_art(data) < 0) write_error();
  233.     }
  234.  
  235.     if (gh->first_db_article == 0) {
  236.     gh->first_db_article = old_last_article + 1;
  237.     gh->last_db_article = old_last_article;
  238.     } else {
  239.     gh->data_write_offset = ftell(data);
  240.     while (gh->last_db_article < old_last_article) {
  241.         /* must fill gab between last index and last article */
  242.         ++gh->last_db_article;
  243.         if (!db_write_offset(ix, &(gh->data_write_offset)))
  244.         write_error();
  245.     }
  246.     gh->index_write_offset = ftell(ix);
  247.     }
  248.  
  249.     gh->master_flag &= ~M_BLOCKED;
  250.  
  251.     db_write_group(gh);
  252.  
  253.  out:
  254.     if (old) fclose(old);
  255.     if (data) fclose(data);
  256.     if (ix) fclose(ix);
  257.  
  258.     if (debug_mode) putchar(NL);
  259.  
  260.     return count;
  261. }
  262. #else
  263. #define expire_in_database expire_sliding
  264. #endif
  265.  
  266. static long expire_sliding(gh)
  267. register group_header *gh;
  268. {
  269.     FILE *old_x, *old_d;
  270.     FILE *new;
  271.     off_t index_offset, data_offset, new_offset;
  272.     long count, expire_count;
  273.     char *err_message;
  274.  
  275. #define expire_error(msg) { \
  276.     err_message = msg; \
  277.     goto error_handler; \
  278. }
  279.  
  280.     if (!init_group(gh)) return 0;
  281.  
  282. #ifdef RENUMBER_DANGER
  283.     /*
  284.      * check whether new first article is collected
  285.      */
  286.  
  287.     if (!art_collected(gh, gh->first_a_article)) {
  288.     expire_count = gh->first_db_article - gh->last_db_article + 1;
  289.     err_message = NULL;
  290.     goto error_handler;    /* renumbering, collect from start */
  291.     }
  292. #else
  293.     if (gh->first_a_article <= gh->first_db_article)
  294.     return 0;
  295. #endif
  296.  
  297.     expire_count = gh->first_a_article - gh->first_db_article;
  298.  
  299.     new = NULL;
  300.  
  301.     /*
  302.      *  Open old files, unlink after open
  303.      */
  304.  
  305.     old_x = open_data_file(gh, 'x', OPEN_READ|OPEN_UNLINK);
  306.     old_d = open_data_file(gh, 'd', OPEN_READ|OPEN_UNLINK);
  307.  
  308.     if (old_x == NULL || old_d == NULL)
  309.     expire_error("INDEX or DATA file missing");
  310.  
  311.     /*
  312.      *    Create new index file; copy from old
  313.      */
  314.  
  315.     new = open_data_file(gh, 'x', OPEN_CREATE);
  316.     if (new == NULL)
  317.     expire_error("INDEX: cannot create");
  318.  
  319.     /*
  320.      *    index_offset is the offset into the old index file for the
  321.      *    first entry in the new index file
  322.      */
  323.  
  324.     index_offset = get_index_offset(gh, gh->first_a_article);
  325.  
  326.     /*
  327.      *    adjust the group's index write offset (the next free entry)
  328.      */
  329.  
  330.     gh->index_write_offset -= index_offset;
  331.  
  332.     /*
  333.      *    calculate the number of entries to copy
  334.      */
  335.  
  336.     count = gh->index_write_offset / sizeof(off_t);
  337.  
  338.     /*
  339.      *    data offset is the offset into the old data file for the
  340.      *    first byte in the new data file; it is initialized in the
  341.      *    loop below, by reading the entry in the old index file at
  342.      *    offset 'index_offset'.
  343.      */
  344.  
  345.     data_offset = (off_t)0;
  346.  
  347.     /*
  348.      *    read 'count' entries from the old index file starting from
  349.      *    index_offset, subtract the 'data_offset', and output the
  350.      *    new offset to the new index file.
  351.      */
  352.  
  353.     fseek(old_x, index_offset, 0);
  354.  
  355.     while (--count >= 0) {
  356.     if (!db_read_offset(old_x, &new_offset))
  357.         expire_error("INDEX: too short");
  358.  
  359.     if (data_offset == (off_t)0) data_offset = new_offset;
  360.  
  361.     new_offset -= data_offset;
  362.     if (!db_write_offset(new, &new_offset))
  363.         expire_error("NEW INDEX: cannot write");
  364.     }
  365.  
  366.     fclose(new);
  367.     fclose(old_x); old_x = NULL;
  368.  
  369.     /*
  370.      *    copy from old data file to new data file
  371.      */
  372.  
  373.     new = open_data_file(gh, 'd', OPEN_CREATE);
  374.     if (new == NULL)
  375.     expire_error("DATA: cannot create");
  376.  
  377.     /*
  378.      *    calculate offset for next free entry in the new data file
  379.      */
  380.  
  381.     gh->data_write_offset -= data_offset;
  382.  
  383.     /*
  384.      *    calculate number of bytes to copy (piece of cake)
  385.      */
  386.  
  387.     count = gh->data_write_offset;
  388.  
  389.     /*
  390.      *    copy 'count' bytes from the old data file, starting at offset
  391.      *     'data_offset', to the new data file
  392.      */
  393.  
  394.     fseek(old_d, data_offset, 0);
  395.     while (count > 0) {
  396.     char block[1024];
  397.     int  count1;
  398.  
  399.     count1 = fread(block, sizeof(char), 1024, old_d);
  400.     if (count1 <= 0)
  401.         expire_error("DATA: read error");
  402.  
  403.     if (fwrite(block, sizeof(char), count1, new) != count1)
  404.         expire_error("DATA: write error");
  405.  
  406.     count -= count1;
  407.     }
  408.  
  409.     fclose(new);
  410.     fclose(old_d);
  411.  
  412.     /*
  413.      *    Update group entry
  414.      */
  415.  
  416.     gh->first_db_article = gh->first_a_article;
  417.  
  418.     /*
  419.      *    Return number of expired articles
  420.      */
  421.  
  422.     return expire_count;
  423.  
  424.     /*
  425.      *    Errors end up here.
  426.      *    We simply recollect the whole group once more.
  427.      */
  428.  
  429. error_handler:
  430.  
  431.     if (new) fclose(new);
  432.     if (old_x) fclose(old_x);
  433.     if (old_d) fclose(old_d);
  434.  
  435.     if (err_message)
  436.     log_entry('E', "Expire Error (%s): %s", gh->group_name, err_message);
  437.  
  438.     clean_group(gh);
  439.  
  440.     /* will be saved & unblocked later */
  441.  
  442.     /*
  443.      *    We cannot say whether any articles actually had to be expired,
  444.      *    but then we must guess...
  445.      */
  446.  
  447.     return expire_count;
  448. }
  449.  
  450. static void
  451. block_group(gh)
  452. register group_header *gh;
  453. {
  454.     if ((gh->master_flag & M_BLOCKED) == 0) {
  455.     gh->master_flag |= M_BLOCKED;
  456.     db_write_group(gh);
  457.     }
  458. }
  459.  
  460. static void
  461. unblock_group(gh)
  462. register group_header *gh;
  463. {
  464.     if (gh->master_flag & M_BLOCKED) {
  465.     gh->master_flag &= ~(M_BLOCKED | M_EXPIRE);
  466.     db_write_group(gh);
  467.     }
  468. }
  469.  
  470. int
  471. do_expire()
  472. {
  473.     register group_header *gh;
  474.     long exp_article_count, temp;
  475.     int exp_group_count, must_expire;
  476.     time_t start_time;
  477.  
  478.     must_expire = 0;
  479.  
  480.     Loop_Groups_Header(gh) {
  481.     if (s_hangup) break;
  482.     if (gh->master_flag & M_IGNORE_GROUP) continue;
  483.  
  484.     if ((gh->master_flag & M_VALID) == 0) {
  485.         log_entry('X', "Group %s removed", gh->group_name);
  486.         gh->master_flag |= M_IGNORE_A;
  487.         clean_group(gh);
  488.         continue;
  489.     }
  490.  
  491. #ifdef RENUMBER_DANGER
  492.     if (gh->last_db_article  > gh->last_a_article ||
  493.         gh->first_db_article > gh->first_a_article) {
  494.         log_entry('X', "group %s renumbered", gh->group_name);
  495.         clean_group(gh);
  496.         continue;
  497.     }
  498. #endif
  499.  
  500.     if (gh->master_flag & M_AUTO_RECOLLECT) {
  501.         switch (recollect_method) {
  502.          case 1: /* expire when new articles arrive */
  503.         if (gh->last_a_article <= gh->last_db_article) break;
  504.         /*FALLTHRU*/
  505.          case 2: /* expire unconditionally */
  506.         gh->master_flag |= M_EXPIRE;
  507.         must_expire = 1;
  508.         continue;
  509.  
  510.          case 3: /* clean when new articles arrive */
  511.         if (gh->last_a_article <= gh->last_db_article) break;
  512.         /*FALLTHRU*/
  513.          case 4: /* clean unconditionally */
  514.         gh->master_flag |= M_MUST_CLEAN;
  515.         continue;
  516.          default:        /* ignore auto-recollect */
  517.         break;
  518.         }
  519.     }
  520.  
  521.     if (gh->index_write_offset > 0) {
  522.         if (gh->first_a_article > gh->last_db_article) {
  523.         if (trace)
  524.             log_entry('T', "%s expire void", gh->group_name);
  525.         if (debug_mode)
  526.             printf("%s expire void\n", gh->group_name);
  527.         clean_group(gh);
  528.         continue;
  529.         }
  530.     }
  531.  
  532.     if (gh->master_flag & M_EXPIRE) {
  533.         must_expire = 1;
  534.         continue;
  535.     }
  536.  
  537.     if (expire_level > 0 &&
  538.         (gh->first_db_article + expire_level) <= gh->first_a_article) {
  539.         if (trace)
  540.         log_entry('T', "%s expire level", gh->group_name);
  541.         if (debug_mode)
  542.         printf("Expire level: %s\n", gh->group_name);
  543.         gh->master_flag |= M_EXPIRE;
  544.         must_expire = 1;
  545.         continue;
  546.     }
  547.     }
  548.  
  549.     if (!must_expire) return 1;
  550.  
  551.     start_time = cur_time();
  552.     exp_article_count = exp_group_count = 0;
  553.     temp = 0;
  554.  
  555.     Loop_Groups_Header(gh) {
  556.     if (s_hangup) {
  557.         temp = -1;
  558.         break;
  559.     }
  560.  
  561.     if (gh->master_flag & M_IGNORE_GROUP) continue;
  562.     if ((gh->master_flag & M_EXPIRE) == 0) continue;
  563.     if (gh->master_flag & M_MUST_CLEAN) continue;
  564.  
  565.     if (trace)
  566.         log_entry('T', "Exp %s (%ld -> %ld)", gh->group_name,
  567.               (long)gh->first_db_article, (long)gh->first_a_article);
  568.  
  569.     switch (expire_method) {
  570.      case 1:
  571.         block_group(gh);
  572.         temp = expire_in_database(gh);
  573.         unblock_group(gh);
  574.         break;
  575.  
  576.      case 2:
  577.         block_group(gh);
  578.         temp = expire_sliding(gh);
  579.         unblock_group(gh);
  580.         break;
  581.  
  582.      case 4:
  583.         temp = gh->first_a_article - gh->first_db_article;
  584.         if (temp <= 0) break;
  585.         /*FALLTHRU*/
  586.      case 3:
  587.         gh->master_flag |= M_MUST_CLEAN;
  588.         break;
  589.  
  590.     }
  591.  
  592. #ifdef NNTP
  593.     if (nntp_failed) {
  594.         /* connection broken while reading article list */
  595.         /* so no harm was done - leave the group to be expired */
  596.         /* again on next expire */
  597.         break;
  598.     }
  599. #endif
  600.  
  601.     if (temp > 0) {
  602.         exp_article_count += temp;
  603.         exp_group_count++;
  604.     }
  605.     }
  606.  
  607.     if (exp_article_count > 0)
  608.     log_entry('X', "Expire: %ld art, %d gr, %ld s",
  609.           exp_article_count, exp_group_count,
  610.           (long)(cur_time() - start_time));
  611.  
  612.     return temp >= 0;
  613. }
  614.