1 /*
2  *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
3  *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
4  *
5  *	.newsrc parsing and update.
6  */
7 
8 #include <stdlib.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include "config.h"
12 #include "global.h"
13 #include "articles.h"
14 #include "active.h"
15 #include "db.h"
16 #include "newsrc.h"
17 #include "nn.h"
18 #include "options.h"
19 #include "regexp.h"
20 #include "nn_term.h"
21 
22 /* newsrc.c */
23 
24 struct rc_info;
25 
26 static int      dump_file(char *path, int mode);
27 static void     dump_newsrc(void);
28 static void     dump_select(void);
29 static time_t   get_last_new(void);
30 static void     update_last_new(group_header * lastg);
31 static article_number get_last_article(group_header * gh);
32 static void     init_rctest(group_header * gh, register struct rc_info * r);
33 static attr_type rctest(register article_header * ah, register struct rc_info * r);
34 static void     append_range(int pp, char delim, article_number rmin, article_number rmax);
35 static void     begin_rc_update(register group_header * gh);
36 static void     end_rc_update(register group_header * gh);
37 static void     mark_article(register article_header * ah, attr_type how);
38 
39 
40 #ifdef sel
41 /* Remove Gould SELbus software name collision */
42 #undef sel
43 #endif
44 
45 #define TR(arg) tprintf arg
46 
47 extern char    *news_lib_directory, *db_directory;
48 extern char    *home_directory;
49 extern char    *pname;
50 
51 extern int      verbose;
52 extern int      silent;
53 
54 extern long     n_selected;
55 extern char    *tmp_directory;
56 
57 int             keep_rc_backup = 1;
58 char           *bak_suffix = ".bak";
59 char           *initial_newsrc_path = ".defaultnewsrc";
60 
61 int             no_update = 0;
62 int             use_selections = 1;
63 int             quick_unread_count = 1;	/* make a quick count of unread art. */
64 int             newsrc_update_freq = 1;	/* how often to write .newsrc */
65 char           *newsrc_file = NULL;
66 
67 #define RCX_NEVER	0	/* ignore missing groups */
68 #define RCX_HEAD	1	/* prepend missing groups to .newsrc when
69 				 * read */
70 #define	RCX_TAIL	2	/* append missing groups to .newsrc when read */
71 #define RCX_TIME	3	/* append NEW groups as they arrive */
72 #define RCX_TIME_CONF	4	/* append NEW groups with confirmation */
73 #define RCX_RNLAST	5	/* .rnlast compatible functionality */
74 
75 int             new_group_action = RCX_TIME;	/* append new groups to
76 						 * .newsrc */
77 int             keep_unsubscribed = 1;	/* keep unsubscribed groups in
78 					 * .newsrc */
79 int             keep_unsub_long = 0;	/* keep unread in unsubscribed groups */
80 
81 int             tidy_newsrc = 0;/* remove obsolete groups from .newsrc */
82 
83 int             auto_junk_seen = 1;	/* junk seen articles ... */
84 int             conf_junk_seen = 0;	/* ... if confirmed by user ... */
85 int             retain_seen_status = 0;	/* ... or remember seen articles. */
86 
87 long            unread_articles;/* estimate of unread articles */
88 int             unread_groups;
89 
90 group_header   *rc_sequence = NULL;
91 
92 static char    *sel_path = NULL;
93 
94 
95 /* delimitors on newsrc lines */
96 
97 #define RC_SUBSCR	':'	/* subscription to group */
98 #define RC_UNSUBSCR	'!'	/* no subscription to group */
99 
100 #define RC_DELIM	','	/* separator on rc lines */
101 #define RC_RANGE	'-'	/* range */
102 
103 /* delimitors on select lines */
104 
105 #define SEL_RANGE	'-'	/* range */
106 #define SEL_SELECT	','	/* following articles are selected */
107 #define SEL_LEAVE	'+'	/* following articles are left over */
108 #define SEL_SEEN	';'	/* following articles are seen */
109 #define SEL_UNREAD	'~'	/* in digests */
110 #define SEL_DIGEST	'('	/* start digest list */
111 #define SEL_END_DIGEST	')'	/* end digest list */
112 #define SEL_NEW		'&'	/* new group (group.name&nnn) */
113 
114 #define END_OF_LIST	0x7fffffffL	/* Greater than any article number */
115 
116 /* line buffers */
117 
118 #define RC_LINE_MAX	32768
119 
120 static char     rcbuf[RC_LINE_MAX];
121 static char     selbuf[RC_LINE_MAX];
122 
123 static group_header *rc_seq_tail = NULL;
124 
125 static int      newsrc_update_count = 0, select_update_count = 0;
126 
127 #define DM_NEWSRC	0
128 #define DM_SELECT	1
129 #define DM_ORIG_NEWSRC	2
130 #define DM_ORIG_SELECT	3
131 
132 static int
dump_file(char * path,int mode)133 dump_file(char *path, int mode)
134 {
135     FILE           *f = NULL;
136     register group_header *gh;
137     char           *line = NULL;
138 
139     Loop_Groups_Newsrc(gh) {
140 	switch (mode) {
141 	    case DM_NEWSRC:
142 		if (tidy_newsrc) {
143 		    if ((gh->master_flag & M_VALID) == 0)
144 			continue;
145 		    if (!keep_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED))
146 			continue;
147 		}
148 		line = gh->newsrc_line;
149 		break;
150 	    case DM_SELECT:
151 		if (tidy_newsrc && (gh->master_flag & M_VALID) == 0)
152 		    continue;
153 		if (gh->group_flag & G_UNSUBSCRIBED)
154 		    continue;
155 		line = gh->select_line;
156 		break;
157 	    case DM_ORIG_NEWSRC:
158 		line = gh->newsrc_orig;
159 		break;
160 	    case DM_ORIG_SELECT:
161 		line = gh->select_orig;
162 		break;
163 	}
164 	if (line == NULL)
165 	    continue;
166 	if (f == NULL)
167 	    f = open_file(path, OPEN_CREATE | MUST_EXIST);
168 	fputs(line, f);
169     }
170     if (f != NULL)
171 	if (fclose(f) == EOF)
172 	    return -1;
173     return 0;
174 }
175 
176 
177 static void
dump_newsrc(void)178 dump_newsrc(void)
179 {
180     char            bak[FILENAME];
181     static int      first = 1;
182 
183     if (no_update)
184 	return;
185     if (++newsrc_update_count < newsrc_update_freq)
186 	return;
187 
188     if (first && keep_rc_backup) {
189 	sprintf(bak, "%s%s", newsrc_file, bak_suffix);
190 	if (dump_file(bak, DM_ORIG_NEWSRC))
191 	    nn_exitmsg(1, "Cannot backup %s", newsrc_file);
192 	first = 0;
193     }
194     if (dump_file(newsrc_file, DM_NEWSRC)) {
195 	char            temp[FILENAME];
196 	sprintf(temp, "%s/newsrc-%d", tmp_directory, process_id);
197 	if (dump_file(temp, DM_NEWSRC))
198 	    nn_exitmsg(1, "Cannot update %s -- restore %s file!!!",
199 		       newsrc_file, bak_suffix);
200 	else
201 	    nn_exitmsg(1, "Cannot update %s -- saved in %s", newsrc_file, temp);
202     }
203     newsrc_update_count = 0;
204 }
205 
206 static void
dump_select(void)207 dump_select(void)
208 {
209     char            bak[FILENAME];
210     static int      first = 1;
211 
212     if (no_update)
213 	return;
214     if (++select_update_count < newsrc_update_freq)
215 	return;
216 
217     if (first && keep_rc_backup) {
218 	sprintf(bak, "%s%s", sel_path, bak_suffix);
219 	dump_file(bak, DM_ORIG_SELECT);
220 	first = 0;
221     }
222     dump_file(sel_path, DM_SELECT);
223 
224     select_update_count = 0;
225 }
226 
227 #define RN_LAST_GROUP_READ	0
228 #define RN_LAST_TIME_RUN	1
229 #define RN_LAST_ACTIVE_SIZE	2
230 #define RN_LAST_CREATION_TIME	3
231 #define RN_LAST_NEW_GROUP	4
232 #define RN_ACTIVE_TIMES_OFFSET	5
233 
234 #define MAX_RNLAST_LINE 6
235 
236 static char    *rnlast_line[MAX_RNLAST_LINE];
237 #define rnlast_path relative(home_directory, ".rnlast")
238 
239 static          time_t
get_last_new(void)240 get_last_new(void)
241 {
242     FILE           *lf;
243     char            buf[FILENAME];
244     register int    i;
245     time_t          t;
246 
247     if (new_group_action == RCX_RNLAST) {
248 	lf = open_file(rnlast_path, OPEN_READ);
249 	if (lf == NULL)
250 	    goto no_file;
251 
252 	for (i = 0; i < MAX_RNLAST_LINE; i++) {
253 	    if (fgets(buf, FILENAME, lf) == NULL)
254 		break;
255 	    rnlast_line[i] = copy_str(buf);
256 	}
257 	if (i != MAX_RNLAST_LINE) {
258 	    while (i <= MAX_RNLAST_LINE) {
259 		rnlast_line[i] = copy_str("");
260 		i++;
261 	    }
262 	    fclose(lf);
263 	    return (time_t) atol(rnlast_line[RN_LAST_TIME_RUN]);
264 	}
265 	fclose(lf);
266 	return (time_t) atol(rnlast_line[RN_LAST_CREATION_TIME]);
267     }
268     lf = open_file(relative(nn_directory, "LAST"), OPEN_READ);
269     if (lf == NULL)
270 	goto no_file;
271     if (fgets(buf, FILENAME, lf) == NULL)
272 	goto no_file;
273 
274     fclose(lf);
275     return (time_t) atol(buf);
276 
277 no_file:
278     if (lf != NULL)
279 	fclose(lf);
280     t = file_exist(newsrc_file, (char *) NULL);
281     return t > 0 ? t : (time_t) (-1);
282 }
283 
284 static void
update_last_new(group_header * lastg)285 update_last_new(group_header * lastg)
286 {
287     FILE           *lf;
288     register int    i;
289     struct stat     st;
290 
291     if (no_update)
292 	return;
293 
294     if (new_group_action == RCX_RNLAST) {
295 	if (rnlast_line[0] == NULL)
296 	    for (i = 0; i < MAX_RNLAST_LINE; i++)
297 		rnlast_line[i] = copy_str("");
298 	lf = open_file(rnlast_path, OPEN_CREATE | MUST_EXIST);
299 	fputs(rnlast_line[RN_LAST_GROUP_READ], lf);	/* as good as any */
300 	fprintf(lf, "%ld\n", (long) cur_time());	/* RN_LAST_TIME_RUN */
301 	fprintf(lf, "%ld\n", (long) master.last_size);	/* RN_LAST_ACTIVE_SIZE */
302 
303 	fprintf(lf, "%ld\n", (long) lastg->creation_time);	/* RN_LAST_CREATION_TIME */
304 	fprintf(lf, "%s\n", lastg->group_name);	/* RN_LAST_NEW_GROUP */
305 
306 	if (stat(relative(news_lib_directory, "active.times"), &st) == 0)
307 	    fprintf(lf, "%ld\n", (long) st.st_size);
308 	else			/* can't be perfect -- don't update */
309 	    fputs(rnlast_line[RN_ACTIVE_TIMES_OFFSET], lf);
310 	for (i = 0; i < MAX_RNLAST_LINE; i++)
311 	    freeobj(rnlast_line[i]);
312     } else {
313 	lf = open_file(relative(nn_directory, "LAST"), OPEN_CREATE | MUST_EXIST);
314 	fprintf(lf, "%ld\n%s\n", (long) lastg->creation_time, lastg->group_name);
315     }
316 
317     fclose(lf);
318 }
319 
320 static          article_number
get_last_article(group_header * gh)321 get_last_article(group_header * gh)
322 {
323     register char  *line;
324 
325     if ((line = gh->newsrc_line) == NULL)
326 	return -1;
327 
328     line += gh->group_name_length + 1;
329     while (*line && isspace(*line))
330 	line++;
331     if (*line == NUL)
332 	return -1;
333 
334     if (line[0] == '1') {
335 	if (line[1] == RC_RANGE)
336 	    return atol(line + 2);
337 	if (!isdigit(line[1]))
338 	    return 1;
339     }
340     return 0;
341 }
342 
343 
344 void
visit_rc_file(void)345 visit_rc_file(void)
346 {
347     FILE           *rc, *sel;
348     register group_header *gh;
349     int             subscr;
350     register char  *bp;
351     register int    c;
352     char            bak[FILENAME];
353     time_t          last_new_group = 0, rc_age, newsrc_age;
354     group_header   *last_new_gh = NULL;
355 
356     if (newsrc_file == NULL)
357 	newsrc_file = home_relative(".newsrc");
358 
359     sel_path = mk_file_name(nn_directory, "select");
360 
361     Loop_Groups_Header(gh) {
362 	gh->newsrc_line = NULL;
363 	gh->newsrc_orig = NULL;
364 	gh->select_line = NULL;
365 	gh->select_orig = NULL;
366     }
367 
368     if ((rc_age = file_exist(relative(nn_directory, "rc"), (char *) NULL))) {
369 	if (who_am_i != I_AM_NN)
370 	    nn_exitmsg(1, "A release 6.3 rc file exists. Run nn to upgrade");
371 
372 	sprintf(bak, "%s/upgrade_rc", lib_directory);
373 
374 	if ((newsrc_age = file_exist(newsrc_file, (char *) NULL)) == 0) {
375 	    display_file("adm.upgrade1", CLEAR_DISPLAY);
376 	} else {
377 	    if (rc_age + 60 > newsrc_age) {
378 		/* rc file is newest (or .newsrc does not exist) */
379 		display_file("adm.upgrade2", CLEAR_DISPLAY);
380 		prompt("Convert rc file to .newsrc now? ");
381 		if (yes(1) <= 0)
382 		    nn_exit(0);
383 	    } else {
384 		/* .newsrc file is newest */
385 		display_file("adm.upgrade3", CLEAR_DISPLAY);
386 		prompt("Use current .newsrc file? ");
387 		if (yes(1) > 0) {
388 		    strcat(bak, " n");
389 		} else {
390 		    display_file("adm.upgrade4", CLEAR_DISPLAY);
391 		    prompt("Convert rc file to .newsrc? ");
392 		    if (yes(1) <= 0)
393 			nn_exitmsg(0, "Then you will have to upgrade manually");
394 		}
395 	    }
396 	}
397 
398 	tprintf("\r\n\n");
399 	system(bak);
400 	any_key(prompt_line);
401     }
402     rc = open_file(newsrc_file, OPEN_READ);
403     if (rc != NULL) {
404 	fseek(rc, 0, 2);
405 	if (ftell(rc))
406 	    rewind(rc);
407 	else {
408 	    fclose(rc);
409 	    rc = NULL;
410 	}
411     }
412     if (rc == NULL) {
413 	sprintf(bak, "%s%s", newsrc_file, bak_suffix ? bak_suffix : ".bak");
414 	if ((rc = open_file(bak, OPEN_READ))) {
415 	    int             ans;
416 	    time_t          rc_mtime;
417 
418 	    tprintf("\nThere is no .newsrc file - restore backup? (y) ");
419 	    if ((ans = yes(0)) < 0)
420 		nn_exit(1);
421 	    if (!ans) {
422 		tprintf("\nConfirm building .newsrc from scratch: (y/n) ");
423 		if ((ans = yes(1)) <= 0)
424 		    nn_exit(1);
425 		fclose(rc);
426 		rc = NULL;
427 	    } else {
428 		switch (new_group_action) {
429 		    case RCX_TIME:
430 		    case RCX_TIME_CONF:
431 		    case RCX_RNLAST:
432 			last_new_group = get_last_new();
433 			rc_mtime = (rc ? m_time(rc) : 0);
434 			if (last_new_group < 0 || rc_mtime < last_new_group)
435 			    last_new_group = rc_mtime;
436 		}
437 	    }
438 	}
439     }
440     if (rc == NULL) {
441 
442 	last_new_group = -1;	/* ignore LAST & .rnlast if no .newsrc */
443 	rc = open_file_search_path(initial_newsrc_path, OPEN_READ);
444 	if (rc == NULL)
445 	    goto new_user;
446 	/* ignore groups not in the initial newsrc file */
447 	last_new_gh = ACTIVE_GROUP(master.number_of_groups - 1);
448 	last_new_group = last_new_gh->creation_time;
449     }
450     while (fgets(rcbuf, RC_LINE_MAX, rc) != NULL) {
451 	gh = NULL;
452 	subscr = 0;
453 	for (bp = rcbuf; ((c = *bp)); bp++) {
454 	    if (isspace(c))
455 		break;		/* not a valid line */
456 
457 	    if (c == RC_UNSUBSCR || c == RC_SUBSCR) {
458 		subscr = (c == RC_SUBSCR);
459 		*bp = NUL;
460 		gh = lookup_no_alias(rcbuf);
461 		*bp = c;
462 		break;
463 	    }
464 	}
465 
466 	if (gh == NULL) {
467 	    gh = newobj(group_header, 1);
468 	    gh->group_flag |= G_FAKED;
469 	    gh->master_flag |= M_VALID;
470 	}
471 	if (gh->master_flag & M_ALIASED)
472 	    continue;
473 
474 	if (rc_seq_tail == NULL)
475 	    rc_sequence = rc_seq_tail = gh;
476 	else {
477 	    rc_seq_tail->newsrc_seq = gh;
478 	    rc_seq_tail = gh;
479 	}
480 
481 	gh->newsrc_orig = gh->newsrc_line = copy_str(rcbuf);
482 	if (gh->group_flag & G_FAKED)
483 	    gh->group_name = gh->newsrc_line;
484 	else if (!subscr)
485 	    gh->group_flag |= G_UNSUBSCRIBED;
486     }
487     fclose(rc);
488 
489 new_user:
490     Loop_Groups_Header(gh) {
491 	if (gh->master_flag & M_IGNORE_GROUP)
492 	    continue;
493 	if (gh->group_flag & G_UNSUBSCRIBED)
494 	    continue;
495 	if (gh->newsrc_line == NULL) {
496 	    char            buf[FILENAME];
497 
498 	    /* NEW GROUP - ADD TO NEWSRC AS APPROPRIATE */
499 
500 	    switch (new_group_action) {
501 		case RCX_NEVER:
502 		    /* no not add new groups */
503 		    gh->group_flag |= G_DONE | G_UNSUBSCRIBED;
504 		    continue;
505 
506 		case RCX_HEAD:
507 		    /* insert at top */
508 		    gh->newsrc_seq = rc_sequence;
509 		    rc_sequence = gh;
510 		    break;
511 
512 
513 		case RCX_TIME:
514 		case RCX_TIME_CONF:
515 		case RCX_RNLAST:
516 		    if (last_new_group == 0) {
517 			last_new_group = get_last_new();
518 			if (last_new_group < 0 && new_group_action != RCX_RNLAST) {
519 			    /* maybe this is a first time rn convert ? */
520 			    int             nga = new_group_action;
521 			    new_group_action = RCX_RNLAST;
522 			    last_new_group = get_last_new();
523 			    new_group_action = nga;
524 			}
525 		    }
526 		    if (gh->creation_time <= last_new_group) {
527 			/* old groups not in .newsrc are unsubscribed */
528 			gh->group_flag |= G_UNSUBSCRIBED;
529 			continue;
530 		    }
531 		    if (last_new_gh == NULL || last_new_gh->creation_time <= gh->creation_time)
532 			last_new_gh = gh;
533 
534 		    if (new_group_action != RCX_TIME && !no_update) {
535 			tprintf("\nNew group: %s -- append to .newsrc? (y)",
536 				gh->group_name);
537 			if (yes(0) <= 0) {
538 			    gh->group_flag |= G_DONE | G_UNSUBSCRIBED;
539 			    continue;
540 			}
541 		    }
542 		    sprintf(buf, "%s:\n", gh->group_name);
543 		    /* to avoid fooling the LAST mechanism, we must fake */
544 		    /* that the group was also in the original .newsrc */
545 
546 		    gh->newsrc_orig = gh->newsrc_line = copy_str(buf);
547 		    newsrc_update_count++;
548 
549 		    /* FALLTHRU */
550 		case RCX_TAIL:
551 		    /* insert at bottom */
552 		    if (rc_seq_tail == NULL)
553 			rc_sequence = rc_seq_tail = gh;
554 		    else {
555 			rc_seq_tail->newsrc_seq = gh;
556 			rc_seq_tail = gh;
557 		    }
558 		    break;
559 	    }
560 
561 	    gh->last_article = -1;
562 	} else
563 	    gh->last_article = get_last_article(gh);
564 
565 	if (gh->last_article < 0) {
566 	    gh->group_flag |= G_NEW;
567 	    gh->last_article = gh->first_db_article - 1;
568 	} else if (gh->first_db_article > gh->last_article)
569 	    gh->last_article = gh->first_db_article - 1;
570 
571 	if (gh->last_article < 0)
572 	    gh->last_article = 0;
573 	gh->first_article = gh->last_article;
574     }
575 
576     if (rc_seq_tail)
577 	rc_seq_tail->newsrc_seq = NULL;
578 
579     if (last_new_gh != NULL)
580 	update_last_new(last_new_gh);
581 
582     if (!use_selections)
583 	return;
584 
585     sel = open_file(sel_path, OPEN_READ);
586     if (sel == NULL)
587 	return;
588 
589     while (fgets(selbuf, RC_LINE_MAX, sel) != NULL) {
590 	for (bp = selbuf; ((c = *bp)); bp++)
591 	    if (c == SP || c == SEL_NEW)
592 		break;
593 
594 	if (c == NUL)
595 	    continue;
596 	*bp = NUL;
597 	gh = lookup_no_alias(selbuf);
598 	if (gh == NULL || gh->master_flag & M_ALIASED)
599 	    continue;
600 	*bp = c;
601 	if (c == SEL_NEW)
602 	    gh->group_flag |= G_NEW;
603 	gh->select_orig = gh->select_line = copy_str(selbuf);
604     }
605     fclose(sel);
606 }
607 
608 /*
609  * prepare to use newsrc & select information for a specific group
610  */
611 
612 static struct rc_info {
613     char           *rc_p;	/* pointer into newsrc_line */
614     article_number  rc_min;	/* current newsrc range min */
615     article_number  rc_max;	/* current newsrc range max */
616     char            rc_delim;	/* delimiter character */
617 
618     char           *sel_p;	/* pointer into select_line */
619     char           *sel_initp;	/* rc_p after initialization */
620     article_number  sel_min;	/* current select range min */
621     article_number  sel_max;	/* current select range max */
622     article_number  sel_digest;	/* current digest */
623     attr_type       sel_type;	/* current select range type */
624     char            sel_delim;	/* delimiter character */
625 }               orig, cur;
626 
627 static int      rctest_mode;
628 
629 static void
init_rctest(group_header * gh,register struct rc_info * r)630 init_rctest(group_header * gh, register struct rc_info * r)
631 {
632     if (r->rc_p == NULL) {
633 	r->rc_min = r->rc_max = END_OF_LIST;
634     } else {
635 	r->rc_min = r->rc_max = -1;
636 	r->rc_delim = SP;
637 	r->rc_p += gh->group_name_length + 1;
638     }
639 
640     r->sel_digest = 0;
641     if (r->sel_p == NULL) {
642 	r->sel_min = r->sel_max = END_OF_LIST;
643     } else {
644 	r->sel_p += gh->group_name_length + 1;
645 	r->sel_min = r->sel_max = -1;
646 	r->sel_delim = SP;
647     }
648 }
649 
650 void
use_newsrc(register group_header * gh,int mode)651 use_newsrc(register group_header * gh, int mode)
652 {
653     orig.rc_p = gh->newsrc_orig;
654     orig.sel_p = gh->select_orig;
655     cur.rc_p = gh->newsrc_line;
656     cur.sel_p = gh->select_line;
657 
658     rctest_mode = mode;
659 
660     switch (mode) {
661 	case 0:
662 	    init_rctest(gh, &cur);
663 	    break;
664 	case 1:
665 	    init_rctest(gh, &orig);
666 	    break;
667 	case 2:
668 	    init_rctest(gh, &cur);
669 	    init_rctest(gh, &orig);
670 	    break;
671     }
672 }
673 
674 /*
675 #define TRC(wh)  TR( ("r%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, rc_min, rc_max, rc_delim) )
676 #define TSEL(wh) TR( ("s%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, sel_min, sel_max, sel_delim) )
677 */
678 #define TRC(wh)
679 #define TSEL(wh)
680 
681 static          attr_type
rctest(register article_header * ah,register struct rc_info * r)682 rctest(register article_header * ah, register struct rc_info * r)
683 {
684     register char  *p;
685     register int    c;
686     register article_number   n = ah->a_number, x;
687 
688     while (n > r->rc_max) {
689 	/* get next interval from newsrc line */
690 	r->rc_min = -1;
691 	x = 0;
692 	p = r->rc_p;
693 	TRC(1);
694 
695 	if (*p == RC_DELIM)
696 	    p++;
697 	if (*p == NUL || *p == NL)
698 	    r->rc_min = r->rc_max = END_OF_LIST;
699 	else {
700 	    for (; ((c = *p)) && c != RC_DELIM && c != NL; p++) {
701 		if (c == RC_RANGE) {
702 		    if (r->rc_min < 0)
703 			r->rc_min = x;
704 		    else
705 			msg("syntax error in rc file");
706 		    x = 0;
707 		    continue;
708 		}
709 		if (isascii(*p) && isdigit(*p))
710 		    x = x * 10 + c - '0';
711 	    }
712 	    r->rc_max = x;
713 	    if (r->rc_min < 0)
714 		r->rc_min = x;
715 	    r->rc_p = p;
716 	}
717     }
718     TRC(2);
719 
720     if (n >= r->rc_min && n <= r->rc_max)
721 	return A_READ;
722 
723     p = r->sel_p;
724     if (r->sel_digest != 0) {
725 	if (n == r->sel_digest && (ah->flag & A_DIGEST)) {
726 	    if (*(r->sel_p) == SEL_END_DIGEST)
727 		return A_READ;
728 	    n = ah->fpos;
729 	} else {
730 	    if (n < r->sel_digest)
731 		return 0;
732 	    while (*p && *p++ != SEL_END_DIGEST);
733 	    r->sel_digest = 0;
734 	    r->sel_min = r->sel_max = -1;
735 	}
736     }
737     while (n > r->sel_max) {
738 	r->sel_min = -1;
739 	r->sel_type = A_SELECT;
740 	x = 0;
741 	TSEL(3);
742 
743 	for (;;) {
744 	    switch (*p) {
745 		case SEL_SELECT:
746 		    r->sel_type = A_SELECT;
747 		    p++;
748 		    continue;
749 		case SEL_LEAVE:
750 		    r->sel_type = A_LEAVE;
751 		    p++;
752 		    continue;
753 		case SEL_SEEN:
754 		    r->sel_type = A_SEEN;
755 		    p++;
756 		    continue;
757 		case SEL_UNREAD:
758 		    r->sel_type = 0;
759 		    p++;
760 		    continue;
761 		case SEL_DIGEST:
762 		    while (*p && *p++ != SEL_END_DIGEST);
763 		    continue;
764 		case SEL_END_DIGEST:
765 		    if (r->sel_digest) {
766 			if (r->sel_digest == ah->a_number) {
767 			    r->sel_p = p;
768 			    return A_READ;
769 			}
770 			r->sel_digest = 0;
771 		    }
772 		    p++;
773 		    r->sel_type = A_SELECT;
774 		    continue;
775 		default:
776 		    break;
777 	    }
778 	    break;
779 	}
780 
781 	if (*p == NUL || *p == NL) {
782 	    r->sel_min = r->sel_max = END_OF_LIST;
783 	    break;
784 	}
785 	for (; ((c = *p)); p++) {
786 	    switch (c) {
787 		case '0':
788 		case '1':
789 		case '2':
790 		case '3':
791 		case '4':
792 		case '5':
793 		case '6':
794 		case '7':
795 		case '8':
796 		case '9':
797 		    x = x * 10 + c - '0';
798 		    continue;
799 
800 		case SEL_SELECT:
801 		case SEL_LEAVE:
802 		case SEL_SEEN:
803 		case SEL_UNREAD:
804 		    break;
805 
806 		case SEL_RANGE:
807 		    if (r->sel_min < 0)
808 			r->sel_min = x;
809 		    else
810 			msg("syntax error in sel file");
811 		    x = 0;
812 		    continue;
813 
814 		case SEL_DIGEST:
815 		    n = ah->a_number;
816 		    if (n > x) {
817 			while (*p && (*p++ != SEL_END_DIGEST));
818 			x = -1;
819 			break;
820 		    }
821 		    p++;
822 		    r->sel_digest = x;
823 		    if (n < r->sel_digest) {
824 			r->sel_p = p;
825 			return 0;
826 		    }
827 		    n = ah->fpos;
828 		    x = -1;
829 		    break;
830 
831 		case NL:
832 		    if (r->sel_digest == 0)
833 			break;
834 		    /* FALLTHRU */
835 		case SEL_END_DIGEST:
836 		    if (r->sel_digest == ah->a_number) {
837 			r->sel_p = p;
838 			return (ah->fpos == x) ? r->sel_type : A_READ;
839 		    }
840 		    r->sel_digest = 0;
841 		    x = -1;
842 		    break;
843 	    }
844 	    break;
845 	}
846 	r->sel_max = x;
847 	if (r->sel_min < 0)
848 	    r->sel_min = x;
849 	r->sel_p = p;
850     }
851 
852     if (n >= r->sel_min && n <= r->sel_max)
853 	return r->sel_type;
854 
855     if (r->sel_digest)
856 	return A_READ;		/* only read articles are not listed */
857 
858     return 0;			/* unread, unseen, unselected */
859 }
860 
861 attr_type
test_article(article_header * ah)862 test_article(article_header * ah)
863 {
864     attr_type       a;
865 
866     switch (rctest_mode) {
867 	case 0:
868 	    return rctest(ah, &cur);
869 	case 1:
870 	    return rctest(ah, &orig);
871 	case 2:
872 	    a = rctest(ah, &cur);
873 	    if (a != A_READ)
874 		return a;
875 	    if (rctest(ah, &orig) == A_READ)
876 		return A_KILL;
877 	    return a;
878     }
879     return 0;
880 }
881 
882 /*
883  * We only mark the articles that should remain unread
884  */
885 static char    *rc_p;		/* pointer into newsrc_line */
886 static article_number rc_min;	/* current newsrc range min */
887 static char     rc_delim;	/* delimiter character */
888 
889 static char    *sel_p;		/* pointer into select_line */
890 static char    *sel_initp;	/* rc_p after initialization */
891 static article_number sel_min;	/* current select range min */
892 static article_number sel_max;	/* current select range max */
893 static article_number sel_digest;	/* current digest */
894 static char     sel_delim;	/* delimiter character */
895 
896 static void
append(int x,char * fmt,...)897 append(int x, char *fmt,...)
898 {
899     va_list         ap;
900     register char  *p;
901     char          **pp;
902 
903     va_start(ap, fmt);
904     pp = x ? &sel_p : &rc_p;
905     p = *pp;
906     if (p > (x ? &selbuf[RC_LINE_MAX - 16] : &rcbuf[RC_LINE_MAX - 16])) {
907 	msg("%s line too long", x ? "select" : ".newsrc");
908 	va_end(ap);
909 	return;
910     }
911     vsprintf(p, fmt, ap);
912     va_end(ap);
913 
914     while (*p)
915 	p++;
916     *p = NL;
917     p[1] = NUL;
918     *pp = p;
919 }
920 
921 static void
append_range(int pp,char delim,article_number rmin,article_number rmax)922 append_range(int pp, char delim, article_number rmin, article_number rmax)
923 {
924     if (rmin == rmax)
925 	append(pp, "%c%ld", delim, (long) rmin);
926     else
927 	append(pp, "%c%ld%c%ld", delim, (long) rmin, RC_RANGE, (long) rmax);
928 }
929 
930 static int32    mark_counter;
931 
932 static void
begin_rc_update(register group_header * gh)933 begin_rc_update(register group_header * gh)
934 {
935     add_unread(gh, -1);
936     mark_counter = 0;
937 
938     rc_p = rcbuf;
939     rc_min = 1;
940     append(0, "%s%c", gh->group_name,
941 	   gh->group_flag & G_UNSUBSCRIBED ? RC_UNSUBSCR : RC_SUBSCR);
942     rc_delim = SP;
943     sel_p = selbuf;
944     sel_min = 0;
945     sel_max = 0;
946     sel_digest = 0;
947     sel_delim = SP;
948     append(1, "%s%c", gh->group_name,
949 	   (gh->group_flag & G_NEW) ? SEL_NEW : SP);
950     /* sel_initp == sep_p => empty list */
951     sel_initp = (gh->group_flag & G_NEW) ? NULL : sel_p;
952 }
953 
954 static void
end_rc_update(register group_header * gh)955 end_rc_update(register group_header * gh)
956 {
957     if (rc_min <= gh->last_db_article)
958 	append_range(0, rc_delim, rc_min, gh->last_db_article);
959 
960     if (gh->newsrc_line != NULL && strcmp(rcbuf, gh->newsrc_line)) {
961 	if (gh->newsrc_orig != gh->newsrc_line)
962 	    freeobj(gh->newsrc_line);
963 	gh->newsrc_line = NULL;
964     }
965     if (gh->newsrc_line == NULL) {
966 	gh->newsrc_line = copy_str(rcbuf);
967 	dump_newsrc();
968     }
969     if (sel_digest)
970 	append(1, "%c", SEL_END_DIGEST);
971     else if (sel_min)
972 	append_range(1, sel_delim, sel_min, sel_max);
973 
974     if (gh->select_line) {
975 	if (strcmp(selbuf, gh->select_line) == 0)
976 	    goto out;
977     } else if (sel_p == sel_initp)
978 	goto out;
979 
980     if (gh->select_line && gh->select_orig != gh->select_line)
981 	freeobj(gh->select_line);
982 
983     gh->select_line = (sel_p == sel_initp) ? NULL : copy_str(selbuf);
984     dump_select();
985 
986 out:
987     if ((gh->last_article = get_last_article(gh)) < 0)
988 	gh->last_article = 0;
989     if (gh->last_article < gh->first_article)
990 	gh->first_article = gh->last_article;
991 
992     gh->group_flag |= G_READ;	/* should not call update_group again */
993     if (mark_counter > 0) {
994 	gh->unread_count = mark_counter;
995 	add_unread(gh, 0);
996     }
997 }
998 
999 static void
mark_article(register article_header * ah,attr_type how)1000 mark_article(register article_header * ah, attr_type how)
1001 {
1002     register article_number anum;
1003     char            delim = 0;
1004 
1005     switch (how) {
1006 	case A_SELECT:
1007 	    delim = SEL_SELECT;
1008 	    break;
1009 	case A_SEEN:
1010 	    delim = SEL_SEEN;
1011 	    break;
1012 	case A_LEAVE:
1013 	case A_LEAVE_NEXT:
1014 	    delim = SEL_LEAVE;
1015 	    break;
1016 	case 0:
1017 	    delim = SEL_UNREAD;
1018 	    break;
1019     }
1020 
1021     mark_counter++;
1022     anum = ah->a_number;
1023 
1024     if (rc_min < anum) {
1025 	append_range(0, rc_delim, rc_min, anum - 1);
1026 	rc_delim = RC_DELIM;
1027 
1028 	if ((ah->flag & A_DIGEST) == 0
1029 	    && sel_min && delim == sel_delim && sel_max == (rc_min - 1))
1030 	    sel_max = anum - 1;	/* expand select range over read articles */
1031     }
1032     rc_min = anum + 1;
1033 
1034     if (ah->flag & A_DIGEST) {
1035 	if (sel_digest != anum) {
1036 	    if (sel_digest) {
1037 		append(1, "%c", SEL_END_DIGEST);
1038 	    } else if (sel_min) {
1039 		append_range(1, sel_delim, sel_min, sel_max);
1040 		sel_min = 0;
1041 	    }
1042 	    append(1, "%c%ld%c", SEL_SELECT, (long) anum, SEL_DIGEST);
1043 	    sel_digest = anum;
1044 	}
1045 	append(1, "%c%ld", delim, ah->fpos);
1046 	return;
1047     }
1048     if (sel_digest) {
1049 	append(1, "%c", SEL_END_DIGEST);
1050 	sel_digest = 0;
1051     }
1052     if (sel_min) {
1053 	if (delim != sel_delim || delim == SEL_UNREAD) {
1054 	    append_range(1, sel_delim, sel_min, sel_max);
1055 	    sel_delim = delim;
1056 	    if (delim == SEL_UNREAD)
1057 		sel_min = 0;
1058 	    else
1059 		sel_min = anum;
1060 	} else
1061 	    sel_max = anum;
1062     } else if (delim != SEL_UNREAD) {
1063 	sel_min = sel_max = anum;
1064 	sel_delim = delim;
1065     }
1066 }
1067 
1068 void
flush_newsrc(void)1069 flush_newsrc(void)
1070 {
1071     newsrc_update_freq = 0;
1072     if (select_update_count)
1073 	dump_select();
1074     if (newsrc_update_count)
1075 	dump_newsrc();
1076 }
1077 
1078 int
restore_bak(void)1079 restore_bak(void)
1080 {
1081     if (no_update)
1082 	return 1;
1083 
1084     prompt("Are you sure? ");
1085     if (!yes(1))
1086 	return 0;
1087 
1088     if (dump_file(newsrc_file, DM_ORIG_NEWSRC))
1089 	nn_exitmsg(1, "Could not restore %s -- restore %s file manually",
1090 		   newsrc_file, bak_suffix);
1091 
1092     prompt("Restore selections? ");
1093     if (yes(1))
1094 	dump_file(sel_path, DM_ORIG_SELECT);
1095 
1096     no_update = 1;		/* so current group is not updated */
1097     return 1;
1098 }
1099 
1100 /*
1101  *	Update .newsrc for one group.
1102  *	sort_articles(-2) MUST HAVE BEEN CALLED BEFORE USE.
1103  */
1104 
1105 int             rc_merged_groups_hack = 0;
1106 
1107 void
update_rc(register group_header * gh)1108 update_rc(register group_header * gh)
1109 {
1110     register article_header *ah, **ahp;
1111     register article_number art;
1112     static int      junk_seen = 0;
1113 
1114     if (!rc_merged_groups_hack)
1115 	junk_seen = 0;
1116 
1117     if (gh->group_flag & (G_FOLDER | G_FAKED))
1118 	return;
1119 
1120     begin_rc_update(gh);
1121 
1122     for (ahp = articles, art = 0; art < n_articles; ahp++, art++) {
1123 	ah = *ahp;
1124 	if (ah->a_group != NULL && ah->a_group != gh)
1125 	    continue;
1126 
1127 	switch (ah->attr) {
1128 	    case A_READ:
1129 	    case A_KILL:
1130 		continue;
1131 
1132 	    case A_LEAVE:
1133 	    case A_LEAVE_NEXT:
1134 	    case A_SELECT:
1135 		mark_article(ah, ah->attr);
1136 		continue;
1137 
1138 	    case A_SEEN:
1139 		if (junk_seen == 0) {
1140 		    junk_seen = -1;
1141 		    if (auto_junk_seen) {
1142 			if (conf_junk_seen) {
1143 			    prompt("\1Junk seen articles\1 ");
1144 			    if (yes(0) > 0)
1145 				junk_seen = 1;
1146 			} else
1147 			    junk_seen = 1;
1148 		    }
1149 		}
1150 		if (junk_seen > 0)
1151 		    continue;
1152 		mark_article(ah, (attr_type) (retain_seen_status ? A_SEEN : 0));
1153 		continue;
1154 
1155 	    case A_AUTO_SELECT:
1156 	    default:
1157 		mark_article(ah, (attr_type) 0);
1158 		continue;
1159 	}
1160     }
1161 
1162     end_rc_update(gh);
1163 }
1164 
1165 void
update_rc_all(register group_header * gh,int unsub)1166 update_rc_all(register group_header * gh, int unsub)
1167 {
1168     if (unsub) {
1169 	gh->group_flag &= ~G_NEW;
1170 	gh->group_flag |= G_UNSUBSCRIBED;
1171 
1172 	if (!keep_unsubscribed) {
1173 	    add_unread(gh, -1);
1174 	    if (gh->newsrc_line != NULL && gh->newsrc_orig != gh->newsrc_line)
1175 		freeobj(gh->newsrc_line);
1176 	    gh->newsrc_line = NULL;
1177 	    if (gh->newsrc_orig != NULL)
1178 		dump_newsrc();
1179 	    if (gh->select_line != NULL && gh->newsrc_orig != gh->select_line)
1180 		freeobj(gh->select_line);
1181 	    gh->select_line = NULL;
1182 	    if (gh->select_orig != NULL)
1183 		dump_select();
1184 	    return;
1185 	}
1186 	if (keep_unsub_long) {
1187 	    update_rc(gh);
1188 	    return;
1189 	}
1190     }
1191     begin_rc_update(gh);
1192     end_rc_update(gh);
1193 }
1194 
1195 void
add_to_newsrc(group_header * gh)1196 add_to_newsrc(group_header * gh)
1197 {
1198     gh->group_flag &= ~G_UNSUBSCRIBED;
1199 
1200     if (gh->newsrc_seq != NULL || gh == rc_seq_tail) {
1201 	update_rc(gh);
1202 	return;
1203     }
1204     rc_seq_tail->newsrc_seq = gh;
1205     rc_seq_tail = gh;
1206     if (gh->last_db_article > 0)
1207 	sprintf(rcbuf, "%s: %s%ld\n", gh->group_name,
1208 		gh->last_db_article > 1 ? "1-" : "",
1209 		(long) gh->last_db_article);
1210     else
1211 	sprintf(rcbuf, "%s:\n", gh->group_name);
1212     gh->newsrc_line = copy_str(rcbuf);
1213     dump_newsrc();
1214 }
1215 
1216 article_number
restore_rc(register group_header * gh,article_number last)1217 restore_rc(register group_header * gh, article_number last)
1218 {
1219     register article_number *numtab, n;
1220     register attr_type *attrtab, attr;
1221     register int32  at, atmax;
1222     article_header  ahdr;
1223     article_number  count;
1224 
1225     if (last > gh->last_db_article)
1226 	return 0;
1227 
1228     if (gh->unread_count <= 0) {
1229 	/* no unread articles to account for -- quick update */
1230 	n = gh->last_db_article;/* fake for end_rc_update */
1231 	gh->last_db_article = last;
1232 	begin_rc_update(gh);
1233 	end_rc_update(gh);
1234 	gh->last_db_article = n;
1235 	add_unread(gh, 1);	/* not done by end_rc_update bec.
1236 				 * mark_counter==0 */
1237 	return gh->unread_count;
1238     }
1239     /* there are unread articles in the group */
1240     /* we must truncate rc&select lines to retain older unread articles */
1241 
1242     atmax = at = 0;
1243     numtab = NULL;
1244     attrtab = NULL;
1245 
1246     use_newsrc(gh, 0);
1247     ahdr.flag = 0;
1248     count = gh->unread_count;
1249 
1250     for (n = gh->last_article + 1; n <= last; n++) {
1251 	if (cur.rc_min == END_OF_LIST) {
1252 	    /* current & rest is unread */
1253 	    last = n - 1;
1254 	    break;
1255 	}
1256 	ahdr.a_number = n;
1257 	if ((attr = rctest(&ahdr, &cur)) == A_READ)
1258 	    continue;
1259 	if (at >= atmax) {
1260 	    atmax += 100;
1261 	    numtab = resizeobj(numtab, article_number, atmax);
1262 	    attrtab = resizeobj(attrtab, attr_type, atmax);
1263 	}
1264 	numtab[at] = n;
1265 	attrtab[at] = attr;
1266 	at++;
1267     }
1268 
1269     begin_rc_update(gh);
1270     while (--at >= 0) {
1271 	ahdr.a_number = *numtab++;
1272 	mark_article(&ahdr, *attrtab++);
1273     }
1274     for (n = last + 1; n <= gh->last_db_article; n++) {
1275 	ahdr.a_number = n;
1276 	mark_article(&ahdr, (attr_type) 0);
1277     }
1278     end_rc_update(gh);
1279     return gh->unread_count - count;
1280 }
1281 
1282 int
restore_unread(register group_header * gh)1283 restore_unread(register group_header * gh)
1284 {
1285     if (gh->select_line != gh->select_orig) {
1286 	if (gh->select_line != NULL)
1287 	    freeobj(gh->select_line);
1288 	gh->select_line = gh->select_orig;
1289 	dump_select();
1290     }
1291     if (gh->newsrc_orig == gh->newsrc_line)
1292 	return 0;
1293 
1294     add_unread(gh, -1);
1295     if (gh->newsrc_line != NULL)
1296 	freeobj(gh->newsrc_line);
1297     gh->newsrc_line = gh->newsrc_orig;
1298     gh->last_article = gh->first_article;
1299     dump_newsrc();
1300 
1301     add_unread(gh, 1);
1302 
1303     return 1;
1304 }
1305 
1306 void
count_unread_articles(void)1307 count_unread_articles(void)
1308 {
1309     register group_header *gh;
1310     long            n = 0;
1311 
1312     unread_articles = 0;
1313     unread_groups = 0;
1314 
1315     Loop_Groups_Sequence(gh) {
1316 	gh->unread_count = 0;
1317 
1318 	if (gh->master_flag & M_NO_DIRECTORY)
1319 	    continue;
1320 
1321 	if (gh->last_db_article > gh->last_article) {
1322 	    n = unread_articles;
1323 	    add_unread(gh, 1);
1324 	}
1325 	if ((gh->group_flag & G_COUNTED) == 0)
1326 	    continue;
1327 	if (verbose)
1328 	    tprintf("%6d %s\n", unread_articles - n, gh->group_name);
1329     }
1330 }
1331 
1332 
1333 void
prt_unread(register char * format)1334 prt_unread(register char *format)
1335 {
1336     if (format == NULL) {
1337 	clrdisp();
1338 	tprintf("No News (is good news)\n");
1339 	return;
1340     }
1341     while (*format) {
1342 	if (*format != '%') {
1343 	    tputc(*format++);
1344 	    continue;
1345 	}
1346 	format++;
1347 	switch (*format++) {
1348 	    case 'u':
1349 		tprintf("%ld unread article%s", unread_articles, plural((long) unread_articles));
1350 		continue;
1351 	    case 'g':
1352 		tprintf("%d group%s", unread_groups, plural((long) unread_groups));
1353 		continue;
1354 	    case 'i':
1355 		tprintf(unread_articles == 1 ? "is" : "are");
1356 		continue;
1357 	    case 'U':
1358 		tprintf("%ld", unread_articles);
1359 		continue;
1360 	    case 'G':
1361 		tprintf("%d", unread_groups);
1362 		continue;
1363 	}
1364     }
1365 }
1366 
1367 article_number
add_unread(group_header * gh,int mode)1368 add_unread(group_header * gh, int mode)
1369  /* mode:  +1 => count + add, 0 => gh->unread_count, -1 => subtract */
1370 {
1371     article_number  old_count;
1372     article_header  ahdr;
1373 
1374     old_count = gh->unread_count;
1375 
1376     if (mode == 0)
1377 	goto add_directly;
1378 
1379     if (gh->group_flag & G_COUNTED) {
1380 	unread_articles -= gh->unread_count;
1381 	unread_groups--;
1382 	gh->unread_count = 0;
1383 	gh->group_flag &= ~G_COUNTED;
1384     }
1385     if (mode < 0)
1386 	goto out;
1387 
1388     if (quick_unread_count)
1389 	gh->unread_count = gh->last_db_article - gh->last_article;
1390     else {
1391 	use_newsrc(gh, 0);
1392 	ahdr.flag = 0;
1393 	for (ahdr.a_number = gh->last_article + 1;
1394 	     ahdr.a_number <= gh->last_db_article;
1395 	     ahdr.a_number++) {
1396 	    if (cur.rc_min == END_OF_LIST) {
1397 		gh->unread_count += gh->last_db_article - ahdr.a_number + 1;
1398 		break;
1399 	    }
1400 	    if (rctest(&ahdr, &cur) != A_READ)
1401 		gh->unread_count++;
1402 	}
1403     }
1404 
1405 add_directly:
1406     if (gh->unread_count <= 0) {
1407 	gh->unread_count = 0;
1408 	goto out;
1409     }
1410     if (gh->group_flag & G_UNSUBSCRIBED)
1411 	goto out;
1412 
1413     unread_articles += gh->unread_count;
1414     unread_groups++;
1415     gh->group_flag |= G_COUNTED;
1416 
1417 out:
1418     return old_count != gh->unread_count;
1419 }
1420 
1421 /*
1422  *	nngrep
1423  */
1424 
1425 static int
1426                 grep_all = 0, grep_new = 0, grep_not_sequence = 0,
1427                 grep_pending = 0, grep_read = 0, grep_sequence = 0,
1428                 grep_unsub = 0, grep_long = 0, grep_patterns;
1429 
1430 static
Option_Description(grep_options)1431 Option_Description(grep_options)
1432 {
1433     'a', Bool_Option(grep_all),
1434     'i', Bool_Option(grep_not_sequence),
1435     'n', Bool_Option(grep_new),
1436     'p', Bool_Option(grep_pending),
1437     'r', Bool_Option(grep_read),
1438     's', Bool_Option(grep_sequence),
1439     'u', Bool_Option(grep_unsub),
1440     'l', Bool_Option(grep_long),
1441     '\0',
1442 };
1443 
1444 void
opt_nngrep(int argc,char * argv[])1445 opt_nngrep(int argc, char *argv[])
1446 {
1447     grep_patterns =
1448     parse_options(argc, argv, (char *) NULL, grep_options, " pattern...");
1449 }
1450 
1451 void
do_grep(char ** pat)1452 do_grep(char **pat)
1453 {
1454     register group_header *gh;
1455     register regexp **re = NULL;
1456     register int    i;
1457     int             header = 1;
1458 
1459     if (grep_patterns > 0) {
1460 	re = newobj(regexp *, grep_patterns);
1461 	for (i = 0; i < grep_patterns; i++)
1462 	    re[i] = regcomp(pat[i]);
1463     }
1464     Loop_Groups_Sorted(gh) {
1465 	if (gh->master_flag & M_IGNORE_GROUP)
1466 	    continue;
1467 
1468 	if (grep_pending && gh->unread_count <= 0)
1469 	    continue;
1470 	if (grep_read && gh->unread_count > 0)
1471 	    continue;
1472 	if (grep_sequence && (gh->group_flag & G_SEQUENCE) == 0)
1473 	    continue;
1474 	if (grep_not_sequence && (gh->group_flag & G_SEQUENCE))
1475 	    continue;
1476 	if (grep_new && (gh->group_flag & G_NEW) == 0)
1477 	    continue;
1478 	if (!grep_all) {
1479 	    if (grep_unsub && (gh->group_flag & G_UNSUBSCRIBED) == 0)
1480 		continue;
1481 	    if (!grep_unsub && (gh->group_flag & G_UNSUBSCRIBED))
1482 		continue;
1483 	}
1484 	if (grep_patterns > 0) {
1485 	    for (i = 0; i < grep_patterns; i++)
1486 		if (regexec(re[i], gh->group_name))
1487 		    break;
1488 	    if (i == grep_patterns)
1489 		continue;
1490 	}
1491 	if (grep_long) {
1492 	    if (header)
1493 		tprintf("SUBSCR IN_RC NEW UNREAD SEQUENCE GROUP\n");
1494 	    header = 0;
1495 
1496 	    tprintf(" %s   %s   %s ",
1497 		    (gh->group_flag & G_UNSUBSCRIBED) ? "no " : "yes",
1498 		    (gh->newsrc_line == NULL) ? "no " : "yes",
1499 		    (gh->group_flag & G_NEW) ? "yes" : "no ");
1500 
1501 	    if (gh->unread_count > 0)
1502 		tprintf("%6d ", gh->unread_count);
1503 	    else
1504 		tprintf("       ");
1505 	    if (gh->group_flag & G_SEQUENCE)
1506 		tprintf("  %4d   ", gh->preseq_index);
1507 	    else
1508 		tprintf("         ");
1509 	}
1510 	tprintf("%s\n", gh->group_name);
1511     }
1512 }
1513 
1514 
1515 /*
1516  *	nntidy
1517  */
1518 
1519 static int
1520                 tidy_unsubscribed = 0,	/* truncate lines for unsub groups */
1521                 tidy_remove_unsub = 0,	/* remove lines for unsub groups */
1522                 tidy_sequence = 0,	/* remove groups not in sequence */
1523                 tidy_ignored = 0,	/* remove G_IGN groups */
1524                 tidy_crap = 0,	/* remove unrecognized lines */
1525                 tidy_all = 0;	/* all of the above */
1526 
1527 static
Option_Description(tidy_options)1528 Option_Description(tidy_options)
1529 {
1530     'N', Bool_Option(no_update),
1531     'Q', Bool_Option(silent),
1532     'v', Bool_Option(verbose),
1533     'a', Bool_Option(tidy_all),
1534     'c', Bool_Option(tidy_crap),
1535     'i', Bool_Option(tidy_ignored),
1536     'r', Bool_Option(tidy_remove_unsub),
1537     's', Bool_Option(tidy_sequence),
1538     'u', Bool_Option(tidy_unsubscribed),
1539     '\0',
1540 };
1541 
1542 int
opt_nntidy(int argc,char * argv[])1543 opt_nntidy(int argc, char *argv[])
1544 {
1545     return parse_options(argc, argv, (char *) NULL,
1546 			 tidy_options, " [group]...");
1547 }
1548 
1549 void
do_tidy_newsrc(void)1550 do_tidy_newsrc(void)
1551 {
1552     register group_header *gh;
1553     int             changed;
1554     char           *why;
1555 
1556     /* visit_rc_file has been called. */
1557 
1558     keep_rc_backup = 1;
1559     bak_suffix = ".tidy";
1560 
1561     tidy_newsrc = 0;
1562     changed = 0;
1563 
1564     if (tidy_all)
1565 	tidy_sequence = tidy_ignored = tidy_crap = tidy_unsubscribed = 1;
1566 
1567     newsrc_update_freq = 9999;
1568 
1569     Loop_Groups_Newsrc(gh) {
1570 	if ((gh->master_flag & M_VALID) == 0) {
1571 	    why = "Unknown group:   ";
1572 	    goto delete;
1573 	}
1574 	if (tidy_sequence && (gh->group_flag & G_SEQUENCE) == 0) {
1575 	    why = "Not in sequence: ";
1576 	    goto delete;
1577 	}
1578 	if (tidy_ignored && (gh->master_flag & M_IGNORE_GROUP)) {
1579 	    why = "Ignored group:   ";
1580 	    goto delete;
1581 	}
1582 	if (tidy_crap && (gh->group_flag & G_FAKED)) {
1583 	    why = "Crap in .newsrc: ";
1584 	    goto delete;
1585 	}
1586 	if (tidy_remove_unsub && (gh->group_flag & G_UNSUBSCRIBED)) {
1587 	    if (gh->group_flag & G_FAKED)
1588 		continue;
1589 	    why = "Unsubscribed:    ";
1590 	    goto delete;
1591 	}
1592 	if (tidy_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED)) {
1593 	    if (gh->group_flag & G_FAKED)
1594 		continue;
1595 
1596 	    begin_rc_update(gh);
1597 	    gh->last_db_article = 0;
1598 	    end_rc_update(gh);
1599 
1600 	    if (gh->newsrc_line != gh->newsrc_orig) {
1601 		why = "Truncated:       ";
1602 		goto change;
1603 	    }
1604 	}
1605 	if (verbose) {
1606 	    why = "Ok:              ";
1607 	    goto report;
1608 	}
1609 	continue;
1610 
1611 delete:
1612 	gh->newsrc_line = NULL;
1613 	gh->select_line = NULL;
1614 
1615 change:
1616 	changed = 1;
1617 
1618 report:
1619 	if (!silent)
1620 	    tprintf("%s%s\n", why, gh->group_name);
1621     }
1622 
1623     if (no_update) {
1624 	if (changed)
1625 	    tprintf("Files were NOT updated\n");
1626 	return;
1627     }
1628     if (changed) {
1629 	newsrc_update_freq = 0;
1630 	dump_newsrc();
1631 	dump_select();
1632 	tprintf("NOTICE: Original files are saved with %s extention\n", bak_suffix);
1633     }
1634 }
1635 
1636 /*
1637  *	nngoback
1638  */
1639 
1640 static int
1641                 goback_interact = 0,	/* interactive nngoback */
1642                 goback_days = -1, goback_alsounsub = 0;	/* unsubscribed groups
1643 							 * also */
1644 
1645 static
Option_Description(goback_options)1646 Option_Description(goback_options)
1647 {
1648     'N', Bool_Option(no_update),
1649     'Q', Bool_Option(silent),
1650     'd', Int_Option(goback_days),
1651     'i', Bool_Option(goback_interact),
1652     'u', Bool_Option(goback_alsounsub),
1653     'v', Bool_Option(verbose),
1654     '\0',
1655 };
1656 
1657 int
opt_nngoback(int argc,char *** argvp)1658 opt_nngoback(int argc, char ***argvp)
1659 {
1660     int             n;
1661 
1662     n = parse_options(argc, *argvp, (char *) NULL, goback_options,
1663 		      " days [groups]...");
1664 
1665     if (goback_days < 0) {
1666 	if (n == 0 || !isdigit((*argvp)[1][0])) {
1667 	    fprintf(stderr, "usage: %s [-NQvi] days [groups]...\n", pname);
1668 	    nn_exit(1);
1669 	}
1670 	goback_days = atoi((*argvp)[1]);
1671 	n--;
1672 	++*argvp;
1673     }
1674     return n;
1675 }
1676 
1677 void
do_goback(void)1678 do_goback(void)
1679 {
1680     char            back_act[FILENAME];
1681     FILE           *ba;
1682     register group_header *gh;
1683     article_number  count, total;
1684     int             groups, y;
1685 
1686     sprintf(back_act, "%s/active.%d", db_directory, goback_days);
1687     if ((ba = open_file(back_act, OPEN_READ)) == NULL) {
1688 	fprintf(stderr, "Cannot go back %d days\n", goback_days);
1689 	nn_exit(1);
1690     }
1691     read_active_file(ba, (FILE *) NULL);
1692 
1693     fclose(ba);
1694 
1695     /* visit_rc_file has been called. */
1696 
1697     keep_rc_backup = 1;
1698     bak_suffix = ".goback";
1699     newsrc_update_freq = 9999;
1700     quick_unread_count = 0;
1701     total = groups = 0;
1702 
1703     if (goback_interact) {
1704 	if (no_update)
1705 	    tprintf("Warning: changes will not be saved\n");
1706 	init_term(1);
1707 	nn_raw();
1708     }
1709     Loop_Groups_Sequence(gh) {
1710 	if ((gh->master_flag & M_VALID) == 0)
1711 	    continue;
1712 	if (!goback_alsounsub && (gh->group_flag & G_UNSUBSCRIBED))
1713 	    continue;
1714 
1715 	add_unread(gh, 1);
1716 
1717 	count = restore_rc(gh, gh->last_a_article);
1718 	if (count > 0) {
1719 	    if (goback_interact) {
1720 		tprintf("%s + %ld ?  (y) ", gh->group_name, (long) count);
1721 		fl;
1722 		y = yes(0);
1723 		tputc(CR);
1724 		tputc(NL);
1725 		switch (y) {
1726 		    case 1:
1727 			break;
1728 		    case 0:
1729 			gh->newsrc_line = gh->newsrc_orig;
1730 			gh->select_line = gh->select_orig;
1731 			continue;
1732 		    case -1:
1733 			if (total > 0) {
1734 			    tprintf("\nSave changes sofar? (n) ");
1735 			    fl;
1736 			    if (yes(1) <= 0)
1737 				nn_exit(0);
1738 			}
1739 			goto out;
1740 		}
1741 	    } else if (verbose)
1742 		tprintf("%5ld\t%s\n", (long) count, gh->group_name);
1743 
1744 	    total += count;
1745 	    groups++;
1746 	}
1747     }
1748 
1749 out:
1750 
1751     if (goback_interact)
1752 	unset_raw();
1753 
1754     if (total == 0) {
1755 	tprintf("No articles marked\n");
1756 	return;
1757     }
1758     flush_newsrc();
1759 
1760     if (verbose)
1761 	tputc(NL);
1762     if (!silent)
1763 	tprintf("%ld article%s marked unread in %d group%s\n",
1764 		(long) total, plural((long) total),
1765 		groups, plural((long) groups));
1766 
1767     if (no_update)
1768 	tprintf("No files were updated\n");
1769 }
1770 
1771 /* fake this for read_active_file */
1772 
1773 group_header   *
add_new_group(char * name)1774 add_new_group(char *name)
1775 {
1776     return NULL;
1777 }
1778