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