No empty .Rs/.Re
[netbsd-mini2440.git] / gnu / usr.bin / rcs / lib / rcsrev.c
blobe2f06f9f090e4d9de5c02af65284ea065b0b53a7
1 /* $NetBSD: rcsrev.c,v 1.5 1996/10/15 07:00:24 veego Exp $ */
3 /* Handle RCS revision numbers. */
5 /* Copyright 1982, 1988, 1989 Walter Tichy
6 Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
7 Distributed under license by the Free Software Foundation, Inc.
9 This file is part of RCS.
11 RCS is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2, or (at your option)
14 any later version.
16 RCS is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with RCS; see the file COPYING.
23 If not, write to the Free Software Foundation,
24 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 Report problems and direct all questions to:
28 rcs-bugs@cs.purdue.edu
33 * $Log: rcsrev.c,v $
34 * Revision 1.5 1996/10/15 07:00:24 veego
35 * Merge rcs 5.7.
37 * Revision 5.10 1995/06/16 06:19:24 eggert
38 * Update FSF address.
40 * Revision 5.9 1995/06/01 16:23:43 eggert
41 * (cmpdate, normalizeyear): New functions work around MKS RCS incompatibility.
42 * (cmpnum, compartial): s[d] -> *(s+d) to work around Cray compiler bug.
43 * (genrevs, genbranch): cmpnum -> cmpdate
45 * Revision 5.8 1994/03/17 14:05:48 eggert
46 * Remove lint.
48 * Revision 5.7 1993/11/09 17:40:15 eggert
49 * Fix format string typos.
51 * Revision 5.6 1993/11/03 17:42:27 eggert
52 * Revision number `.N' now stands for `D.N', where D is the default branch.
53 * Add -z. Improve quality of diagnostics. Add `namedrev' for Name support.
55 * Revision 5.5 1992/07/28 16:12:44 eggert
56 * Identifiers may now start with a digit. Avoid `unsigned'.
58 * Revision 5.4 1992/01/06 02:42:34 eggert
59 * while (E) ; -> while (E) continue;
61 * Revision 5.3 1991/08/19 03:13:55 eggert
62 * Add `-r$', `-rB.'. Remove botches like `<now>' from messages. Tune.
64 * Revision 5.2 1991/04/21 11:58:28 eggert
65 * Add tiprev().
67 * Revision 5.1 1991/02/25 07:12:43 eggert
68 * Avoid overflow when comparing revision numbers.
70 * Revision 5.0 1990/08/22 08:13:43 eggert
71 * Remove compile-time limits; use malloc instead.
72 * Ansify and Posixate. Tune.
73 * Remove possibility of an internal error. Remove lint.
75 * Revision 4.5 89/05/01 15:13:22 narten
76 * changed copyright header to reflect current distribution rules
78 * Revision 4.4 87/12/18 11:45:22 narten
79 * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
80 * since there's now a return value there with a value. (Guy Harris)
82 * Revision 4.3 87/10/18 10:38:42 narten
83 * Updating version numbers. Changes relative to version 1.1 actually
84 * relative to 4.1
86 * Revision 1.3 87/09/24 14:00:37 narten
87 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
88 * warnings)
90 * Revision 1.2 87/03/27 14:22:37 jenkins
91 * Port to suns
93 * Revision 4.1 83/03/25 21:10:45 wft
94 * Only changed $Header to $Id.
96 * Revision 3.4 82/12/04 13:24:08 wft
97 * Replaced getdelta() with gettree().
99 * Revision 3.3 82/11/28 21:33:15 wft
100 * fixed compartial() and compnum() for nil-parameters; fixed nils
101 * in error messages. Testprogram output shortenend.
103 * Revision 3.2 82/10/18 21:19:47 wft
104 * renamed compnum->cmpnum, compnumfld->cmpnumfld,
105 * numericrevno->numricrevno.
107 * Revision 3.1 82/10/11 19:46:09 wft
108 * changed expandsym() to check for source==nil; returns zero length string
109 * in that case.
112 #include "rcsbase.h"
114 libId(revId, "Id: rcsrev.c,v 5.10 1995/06/16 06:19:24 eggert Exp")
116 static char const *branchtip P((char const*));
117 static char const *lookupsym P((char const*));
118 static char const *normalizeyear P((char const*,char[5]));
119 static struct hshentry *genbranch P((struct hshentry const*,char const*,int,char const*,char const*,char const*,struct hshentries**));
120 static void absent P((char const*,int));
121 static void cantfindbranch P((char const*,char const[datesize],char const*,char const*));
122 static void store1 P((struct hshentries***,struct hshentry*));
127 countnumflds(s)
128 char const *s;
129 /* Given a pointer s to a dotted number (date or revision number),
130 * countnumflds returns the number of digitfields in s.
133 register char const *sp;
134 register int count;
135 if (!(sp=s) || !*sp)
136 return 0;
137 count = 1;
138 do {
139 if (*sp++ == '.') count++;
140 } while (*sp);
141 return(count);
144 void
145 getbranchno(revno,branchno)
146 char const *revno;
147 struct buf *branchno;
148 /* Given a revision number revno, getbranchno copies the number of the branch
149 * on which revno is into branchno. If revno itself is a branch number,
150 * it is copied unchanged.
153 register int numflds;
154 register char *tp;
156 bufscpy(branchno, revno);
157 numflds=countnumflds(revno);
158 if (!(numflds & 1)) {
159 tp = branchno->string;
160 while (--numflds)
161 while (*tp++ != '.')
162 continue;
163 *(tp-1)='\0';
169 int cmpnum(num1, num2)
170 char const *num1, *num2;
171 /* compares the two dotted numbers num1 and num2 lexicographically
172 * by field. Individual fields are compared numerically.
173 * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
174 * omitted fields are assumed to be higher than the existing ones.
177 register char const *s1, *s2;
178 register size_t d1, d2;
179 register int r;
181 s1 = num1 ? num1 : "";
182 s2 = num2 ? num2 : "";
184 for (;;) {
185 /* Give precedence to shorter one. */
186 if (!*s1)
187 return (unsigned char)*s2;
188 if (!*s2)
189 return -1;
191 /* Strip leading zeros, then find number of digits. */
192 while (*s1=='0') ++s1;
193 while (*s2=='0') ++s2;
194 for (d1=0; isdigit(*(s1+d1)); d1++) continue;
195 for (d2=0; isdigit(*(s2+d2)); d2++) continue;
197 /* Do not convert to integer; it might overflow! */
198 if (d1 != d2)
199 return d1<d2 ? -1 : 1;
200 if ((r = memcmp(s1, s2, d1)))
201 return r;
202 s1 += d1;
203 s2 += d1;
205 /* skip '.' */
206 if (*s1) s1++;
207 if (*s2) s2++;
213 int cmpnumfld(num1, num2, fld)
214 char const *num1, *num2;
215 int fld;
216 /* Compare the two dotted numbers at field fld.
217 * num1 and num2 must have at least fld fields.
218 * fld must be positive.
221 register char const *s1, *s2;
222 register size_t d1, d2;
224 s1 = num1;
225 s2 = num2;
226 /* skip fld-1 fields */
227 while (--fld) {
228 while (*s1++ != '.')
229 continue;
230 while (*s2++ != '.')
231 continue;
233 /* Now s1 and s2 point to the beginning of the respective fields */
234 while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue;
235 while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue;
237 return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
242 cmpdate(d1, d2)
243 char const *d1, *d2;
245 * Compare the two dates. This is just like cmpnum,
246 * except that for compatibility with old versions of RCS,
247 * 1900 is added to dates with two-digit years.
250 char year1[5], year2[5];
251 int r = cmpnumfld(normalizeyear(d1,year1), normalizeyear(d2,year2), 1);
253 if (r)
254 return r;
255 else {
256 while (isdigit(*d1)) d1++; d1 += *d1=='.';
257 while (isdigit(*d2)) d2++; d2 += *d2=='.';
258 return cmpnum(d1, d2);
262 static char const *
263 normalizeyear(date, year)
264 char const *date;
265 char year[5];
267 if (isdigit(date[0]) && isdigit(date[1]) && !isdigit(date[2])) {
268 year[0] = '1';
269 year[1] = '9';
270 year[2] = date[0];
271 year[3] = date[1];
272 year[4] = 0;
273 return year;
274 } else
275 return date;
279 static void
280 cantfindbranch(revno, date, author, state)
281 char const *revno, date[datesize], *author, *state;
283 char datebuf[datesize + zonelenmax];
285 rcserror("No revision on branch %s has%s%s%s%s%s%s.",
286 revno,
287 date ? " a date before " : "",
288 date ? date2str(date,datebuf) : "",
289 author ? " and author "+(date?0:4) : "",
290 author ? author : "",
291 state ? " and state "+(date||author?0:4) : "",
292 state ? state : ""
296 static void
297 absent(revno, field)
298 char const *revno;
299 int field;
301 struct buf t;
302 bufautobegin(&t);
303 rcserror("%s %s absent", field&1?"revision":"branch",
304 partialno(&t,revno,field)
306 bufautoend(&t);
311 compartial(num1, num2, length)
312 char const *num1, *num2;
313 int length;
315 /* compare the first "length" fields of two dot numbers;
316 the omitted field is considered to be larger than any number */
317 /* restriction: at least one number has length or more fields */
320 register char const *s1, *s2;
321 register size_t d1, d2;
322 register int r;
324 s1 = num1; s2 = num2;
325 if (!s1) return 1;
326 if (!s2) return -1;
328 for (;;) {
329 if (!*s1) return 1;
330 if (!*s2) return -1;
332 while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue;
333 while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue;
335 if (d1 != d2)
336 return d1<d2 ? -1 : 1;
337 if ((r = memcmp(s1, s2, d1)))
338 return r;
339 if (!--length)
340 return 0;
342 s1 += d1;
343 s2 += d1;
345 if (*s1 == '.') s1++;
346 if (*s2 == '.') s2++;
351 char * partialno(rev1,rev2,length)
352 struct buf *rev1;
353 char const *rev2;
354 register int length;
355 /* Function: Copies length fields of revision number rev2 into rev1.
356 * Return rev1's string.
359 register char *r1;
361 bufscpy(rev1, rev2);
362 r1 = rev1->string;
363 while (length) {
364 while (*r1!='.' && *r1)
365 ++r1;
366 ++r1;
367 length--;
369 /* eliminate last '.'*/
370 *(r1-1)='\0';
371 return rev1->string;
377 static void
378 store1(store, next)
379 struct hshentries ***store;
380 struct hshentry *next;
382 * Allocate a new list node that addresses NEXT.
383 * Append it to the list that **STORE is the end pointer of.
386 register struct hshentries *p;
388 p = ftalloc(struct hshentries);
389 p->first = next;
390 **store = p;
391 *store = &p->rest;
394 struct hshentry * genrevs(revno,date,author,state,store)
395 char const *revno, *date, *author, *state;
396 struct hshentries **store;
397 /* Function: finds the deltas needed for reconstructing the
398 * revision given by revno, date, author, and state, and stores pointers
399 * to these deltas into a list whose starting address is given by store.
400 * The last delta (target delta) is returned.
401 * If the proper delta could not be found, 0 is returned.
404 int length;
405 register struct hshentry * next;
406 int result;
407 char const *branchnum;
408 struct buf t;
409 char datebuf[datesize + zonelenmax];
411 bufautobegin(&t);
413 if (!(next = Head)) {
414 rcserror("RCS file empty");
415 goto norev;
418 length = countnumflds(revno);
420 if (length >= 1) {
421 /* at least one field; find branch exactly */
422 while ((result=cmpnumfld(revno,next->num,1)) < 0) {
423 store1(&store, next);
424 next = next->next;
425 if (!next) {
426 rcserror("branch number %s too low", partialno(&t,revno,1));
427 goto norev;
431 if (result>0) {
432 absent(revno, 1);
433 goto norev;
436 if (length<=1){
437 /* pick latest one on given branch */
438 branchnum = next->num; /* works even for empty revno*/
439 while (next &&
440 cmpnumfld(branchnum,next->num,1) == 0 &&
442 (date && cmpdate(date,next->date) < 0) ||
443 (author && strcmp(author,next->author) != 0) ||
444 (state && strcmp(state,next->state) != 0)
448 store1(&store, next);
449 next=next->next;
451 if (!next ||
452 (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
453 cantfindbranch(
454 length ? revno : partialno(&t,branchnum,1),
455 date, author, state
457 goto norev;
458 } else {
459 store1(&store, next);
461 *store = 0;
462 return next;
465 /* length >=2 */
466 /* find revision; may go low if length==2*/
467 while ((result=cmpnumfld(revno,next->num,2)) < 0 &&
468 (cmpnumfld(revno,next->num,1)==0) ) {
469 store1(&store, next);
470 next = next->next;
471 if (!next)
472 break;
475 if (!next || cmpnumfld(revno,next->num,1) != 0) {
476 rcserror("revision number %s too low", partialno(&t,revno,2));
477 goto norev;
479 if ((length>2) && (result!=0)) {
480 absent(revno, 2);
481 goto norev;
484 /* print last one */
485 store1(&store, next);
487 if (length>2)
488 return genbranch(next,revno,length,date,author,state,store);
489 else { /* length == 2*/
490 if (date && cmpdate(date,next->date)<0) {
491 rcserror("Revision %s has date %s.",
492 next->num,
493 date2str(next->date, datebuf)
495 return 0;
497 if (author && strcmp(author,next->author)!=0) {
498 rcserror("Revision %s has author %s.",
499 next->num, next->author
501 return 0;
503 if (state && strcmp(state,next->state)!=0) {
504 rcserror("Revision %s has state %s.",
505 next->num,
506 next->state ? next->state : "<empty>"
508 return 0;
510 *store = 0;
511 return next;
514 norev:
515 bufautoend(&t);
516 return 0;
522 static struct hshentry *
523 genbranch(bpoint, revno, length, date, author, state, store)
524 struct hshentry const *bpoint;
525 char const *revno;
526 int length;
527 char const *date, *author, *state;
528 struct hshentries **store;
529 /* Function: given a branchpoint, a revision number, date, author, and state,
530 * genbranch finds the deltas necessary to reconstruct the given revision
531 * from the branch point on.
532 * Pointers to the found deltas are stored in a list beginning with store.
533 * revno must be on a side branch.
534 * Return 0 on error.
537 int field;
538 register struct hshentry * next, * trail;
539 register struct branchhead const *bhead;
540 int result;
541 struct buf t;
542 char datebuf[datesize + zonelenmax];
544 field = 3;
545 bhead = bpoint->branches;
547 do {
548 if (!bhead) {
549 bufautobegin(&t);
550 rcserror("no side branches present for %s",
551 partialno(&t,revno,field-1)
553 bufautoend(&t);
554 return 0;
557 /*find branch head*/
558 /*branches are arranged in increasing order*/
559 while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
560 bhead = bhead->nextbranch;
561 if (!bhead) {
562 bufautobegin(&t);
563 rcserror("branch number %s too high",
564 partialno(&t,revno,field)
566 bufautoend(&t);
567 return 0;
571 if (result<0) {
572 absent(revno, field);
573 return 0;
576 next = bhead->hsh;
577 if (length==field) {
578 /* pick latest one on that branch */
579 trail = 0;
580 do { if ((!date || cmpdate(date,next->date)>=0) &&
581 (!author || strcmp(author,next->author)==0) &&
582 (!state || strcmp(state,next->state)==0)
583 ) trail = next;
584 next=next->next;
585 } while (next);
587 if (!trail) {
588 cantfindbranch(revno, date, author, state);
589 return 0;
590 } else { /* print up to last one suitable */
591 next = bhead->hsh;
592 while (next!=trail) {
593 store1(&store, next);
594 next=next->next;
596 store1(&store, next);
598 *store = 0;
599 return next;
602 /* length > field */
603 /* find revision */
604 /* check low */
605 if (cmpnumfld(revno,next->num,field+1)<0) {
606 bufautobegin(&t);
607 rcserror("revision number %s too low",
608 partialno(&t,revno,field+1)
610 bufautoend(&t);
611 return 0;
613 do {
614 store1(&store, next);
615 trail = next;
616 next = next->next;
617 } while (next && cmpnumfld(revno,next->num,field+1)>=0);
619 if ((length>field+1) && /*need exact hit */
620 (cmpnumfld(revno,trail->num,field+1) !=0)){
621 absent(revno, field+1);
622 return 0;
624 if (length == field+1) {
625 if (date && cmpdate(date,trail->date)<0) {
626 rcserror("Revision %s has date %s.",
627 trail->num,
628 date2str(trail->date, datebuf)
630 return 0;
632 if (author && strcmp(author,trail->author)!=0) {
633 rcserror("Revision %s has author %s.",
634 trail->num, trail->author
636 return 0;
638 if (state) {
639 const char *st;
641 if (trail->state == NULL)
642 st = "<empty>";
643 else if (strcmp(trail->state, state) != 0)
644 st = trail->state;
645 else
646 st = NULL;
648 if (st)
649 rcserror("Revision %s has state %s.",
650 trail->num, st);
651 return 0;
654 bhead = trail->branches;
656 } while ((field+=2) <= length);
657 *store = 0;
658 return trail;
662 static char const *
663 lookupsym(id)
664 char const *id;
665 /* Function: looks up id in the list of symbolic names starting
666 * with pointer SYMBOLS, and returns a pointer to the corresponding
667 * revision number. Return 0 if not present.
670 register struct assoc const *next;
671 for (next = Symbols; next; next = next->nextassoc)
672 if (strcmp(id, next->symbol)==0)
673 return next->num;
674 return 0;
677 int expandsym(source, target)
678 char const *source;
679 struct buf *target;
680 /* Function: Source points to a revision number. Expandsym copies
681 * the number to target, but replaces all symbolic fields in the
682 * source number with their numeric values.
683 * Expand a branch followed by `.' to the latest revision on that branch.
684 * Ignore `.' after a revision. Remove leading zeros.
685 * returns false on error;
688 return fexpandsym(source, target, (RILE*)0);
692 fexpandsym(source, target, fp)
693 char const *source;
694 struct buf *target;
695 RILE *fp;
696 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */
698 register char const *sp, *bp;
699 register char *tp;
700 char const *tlim;
701 int dots;
703 sp = source;
704 bufalloc(target, 1);
705 tp = target->string;
706 if (!sp || !*sp) { /* Accept 0 pointer as a legal value. */
707 *tp='\0';
708 return true;
710 if (sp[0] == KDELIM && !sp[1]) {
711 if (!getoldkeys(fp))
712 return false;
713 if (!*prevrev.string) {
714 workerror("working file lacks revision number");
715 return false;
717 bufscpy(target, prevrev.string);
718 return true;
720 tlim = tp + target->size;
721 dots = 0;
723 for (;;) {
724 register char *p = tp;
725 size_t s = tp - target->string;
726 int id = false;
727 for (;;) {
728 switch (ctab[(unsigned char)*sp]) {
729 case IDCHAR:
730 case LETTER:
731 case Letter:
732 id = true;
733 /* fall into */
734 case DIGIT:
735 if (tlim <= p)
736 p = bufenlarge(target, &tlim);
737 *p++ = *sp++;
738 continue;
740 default:
741 break;
743 break;
745 if (tlim <= p)
746 p = bufenlarge(target, &tlim);
747 *p = 0;
748 tp = target->string + s;
750 if (id) {
751 bp = lookupsym(tp);
752 if (!bp) {
753 rcserror("Symbolic name `%s' is undefined.",tp);
754 return false;
756 } else {
757 /* skip leading zeros */
758 for (bp = tp; *bp=='0' && isdigit(bp[1]); bp++)
759 continue;
761 if (!*bp)
762 if (s || *sp!='.')
763 break;
764 else {
765 /* Insert default branch before initial `.'. */
766 char const *b;
767 if (Dbranch)
768 b = Dbranch;
769 else if (Head)
770 b = Head->num;
771 else
772 break;
773 getbranchno(b, target);
774 bp = tp = target->string;
775 tlim = tp + target->size;
779 while ((*tp++ = *bp++))
780 if (tlim <= tp)
781 tp = bufenlarge(target, &tlim);
783 switch (*sp++) {
784 case '\0':
785 return true;
787 case '.':
788 if (!*sp) {
789 if (dots & 1)
790 break;
791 if (!(bp = branchtip(target->string)))
792 return false;
793 bufscpy(target, bp);
794 return true;
796 ++dots;
797 tp[-1] = '.';
798 continue;
800 break;
803 rcserror("improper revision number: %s", source);
804 return false;
807 char const *
808 namedrev(name, delta)
809 char const *name;
810 struct hshentry *delta;
811 /* Yield NAME if it names DELTA, 0 otherwise. */
813 if (name) {
814 char const *id = 0, *p, *val;
815 for (p = name; ; p++)
816 switch (ctab[(unsigned char)*p]) {
817 case IDCHAR:
818 case LETTER:
819 case Letter:
820 id = name;
821 break;
823 case DIGIT:
824 break;
826 case UNKN:
827 if (!*p && id &&
828 (val = lookupsym(id)) &&
829 strcmp(val, delta->num) == 0
831 return id;
832 /* fall into */
833 default:
834 return 0;
837 return 0;
840 static char const *
841 branchtip(branch)
842 char const *branch;
844 struct hshentry *h;
845 struct hshentries *hs;
847 h = genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
848 return h ? h->num : (char const*)0;
851 char const *
852 tiprev()
854 return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
859 #ifdef REVTEST
862 * Test the routines that generate a sequence of delta numbers
863 * needed to regenerate a given delta.
866 char const cmdid[] = "revtest";
869 main(argc,argv)
870 int argc; char * argv[];
872 static struct buf numricrevno;
873 char symrevno[100]; /* used for input of revision numbers */
874 char author[20];
875 char state[20];
876 char date[20];
877 struct hshentries *gendeltas;
878 struct hshentry * target;
879 int i;
881 if (argc<2) {
882 aputs("No input file\n",stderr);
883 exitmain(EXIT_FAILURE);
885 if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
886 faterror("can't open input file %s", argv[1]);
888 Lexinit();
889 getadmin();
891 gettree();
893 getdesc(false);
895 do {
896 /* all output goes to stderr, to have diagnostics and */
897 /* errors in sequence. */
898 aputs("\nEnter revision number or <return> or '.': ",stderr);
899 if (!fgets(symrevno, 100, stdin)) break;
900 if (*symrevno == '.') break;
901 aprintf(stderr,"%s;\n",symrevno);
902 expandsym(symrevno,&numricrevno);
903 aprintf(stderr,"expanded number: %s; ",numricrevno.string);
904 aprintf(stderr,"Date: ");
905 fgets(date, 20, stdin); aprintf(stderr,"%s; ",date);
906 aprintf(stderr,"Author: ");
907 fgets(author, 20, stdin); aprintf(stderr,"%s; ",author);
908 aprintf(stderr,"State: ");
909 fgets(state, 20, stdin); aprintf(stderr, "%s;\n", state);
910 target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0,
911 *state?state:(char*)0, &gendeltas);
912 if (target) {
913 while (gendeltas) {
914 aprintf(stderr,"%s\n",gendeltas->first->num);
915 gendeltas = gendeltas->next;
918 } while (true);
919 aprintf(stderr,"done\n");
920 exitmain(EXIT_SUCCESS);
923 void exiterr() { _exit(EXIT_FAILURE); }
925 #endif