gprof.c revision 1590
1/*
2 * Copyright (c) 1983, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static char copyright[] =
36"@(#) Copyright (c) 1983, 1993\n\
37	The Regents of the University of California.  All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41static char sccsid[] = "@(#)gprof.c	8.1 (Berkeley) 6/6/93";
42#endif /* not lint */
43
44#include "gprof.h"
45
46char	*whoami = "gprof";
47
48    /*
49     *	things which get -E excluded by default.
50     */
51char	*defaultEs[] = { "mcount" , "__mcleanup" , 0 };
52
53static struct gmonhdr	gmonhdr;
54
55main(argc, argv)
56    int argc;
57    char **argv;
58{
59    char	**sp;
60    nltype	**timesortnlp;
61
62    --argc;
63    argv++;
64    debug = 0;
65    bflag = TRUE;
66    while ( *argv != 0 && **argv == '-' ) {
67	(*argv)++;
68	switch ( **argv ) {
69	case 'a':
70	    aflag = TRUE;
71	    break;
72	case 'b':
73	    bflag = FALSE;
74	    break;
75	case 'C':
76	    Cflag = TRUE;
77	    cyclethreshold = atoi( *++argv );
78	    break;
79	case 'c':
80#if defined(vax) || defined(tahoe)
81	    cflag = TRUE;
82#else
83	    fprintf(stderr, "gprof: -c isn't supported on this architecture yet\n");
84	    exit(1);
85#endif
86	    break;
87	case 'd':
88	    dflag = TRUE;
89	    setlinebuf(stdout);
90	    debug |= atoi( *++argv );
91	    debug |= ANYDEBUG;
92#	    ifdef DEBUG
93		printf("[main] debug = %d\n", debug);
94#	    else not DEBUG
95		printf("%s: -d ignored\n", whoami);
96#	    endif DEBUG
97	    break;
98	case 'E':
99	    ++argv;
100	    addlist( Elist , *argv );
101	    Eflag = TRUE;
102	    addlist( elist , *argv );
103	    eflag = TRUE;
104	    break;
105	case 'e':
106	    addlist( elist , *++argv );
107	    eflag = TRUE;
108	    break;
109	case 'F':
110	    ++argv;
111	    addlist( Flist , *argv );
112	    Fflag = TRUE;
113	    addlist( flist , *argv );
114	    fflag = TRUE;
115	    break;
116	case 'f':
117	    addlist( flist , *++argv );
118	    fflag = TRUE;
119	    break;
120	case 'k':
121	    addlist( kfromlist , *++argv );
122	    addlist( ktolist , *++argv );
123	    kflag = TRUE;
124	    break;
125	case 's':
126	    sflag = TRUE;
127	    break;
128	case 'z':
129	    zflag = TRUE;
130	    break;
131	}
132	argv++;
133    }
134    if ( *argv != 0 ) {
135	a_outname  = *argv;
136	argv++;
137    } else {
138	a_outname  = A_OUTNAME;
139    }
140    if ( *argv != 0 ) {
141	gmonname = *argv;
142	argv++;
143    } else {
144	gmonname = GMONNAME;
145    }
146	/*
147	 *	turn off default functions
148	 */
149    for ( sp = &defaultEs[0] ; *sp ; sp++ ) {
150	Eflag = TRUE;
151	addlist( Elist , *sp );
152	eflag = TRUE;
153	addlist( elist , *sp );
154    }
155	/*
156	 *	get information about a.out file.
157	 */
158    getnfile();
159	/*
160	 *	get information about mon.out file(s).
161	 */
162    do	{
163	getpfile( gmonname );
164	if ( *argv != 0 ) {
165	    gmonname = *argv;
166	}
167    } while ( *argv++ != 0 );
168	/*
169	 *	how many ticks per second?
170	 *	if we can't tell, report time in ticks.
171	 */
172    if (hz == 0) {
173	hz = 1;
174	fprintf(stderr, "time is in ticks, not seconds\n");
175    }
176	/*
177	 *	dump out a gmon.sum file if requested
178	 */
179    if ( sflag ) {
180	dumpsum( GMONSUM );
181    }
182	/*
183	 *	assign samples to procedures
184	 */
185    asgnsamples();
186	/*
187	 *	assemble the dynamic profile
188	 */
189    timesortnlp = doarcs();
190	/*
191	 *	print the dynamic profile
192	 */
193    printgprof( timesortnlp );
194	/*
195	 *	print the flat profile
196	 */
197    printprof();
198	/*
199	 *	print the index
200	 */
201    printindex();
202    done();
203}
204
205    /*
206     * Set up string and symbol tables from a.out.
207     *	and optionally the text space.
208     * On return symbol table is sorted by value.
209     */
210getnfile()
211{
212    FILE	*nfile;
213    int		valcmp();
214
215    nfile = fopen( a_outname ,"r");
216    if (nfile == NULL) {
217	perror( a_outname );
218	done();
219    }
220    fread(&xbuf, 1, sizeof(xbuf), nfile);
221    if (N_BADMAG(xbuf)) {
222	fprintf(stderr, "%s: %s: bad format\n", whoami , a_outname );
223	done();
224    }
225    getstrtab(nfile);
226    getsymtab(nfile);
227    gettextspace( nfile );
228    qsort(nl, nname, sizeof(nltype), valcmp);
229    fclose(nfile);
230#   ifdef DEBUG
231	if ( debug & AOUTDEBUG ) {
232	    register int j;
233
234	    for (j = 0; j < nname; j++){
235		printf("[getnfile] 0X%08x\t%s\n", nl[j].value, nl[j].name);
236	    }
237	}
238#   endif DEBUG
239}
240
241getstrtab(nfile)
242    FILE	*nfile;
243{
244
245    fseek(nfile, (long)(N_SYMOFF(xbuf) + xbuf.a_syms), 0);
246    if (fread(&ssiz, sizeof (ssiz), 1, nfile) == 0) {
247	fprintf(stderr, "%s: %s: no string table (old format?)\n" ,
248		whoami , a_outname );
249	done();
250    }
251    strtab = calloc(ssiz, 1);
252    if (strtab == NULL) {
253	fprintf(stderr, "%s: %s: no room for %d bytes of string table\n",
254		whoami , a_outname , ssiz);
255	done();
256    }
257    if (fread(strtab+sizeof(ssiz), ssiz-sizeof(ssiz), 1, nfile) != 1) {
258	fprintf(stderr, "%s: %s: error reading string table\n",
259		whoami , a_outname );
260	done();
261    }
262}
263
264    /*
265     * Read in symbol table
266     */
267getsymtab(nfile)
268    FILE	*nfile;
269{
270    register long	i;
271    int			askfor;
272    struct nlist	nbuf;
273
274    /* pass1 - count symbols */
275    fseek(nfile, (long)N_SYMOFF(xbuf), 0);
276    nname = 0;
277    for (i = xbuf.a_syms; i > 0; i -= sizeof(struct nlist)) {
278	fread(&nbuf, sizeof(nbuf), 1, nfile);
279	if ( ! funcsymbol( &nbuf ) ) {
280	    continue;
281	}
282	nname++;
283    }
284    if (nname == 0) {
285	fprintf(stderr, "%s: %s: no symbols\n", whoami , a_outname );
286	done();
287    }
288    askfor = nname + 1;
289    nl = (nltype *) calloc( askfor , sizeof(nltype) );
290    if (nl == 0) {
291	fprintf(stderr, "%s: No room for %d bytes of symbol table\n",
292		whoami, askfor * sizeof(nltype) );
293	done();
294    }
295
296    /* pass2 - read symbols */
297    fseek(nfile, (long)N_SYMOFF(xbuf), 0);
298    npe = nl;
299    nname = 0;
300    for (i = xbuf.a_syms; i > 0; i -= sizeof(struct nlist)) {
301	fread(&nbuf, sizeof(nbuf), 1, nfile);
302	if ( ! funcsymbol( &nbuf ) ) {
303#	    ifdef DEBUG
304		if ( debug & AOUTDEBUG ) {
305		    printf( "[getsymtab] rejecting: 0x%x %s\n" ,
306			    nbuf.n_type , strtab + nbuf.n_un.n_strx );
307		}
308#	    endif DEBUG
309	    continue;
310	}
311	npe->value = nbuf.n_value;
312	npe->name = strtab+nbuf.n_un.n_strx;
313#	ifdef DEBUG
314	    if ( debug & AOUTDEBUG ) {
315		printf( "[getsymtab] %d %s 0x%08x\n" ,
316			nname , npe -> name , npe -> value );
317	    }
318#	endif DEBUG
319	npe++;
320	nname++;
321    }
322    npe->value = -1;
323}
324
325    /*
326     *	read in the text space of an a.out file
327     */
328gettextspace( nfile )
329    FILE	*nfile;
330{
331
332    if ( cflag == 0 ) {
333	return;
334    }
335    textspace = (u_char *) malloc( xbuf.a_text );
336    if ( textspace == 0 ) {
337	fprintf( stderr , "%s: ran out room for %d bytes of text space:  " ,
338			whoami , xbuf.a_text );
339	fprintf( stderr , "can't do -c\n" );
340	return;
341    }
342    (void) fseek( nfile , N_TXTOFF( xbuf ) , 0 );
343    if ( fread( textspace , 1 , xbuf.a_text , nfile ) != xbuf.a_text ) {
344	fprintf( stderr , "%s: couldn't read text space:  " , whoami );
345	fprintf( stderr , "can't do -c\n" );
346	free( textspace );
347	textspace = 0;
348	return;
349    }
350}
351    /*
352     *	information from a gmon.out file is in two parts:
353     *	an array of sampling hits within pc ranges,
354     *	and the arcs.
355     */
356getpfile(filename)
357    char *filename;
358{
359    FILE		*pfile;
360    FILE		*openpfile();
361    struct rawarc	arc;
362
363    pfile = openpfile(filename);
364    readsamples(pfile);
365	/*
366	 *	the rest of the file consists of
367	 *	a bunch of <from,self,count> tuples.
368	 */
369    while ( fread( &arc , sizeof arc , 1 , pfile ) == 1 ) {
370#	ifdef DEBUG
371	    if ( debug & SAMPLEDEBUG ) {
372		printf( "[getpfile] frompc 0x%x selfpc 0x%x count %d\n" ,
373			arc.raw_frompc , arc.raw_selfpc , arc.raw_count );
374	    }
375#	endif DEBUG
376	    /*
377	     *	add this arc
378	     */
379	tally( &arc );
380    }
381    fclose(pfile);
382}
383
384FILE *
385openpfile(filename)
386    char *filename;
387{
388    struct gmonhdr	tmp;
389    FILE		*pfile;
390    int			size;
391    int			rate;
392
393    if((pfile = fopen(filename, "r")) == NULL) {
394	perror(filename);
395	done();
396    }
397    fread(&tmp, sizeof(struct gmonhdr), 1, pfile);
398    if ( s_highpc != 0 && ( tmp.lpc != gmonhdr.lpc ||
399	 tmp.hpc != gmonhdr.hpc || tmp.ncnt != gmonhdr.ncnt ) ) {
400	fprintf(stderr, "%s: incompatible with first gmon file\n", filename);
401	done();
402    }
403    gmonhdr = tmp;
404    if ( gmonhdr.version == GMONVERSION ) {
405	rate = gmonhdr.profrate;
406	size = sizeof(struct gmonhdr);
407    } else {
408	fseek(pfile, sizeof(struct ophdr), SEEK_SET);
409	size = sizeof(struct ophdr);
410	gmonhdr.profrate = rate = hertz();
411	gmonhdr.version = GMONVERSION;
412    }
413    if (hz == 0) {
414	hz = rate;
415    } else if (hz != rate) {
416	fprintf(stderr,
417	    "%s: profile clock rate (%d) %s (%d) in first gmon file\n",
418	    filename, rate, "incompatible with clock rate", hz);
419	done();
420    }
421    s_lowpc = (unsigned long) gmonhdr.lpc;
422    s_highpc = (unsigned long) gmonhdr.hpc;
423    lowpc = (unsigned long)gmonhdr.lpc / sizeof(UNIT);
424    highpc = (unsigned long)gmonhdr.hpc / sizeof(UNIT);
425    sampbytes = gmonhdr.ncnt - size;
426    nsamples = sampbytes / sizeof (UNIT);
427#   ifdef DEBUG
428	if ( debug & SAMPLEDEBUG ) {
429	    printf( "[openpfile] hdr.lpc 0x%x hdr.hpc 0x%x hdr.ncnt %d\n",
430		gmonhdr.lpc , gmonhdr.hpc , gmonhdr.ncnt );
431	    printf( "[openpfile]   s_lowpc 0x%x   s_highpc 0x%x\n" ,
432		s_lowpc , s_highpc );
433	    printf( "[openpfile]     lowpc 0x%x     highpc 0x%x\n" ,
434		lowpc , highpc );
435	    printf( "[openpfile] sampbytes %d nsamples %d\n" ,
436		sampbytes , nsamples );
437	    printf( "[openpfile] sample rate %d\n" , hz );
438	}
439#   endif DEBUG
440    return(pfile);
441}
442
443tally( rawp )
444    struct rawarc	*rawp;
445{
446    nltype		*parentp;
447    nltype		*childp;
448
449    parentp = nllookup( rawp -> raw_frompc );
450    childp = nllookup( rawp -> raw_selfpc );
451    if ( parentp == 0 || childp == 0 )
452	return;
453    if ( kflag
454	 && onlist( kfromlist , parentp -> name )
455	 && onlist( ktolist , childp -> name ) ) {
456	return;
457    }
458    childp -> ncall += rawp -> raw_count;
459#   ifdef DEBUG
460	if ( debug & TALLYDEBUG ) {
461	    printf( "[tally] arc from %s to %s traversed %d times\n" ,
462		    parentp -> name , childp -> name , rawp -> raw_count );
463	}
464#   endif DEBUG
465    addarc( parentp , childp , rawp -> raw_count );
466}
467
468/*
469 * dump out the gmon.sum file
470 */
471dumpsum( sumfile )
472    char *sumfile;
473{
474    register nltype *nlp;
475    register arctype *arcp;
476    struct rawarc arc;
477    FILE *sfile;
478
479    if ( ( sfile = fopen ( sumfile , "w" ) ) == NULL ) {
480	perror( sumfile );
481	done();
482    }
483    /*
484     * dump the header; use the last header read in
485     */
486    if ( fwrite( &gmonhdr , sizeof gmonhdr , 1 , sfile ) != 1 ) {
487	perror( sumfile );
488	done();
489    }
490    /*
491     * dump the samples
492     */
493    if (fwrite(samples, sizeof (UNIT), nsamples, sfile) != nsamples) {
494	perror( sumfile );
495	done();
496    }
497    /*
498     * dump the normalized raw arc information
499     */
500    for ( nlp = nl ; nlp < npe ; nlp++ ) {
501	for ( arcp = nlp -> children ; arcp ; arcp = arcp -> arc_childlist ) {
502	    arc.raw_frompc = arcp -> arc_parentp -> value;
503	    arc.raw_selfpc = arcp -> arc_childp -> value;
504	    arc.raw_count = arcp -> arc_count;
505	    if ( fwrite ( &arc , sizeof arc , 1 , sfile ) != 1 ) {
506		perror( sumfile );
507		done();
508	    }
509#	    ifdef DEBUG
510		if ( debug & SAMPLEDEBUG ) {
511		    printf( "[dumpsum] frompc 0x%x selfpc 0x%x count %d\n" ,
512			    arc.raw_frompc , arc.raw_selfpc , arc.raw_count );
513		}
514#	    endif DEBUG
515	}
516    }
517    fclose( sfile );
518}
519
520valcmp(p1, p2)
521    nltype *p1, *p2;
522{
523    if ( p1 -> value < p2 -> value ) {
524	return LESSTHAN;
525    }
526    if ( p1 -> value > p2 -> value ) {
527	return GREATERTHAN;
528    }
529    return EQUALTO;
530}
531
532readsamples(pfile)
533    FILE	*pfile;
534{
535    register i;
536    UNIT	sample;
537
538    if (samples == 0) {
539	samples = (UNIT *) calloc(sampbytes, sizeof (UNIT));
540	if (samples == 0) {
541	    fprintf( stderr , "%s: No room for %d sample pc's\n",
542		whoami , sampbytes / sizeof (UNIT));
543	    done();
544	}
545    }
546    for (i = 0; i < nsamples; i++) {
547	fread(&sample, sizeof (UNIT), 1, pfile);
548	if (feof(pfile))
549		break;
550	samples[i] += sample;
551    }
552    if (i != nsamples) {
553	fprintf(stderr,
554	    "%s: unexpected EOF after reading %d/%d samples\n",
555		whoami , --i , nsamples );
556	done();
557    }
558}
559
560/*
561 *	Assign samples to the procedures to which they belong.
562 *
563 *	There are three cases as to where pcl and pch can be
564 *	with respect to the routine entry addresses svalue0 and svalue1
565 *	as shown in the following diagram.  overlap computes the
566 *	distance between the arrows, the fraction of the sample
567 *	that is to be credited to the routine which starts at svalue0.
568 *
569 *	    svalue0                                         svalue1
570 *	       |                                               |
571 *	       v                                               v
572 *
573 *	       +-----------------------------------------------+
574 *	       |					       |
575 *	  |  ->|    |<-		->|         |<-		->|    |<-  |
576 *	  |         |		  |         |		  |         |
577 *	  +---------+		  +---------+		  +---------+
578 *
579 *	  ^         ^		  ^         ^		  ^         ^
580 *	  |         |		  |         |		  |         |
581 *	 pcl       pch		 pcl       pch		 pcl       pch
582 *
583 *	For the vax we assert that samples will never fall in the first
584 *	two bytes of any routine, since that is the entry mask,
585 *	thus we give call alignentries() to adjust the entry points if
586 *	the entry mask falls in one bucket but the code for the routine
587 *	doesn't start until the next bucket.  In conjunction with the
588 *	alignment of routine addresses, this should allow us to have
589 *	only one sample for every four bytes of text space and never
590 *	have any overlap (the two end cases, above).
591 */
592asgnsamples()
593{
594    register int	j;
595    UNIT		ccnt;
596    double		time;
597    unsigned long	pcl, pch;
598    register int	i;
599    unsigned long	overlap;
600    unsigned long	svalue0, svalue1;
601
602    /* read samples and assign to namelist symbols */
603    scale = highpc - lowpc;
604    scale /= nsamples;
605    alignentries();
606    for (i = 0, j = 1; i < nsamples; i++) {
607	ccnt = samples[i];
608	if (ccnt == 0)
609		continue;
610	pcl = lowpc + scale * i;
611	pch = lowpc + scale * (i + 1);
612	time = ccnt;
613#	ifdef DEBUG
614	    if ( debug & SAMPLEDEBUG ) {
615		printf( "[asgnsamples] pcl 0x%x pch 0x%x ccnt %d\n" ,
616			pcl , pch , ccnt );
617	    }
618#	endif DEBUG
619	totime += time;
620	for (j = j - 1; j < nname; j++) {
621	    svalue0 = nl[j].svalue;
622	    svalue1 = nl[j+1].svalue;
623		/*
624		 *	if high end of tick is below entry address,
625		 *	go for next tick.
626		 */
627	    if (pch < svalue0)
628		    break;
629		/*
630		 *	if low end of tick into next routine,
631		 *	go for next routine.
632		 */
633	    if (pcl >= svalue1)
634		    continue;
635	    overlap = min(pch, svalue1) - max(pcl, svalue0);
636	    if (overlap > 0) {
637#		ifdef DEBUG
638		    if (debug & SAMPLEDEBUG) {
639			printf("[asgnsamples] (0x%x->0x%x-0x%x) %s gets %f ticks %d overlap\n",
640				nl[j].value/sizeof(UNIT), svalue0, svalue1,
641				nl[j].name,
642				overlap * time / scale, overlap);
643		    }
644#		endif DEBUG
645		nl[j].time += overlap * time / scale;
646	    }
647	}
648    }
649#   ifdef DEBUG
650	if (debug & SAMPLEDEBUG) {
651	    printf("[asgnsamples] totime %f\n", totime);
652	}
653#   endif DEBUG
654}
655
656
657unsigned long
658min(a, b)
659    unsigned long a,b;
660{
661    if (a<b)
662	return(a);
663    return(b);
664}
665
666unsigned long
667max(a, b)
668    unsigned long a,b;
669{
670    if (a>b)
671	return(a);
672    return(b);
673}
674
675    /*
676     *	calculate scaled entry point addresses (to save time in asgnsamples),
677     *	and possibly push the scaled entry points over the entry mask,
678     *	if it turns out that the entry point is in one bucket and the code
679     *	for a routine is in the next bucket.
680     */
681alignentries()
682{
683    register struct nl	*nlp;
684    unsigned long	bucket_of_entry;
685    unsigned long	bucket_of_code;
686
687    for (nlp = nl; nlp < npe; nlp++) {
688	nlp -> svalue = nlp -> value / sizeof(UNIT);
689	bucket_of_entry = (nlp->svalue - lowpc) / scale;
690	bucket_of_code = (nlp->svalue + UNITS_TO_CODE - lowpc) / scale;
691	if (bucket_of_entry < bucket_of_code) {
692#	    ifdef DEBUG
693		if (debug & SAMPLEDEBUG) {
694		    printf("[alignentries] pushing svalue 0x%x to 0x%x\n",
695			    nlp->svalue, nlp->svalue + UNITS_TO_CODE);
696		}
697#	    endif DEBUG
698	    nlp->svalue += UNITS_TO_CODE;
699	}
700    }
701}
702
703bool
704funcsymbol( nlistp )
705    struct nlist	*nlistp;
706{
707    extern char	*strtab;	/* string table from a.out */
708    extern int	aflag;		/* if static functions aren't desired */
709    char	*name, c;
710
711	/*
712	 *	must be a text symbol,
713	 *	and static text symbols don't qualify if aflag set.
714	 */
715    if ( ! (  ( nlistp -> n_type == ( N_TEXT | N_EXT ) )
716	   || ( ( nlistp -> n_type == N_TEXT ) && ( aflag == 0 ) ) ) ) {
717	return FALSE;
718    }
719	/*
720	 *	can't have any `funny' characters in name,
721	 *	where `funny' includes	`.', .o file names
722	 *			and	`$', pascal labels.
723	 *	need to make an exception for sparc .mul & co.
724	 *	perhaps we should just drop this code entirely...
725	 */
726    name = strtab + nlistp -> n_un.n_strx;
727#ifdef sparc
728    if ( *name == '.' ) {
729	char *p = name + 1;
730	if ( *p == 'u' )
731	    p++;
732	if ( strcmp ( p, "mul" ) == 0 || strcmp ( p, "div" ) == 0 ||
733	     strcmp ( p, "rem" ) == 0 )
734		return TRUE;
735    }
736#endif
737    while ( c = *name++ ) {
738	if ( c == '.' || c == '$' ) {
739	    return FALSE;
740	}
741    }
742    return TRUE;
743}
744
745done()
746{
747
748    exit(0);
749}
750