home *** CD-ROM | disk | FTP | other *** search
- /*
- * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved.
- *
- * Expire will remove all entries in the index and data files
- * corresponding to the articles before the first article registered
- * in the active file. No attempt is made to eliminate other
- * expired articles.
- */
-
- #include "config.h"
- #include "db.h"
- #include "dir.h"
-
- import int trace, debug_mode;
- import int nntp_failed;
-
- /*
- * Expire methods:
- * 1: read directory and reuse database info.
- * 2: "slide" index and datafiles (may leave unexpired art. in database)
- * 3: recollect group to expire (also if "min" still exists)
- */
-
- export expire_method = 1; /* expire method */
- export recollect_method = 1; /* recollection method -- see do_expire */
- export expire_level = 0; /* automatic expiration detection */
-
- #ifdef HAVE_DIRECTORY
-
- static article_number *article_list = NULL;
- static long art_list_length = 0;
-
- static sort_art_list(f1, f2)
- register article_number *f1, *f2;
- {
- return (*f1 < *f2) ? -1 : (*f1 == *f2) ? 0 : 1;
- }
-
- static article_number *get_article_list(dir)
- char *dir;
- {
- DIR *dirp;
- register Direntry *dp;
- register char c, *pp, *cp;
- register article_number *art;
- register long count = 0; /* No. of completions plus one */
-
- if ((dirp = opendir(dir)) == NULL)
- return NULL; /* tough luck */
-
- art = article_list;
- count = 0;
-
- while ((dp = readdir(dirp)) != NULL) {
- cp = dp->d_name;
- #ifdef FAKED_DIRECTORY
- if (dp->d_ino == 0) continue;
- cp[14] = NUL;
- #endif
- for (pp = cp; c = *pp++; )
- if (!isascii(c) || !isdigit(c)) break;
- if (c) continue;
-
- if (count == art_list_length) {
- art_list_length += 250;
- article_list = resizeobj(article_list, article_number, art_list_length + 1);
- art = article_list + count;
- }
- *art++ = atol(cp);
- count++;
- }
- closedir(dirp);
- if (count)
- quicksort(article_list, count, article_number, sort_art_list);
- *art = 0;
-
- return article_list;
- }
-
- static long expire_in_database(gh)
- register group_header *gh;
- {
- FILE *old, *data, *ix;
- off_t old_max_offset;
- register article_number *list;
- #ifdef NNTP
- extern article_number *nntp_get_article_list();
- #endif
- article_number old_last_article;
- long count;
-
- if (gh->first_db_article > gh->last_db_article) return 0;
-
- if (!init_group(gh)) return 0;
-
- if (debug_mode == 1) {
- printf("\t\tExp %s (%ld..%ld)\r",
- gh->group_name, gh->first_db_article, gh->last_db_article);
- fl;
- }
-
- count = 0;
- old = data = ix = NULL;
-
- /* get list of currently available articles in the group */
- #ifdef NNTP
- if (use_nntp)
- list = nntp_get_article_list(gh);
- else
- #endif
- list = get_article_list(".");
-
- if (list == NULL || *list == 0) {
- #ifdef NNTP
- if (nntp_failed == 2) {
- log_entry('N', "NNTP server supports neither LISTGROUP nor XHDR");
- sys_error("Cannot use specified expire method (NNTP limitations)");
- }
- if (nntp_failed) return -1;
- #endif
- if (debug_mode == 1) { printf("\rempty"); fl; }
- /* group is empty - clean it */
- count = gh->last_db_article - gh->first_db_article + 1;
- clean_group(gh);
- gh->last_db_article = gh->last_a_article;
- gh->first_db_article = gh->last_db_article + 1;
- db_write_group(gh);
- return count;
- }
-
- /*
- * Clean & block the group while expire is working on it.
- */
-
- gh->first_db_article = 0;
- old_last_article = gh->last_db_article;
- gh->last_db_article = 0;
-
- gh->index_write_offset = (off_t)0;
- old_max_offset = gh->data_write_offset;
- gh->data_write_offset = (off_t)0;
-
- gh->master_flag &= ~M_EXPIRE;
- gh->master_flag |= M_BLOCKED;
-
- db_write_group(gh);
-
- /*
- * We ignore the old index file, and we unlink the data file
- * immediately after open because we want to write a new.
- */
-
- (void)open_data_file(gh, 'x', -1);
- old = open_data_file(gh, 'd', OPEN_READ | OPEN_UNLINK);
- if (old == NULL) goto out;
-
- data = open_data_file(gh, 'd', OPEN_CREATE | MUST_EXIST);
- ix = open_data_file(gh, 'x', OPEN_CREATE | MUST_EXIST);
-
- while (ftell(old) < old_max_offset) {
- if (s_hangup) {
- /* ok, this is what we got -- let collect get the rest */
- old_last_article = gh->last_db_article;
- break;
- }
-
- /*
- * maybe not enough articles, or last one is incomplete
- * we take what there is, and leave the rest to do_collect()
- * It may actually be ok if the last articles have been expired/
- * cancelled!
- */
-
- if (db_read_art(old) <= 0) {
- break;
- }
-
- if (debug_mode == 1) { printf("\r%ld", (long)db_hdr.dh_number); fl; }
-
- /* check whether we want this article */
-
- while (*list && db_hdr.dh_number > *list) {
- /* potentially, we have a problem here: there might be an */
- /* article in the directory which is not in the database! */
- /* For the moment we just ignore this - it might be a bad */
- /* article which has been rejected!! */
- list++;
- }
-
- if (*list == 0) {
- /* no more articles in the directory - the rest must be */
- /* expired. So we ignore the rest of the data file */
- break;
- }
-
- if (db_hdr.dh_number < *list) {
- /* the current article from the data file isn't in the */
- /* article list, so it must be expired! */
- count++;
- if (debug_mode == 1) { printf("\t%ld", count); fl; }
- continue;
- }
-
- if (gh->first_db_article == 0) {
- gh->first_db_article = db_hdr.dh_number;
- gh->last_db_article = db_hdr.dh_number - 1;
- }
-
- if (gh->last_db_article < db_hdr.dh_number) {
- gh->data_write_offset = ftell(data);
-
- /* must fill gab between last index and current article */
- while (gh->last_db_article < db_hdr.dh_number) {
- if (!db_write_offset(ix, &(gh->data_write_offset)))
- write_error();
- gh->last_db_article++;
- }
- }
-
- if (db_write_art(data) < 0) write_error();
- }
-
- if (gh->first_db_article == 0) {
- gh->first_db_article = old_last_article + 1;
- gh->last_db_article = old_last_article;
- } else {
- gh->data_write_offset = ftell(data);
- while (gh->last_db_article < old_last_article) {
- /* must fill gab between last index and last article */
- ++gh->last_db_article;
- if (!db_write_offset(ix, &(gh->data_write_offset)))
- write_error();
- }
- gh->index_write_offset = ftell(ix);
- }
-
- gh->master_flag &= ~M_BLOCKED;
-
- db_write_group(gh);
-
- out:
- if (old) fclose(old);
- if (data) fclose(data);
- if (ix) fclose(ix);
-
- if (debug_mode) putchar(NL);
-
- return count;
- }
- #else
- #define expire_in_database expire_sliding
- #endif
-
- static long expire_sliding(gh)
- register group_header *gh;
- {
- FILE *old_x, *old_d;
- FILE *new;
- off_t index_offset, data_offset, new_offset;
- long count, expire_count;
- char *err_message;
-
- #define expire_error(msg) { \
- err_message = msg; \
- goto error_handler; \
- }
-
- old_x = old_d = new = NULL;
-
- #ifdef RENUMBER_DANGER
- /*
- * check whether new first article is collected
- */
-
- if (!art_collected(gh, gh->first_a_article)) {
- expire_count = gh->first_db_article - gh->last_db_article + 1;
- err_message = NULL;
- goto error_handler; /* renumbering, collect from start */
- }
- #else
- if (gh->first_a_article <= gh->first_db_article)
- return 0;
- #endif
-
- expire_count = gh->first_a_article - gh->first_db_article;
-
- new = NULL;
-
- /*
- * Open old files, unlink after open
- */
-
- old_x = open_data_file(gh, 'x', OPEN_READ|OPEN_UNLINK);
- old_d = open_data_file(gh, 'd', OPEN_READ|OPEN_UNLINK);
-
- if (old_x == NULL || old_d == NULL)
- expire_error("INDEX or DATA file missing");
-
- /*
- * Create new index file; copy from old
- */
-
- new = open_data_file(gh, 'x', OPEN_CREATE);
- if (new == NULL)
- expire_error("INDEX: cannot create");
-
- /*
- * index_offset is the offset into the old index file for the
- * first entry in the new index file
- */
-
- index_offset = get_index_offset(gh, gh->first_a_article);
-
- /*
- * adjust the group's index write offset (the next free entry)
- */
-
- gh->index_write_offset -= index_offset;
-
- /*
- * calculate the number of entries to copy
- */
-
- count = gh->index_write_offset / sizeof(off_t);
-
- /*
- * data offset is the offset into the old data file for the
- * first byte in the new data file; it is initialized in the
- * loop below, by reading the entry in the old index file at
- * offset 'index_offset'.
- */
-
- data_offset = (off_t)0;
-
- /*
- * read 'count' entries from the old index file starting from
- * index_offset, subtract the 'data_offset', and output the
- * new offset to the new index file.
- */
-
- fseek(old_x, index_offset, 0);
-
- while (--count >= 0) {
- if (!db_read_offset(old_x, &new_offset))
- expire_error("INDEX: too short");
-
- if (data_offset == (off_t)0) data_offset = new_offset;
-
- new_offset -= data_offset;
- if (!db_write_offset(new, &new_offset))
- expire_error("NEW INDEX: cannot write");
- }
-
- fclose(new);
- fclose(old_x); old_x = NULL;
-
- /*
- * copy from old data file to new data file
- */
-
- new = open_data_file(gh, 'd', OPEN_CREATE);
- if (new == NULL)
- expire_error("DATA: cannot create");
-
- /*
- * calculate offset for next free entry in the new data file
- */
-
- gh->data_write_offset -= data_offset;
-
- /*
- * calculate number of bytes to copy (piece of cake)
- */
-
- count = gh->data_write_offset;
-
- /*
- * copy 'count' bytes from the old data file, starting at offset
- * 'data_offset', to the new data file
- */
-
- fseek(old_d, data_offset, 0);
- while (count > 0) {
- char block[1024];
- int count1;
-
- count1 = fread(block, sizeof(char), 1024, old_d);
- if (count1 <= 0)
- expire_error("DATA: read error");
-
- if (fwrite(block, sizeof(char), count1, new) != count1)
- expire_error("DATA: write error");
-
- count -= count1;
- }
-
- fclose(new);
- fclose(old_d);
-
- /*
- * Update group entry
- */
-
- gh->first_db_article = gh->first_a_article;
-
- /*
- * Return number of expired articles
- */
-
- return expire_count;
-
- /*
- * Errors end up here.
- * We simply recollect the whole group once more.
- */
-
- error_handler:
-
- if (new) fclose(new);
- if (old_x) fclose(old_x);
- if (old_d) fclose(old_d);
-
- if (err_message)
- log_entry('E', "Expire Error (%s): %s", gh->group_name, err_message);
-
- clean_group(gh);
-
- /* will be saved & unblocked later */
-
- /*
- * We cannot say whether any articles actually had to be expired,
- * but then we must guess...
- */
-
- return expire_count;
- }
-
- static block_group(gh)
- register group_header *gh;
- {
- if ((gh->master_flag & M_BLOCKED) == 0) {
- gh->master_flag |= M_BLOCKED;
- db_write_group(gh);
- }
- }
-
- static unblock_group(gh)
- register group_header *gh;
- {
- if (gh->master_flag & M_BLOCKED) {
- gh->master_flag &= ~(M_BLOCKED | M_EXPIRE);
- db_write_group(gh);
- }
- }
-
- do_expire()
- {
- register group_header *gh;
- long exp_article_count, temp;
- int exp_group_count, must_expire;
- time_t start_time;
-
- must_expire = 0;
-
- Loop_Groups_Header(gh) {
- if (s_hangup) break;
- if (gh->master_flag & M_IGNORE_GROUP) continue;
-
- if ((gh->master_flag & M_VALID) == 0) {
- if (gh->last_db_article == 0) continue;
- log_entry('X', "Group %s removed", gh->group_name);
- gh->master_flag |= M_IGNORE_A;
- clean_group(gh);
- continue;
- }
-
- #ifdef RENUMBER_DANGER
- if (gh->last_db_article > gh->last_a_article ||
- gh->first_db_article > gh->first_a_article) {
- log_entry('X', "group %s renumbered", gh->group_name);
- clean_group(gh);
- continue;
- }
- #endif
-
- if (gh->master_flag & M_AUTO_RECOLLECT) {
- switch (recollect_method) {
- case 1: /* expire when new articles arrive */
- if (gh->last_a_article <= gh->last_db_article) break;
- case 2: /* expire unconditionally */
- gh->master_flag |= M_EXPIRE;
- must_expire = 1;
- continue;
-
- case 3: /* clean when new articles arrive */
- if (gh->last_a_article <= gh->last_db_article) break;
- case 4: /* clean unconditionally */
- gh->master_flag |= M_MUST_CLEAN;
- continue;
- default: /* ignore auto-recollect */
- break;
- }
- }
-
- if (gh->index_write_offset > 0) {
- if (gh->first_a_article > gh->last_db_article) {
- if (trace)
- log_entry('T', "%s expire void", gh->group_name);
- if (debug_mode)
- printf("%s expire void\n", gh->group_name);
- clean_group(gh);
- continue;
- }
- }
-
- if (gh->master_flag & M_EXPIRE) {
- must_expire = 1;
- continue;
- }
-
- if (expire_level > 0 &&
- (gh->first_db_article + expire_level) <= gh->first_a_article) {
- if (trace)
- log_entry('T', "%s expire level", gh->group_name);
- if (debug_mode)
- printf("Expire level: %s\n", gh->group_name);
- gh->master_flag |= M_EXPIRE;
- must_expire = 1;
- continue;
- }
- }
-
- if (!must_expire) return 1;
-
- start_time = cur_time();
- exp_article_count = exp_group_count = 0;
- temp = 0;
-
- Loop_Groups_Header(gh) {
- if (s_hangup) {
- temp = -1;
- break;
- }
-
- if (gh->master_flag & M_IGNORE_GROUP) continue;
- if ((gh->master_flag & M_EXPIRE) == 0) continue;
- if (gh->master_flag & M_MUST_CLEAN) continue;
-
- if (trace)
- log_entry('T', "Exp %s (%ld -> %ld)", gh->group_name,
- (long)gh->first_db_article, (long)gh->first_a_article);
-
- switch (expire_method) {
- case 1:
- block_group(gh);
- temp = expire_in_database(gh);
- unblock_group(gh);
- break;
-
- case 2:
- block_group(gh);
- temp = expire_sliding(gh);
- unblock_group(gh);
- break;
-
- case 4:
- temp = gh->first_a_article - gh->first_db_article;
- if (temp <= 0) break;
- case 3:
- gh->master_flag |= M_MUST_CLEAN;
- break;
-
- }
-
- #ifdef NNTP
- if (nntp_failed) {
- /* connection broken while reading article list */
- /* so no harm was done - leave the group to be expired */
- /* again on next expire */
- break;
- }
- #endif
-
- if (temp > 0) {
- exp_article_count += temp;
- exp_group_count++;
- }
- }
-
- if (exp_article_count > 0)
- log_entry('X', "Expire: %ld art, %d gr, %ld s",
- exp_article_count, exp_group_count,
- (long)(cur_time() - start_time));
-
- return temp >= 0;
- }
-