1 /* $Id: bits.c,v 3.0 1992/02/01 03:09:32 davison Trn $
2 */
3 /* This software is Copyright 1991 by Stan Barber.
4 *
5 * Permission is hereby granted to copy, reproduce, redistribute or otherwise
6 * use this software as long as: there is no monetary profit gained
7 * specifically from the use or reproduction of this software, it is not
8 * sold, rented, traded or otherwise marketed, and this copyright notice is
9 * included prominently in any copy made.
10 *
11 * The authors make no claims as to the fitness or correctness of this software
12 * for any use whatsoever, and it is provided as is. Any use of this software
13 * is at the user's own risk.
14 */
15
16 #include "EXTERN.h"
17 #include "common.h"
18 #include "cache.h"
19 #include "INTERN.h"
20 #include "bits.h"
21 #include "EXTERN.h"
22 #include "rcstuff.h"
23 #include "head.h"
24 #include "term.h"
25 #include "util.h"
26 #include "util2.h"
27 #include "final.h"
28 #include "trn.h"
29 #include "ng.h"
30 #include "artio.h"
31 #include "intrp.h"
32 #include "ngdata.h"
33 #include "rcln.h"
34 #include "ndir.h"
35 #include "nntp.h"
36 #include "rthread.h"
37 #include "rt-select.h"
38 #include "rt-util.h"
39
40 #ifdef DBM_XREFS
41 # ifdef NULL
42 # undef NULL
43 # endif
44 # include <dbm.h>
45 #endif
46
47 static long chase_count = 0;
48
49 void
bits_init()50 bits_init()
51 {
52 ;
53 }
54
55 void
rc_to_bits()56 rc_to_bits()
57 {
58 char *mybuf = buf; /* place to decode rc line */
59 register char *s, *c, *h;
60 register long i;
61 register ART_NUM unread;
62 register ARTICLE *ap;
63
64 /* modify the article flags to reflect what has already been read */
65
66 for (s = rcline[ng] + rcnums[ng]; *s == ' '; s++) ;
67 /* find numbers in rc line */
68 i = strlen(s);
69 #ifndef lint
70 if (i >= LBUFLEN-2) /* bigger than buf? */
71 mybuf = safemalloc((MEM_SIZE)(i+2));
72 #endif
73 strcpy(mybuf,s); /* make scratch copy of line */
74 mybuf[i++] = ','; /* put extra comma on the end */
75 mybuf[i] = '\0';
76 s = mybuf; /* initialize the for loop below */
77 if (strnEQ(s,"1-",2)) { /* can we save some time here? */
78 firstart = atol(s+2)+1; /* process first range thusly */
79 if (firstart < absfirst)
80 firstart = absfirst;
81 s=index(s,',') + 1;
82 for (i = absfirst, ap = article_ptr(i); i < firstart; i++, ap++)
83 ap->flags |= AF_READ;
84 } else {
85 i = firstart = absfirst;
86 ap = article_ptr(i);
87 }
88 unread = lastart - firstart + 1; /* assume this range unread */
89 #ifdef DEBUG
90 if (debug & DEB_CTLAREA_BITMAP) {
91 printf("\n%s\n",mybuf) FLUSH;
92 for (i = absfirst, ap = article_ptr(i); i < firstart; i++, ap++)
93 if (!(ap->flags & AF_READ))
94 printf("%ld ",(long)i) FLUSH;
95 }
96 #endif
97 for ( ; (c = index(s,',')) != Nullch; s = ++c) { /* for each range */
98 ART_NUM min, max;
99
100 *c = '\0'; /* do not let index see past comma */
101 h = index(s,'-');
102 min = atol(s);
103 if (h) /* is there a dash? */
104 max = atol(h+1);
105 else
106 max = min;
107 if (min < firstart) /* make sure range is in range */
108 min = firstart;
109 if (min > lastart)
110 min = lastart+1;
111 for (; i < min; i++, ap++)
112 ap->flags &= ~AF_READ;
113 if (max > lastart)
114 max = lastart;
115 if (min <= max) { /* non-null range? */
116 unread -= max - min + 1;/* adjust unread count */
117 /* mark all arts in range as read */
118 for (; i <= max; i++, ap++)
119 ap->flags |= AF_READ;
120 }
121 #ifdef DEBUG
122 if (debug & DEB_CTLAREA_BITMAP) {
123 printf("\n%s\n",s) FLUSH;
124 for (i=absfirst; i <= lastart; i++)
125 if (!was_read(i))
126 printf("%ld ",(long)i) FLUSH;
127 }
128 #endif
129 }
130 for (; i <= lastart; i++, ap++)
131 ap->flags &= ~AF_READ;
132 #ifdef DEBUG
133 if (debug & DEB_CTLAREA_BITMAP) {
134 fputs("\n(hit CR)",stdout) FLUSH;
135 fgets(cmd_buf, sizeof cmd_buf, stdin);
136 }
137 #endif
138 if (mybuf != buf)
139 free(mybuf);
140 toread[ng] = unread;
141 }
142
143 /* reconstruct the .newsrc line in a human readable form */
144
145 void
bits_to_rc()146 bits_to_rc()
147 {
148 register char *s, *mybuf = buf;
149 register ART_NUM i;
150 ART_NUM count=0;
151 int safelen = LBUFLEN - 32;
152
153 strcpy(buf,rcline[ng]); /* start with the newsgroup name */
154 s = buf + rcnums[ng] - 1; /* use s for buffer pointer */
155 *s++ = RCCHAR(rcchar[ng]); /* put the requisite : or !*/
156 for (i=absfirst; i<=lastart; i++)
157 if (!was_read(i))
158 break;
159 sprintf(s," 1-%ld,",(long)i-1);
160 s += strlen(s);
161 for (; i<=lastart; i++) { /* for each article in newsgroup */
162 if (s-mybuf > safelen) { /* running out of room? */
163 safelen *= 2;
164 if (mybuf == buf) { /* currently static? */
165 *s = '\0';
166 mybuf = safemalloc((MEM_SIZE)safelen + 32);
167 strcpy(mybuf,buf); /* so we must copy it */
168 s = mybuf + (s-buf);
169 /* fix the pointer, too */
170 }
171 else { /* just grow in place, if possible */
172 char *newbuf;
173
174 newbuf = saferealloc(mybuf,(MEM_SIZE)safelen + 32);
175 s = newbuf + (s-mybuf);
176 mybuf = newbuf;
177 }
178 }
179 if (!was_read(i)) /* still unread? */
180 count++; /* then count it */
181 else { /* article was read */
182 ART_NUM oldi;
183
184 sprintf(s,"%ld",(long)i); /* put out the min of the range */
185 s += strlen(s); /* keeping house */
186 oldi = i; /* remember this spot */
187 do i++; while (i <= lastart && was_read(i));
188 /* find 1st unread article or end */
189 i--; /* backup to last read article */
190 if (i > oldi) { /* range of more than 1? */
191 sprintf(s,"-%ld,",(long)i);
192 /* then it out as a range */
193 s += strlen(s); /* and housekeep */
194 }
195 else
196 *s++ = ','; /* otherwise, just a comma will do */
197 }
198 }
199 if (*(s-1) == ',') /* is there a final ','? */
200 s--; /* take it back */
201 *s++ = '\0'; /* and terminate string */
202 #ifdef DEBUG
203 if (debug & DEB_NEWSRC_LINE && !panic) {
204 printf("%s: %s\n",rcline[ng],rcline[ng]+rcnums[ng]) FLUSH;
205 printf("%s\n",mybuf) FLUSH;
206 }
207 #endif
208 free(rcline[ng]); /* return old rc line */
209 if (mybuf == buf) {
210 rcline[ng] = safemalloc((MEM_SIZE)(s-buf)+1);
211 /* grab a new rc line */
212 strcpy(rcline[ng], buf); /* and load it */
213 }
214 else {
215 mybuf = saferealloc(mybuf,(MEM_SIZE)(s-mybuf)+1);
216 /* be nice to the heap */
217 rcline[ng] = mybuf;
218 }
219 *(rcline[ng] + rcnums[ng] - 1) = '\0';
220 if (rcchar[ng] == NEGCHAR) { /* did they unsubscribe? */
221 printf(unsubto,ngname) FLUSH;
222 toread[ng] = TR_UNSUB; /* make line invisible */
223 }
224 else
225 /*NOSTRICT*/
226 toread[ng] = (ART_UNREAD)count; /* remember how many unread there are */
227 }
228
229 #ifdef USE_NNTP
230
231 /* Parse the LISTGROUP output and set anything not mentioned as missing. */
232
233 void
setmissingbits()234 setmissingbits() /* NNTP version */
235 {
236 register ART_NUM num, priornum;
237 register ARTICLE *ap;
238
239 if (!nntp_listgroup())
240 return;
241 for (priornum = absfirst-1, ap = article_ptr(absfirst);; ap++) {
242 nntp_gets(ser_line, sizeof ser_line);
243 if (NNTP_LIST_END(ser_line))
244 break;
245 num = atol(ser_line);
246 while (++priornum < num)
247 uncache_article(ap++,FALSE);
248 }
249 }
250
251 #else /* !USE_NNTP */
252
253 /* Scan the directory to find which articles are present. */
254
255 void
setfoundbits()256 setfoundbits()
257 {
258 register ART_NUM first = lastart+1;
259 register DIR *dirp;
260 register Direntry_t *dp;
261 long an;
262 char ch;
263
264 if (!(dirp = opendir(".")))
265 return;
266
267 found_min = absfirst;
268 an = (lastart-found_min)/BITSPERBYTE+20;
269 found_bits = safemalloc((MEM_SIZE)an);
270 bzero(found_bits, an);
271
272 while ((dp = readdir(dirp)) != Null(Direntry_t*)) {
273 if (sscanf(dp->d_name, "%ld%c", &an, &ch) == 1) {
274 if (an <= lastart && an >= found_min) {
275 if (an < first)
276 first = an;
277 foundart(an);
278 }
279 }
280 }
281 closedir(dirp);
282 abs1st[ng] = first;
283 if (first > absfirst)
284 checkexpired(ng,first);
285 absfirst = first;
286 }
287
288 void
setmissingbits()289 setmissingbits() /* non-NNTP version */
290 {
291 register ARTICLE *ap;
292 register ART_NUM an;
293
294 if (!found_bits)
295 return;
296
297 for (an = absfirst, ap = article_ptr(an); an <= lastart; an++, ap++) {
298 if (artismissing(an))
299 onemissing(ap);
300 }
301 free(found_bits);
302 found_bits = NULL;
303 }
304 #endif /* !USE_NNTP */
305
306 /* mark an article unread, keeping track of toread[] */
307
308 void
onemore(ap)309 onemore(ap)
310 ARTICLE *ap;
311 {
312 if (ap->flags & AF_READ) {
313 register ART_NUM artnum = article_num(ap);
314 check_first(artnum);
315 ap->flags &= ~AF_READ;
316 ++toread[ng];
317 ap->flags &= ~AF_DEL;
318 if (ap->subj) {
319 if (selected_only) {
320 if (ap->subj->flags & sel_mask) {
321 ap->flags |= sel_mask;
322 selected_count++;
323 }
324 } else
325 ap->subj->flags |= SF_VISIT;
326 }
327 }
328 }
329
330 /* mark an article read, keeping track of toread[] */
331
332 void
oneless(ap)333 oneless(ap)
334 ARTICLE *ap;
335 {
336 if (!(ap->flags & AF_READ)) {
337 ap->flags |= AF_READ;
338 /* Keep selected_count accurate */
339 if (ap->flags & sel_mask) {
340 selected_count--;
341 ap->flags &= ~sel_mask;
342 }
343 if (toread[ng] > TR_NONE)
344 --toread[ng];
345 }
346 }
347
348 void
onemissing(ap)349 onemissing(ap)
350 ARTICLE *ap;
351 {
352 oneless(ap);
353 ap->flags = (ap->flags & ~(AF_HAS_RE|AF_YANKBACK|AF_FROMTRUNCED))
354 | AF_MISSING|AF_CACHED|AF_THREADED;
355 }
356
357 /* mark an article as unread, with possible xref chasing */
358
359 void
unmark_as_read()360 unmark_as_read()
361 {
362 register ARTICLE *ap = article_ptr(art);
363 onemore(ap);
364 #ifdef MCHASE
365 if (ap->xrefs != nullstr && !(ap->flags & AF_MCHASE)) {
366 ap->flags |= AF_MCHASE;
367 chase_count++;
368 }
369 #endif
370 }
371
372 /* Mark an article as read in this newsgroup and possibly chase xrefs.
373 ** Don't call this on missing articles.
374 */
375 void
set_read(ap)376 set_read(ap)
377 register ARTICLE *ap;
378 {
379 oneless(ap);
380 if (!olden_days && ap->xrefs != nullstr && !(ap->flags & AF_KCHASE)) {
381 ap->flags |= AF_KCHASE;
382 chase_count++;
383 }
384 }
385
386 /* temporarily mark article as read. When newsgroup is exited, articles */
387 /* will be marked as unread. Called via M command */
388
389 void
delay_unmark(ap)390 delay_unmark(ap)
391 ARTICLE *ap;
392 {
393 if (!(ap->flags & AF_YANKBACK)) {
394 ap->flags |= AF_YANKBACK;
395 dmcount++;
396 }
397 }
398
399 /* mark article as read. If article is cross referenced to other */
400 /* newsgroups, mark them read there also. */
401
402 void
mark_as_read()403 mark_as_read()
404 {
405 register ARTICLE *ap = article_ptr(art);
406 oneless(ap);
407 if (ap->xrefs != nullstr && !(ap->flags & AF_KCHASE)) {
408 ap->flags |= AF_KCHASE;
409 chase_count++;
410 }
411 checkcount++; /* get more worried about crashes */
412 }
413
414 /* keep firstart pointing at the first unread article */
415
416 void
check_first(min)417 check_first(min)
418 ART_NUM min;
419 {
420 if (min < absfirst)
421 min = absfirst;
422 if (min < firstart)
423 firstart = min;
424 }
425
426 /* bring back articles marked with M */
427
428 void
yankback()429 yankback()
430 {
431 register ARTICLE *ap;
432
433 if (dmcount) { /* delayed unmarks pending? */
434 if (mode == 't')
435 sprintf(buf, "Returned %ld Marked article%s.",(long)dmcount,
436 dmcount == 1 ? nullstr : "s");
437 else
438 #ifdef VERBOSE
439 printf("\nReturning %ld Marked article%s...\n",(long)dmcount,
440 dmcount == 1 ? nullstr : "s") FLUSH;
441 #endif
442 for (art=absfirst, ap=article_ptr(art); art <= lastart; art++, ap++) {
443 if ((ap->flags & (AF_YANKBACK|AF_MISSING)) == AF_YANKBACK) {
444 unmark_as_read();
445 if (selected_only)
446 select_article(ap, 0);
447 ap->flags &= ~AF_YANKBACK;
448 }
449 }
450 dmcount = 0;
451 }
452 }
453
454 static int chase_xref _((ART_NUM,int));
455
456 int
chase_xrefs(until_key)457 chase_xrefs(until_key)
458 bool_int until_key;
459 {
460 register ARTICLE *ap;
461 register ART_NUM an;
462
463 if (!chase_count)
464 return 1;
465 if (until_key)
466 setspin(SPIN_BACKGROUND);
467
468 for (an = absfirst, ap = article_ptr(an); an <= lastart; an++, ap++) {
469 if (ap->flags & AF_KCHASE) {
470 chase_xref(an,TRUE);
471 ap->flags &= ~AF_KCHASE;
472 if (!--chase_count)
473 break;
474 }
475 #ifdef MCHASE
476 if (ap->flags & AF_MCHASE) {
477 chase_xref(an,TRUE);
478 ap->flags &= ~AF_MCHASE;
479 if (!--chase_count)
480 break;
481 }
482 #endif
483 if (until_key && input_pending())
484 return 0;
485 }
486 chase_count = 0;
487 return 1;
488 }
489
490 /* run down xref list and mark as read or unread */
491
492 #ifndef DBM_XREFS
493 static int
chase_xref(artnum,markread)494 chase_xref(artnum,markread) /* The Xref-line-using version */
495 ART_NUM artnum;
496 int markread;
497 {
498 # ifdef VALIDATE_XREF_SITE
499 bool valid_xref_site();
500 # endif
501 register char *xartnum;
502 register ART_NUM x;
503 char *xref_buf, *curxref;
504 char tmpbuf[128];
505
506 if (inbackground())
507 spin(10);
508 else {
509 if (output_chase_phrase) {
510 #ifdef VERBOSE
511 IF(verbose)
512 fputs("\nChasing xrefs", stdout);
513 ELSE
514 #endif
515 #ifdef TERSE
516 fputs("\nXrefs", stdout);
517 #endif
518 output_chase_phrase = 0;
519 }
520 putchar('.'), fflush(stdout);
521 }
522
523 xref_buf = fetchcache(artnum, XREF_LINE, FILL_CACHE);
524 if (!xref_buf || !*xref_buf)
525 return 0;
526
527 xref_buf = savestr(xref_buf);
528 # ifdef DEBUG
529 if (debug & DEB_XREF_MARKER)
530 printf("Xref: %s\n",xref_buf) FLUSH;
531 # endif
532 curxref = cpytill(tmpbuf,xref_buf,' ') + 1;
533 # ifdef VALIDATE_XREF_SITE
534 if (valid_xref_site(artnum,tmpbuf))
535 # endif
536 {
537 while (*curxref) { /* for each newsgroup */
538 curxref = cpytill(tmpbuf,curxref,' ');
539 xartnum = index(tmpbuf,':');
540 if (!xartnum)
541 break;
542 *xartnum++ = '\0';
543 if (!(x = atol(xartnum)))
544 continue;
545 if (strEQ(tmpbuf,ngname)) {/* is this the current newsgroup? */
546 if (x < absfirst || x > lastart)
547 continue;
548 if (markread)
549 oneless(article_ptr(x)); /* take care of old C newses */
550 #ifdef MCHASE
551 else
552 onemore(article_ptr(x));
553 #endif
554 } else {
555 if (markread) {
556 if (addartnum(x,tmpbuf))
557 break;
558 }
559 # ifdef MCHASE
560 else
561 subartnum(x,tmpbuf);
562 # endif
563 }
564 while (*curxref && isspace(*curxref))
565 curxref++;
566 }
567 }
568 free(xref_buf);
569 return 0;
570 }
571
572 # ifdef VALIDATE_XREF_SITE
573 /* Make sure the site name on Xref matches what inews thinks the site
574 * is. Check first against last inews_site. If it matches, fine.
575 * If not, fetch inews_site from current Path or Relay-Version line and
576 * check again. This is so that if the new administrator decides
577 * to change the system name as known to inews, rn will still do
578 * Xrefs correctly--each article need only match itself to be valid.
579 */
580 bool
valid_xref_site(artnum,site)581 valid_xref_site(artnum, site)
582 ART_NUM artnum;
583 char *site;
584 {
585 static char *inews_site = Nullch;
586 char *sitebuf, *s;
587
588 if (inews_site && strEQ(site,inews_site))
589 return TRUE;
590
591 if (inews_site)
592 free(inews_site);
593 #ifndef ANCIENT_NEWS
594 /* Grab the site from the first component of the Path line */
595 sitebuf = fetchlines(artnum,PATH_LINE);
596 if ((s = index(sitebuf, '!')) != Nullch) {
597 *s = '\0';
598 inews_site = savestr(sitebuf);
599 }
600 #else /* ANCIENT_NEWS */
601 /* Grab the site from the Posting-Version line */
602 sitebuf = fetchlines(artnum,RVER_LINE);
603 if ((s = instr(sitebuf,"; site ",TRUE)) != Nullch) {
604 char *t = index(s+7, '.');
605 if (t)
606 *t = '\0';
607 inews_site = savestr(s+7);
608 }
609 #endif /* ANCIENT_NEWS */
610 else
611 inews_site = savestr(nullstr);
612 free(sitebuf);
613
614 if (strEQ(site,inews_site))
615 return TRUE;
616
617 #ifdef DEBUG
618 if (debug)
619 printf("Xref not from %s--ignoring\n",inews_site) FLUSH;
620 #endif
621 return FALSE;
622 }
623 # endif /* VALIDATE_XREF_SITE */
624
625 #else /* DBM_XREFS */
626
627 static int
chase_xref(artnum,markread)628 chase_xref(artnum,markread) /* The DBM version */
629 ART_NUM artnum;
630 int markread;
631 {
632 datum lhs, rhs;
633 datum fetch();
634 register char *idp;
635 char *ident_buf;
636 static FILE *hist_file = Nullfp;
637 long pos;
638 register char *xartnum;
639 register ART_NUM x;
640 char *xref_buf, *curxref;
641 char tmpbuf[128];
642
643 if (inbackground())
644 spin(10);
645 else {
646 if (output_chase_phrase) {
647 #ifdef VERBOSE
648 IF(verbose)
649 fputs("\nChasing xrefs", stdout);
650 ELSE
651 #endif
652 #ifdef TERSE
653 fputs("\nXrefs", stdout);
654 #endif
655 output_chase_phrase = 0;
656 }
657 putchar('.'), fflush(stdout);
658 }
659
660 xref_buf = fetchcache(artnum, NGS_LINE, FILL_CACHE);
661 if (!xref_buf || !*xref_buf)
662 return 0;
663
664 xref_buf = safemalloc((MEM_SIZE)BUFSIZ);
665 if (hist_file == Nullfp) { /* Init. file accesses */
666 #ifdef DEBUG
667 if (debug)
668 printf("chase_xref: opening files\n");
669 #endif
670 dbminit(filexp(ARTFILE));
671 if ((hist_file = fopen(filexp(ARTFILE), "r")) == Nullfp)
672 return 0;
673 }
674 ident_buf = fetchlines(artnum,MESSID_LINE); /* get Message-ID */
675 #ifdef DEBUG
676 if (debug)
677 printf ("chase_xref: Message-ID: %s\n", ident_buf);
678 #endif
679
680 if ((idp = index(ident_buf, '@')) != Nullch) {
681 while (*++idp) /* make message-id case insensitive */
682 if (isupper(*idp))
683 *idp = tolower(*idp);
684 }
685 lhs.dptr = ident_buf; /* look up article by id */
686 lhs.dsize = strlen(lhs.dptr) + 1;
687 rhs = fetch(lhs); /* fetch the record */
688 if (rhs.dptr == NULL) /* if null, nothing there */
689 goto wild_goose;
690 bcopy(rhs.dptr,(char*)&pos, 4);
691 fseek(hist_file, pos, 0); /* datum returned is position in hist file */
692 fgets(xref_buf, BUFSIZ, hist_file);
693 #ifdef DEBUG
694 if (debug)
695 printf ("Xref from history: %s\n", xref_buf);
696 #endif
697 curxref = cpytill(tmpbuf, xref_buf, '\t') + 1;
698 curxref = cpytill(tmpbuf, curxref, '\t') + 1;
699 #ifdef DEBUG
700 if (debug)
701 printf ("chase_xref: curxref: %s\n", curxref);
702 #endif
703 while (*curxref) { /* for each newsgroup */
704 curxref = cpytill(tmpbuf,curxref,' ');
705 xartnum = index(tmpbuf,'/');
706 if (!xartnum) /* probably an old-style Xref */
707 break;
708 *xartnum++ = '\0';
709 if (!(x = atol(xartnum)))
710 continue;
711 if (strNE(tmpbuf,ngname)) { /* not the current newsgroup? */
712 if (markread) {
713 if (addartnum(x,tmpbuf))
714 goto wild_goose;
715 }
716 #ifdef MCHASE
717 else
718 subartnum(x,tmpbuf);
719 #endif
720 }
721 while (*curxref && isspace(*curxref))
722 curxref++;
723 }
724 wild_goose:
725 free(xref_buf);
726 free(ident_buf);
727 return 0;
728 }
729 #endif /* DBM_XREFS */
730