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