ftp.nice.ch/pub/next/unix/network/news/nn.6.4.16.s.tar.gz#/nn/newsrc.c

This is newsrc.c in view mode; [Download] [Up]

/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	.newsrc parsing and update.
 */

#include "config.h"
#include "options.h"
#include "regexp.h"
#include "term.h"
#include "articles.h"

#ifdef sel
/* Remove Gould SELbus software name collision */
#undef sel
#endif

#define TR(arg) printf arg

import char *news_lib_directory, *db_directory;
import char *home_directory;
import char *pname;

import int  verbose;
import int  silent;

export int  keep_rc_backup = 1;
export char *bak_suffix = ".bak";
export char *initial_newsrc_path = ".defaultnewsrc";

export int  no_update = 0;
export int  use_selections = 1;
export int  quick_unread_count = 1; /* make a quick count of unread art. */
export int  newsrc_update_freq = 1; /* how often to write .newsrc */
export char *newsrc_file = NULL;

#define RCX_NEVER	0 /* ignore missing groups */
#define RCX_HEAD	1 /* prepend missing groups to .newsrc when read */
#define	RCX_TAIL	2 /* append missing groups to .newsrc when read */
#define RCX_TIME	3 /* append NEW groups as they arrive */
#define RCX_TIME_CONF	4 /* append NEW groups with confirmation */
#define RCX_RNLAST	5 /* .rnlast compatible functionality */

export int  new_group_action = RCX_TIME; /* append new groups to .newsrc */
export int  keep_unsubscribed = 1; /* keep unsubscribed groups in .newsrc */
export int  keep_unsub_long = 0; /* keep unread in unsubscribed groups */

export int  tidy_newsrc = 0;	   /* remove obsolete groups from .newsrc */

export int  auto_junk_seen = 1;	/* junk seen articles ... */
export int  conf_junk_seen = 0; /* ... if confirmed by user ... */
export int  retain_seen_status = 0; /* ... or remember seen articles. */

export long unread_articles;	/* estimate of unread articles */
export int  unread_groups;

export group_header *rc_sequence = NULL;

static char *sel_path = NULL;


/* delimitors on newsrc lines */

#define RC_SUBSCR	':'	/* subscription to group */
#define RC_UNSUBSCR	'!'	/* no subscription to group */

#define RC_DELIM	','	/* separator on rc lines */
#define RC_RANGE	'-'	/* range */

/* delimitors on select lines */

#define SEL_RANGE	'-'	/* range */
#define SEL_SELECT	','	/* following articles are selected */
#define SEL_LEAVE	'+'	/* following articles are left over */
#define SEL_SEEN	';'	/* following articles are seen */
#define SEL_UNREAD	'~'	/* in digests */
#define SEL_DIGEST	'('	/* start digest list */
#define SEL_END_DIGEST	')'	/* end digest list */
#define SEL_NEW		'&'	/* new group (group.name&nnn) */

#define END_OF_LIST	10000000L /* Greater than any article number */

/* line buffers */

#define RC_LINE_MAX	8192

static char rcbuf[RC_LINE_MAX];
static char selbuf[RC_LINE_MAX];

static group_header *rc_seq_tail = NULL;

static int newsrc_update_count = 0, select_update_count = 0;

#define DM_NEWSRC	0
#define DM_SELECT	1
#define DM_ORIG_NEWSRC	2
#define DM_ORIG_SELECT	3

static dump_file(path, mode)
char *path;
int mode;
{
    FILE *f = NULL;
    register group_header *gh;
    char *line;

    Loop_Groups_Newsrc(gh) {
	switch (mode) {
	 case DM_NEWSRC:
	    if (tidy_newsrc) {
		if ((gh->master_flag & M_VALID) == 0)
		    continue;
		if (!keep_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED))
		    continue;
	    }
	    line = gh->newsrc_line;
	    break;
	 case DM_SELECT:
	    if (tidy_newsrc && (gh->master_flag & M_VALID) == 0) continue;
	    if (gh->group_flag & G_UNSUBSCRIBED) continue;
	    line = gh->select_line;
	    break;
	 case DM_ORIG_NEWSRC:
	    line = gh->newsrc_orig;
	    break;
	 case DM_ORIG_SELECT:
	    line = gh->select_orig;
	    break;
	}
	if (line == NULL) continue;
	if (f == NULL)
	    f = open_file(path, OPEN_CREATE|MUST_EXIST);
	fputs(line, f);
    }
    if (f != NULL) 
	if (fclose(f) == EOF) return -1;
    return 0;
}


static dump_newsrc()
{
    char bak[FILENAME];
    static int first = 1;

    if (no_update) return;
    if (++newsrc_update_count < newsrc_update_freq) return;

    if (first && keep_rc_backup) {
	sprintf(bak, "%s%s", newsrc_file, bak_suffix);
	if (dump_file(bak, DM_ORIG_NEWSRC))
	    user_error("Cannot backup %s", newsrc_file);
	first = 0;
    }

    if (dump_file(newsrc_file, DM_NEWSRC)) {
	extern char *tmp_directory;
	char temp[FILENAME];
	sprintf(temp, "%s/newsrc-%d", tmp_directory, process_id);
	if (dump_file(temp, DM_NEWSRC))
	    user_error("Cannot update %s -- restore %s file!!!",
		       newsrc_file, bak_suffix);
	else
	    user_error("Cannot update %s -- saved in %s", newsrc_file, temp);
    }
    newsrc_update_count = 0;
}

static dump_select()
{
    char bak[FILENAME];
    static int first = 1;

    if (no_update) return;
    if (++select_update_count < newsrc_update_freq) return;

    if (first && keep_rc_backup) {
	sprintf(bak, "%s%s", sel_path, bak_suffix);
	dump_file(bak, DM_ORIG_SELECT);
	first = 0;
    }

    dump_file(sel_path, DM_SELECT);

    select_update_count = 0;
}

#define RN_LAST_GROUP_READ	0
#define RN_LAST_TIME_RUN	1
#define RN_LAST_ACTIVE_SIZE	2
#define RN_LAST_CREATION_TIME	3
#define RN_LAST_NEW_GROUP	4
#define RN_ACTIVE_TIMES_OFFSET	5

#define MAX_RNLAST_LINE 6

static char *rnlast_line[MAX_RNLAST_LINE];
#define rnlast_path relative(home_directory, ".rnlast")

static time_t get_last_new()
{
    FILE *lf = NULL;
    char buf[FILENAME];
    register int i;
    time_t t;

    if (new_group_action == RCX_RNLAST) {
	lf = open_file(rnlast_path, OPEN_READ);
	if (lf == NULL) goto no_file;

	for (i = 0; i < MAX_RNLAST_LINE; i++) {
	    if (fgets(buf, FILENAME, lf) == NULL) break;
	    rnlast_line[i] = copy_str(buf);
	}
	if (i != MAX_RNLAST_LINE) {
	    while (i <= MAX_RNLAST_LINE) {
		rnlast_line[i] = copy_str("");
		i++;
	    }
	    fclose(lf);
	    return (time_t)atol(rnlast_line[RN_LAST_TIME_RUN]);
	}
	fclose(lf);
	return (time_t)atol(rnlast_line[RN_LAST_CREATION_TIME]);
    }

    lf = open_file(relative(nn_directory, "LAST"), OPEN_READ);
    if (lf == NULL) goto no_file;
    if (fgets(buf, FILENAME, lf) == NULL) goto no_file;

    fclose(lf);
    return (time_t)atol(buf);

 no_file:
    if (lf != NULL) fclose(lf);
    t = file_exist(newsrc_file, (char *)NULL);
    return t > 0 ? t : (time_t)(-1);
}

static update_last_new(lastg)
group_header *lastg;
{
    FILE *lf = NULL;
    register int i;
    struct stat st;

    if (no_update) return;

    if (new_group_action == RCX_RNLAST) {
	if (rnlast_line[0] == NULL)
	    for (i = 0; i < MAX_RNLAST_LINE; i++)
		rnlast_line[i] = copy_str("");
	lf = open_file(rnlast_path, OPEN_CREATE|MUST_EXIST);
	fputs(rnlast_line[RN_LAST_GROUP_READ], lf);	/* as good as any */
	fprintf(lf, "%ld\n", (long)cur_time()); /* RN_LAST_TIME_RUN */
	fprintf(lf, "%ld\n", (long)master.last_size); /* RN_LAST_ACTIVE_SIZE */

	fprintf(lf, "%ld\n", (long)lastg->creation_time); /* RN_LAST_CREATION_TIME */
	fprintf(lf, "%s\n",lastg->group_name); /* RN_LAST_NEW_GROUP */

	if (stat(relative(news_lib_directory, "active.times"), &st) == 0)
	    fprintf(lf, "%ld\n", (long)st.st_size);
	else /* can't be perfect -- don't update */
	    fputs(rnlast_line[RN_ACTIVE_TIMES_OFFSET], lf);
	for (i = 0; i < MAX_RNLAST_LINE; i++) freeobj(rnlast_line[i]);
    } else {
	lf = open_file(relative(nn_directory, "LAST"), OPEN_CREATE|MUST_EXIST);
	fprintf(lf, "%ld\n%s\n", (long)lastg->creation_time, lastg->group_name);
    }

    fclose(lf);
}

static article_number get_last_article(gh)
group_header *gh;
{
    register char *line;

    if ((line = gh->newsrc_line) == NULL) return -1;

    line += gh->group_name_length+1;
    while (*line && isspace(*line)) line++;
    if (*line == NUL) return -1;

    if (line[0] == '1') {
	if (line[1] == RC_RANGE)
	    return atol(line+2);
	if (!isdigit(line[1])) return 1;
    }
    return 0;
}


visit_rc_file()
{
    FILE *rc, *sel;
    register group_header *gh;
    int subscr;
    register char *bp;
    register int c;
    char bak[FILENAME];
    time_t last_new_group = 0, rc_age, newsrc_age;
    group_header *last_new_gh = NULL;
    extern group_header *lookup_no_alias();

    if (newsrc_file == NULL)
	newsrc_file = home_relative(".newsrc");

    sel_path = mk_file_name(nn_directory, "select");

    Loop_Groups_Header(gh) {
	gh->newsrc_line = NULL;
	gh->newsrc_orig = NULL;
	gh->select_line = NULL;
	gh->select_orig = NULL;
    }

    if (rc_age = file_exist(relative(nn_directory, "rc"), (char *)NULL)) {
	if (who_am_i != I_AM_NN)
	    user_error("A release 6.3 rc file exists. Run nn to upgrade");

	sprintf(bak, "%s/upgrade_rc", lib_directory);

	if ((newsrc_age = file_exist(newsrc_file, (char *)NULL)) == 0) {
	    display_file("adm.upgrade1", CLEAR_DISPLAY);
	} else {
	    if (rc_age + 60 > newsrc_age) {
		/* rc file is newest (or .newsrc does not exist) */
		display_file("adm.upgrade2", CLEAR_DISPLAY);
		prompt("Convert rc file to .newsrc now? ");
		if (yes(1) <= 0) nn_exit(0);
	    } else {
		/* .newsrc file is newest */
		display_file("adm.upgrade3", CLEAR_DISPLAY);
		prompt("Use current .newsrc file? ");
		if (yes(1) > 0) {
		    strcat(bak, " n");
		} else {
		    display_file("adm.upgrade4", CLEAR_DISPLAY);
		    prompt("Convert rc file to .newsrc? ");
		    if (yes(1) <= 0) {
			printf("Then you will have to upgrade manually\n");
			nn_exit(0);
		    }
		}
	    }
	}

	printf("\r\n\n");
	system(bak);
	any_key(prompt_line);
    }

    rc = open_file(newsrc_file, OPEN_READ);
    if (rc != NULL) {
	fseek(rc, (off_t)0, 2);
	if (ftell(rc))
	    rewind(rc);
	else {
	    fclose(rc);
	    rc = NULL;
	}
    }

    if (rc == NULL) {
	sprintf(bak, "%s%s", newsrc_file, bak_suffix ? bak_suffix : ".bak");
	if (rc = open_file(bak, OPEN_READ)) {
	    int ans;
	    time_t rc_mtime;
	    
	    printf("\nThere is no .newsrc file - restore backup? (y) ");
	    if ((ans = yes(0)) < 0) nn_exit(1);
	    if (!ans) {
		printf("\nConfirm building .newsrc from scratch: (y/n) ");
		if ((ans = yes(1)) <= 0) nn_exit(1);
		fclose(rc);
		rc = NULL;
	    }
	    switch (new_group_action) {
	     case RCX_TIME:
	     case RCX_TIME_CONF:
	     case RCX_RNLAST:
		last_new_group = get_last_new();
		rc_mtime = m_time(rc);
		if (last_new_group < 0 || rc_mtime < last_new_group)
		    last_new_group = rc_mtime;
	    }
	}
    }

    if (rc == NULL) {
	extern FILE *open_file_search_path();

	last_new_group = -1;	/* ignore LAST & .rnlast if no .newsrc */
	rc = open_file_search_path(initial_newsrc_path, OPEN_READ);
	if (rc == NULL) goto new_user;
	/* ignore groups not in the initial newsrc file */
	last_new_gh = ACTIVE_GROUP(master.number_of_groups - 1);
	last_new_group = last_new_gh->creation_time;
    }

    while (fgets(rcbuf, RC_LINE_MAX, rc) != NULL) {
	gh = NULL;
	subscr = 0;
	for (bp = rcbuf; (c = *bp); bp++) {
	    if (isspace(c)) break;	/* not a valid line */

	    if (c == RC_UNSUBSCR || c == RC_SUBSCR) {
		subscr = (c == RC_SUBSCR);
		*bp = NUL;
		gh = lookup_no_alias(rcbuf);
		*bp = c;
		break;
	    }
	}

	if (gh == NULL) {
	    gh = newobj(group_header, 1);
	    gh->group_flag |= G_FAKED;
	    gh->master_flag |= M_VALID;
	}

	if (gh->master_flag & M_ALIASED) continue;

	if (rc_seq_tail == NULL)
	    rc_sequence = rc_seq_tail = gh;
	else {
	    rc_seq_tail->newsrc_seq = gh;
	    rc_seq_tail = gh;
	}

	gh->newsrc_orig = gh->newsrc_line = copy_str(rcbuf);
	if (gh->group_flag & G_FAKED)
	    gh->group_name = gh->newsrc_line;
	else
	    if (!subscr)
		gh->group_flag |= G_UNSUBSCRIBED;
    }
    fclose(rc);

 new_user:
    rc = NULL;
    Loop_Groups_Header(gh) {
	if (gh->master_flag & M_IGNORE_GROUP) continue;
	if (gh->group_flag & G_UNSUBSCRIBED) continue;
	if (gh->newsrc_line == NULL) {
	    char buf[FILENAME];

	    /* NEW GROUP - ADD TO NEWSRC AS APPROPRIATE */

	    switch (new_group_action) {
	     case RCX_NEVER:
		/* no not add new groups */
		gh->group_flag |= G_DONE | G_UNSUBSCRIBED;
		continue;

	     case RCX_HEAD:
		/* insert at top */
		gh->newsrc_seq = rc_sequence;
		rc_sequence = gh;
		break;


	     case RCX_TIME:
	     case RCX_TIME_CONF:
	     case RCX_RNLAST:
		if (last_new_group == 0) {
		    last_new_group = get_last_new();
		    if (last_new_group < 0 && new_group_action != RCX_RNLAST) {
			/* maybe this is a first time rn convert ? */
			int nga = new_group_action;
			new_group_action = RCX_RNLAST;
			last_new_group = get_last_new();
			new_group_action = nga;
		    }
		}

		if (gh->creation_time <= last_new_group) {
		    /* old groups not in .newsrc are unsubscribed */
		    gh->group_flag |= G_UNSUBSCRIBED;
		    continue;
		}

		if (last_new_gh == NULL || last_new_gh->creation_time <= gh->creation_time)
		    last_new_gh = gh;

		if (new_group_action != RCX_TIME && !no_update) {
		    printf("\nNew group: %s -- append to .newsrc? (y)",
			   gh->group_name);
		    if (yes(0) <= 0) continue;
		}
		sprintf(buf, "%s:\n", gh->group_name);
		/* to avoid fooling the LAST mechanism, we must fake */
		/* that the group was also in the original .newsrc */

		gh->newsrc_orig = gh->newsrc_line = copy_str(buf);
		newsrc_update_count++;

		/* fall thru */

	     case RCX_TAIL:
		/* insert at bottom */
		if (rc_seq_tail == NULL)
		    rc_sequence = rc_seq_tail = gh;
		else {
		    rc_seq_tail->newsrc_seq = gh;
		    rc_seq_tail = gh;
		}
		break;
	    }

	    gh->last_article = -1;
	} else
	    gh->last_article = get_last_article(gh);

	if (gh->last_article < 0) {
	    gh->group_flag |= G_NEW;
	    gh->last_article = gh->first_db_article - 1;
	} else
	    if (gh->first_db_article > gh->last_article)
		gh->last_article = gh->first_db_article - 1;

	if (gh->last_article < 0) gh->last_article = 0;
	gh->first_article = gh->last_article;
    }

    if (rc_seq_tail)
	rc_seq_tail->newsrc_seq = NULL;

    if (last_new_gh != NULL)
	update_last_new(last_new_gh);

    if (!use_selections) return;

    sel = open_file(sel_path, OPEN_READ);
    if (sel == NULL) return;

    while (fgets(selbuf, RC_LINE_MAX, sel) != NULL) {
	gh = NULL;
	for (bp = selbuf; (c = *bp); bp++)
	    if (c == SP || c == SEL_NEW) break;

	if (c == NUL) continue;
	*bp = NUL;
	gh = lookup_no_alias(selbuf);
	if (gh == NULL || gh->master_flag & M_ALIASED) continue;
	*bp = c;
	if (c == SEL_NEW) gh->group_flag |= G_NEW;
	gh->select_orig = gh->select_line = copy_str(selbuf);
    }
    fclose(sel);
}

/*
 * prepare to use newsrc & select information for a specific group
 */

static struct rc_info {
    char *rc_p;			/* pointer into newsrc_line */
    article_number rc_min;	/* current newsrc range min */
    article_number rc_max;	/* current newsrc range max */
    char rc_delim;		/* delimiter character */

    char *sel_p;		/* pointer into select_line */
    char *sel_initp;		/* rc_p after initialization */
    article_number sel_min;	/* current select range min */
    article_number sel_max;	/* current select range max */
    article_number sel_digest;	/* current digest */
    attr_type sel_type;		/* current select range type */
    char sel_delim;		/* delimiter character */
} orig, cur;

static int rctest_mode;

static init_rctest(gh, r)
group_header *gh;
register struct rc_info *r;
{
    if (r->rc_p == NULL) {
	r->rc_min = r->rc_max = END_OF_LIST;
    } else {
	r->rc_min = r->rc_max = -1;
	r->rc_delim = SP;
	r->rc_p += gh->group_name_length + 1;
    }

    r->sel_digest = 0;
    if (r->sel_p == NULL) {
	r->sel_min = r->sel_max = END_OF_LIST;
    } else {
	r->sel_p += gh->group_name_length + 1;
	r->sel_min = r->sel_max = -1;
	r->sel_delim = SP;
    }
}

use_newsrc(gh, mode)
register group_header *gh;
int mode;
{
    orig.rc_p = gh->newsrc_orig;
    orig.sel_p = gh->select_orig;
    cur.rc_p = gh->newsrc_line;
    cur.sel_p = gh->select_line;

    rctest_mode = mode;

    switch (mode) {
     case 0:
	init_rctest(gh, &cur);
	break;
     case 1:
	init_rctest(gh, &orig);
	break;
     case 2:
	init_rctest(gh, &cur);
	init_rctest(gh, &orig);
	break;
    }
}

/*
#define TRC(wh)  TR( ("r%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, rc_min, rc_max, rc_delim) )
#define TSEL(wh) TR( ("s%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, sel_min, sel_max, sel_delim) )
*/
#define TRC(wh)
#define TSEL(wh)

static attr_type rctest(ah, r)
register article_header *ah;
register struct rc_info *r;
{
    register char *p;
    register int c;
    register int32 n = ah->a_number, x;

    while (n > r->rc_max) {
	/* get next interval from newsrc line */
	r->rc_min = -1;
	x = 0;
	p = r->rc_p;
	TRC(1);

	if (*p == RC_DELIM) p++;
	if (*p == NUL || *p == NL)
	    r->rc_min = r->rc_max = END_OF_LIST;
	else {
	    for ( ; (c = *p) && c != RC_DELIM && c != NL; p++) {
		if (c == RC_RANGE) {
		    if (r->rc_min < 0)
			r->rc_min = x;
		    else
			msg("syntax error in rc file");
		    x = 0;
		    continue;
		}

		if (isascii(*p) && isdigit(*p))
		    x = x*10 + c - '0';
	    }
	    r->rc_max = x;
	    if (r->rc_min < 0) r->rc_min = x;
	    r->rc_p = p;
	}
    }
    TRC(2);

    if (n >= r->rc_min && n <= r->rc_max) return A_READ;

    p = r->sel_p;
    if (r->sel_digest != 0) {
	if (n == r->sel_digest && (ah->flag & A_DIGEST)) {
	    if (*(r->sel_p) == SEL_END_DIGEST) return A_READ;
	    n = ah->fpos;
	} else {
	    if (n < r->sel_digest) return 0;
	    while (*p && *p++ != SEL_END_DIGEST);
	    r->sel_digest = 0;
	    r->sel_min = r->sel_max = -1;
	}
    }

    while (n > r->sel_max) {
	r->sel_min = -1;
	r->sel_type = A_SELECT;
	x = 0;
	TSEL(3);

	for (;;) {
	    switch (*p) {
	     case SEL_SELECT:
		r->sel_type = A_SELECT;
		p++;
		continue;
	     case SEL_LEAVE:
		r->sel_type = A_LEAVE;
		p++;
		continue;
	     case SEL_SEEN:
		r->sel_type = A_SEEN;
		p++;
		continue;
	     case SEL_UNREAD:
		r->sel_type = 0;
		p++;
		continue;
	     case SEL_DIGEST:
		while (*p && *p++ != SEL_END_DIGEST);
		continue;
	     case SEL_END_DIGEST:
		if (r->sel_digest) {
		    if (r->sel_digest == ah->a_number) {
			r->sel_p = p;
			return A_READ;
		    }
		    r->sel_digest = 0;
		}
		p++;
		r->sel_type = A_SELECT;
		continue;
	     default:
		break;
	    }
	    break;
	}

	if (*p == NUL || *p == NL) {
	    r->sel_min = r->sel_max = END_OF_LIST;
	    break;
	}

	for ( ; c = *p ; p++ ) {
	    switch (c) {
	     case '0':
	     case '1':
	     case '2':
	     case '3':
	     case '4':
	     case '5':
	     case '6':
	     case '7':
	     case '8':
	     case '9':
		x = x*10 + c - '0';
		continue;

	     case SEL_SELECT:
	     case SEL_LEAVE:
	     case SEL_SEEN:
	     case SEL_UNREAD:
		break;

	     case SEL_RANGE:
		if (r->sel_min < 0)
		    r->sel_min = x;
		else
		    msg("syntax error in sel file");
		x = 0;
		continue;

	     case SEL_DIGEST:
		n = ah->a_number;
		if (n > x) {
		    while (*p && (*p++ != SEL_END_DIGEST));
		    x = -1;
		    break;
		}
		p++;
		r->sel_digest = x;
		if (n < r->sel_digest) {
		    r->sel_p = p;
		    return 0;
		}
		n = ah->fpos;
		x = -1;
		break;

	     case NL:
		if (r->sel_digest == 0) break;
		/* fall thru */
	     case SEL_END_DIGEST:
		if (r->sel_digest == ah->a_number) {
		    r->sel_p = p;
		    return (ah->fpos == x) ? r->sel_type : A_READ;
		}
		r->sel_digest = 0;
		x = -1;
		break;
	    }
	    break;
	}
	r->sel_max = x;
	if (r->sel_min < 0) r->sel_min = x;
	r->sel_p = p;
    }

    if (n >= r->sel_min && n <= r->sel_max) return r->sel_type;

    if (r->sel_digest) return A_READ; /* only read articles are not listed */

    return 0;	/* unread, unseen, unselected */
}

attr_type test_article(ah)
article_header *ah;
{
    attr_type a;

    switch (rctest_mode) {
     case 0:
	return rctest(ah, &cur);
     case 1:
	return rctest(ah, &orig);
     case 2:
	a = rctest(ah, &cur);
	if (a != A_READ) return a;
	if (rctest(ah, &orig) == A_READ) return A_KILL;
	return a;
    }
}

/*
 * We only mark the articles that should remain unread
 */
static char *rc_p;		/* pointer into newsrc_line */
static article_number rc_min;	/* current newsrc range min */
static char rc_delim;		/* delimiter character */

static char *sel_p;		/* pointer into select_line */
static char *sel_initp;		/* rc_p after initialization */
static article_number sel_min;	/* current select range min */
static article_number sel_max;	/* current select range max */
static article_number sel_digest; /* current digest */
static char sel_delim;		/* delimiter character */

/*VARARGS*/
static append(va_alist)
va_dcl
{
    int x;
    register char *p;
    char **pp, *fmt;
    use_vararg;

    start_vararg;
    x = va_arg1(int);
    pp = x ? &sel_p : &rc_p;
    p = *pp;
    if (p > (x ? &selbuf[RC_LINE_MAX - 16] : &rcbuf[RC_LINE_MAX - 16])) {
	msg("%s line too long", x ? "select" : ".newsrc");
	end_vararg;
	return;
    }
    fmt = va_arg2(char *);
    vsprintf(p, fmt, va_args3toN);
    end_vararg;

    while (*p) p++;
    *p = NL;
    p[1] = NUL;
    *pp = p;
}

static append_range(pp, delim, rmin, rmax)
int pp;
char delim;
article_number rmin, rmax;
{
    if (rmin == rmax)
	append(pp, "%c%ld", delim, (long)rmin);
    else
	append(pp, "%c%ld%c%ld", delim, (long)rmin, RC_RANGE, (long)rmax);
}

static int32 mark_counter;

static begin_rc_update(gh)
register group_header *gh;
{
    add_unread(gh, -1);
    mark_counter = 0;

    rc_p = rcbuf;
    rc_min = 1;
    append(0, "%s%c", gh->group_name,
	   gh->group_flag & G_UNSUBSCRIBED ? RC_UNSUBSCR : RC_SUBSCR);
    rc_delim = SP;
    sel_p = selbuf;
    sel_min = 0;
    sel_max = 0;
    sel_digest = 0;
    sel_delim = SP;
    append(1, "%s%c", gh->group_name,
	   (gh->group_flag & G_NEW) ? SEL_NEW : SP);
    /* sel_initp == sep_p => empty list */
    sel_initp = (gh->group_flag & G_NEW) ? NULL : sel_p;
}

static end_rc_update(gh)
register group_header *gh;
{
    if (rc_min <= gh->last_db_article)
	append_range(0, rc_delim, rc_min, gh->last_db_article);

    if (gh->newsrc_line != NULL && strcmp(rcbuf, gh->newsrc_line)) {
	if (gh->newsrc_orig != gh->newsrc_line)
	    freeobj(gh->newsrc_line);
	gh->newsrc_line = NULL;
    }

    if (gh->newsrc_line == NULL) {
	gh->newsrc_line = copy_str(rcbuf);
	dump_newsrc();
    }

    if (sel_digest)
	append(1, "%c", SEL_END_DIGEST);
    else
	if (sel_min)
	    append_range(1, sel_delim, sel_min, sel_max);

    if (gh->select_line) {
	if (strcmp(selbuf, gh->select_line) == 0) goto out;
    } else
        if (sel_p == sel_initp) goto out;

    if (gh->select_line && gh->select_orig != gh->select_line)
	freeobj(gh->select_line);

    gh->select_line = (sel_p == sel_initp) ? NULL : copy_str(selbuf);
    dump_select();

 out:
    if ((gh->last_article = get_last_article(gh)) < 0)
	gh->last_article = 0;
    if (gh->last_article < gh->first_article)
	gh->first_article = gh->last_article;

    gh->group_flag |= G_READ;	/* should not call update_group again */
    if (mark_counter > 0) {
	gh->unread_count = mark_counter;
	add_unread(gh, 0);
    }
}

static mark_article(ah, how)
register article_header *ah;
attr_type how;
{
    register article_number anum;
    char delim;

    switch (how) {
     case A_SELECT:
	delim = SEL_SELECT;
	break;
     case A_SEEN:
	delim = SEL_SEEN;
	break;
     case A_LEAVE:
     case A_LEAVE_NEXT:
	delim = SEL_LEAVE;
	break;
     case 0:
	delim = SEL_UNREAD;
	break;
    }

    mark_counter++;
    anum = ah->a_number;

    if (rc_min < anum) {
	append_range(0, rc_delim, rc_min, anum - 1);
	rc_delim = RC_DELIM;

	if ((ah->flag & A_DIGEST) == 0
	    && sel_min && delim == sel_delim && sel_max == (rc_min - 1))
	    sel_max = anum - 1;	/* expand select range over read articles */
    }
    rc_min = anum + 1;

    if (ah->flag & A_DIGEST) {
	if (sel_digest != anum) {
	    if (sel_digest) {
		append(1, "%c", SEL_END_DIGEST);
	    } else
		if (sel_min) {
		    append_range(1, sel_delim, sel_min, sel_max);
		    sel_min = 0;
		}
	    append(1, "%c%ld%c", SEL_SELECT, (long)anum, SEL_DIGEST);
	    sel_digest = anum;
	}

	append(1, "%c%ld", delim, (long)ah->fpos);
	return;
    }

    if (sel_digest) {
	append(1, "%c", SEL_END_DIGEST);
	sel_digest = 0;
    }

    if (sel_min) {
	if (delim != sel_delim || delim == SEL_UNREAD) {
	    append_range(1, sel_delim, sel_min, sel_max);
	    sel_delim = delim;
	    if (delim == SEL_UNREAD)
		sel_min = 0;
	    else
		sel_min = anum;
	} else
	    sel_max = anum;
    } else
	if (delim != SEL_UNREAD) {
	    sel_min = sel_max = anum;
	    sel_delim = delim;
	}
}

flush_newsrc()
{
    newsrc_update_freq = 0;
    if (select_update_count) dump_select();
    if (newsrc_update_count) dump_newsrc();
}

restore_bak()
{
    if (no_update)
	return 1;

    prompt("Are you sure? ");
    if (!yes(1)) return 0;

    if (dump_file(newsrc_file, DM_ORIG_NEWSRC))
	user_error("Could not restore %s -- restore %s file manually",
		   newsrc_file, bak_suffix);

    prompt("Restore selections? ");
    if (yes(1)) dump_file(sel_path, DM_ORIG_SELECT);

    no_update = 1;	/* so current group is not updated */
    return 1;
}

/*
 *	Update .newsrc for one group.
 *	sort_articles(-2) MUST HAVE BEEN CALLED BEFORE USE.
 */

export int rc_merged_groups_hack = 0;

update_rc(gh)
register group_header *gh;
{
    register article_header *ah, **ahp;
    register article_number art;
    static int junk_seen = 0;

    if (!rc_merged_groups_hack) junk_seen = 0;

    if (gh->group_flag & (G_FOLDER | G_FAKED)) return;

    begin_rc_update(gh);

    for (ahp = articles, art = 0; art < n_articles; ahp++, art++) {
	ah = *ahp;
	if (ah->a_group != NULL && ah->a_group != gh) continue;

	switch (ah->attr) {
	 case A_READ:
	 case A_KILL:
	    continue;

	 case A_LEAVE:
	 case A_LEAVE_NEXT:
	 case A_SELECT:
	    mark_article(ah, ah->attr);
	    continue;

	 case A_SEEN:
	    if (junk_seen == 0) {
		junk_seen = -1;
		if (auto_junk_seen) {
		    if (conf_junk_seen) {
			prompt("\1Junk seen articles\1 ");
			if (yes(0) > 0) junk_seen = 1;
		    } else
			junk_seen = 1;
		}
	    }
	    if (junk_seen > 0) continue;
	    mark_article(ah, (attr_type)(retain_seen_status ? A_SEEN : 0));
	    continue;

	 case A_AUTO_SELECT:
	 default:
	    mark_article(ah, (attr_type)0);
	    continue;
	}
    }

    end_rc_update(gh);
}

update_rc_all(gh, unsub)
register group_header *gh;
int unsub;
{
    if (unsub) {
	gh->group_flag &= ~G_NEW;
	gh->group_flag |= G_UNSUBSCRIBED;

	if (!keep_unsubscribed) {
	    add_unread(gh, -1);
	    if (gh->newsrc_line != NULL && gh->newsrc_orig != gh->newsrc_line)
		freeobj(gh->newsrc_line);
	    gh->newsrc_line = NULL;
	    if (gh->newsrc_orig != NULL) dump_newsrc();
	    if (gh->select_line != NULL && gh->newsrc_orig != gh->select_line)
		freeobj(gh->select_line);
	    gh->select_line = NULL;
	    if (gh->select_orig != NULL) dump_select();
	    return;
	}
	
	if (keep_unsub_long) {
	    update_rc(gh);
	    return;
	}
    }

    begin_rc_update(gh);
    end_rc_update(gh);
}

add_to_newsrc(gh)
group_header *gh;
{
    gh->group_flag &= ~G_UNSUBSCRIBED;

    if (gh->newsrc_seq != NULL || gh == rc_seq_tail) {
	update_rc(gh);
	return;
    }
    
    rc_seq_tail->newsrc_seq = gh;
    rc_seq_tail = gh;
    if (gh->last_db_article > 0)
	sprintf(rcbuf, "%s: %s%ld\n", gh->group_name,
		gh->last_db_article > 1 ? "1-" : "",
		(long)gh->last_db_article);
    else
	sprintf(rcbuf, "%s:\n", gh->group_name);
    gh->newsrc_line = copy_str(rcbuf);
    dump_newsrc();
}

int32 restore_rc(gh, last)
register group_header *gh;
article_number last;
{
    register article_number *numtab, n;
    register attr_type *attrtab, attr;
    register int32 at, atmax;
    article_header ahdr;
    int32 count;

    if (last > gh->last_db_article) return 0;

    if (gh->unread_count <= 0) {
	/* no unread articles to account for -- quick update */
	n = gh->last_db_article;	/* fake for end_rc_update */
	gh->last_db_article = last;
	begin_rc_update(gh);
	end_rc_update(gh);
	gh->last_db_article = n;
	add_unread(gh, 1); /* not done by end_rc_update bec. mark_counter==0 */
	return gh->unread_count;
    }

    /* there are unread articles in the group */
    /* we must truncate rc&select lines to retain older unread articles */

    atmax = at = 0;
    numtab = NULL;
    attrtab = NULL;
    
    use_newsrc(gh, 0);
    ahdr.flag = 0;
    count = gh->unread_count;

    for (n = gh->last_article + 1; n <= last; n++) {
	if (cur.rc_min == END_OF_LIST) {
	    /* current & rest is unread */
	    last = n - 1;
	    break;
	}
	ahdr.a_number = n;
	if ((attr = rctest(&ahdr, &cur)) == A_READ) continue;
	if (at >= atmax) {
	    atmax += 100;
	    numtab = resizeobj(numtab, article_number, atmax);
	    attrtab = resizeobj(attrtab, attr_type, atmax);
	}
	numtab[at] = n;
	attrtab[at] = attr;
	at++;
    }

    begin_rc_update(gh);
    while (--at >= 0) {
	ahdr.a_number = *numtab++;
	mark_article(&ahdr, *attrtab++);
    }
    for (n = last+1; n <= gh->last_db_article; n++) {
	ahdr.a_number = n;
	mark_article(&ahdr, (attr_type)0);
    }
    end_rc_update(gh);
    return gh->unread_count - count;
}

restore_unread(gh)
register group_header *gh;
{
    if (gh->select_line != gh->select_orig) {
	if (gh->select_line != NULL) freeobj(gh->select_line);
	gh->select_line = gh->select_orig;
	dump_select();
    }

    if (gh->newsrc_orig == gh->newsrc_line) return 0;

    add_unread(gh, -1);
    if (gh->newsrc_line != NULL) freeobj(gh->newsrc_line);
    gh->newsrc_line = gh->newsrc_orig;
    gh->last_article = gh->first_article;
    dump_newsrc();

    add_unread(gh, 1);

    return 1;
}


count_unread_articles()
{
    register group_header *gh;
    long n;

    unread_articles = 0;
    unread_groups = 0;

    Loop_Groups_Sequence(gh) {
	gh->unread_count = 0;

	if (gh->master_flag & M_NO_DIRECTORY) continue;

	if (gh->last_db_article > gh->last_article) {
	    n = unread_articles;
	    add_unread(gh, 1);
	}
	
	if ((gh->group_flag & G_COUNTED) == 0) continue;
	if (verbose)
	    printf("%6d %s\n", unread_articles - n, gh->group_name);
    }
}


prt_unread(format)
register char *format;
{
    if (format == NULL) {
	printf("No News (is good news)\n");
	return;
    }

    while (*format) {
	if (*format != '%') {
	    putchar(*format++);
	    continue;
	}
	format++;
	switch (*format++) {
	 case 'u':
	    printf("%ld unread article%s", unread_articles, plural((long)unread_articles));
	    continue;
	 case 'g':
	    printf("%d group%s", unread_groups, plural((long)unread_groups));
	    continue;
	 case 'i':
	    printf(unread_articles == 1 ? "is" : "are");
	    continue;
	 case 'U':
	    printf("%ld", unread_articles);
	    continue;
	 case 'G':
	    printf("%d", unread_groups);
	    continue;
	}
    }
}


add_unread(gh, mode)
group_header *gh;
int mode;	/* +1 => count + add, 0 => gh->unread_count, -1 => subtract */
{
    int32 old_count;
    article_header ahdr;

    old_count = gh->unread_count;

    if (mode == 0) goto add_directly;

    if (gh->group_flag & G_COUNTED) {
	unread_articles -= gh->unread_count;
	unread_groups --;
	gh->unread_count = 0;
	gh->group_flag &= ~G_COUNTED;
    }

    if (mode < 0) goto out;

    if (quick_unread_count)
	gh->unread_count = gh->last_db_article - gh->last_article;
    else {
	use_newsrc(gh, 0);
	ahdr.flag = 0;
	for (ahdr.a_number = gh->last_article + 1;
	     ahdr.a_number <= gh->last_db_article;
	     ahdr.a_number++) {
	    if (cur.rc_min == END_OF_LIST) {
		gh->unread_count += gh->last_db_article - ahdr.a_number + 1;
		break;
	    }
	    if (rctest(&ahdr, &cur) != A_READ)
		gh->unread_count++;
	}
    }

 add_directly:
    if (gh->unread_count <= 0) {
	gh->unread_count = 0;
	goto out;
    }

    if (gh->group_flag & G_UNSUBSCRIBED) goto out;

    unread_articles += gh->unread_count;
    unread_groups++;
    gh->group_flag |= G_COUNTED;
    
 out:
    return old_count != gh->unread_count;
}

/*
 *	nngrep
 */

static int
    grep_all = 0,
    grep_new = 0,
    grep_not_sequence = 0,
    grep_pending = 0,
    grep_read = 0,
    grep_sequence = 0,
    grep_unsub = 0,
    grep_long = 0,
    grep_patterns;

Option_Description(grep_options) {
    'a', Bool_Option(grep_all),
    'i', Bool_Option(grep_not_sequence),
    'n', Bool_Option(grep_new),
    'p', Bool_Option(grep_pending),
    'r', Bool_Option(grep_read),
    's', Bool_Option(grep_sequence),
    'u', Bool_Option(grep_unsub),
    'l', Bool_Option(grep_long),
    '\0',
};

opt_nngrep(argc, argv)
int argc;
char *argv[];
{
    grep_patterns =
	parse_options(argc, argv, (char *)NULL, grep_options, " pattern...");
}

do_grep(pat)
char **pat;
{
    register group_header *gh;
    register regexp **re;
    register int i;
    int header = 1;

    if (grep_patterns > 0) {
	re = newobj(regexp *, grep_patterns);
	for (i = 0; i < grep_patterns; i++)
	    re[i] = regcomp(pat[i]);
    }

    Loop_Groups_Sorted(gh) {
	if (gh->master_flag & M_IGNORE_GROUP) continue;

	if (grep_pending && gh->unread_count <= 0) continue;
	if (grep_read && gh->unread_count > 0) continue;
	if (grep_sequence && (gh->group_flag & G_SEQUENCE) == 0) continue;
	if (grep_not_sequence && (gh->group_flag & G_SEQUENCE)) continue;
	if (grep_new && (gh->group_flag & G_NEW) == 0) continue;
	if (!grep_all) {
	    if (grep_unsub && (gh->group_flag & G_UNSUBSCRIBED) == 0) continue;
	    if (!grep_unsub && (gh->group_flag & G_UNSUBSCRIBED)) continue;
	}

	if (grep_patterns > 0) {
	    for (i = 0; i < grep_patterns; i++)
		if (regexec(re[i], gh->group_name)) break;
	    if (i == grep_patterns) continue;
	}

	if (grep_long) {
	    if (header)
		printf("SUBSCR IN_RC NEW UNREAD SEQUENCE GROUP\n");
	    header = 0;

	    printf(" %s   %s   %s ",
		   (gh->group_flag & G_UNSUBSCRIBED) ? "no " : "yes",
		   (gh->newsrc_line == NULL) ? "no " : "yes",
		   (gh->group_flag & G_NEW) ? "yes" : "no ");

	    if (gh->unread_count > 0)
		printf("%6d ", gh->unread_count);
	    else
		printf("       ");
	    if (gh->group_flag & G_SEQUENCE)
		printf("  %4d   ", gh->preseq_index);
	    else
		printf("         ");
	}

	printf("%s\n", gh->group_name);
    }
}


/*
 *	nntidy
 */

static int
    tidy_unsubscribed = 0,	/* truncate lines for unsub groups*/
    tidy_remove_unsub = 0,	/* remove lines for unsub groups*/
    tidy_sequence = 0,		/* remove groups not in sequence */
    tidy_ignored = 0,		/* remove G_IGN groups */
    tidy_crap = 0,		/* remove unrecognized lines */
    tidy_all = 0;		/* all of the above */

Option_Description(tidy_options) {
    'N', Bool_Option(no_update),
    'Q', Bool_Option(silent),
    'v', Bool_Option(verbose),
    'a', Bool_Option(tidy_all),
    'c', Bool_Option(tidy_crap),
    'i', Bool_Option(tidy_ignored),
    'r', Bool_Option(tidy_remove_unsub),
    's', Bool_Option(tidy_sequence),
    'u', Bool_Option(tidy_unsubscribed),
    '\0',
};

opt_nntidy(argc, argv)
int argc;
char *argv[];
{
    return parse_options(argc, argv, (char *)NULL, 
			 tidy_options, " [group]...");
}

do_tidy_newsrc()
{
    register group_header *gh;
    int changed;
    char *why;

    /* visit_rc_file has been called. */

    keep_rc_backup = 1;
    bak_suffix = ".tidy";

    tidy_newsrc = 0;
    changed = 0;

    if (tidy_all)
	tidy_sequence = tidy_ignored = tidy_crap = tidy_unsubscribed = 1;

    newsrc_update_freq = 9999;

    Loop_Groups_Newsrc(gh) {
	if ((gh->master_flag & M_VALID) == 0) {
	    why = "Unknown group:   ";
	    goto delete;
	}
	if (tidy_sequence && (gh->group_flag & G_SEQUENCE) == 0) {
	    why = "Not in sequence: ";
	    goto delete;
	}
	if (tidy_ignored && (gh->master_flag & M_IGNORE_GROUP)) {
	    why = "Ignored group:   ";
	    goto delete;
	}
	if (tidy_crap && (gh->group_flag & G_FAKED)) {
	    why = "Crap in .newsrc: ";
	    goto delete;
	}
	if (tidy_remove_unsub && (gh->group_flag & G_UNSUBSCRIBED)) {
	    if (gh->group_flag & G_FAKED) continue;
	    why = "Unsubscribed:    ";
	    goto delete;
	}

	if (tidy_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED)) {
	    if (gh->group_flag & G_FAKED) continue;

	    begin_rc_update(gh);
	    gh->last_db_article = 0;
	    end_rc_update(gh);

	    if (gh->newsrc_line != gh->newsrc_orig) {
		why = "Truncated:       ";
		goto change;
	    }
	}
	if (verbose) {
	    why = "Ok:              ";
	    goto report;
	}
	continue;

     delete:
	gh->newsrc_line = NULL;
	gh->select_line = NULL;

     change:
	changed = 1;

     report:
	if (!silent) printf("%s%s\n", why, gh->group_name);
    }

    if (no_update) {
	if (changed) printf("Files were NOT updated\n");
	return;
    }

    if (changed) {
	newsrc_update_freq = 0;
	dump_newsrc();
	dump_select();
	printf("NOTICE: Original files are saved with %s extention\n", bak_suffix);
    }
}

/*
 *	nngoback
 */

static int
    goback_interact = 0, /* interactive nngoback */
    goback_days = -1,
    goback_alsounsub = 0; /* unsubscribed groups also */

Option_Description(goback_options) {
    'N', Bool_Option(no_update),
    'Q', Bool_Option(silent),
    'd', Int_Option(goback_days),
    'i', Bool_Option(goback_interact),
    'u', Bool_Option(goback_alsounsub),
    'v', Bool_Option(verbose),
    '\0',
};

opt_nngoback(argc, argvp)
int argc;
char ***argvp;
{
    int n;
    
    n = parse_options(argc, *argvp, (char *)NULL, goback_options,
		      " days [groups]...");

    if (goback_days < 0) {
	if (n == 0 || !isdigit((*argvp)[1][0])) {
	    fprintf(stderr, "usage: %s [-NQvi] days [groups]...\n", pname);
	    nn_exit(1);
	}
	goback_days = atoi((*argvp)[1]);
	n--;
	++*argvp;
    }
    return n;
}

do_goback()
{
    char back_act[FILENAME];
    FILE *ba;
    register group_header *gh;
    int32 count, total;
    int groups, y;
    
    sprintf(back_act, "%s/active.%d", db_directory, goback_days);
    if ((ba = open_file(back_act, OPEN_READ)) == NULL) {
	fprintf(stderr, "Cannot go back %d days\n", goback_days);
	nn_exit(1);
    }

    read_active_file(ba, (FILE *)NULL);

    fclose(ba);

    /* visit_rc_file has been called. */

    keep_rc_backup = 1;
    bak_suffix = ".goback";
    newsrc_update_freq = 9999;
    quick_unread_count = 0;
    total = groups = 0;

    if (goback_interact) {
	if (no_update) printf("Warning: changes will not be saved\n");
	init_term(1);
	raw();
    }

    Loop_Groups_Sequence(gh) {
	if ((gh->master_flag & M_VALID) == 0) continue;
	if (!goback_alsounsub && (gh->group_flag & G_UNSUBSCRIBED)) continue;

	add_unread(gh, 1);

	count = restore_rc(gh, gh->last_a_article);
	if (count > 0) {
	    if (goback_interact) {
		printf("%s + %ld ?  (y) ", gh->group_name, (long)count); fl;
		y = yes(0);
		putchar(CR); putchar(NL);
		switch (y) {
		 case 1:
		    break;
		 case 0:
		    gh->newsrc_line = gh->newsrc_orig;
		    gh->select_line = gh->select_orig;
		    continue;
		 case -1:
		    if (total > 0) {
			printf("\nSave changes sofar? (n) "); fl;
			if (yes(1) <= 0) nn_exit(0);
		    }
		    goto out;
		}
	    } else
		if (verbose)
		    printf("%5ld\t%s\n", (long)count, gh->group_name);

	    total += count;
	    groups++;
	}
    }

 out:

    if (goback_interact)
	unset_raw();

    if (total == 0) {
	printf("No articles marked\n");
	return;
    }

    flush_newsrc();

    if (verbose) putchar(NL);
    if (!silent)
	printf("%ld article%s marked unread in %d group%s\n",
	       (long)total, plural((long)total),
	       groups, plural((long)groups));

    if (no_update) printf("No files were updated\n");
}

/* fake this for read_active_file */

group_header *add_new_group(name)
char *name;
{
    return NULL;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.