xref: /netbsd-src/usr.bin/crunch/crunchgen/crunchgen.c (revision 93f9db1b75d415b78f73ed629beeb86235153473)
1 /*	$NetBSD: crunchgen.c,v 1.9 1998/09/13 05:32:18 wrstuden Exp $	*/
2 /*
3  * Copyright (c) 1994 University of Maryland
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Author: James da Silva, Systems Design and Analysis Group
24  *			   Computer Science Department
25  *			   University of Maryland at College Park
26  */
27 /*
28  * ========================================================================
29  * crunchgen.c
30  *
31  * Generates a Makefile and main C file for a crunched executable,
32  * from specs given in a .conf file.
33  */
34 #include <sys/cdefs.h>
35 #ifndef lint
36 __RCSID("$NetBSD: crunchgen.c,v 1.9 1998/09/13 05:32:18 wrstuden Exp $");
37 #endif
38 
39 #include <stdlib.h>
40 #include <unistd.h>
41 #include <stdio.h>
42 #include <ctype.h>
43 #include <string.h>
44 
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <sys/param.h>
48 
49 #define CRUNCH_VERSION	"0.2"
50 
51 #define MAXLINELEN	16384
52 #define MAXFIELDS 	 2048
53 
54 
55 /* internal representation of conf file: */
56 
57 /* simple lists of strings suffice for most parms */
58 
59 typedef struct strlst {
60     struct strlst *next;
61     char *str;
62 } strlst_t;
63 
64 /* progs have structure, each field can be set with "special" or calculated */
65 
66 typedef struct prog {
67     struct prog *next;
68     char *name, *ident;
69     char *srcdir, *objdir;
70     strlst_t *objs, *objpaths;
71     strlst_t *links;
72     int goterror;
73 } prog_t;
74 
75 
76 /* global state */
77 
78 strlst_t *srcdirs = NULL;
79 strlst_t *libs    = NULL;
80 prog_t   *progs   = NULL;
81 
82 char line[MAXLINELEN];
83 
84 char confname[MAXPATHLEN], infilename[MAXPATHLEN];
85 char outmkname[MAXPATHLEN], outcfname[MAXPATHLEN], execfname[MAXPATHLEN];
86 char tempfname[MAXPATHLEN], cachename[MAXPATHLEN], curfilename[MAXPATHLEN];
87 char topdir[MAXPATHLEN];
88 char libdir[MAXPATHLEN] = "/usr/lib";
89 int linenum = -1;
90 int goterror = 0;
91 
92 char *pname = "crunchgen";
93 
94 int verbose, readcache;	/* options */
95 int reading_cache;
96 char *machine;
97 
98 /* general library routines */
99 
100 void status(char *str);
101 void out_of_memory(void);
102 void add_string(strlst_t **listp, char *str);
103 int is_dir(char *pathname);
104 int is_nonempty_file(char *pathname);
105 
106 /* helper routines for main() */
107 
108 void usage(void);
109 void parse_conf_file(void);
110 void gen_outputs(void);
111 
112 
113 int main(int argc, char **argv)
114 {
115     char *p;
116     int optc;
117     extern int optind;
118     extern char *optarg;
119 
120     if ((machine = getenv("MACHINE")) == NULL)
121 	machine = MACHINE;
122     verbose = 1;
123     readcache = 1;
124     *outmkname = *outcfname = *execfname = '\0';
125 
126     if(argc > 0) pname = argv[0];
127 
128     while((optc = getopt(argc, argv, "m:c:e:fqD:L:")) != -1) {
129 	switch(optc) {
130 	case 'f':	readcache = 0; break;
131 	case 'q':	verbose = 0; break;
132 
133 	case 'm':	strcpy(outmkname, optarg); break;
134 	case 'c':	strcpy(outcfname, optarg); break;
135 	case 'e':	strcpy(execfname, optarg); break;
136 
137 	case 'D':	strcpy(topdir, optarg); break;
138 	case 'L':	strcpy(libdir, optarg); break;
139 
140 	case '?':
141 	default:	usage();
142 	}
143     }
144 
145     argc -= optind;
146     argv += optind;
147 
148     if(argc != 1) usage();
149 
150     /*
151      * generate filenames
152      */
153 
154     strcpy(infilename, argv[0]);
155 
156     /* confname = `basename infilename .conf` */
157 
158     if((p=strrchr(infilename, '/')) != NULL) strcpy(confname, p+1);
159     else strcpy(confname, infilename);
160     if((p=strrchr(confname, '.')) != NULL && !strcmp(p, ".conf")) *p = '\0';
161 
162     if (!*outmkname)
163 	(void)snprintf(outmkname, sizeof(outmkname), "%s.mk", confname);
164     if (!*outcfname)
165 	(void)snprintf(outcfname, sizeof(outcfname), "%s.c", confname);
166     if (!*execfname)
167 	(void)snprintf(execfname, sizeof(execfname), "%s", confname);
168 
169     (void)snprintf(cachename, sizeof(cachename), "%s.cache", confname);
170     (void)snprintf(tempfname, sizeof(tempfname), ".tmp_%sXXXXXX", confname);
171 
172     parse_conf_file();
173     gen_outputs();
174 
175     exit(goterror);
176 }
177 
178 
179 void usage(void)
180 {
181     fprintf(stderr,
182 	"%s [-fq] [-m <makefile>] [-c <c file>] [-e <exec file>] <conffile>\n",
183 	    pname);
184     exit(1);
185 }
186 
187 
188 /*
189  * ========================================================================
190  * parse_conf_file subsystem
191  *
192  */
193 
194 /* helper routines for parse_conf_file */
195 
196 void parse_one_file(char *filename);
197 void parse_line(char *line, int *fc, char **fv, int nf);
198 void add_srcdirs(int argc, char **argv);
199 void add_progs(int argc, char **argv);
200 void add_link(int argc, char **argv);
201 void add_libs(int argc, char **argv);
202 void add_special(int argc, char **argv);
203 
204 prog_t *find_prog(char *str);
205 void add_prog(char *progname);
206 
207 
208 void parse_conf_file(void)
209 {
210     if(!is_nonempty_file(infilename)) {
211 	fprintf(stderr, "%s: fatal: input file \"%s\" not found.\n",
212 		pname, infilename);
213 	exit(1);
214     }
215     parse_one_file(infilename);
216     if(readcache && is_nonempty_file(cachename)) {
217 	reading_cache = 1;
218 	parse_one_file(cachename);
219     }
220 }
221 
222 
223 void parse_one_file(char *filename)
224 {
225     char *fieldv[MAXFIELDS];
226     int fieldc;
227     void (*f)(int c, char **v);
228     FILE *cf;
229 
230     (void)snprintf(line, sizeof(line), "reading %s", filename);
231     status(line);
232     strcpy(curfilename, filename);
233 
234     if((cf = fopen(curfilename, "r")) == NULL) {
235 	perror(curfilename);
236 	goterror = 1;
237 	return;
238     }
239 
240     linenum = 0;
241     while(fgets(line, MAXLINELEN, cf) != NULL) {
242 	linenum++;
243 	parse_line(line, &fieldc, fieldv, MAXFIELDS);
244 	if(fieldc < 1) continue;
245 	if(!strcmp(fieldv[0], "srcdirs"))	f = add_srcdirs;
246 	else if(!strcmp(fieldv[0], "progs"))    f = add_progs;
247 	else if(!strcmp(fieldv[0], "ln"))	f = add_link;
248 	else if(!strcmp(fieldv[0], "libs"))	f = add_libs;
249 	else if(!strcmp(fieldv[0], "special"))	f = add_special;
250 	else {
251 	    fprintf(stderr, "%s:%d: skipping unknown command `%s'.\n",
252 		    curfilename, linenum, fieldv[0]);
253 	    goterror = 1;
254 	    continue;
255 	}
256 	if(fieldc < 2) {
257 	    fprintf(stderr,
258 		    "%s:%d: %s command needs at least 1 argument, skipping.\n",
259 		    curfilename, linenum, fieldv[0]);
260 	    goterror = 1;
261 	    continue;
262 	}
263 	f(fieldc, fieldv);
264     }
265 
266     if(ferror(cf)) {
267 	perror(curfilename);
268 	goterror = 1;
269     }
270     fclose(cf);
271 }
272 
273 
274 void parse_line(char *line, int *fc, char **fv, int nf)
275 {
276     char *p;
277 
278     p = line;
279     *fc = 0;
280     while(1) {
281 	while(isspace(*p)) p++;
282 	if(*p == '\0' || *p == '#') break;
283 
284 	if(*fc < nf) fv[(*fc)++] = p;
285 	while(*p && !isspace(*p) && *p != '#') p++;
286 	if(*p == '\0' || *p == '#') break;
287 	*p++ = '\0';
288     }
289     if(*p) *p = '\0';		/* needed for '#' case */
290 }
291 
292 
293 void add_srcdirs(int argc, char **argv)
294 {
295     int i;
296     char tmppath[MAXPATHLEN];
297 
298     for(i=1;i<argc;i++) {
299 	if (argv[i][0] == '/' || topdir[0] == '\0')
300 		strcpy(tmppath, argv[i]);
301 	else {
302 		strcpy(tmppath, topdir);
303 		strcat(tmppath, "/");
304 		strcat(tmppath, argv[i]);
305 	}
306 	if(is_dir(tmppath))
307 	    add_string(&srcdirs, tmppath);
308 	else {
309 	    fprintf(stderr, "%s:%d: `%s' is not a directory, skipping it.\n",
310 		    curfilename, linenum, tmppath);
311 	    goterror = 1;
312 	}
313     }
314 }
315 
316 
317 void add_progs(int argc, char **argv)
318 {
319     int i;
320 
321     for(i=1;i<argc;i++)
322 	add_prog(argv[i]);
323 }
324 
325 
326 void add_prog(char *progname)
327 {
328     prog_t *p1, *p2;
329 
330     /* add to end, but be smart about dups */
331 
332     for(p1 = NULL, p2 = progs; p2 != NULL; p1 = p2, p2 = p2->next)
333 	if(!strcmp(p2->name, progname)) return;
334 
335     p2 = malloc(sizeof(prog_t));
336     if(p2) p2->name = strdup(progname);
337     if(!p2 || !p2->name)
338 	out_of_memory();
339 
340     p2->next = NULL;
341     if(p1 == NULL) progs = p2;
342     else p1->next = p2;
343 
344     p2->ident = p2->srcdir = p2->objdir = NULL;
345     p2->links = p2->objs = NULL;
346     p2->goterror = 0;
347 }
348 
349 
350 void add_link(int argc, char **argv)
351 {
352     int i;
353     prog_t *p = find_prog(argv[1]);
354 
355     if(p == NULL) {
356 	fprintf(stderr,
357 		"%s:%d: no prog %s previously declared, skipping link.\n",
358 		curfilename, linenum, argv[1]);
359 	goterror = 1;
360 	return;
361     }
362     for(i=2;i<argc;i++)
363 	add_string(&p->links, argv[i]);
364 }
365 
366 
367 void add_libs(int argc, char **argv)
368 {
369     int i;
370 
371     for(i=1;i<argc;i++)
372 	add_string(&libs, argv[i]);
373 }
374 
375 
376 void add_special(int argc, char **argv)
377 {
378     int i;
379     prog_t *p = find_prog(argv[1]);
380 
381     if(p == NULL) {
382 	if(reading_cache) return;
383 	fprintf(stderr,
384 		"%s:%d: no prog %s previously declared, skipping special.\n",
385 		curfilename, linenum, argv[1]);
386 	goterror = 1;
387 	return;
388     }
389 
390     if(!strcmp(argv[2], "ident")) {
391 	if(argc != 4) goto argcount;
392 	if((p->ident = strdup(argv[3])) == NULL)
393 	    out_of_memory();
394     }
395     else if(!strcmp(argv[2], "srcdir")) {
396 	if(argc != 4) goto argcount;
397 	if (argv[3][0] == '/' || topdir[0] == '\0') {
398 	    if((p->srcdir = strdup(argv[3])) == NULL)
399 		out_of_memory();
400 	} else {
401 	    char tmppath[MAXPATHLEN];
402 	    strcpy(tmppath, topdir);
403 	    strcat(tmppath, "/");
404 	    strcat(tmppath, argv[3]);
405 	    if((p->srcdir = strdup(tmppath)) == NULL)
406 		out_of_memory();
407 	}
408     }
409     else if(!strcmp(argv[2], "objdir")) {
410 	if(argc != 4) goto argcount;
411 	if((p->objdir = strdup(argv[3])) == NULL)
412 	    out_of_memory();
413     }
414     else if(!strcmp(argv[2], "objs")) {
415 	p->objs = NULL;
416 	for(i=3;i<argc;i++)
417 	    add_string(&p->objs, argv[i]);
418     }
419     else if(!strcmp(argv[2], "objpaths")) {
420 	p->objpaths = NULL;
421 	for(i=3;i<argc;i++)
422 	    add_string(&p->objpaths, argv[i]);
423     }
424     else {
425 	fprintf(stderr, "%s:%d: bad parameter name `%s', skipping line.\n",
426 		curfilename, linenum, argv[2]);
427 	goterror = 1;
428     }
429     return;
430 
431 
432  argcount:
433     fprintf(stderr,
434 	    "%s:%d: too %s arguments, expected \"special %s %s <string>\".\n",
435 	    curfilename, linenum, argc < 4? "few" : "many", argv[1], argv[2]);
436     goterror = 1;
437 }
438 
439 
440 prog_t *find_prog(char *str)
441 {
442     prog_t *p;
443 
444     for(p = progs; p != NULL; p = p->next)
445 	if(!strcmp(p->name, str)) return p;
446 
447     return NULL;
448 }
449 
450 
451 /*
452  * ========================================================================
453  * gen_outputs subsystem
454  *
455  */
456 
457 /* helper subroutines */
458 
459 void remove_error_progs(void);
460 void fillin_program(prog_t *p);
461 void gen_specials_cache(void);
462 void gen_output_makefile(void);
463 void gen_output_cfile(void);
464 
465 void fillin_program_objs(prog_t *p, char *path);
466 void top_makefile_rules(FILE *outmk);
467 void prog_makefile_rules(FILE *outmk, prog_t *p);
468 void output_strlst(FILE *outf, strlst_t *lst);
469 char *genident(char *str);
470 char *dir_search(char *progname);
471 
472 
473 void gen_outputs(void)
474 {
475     prog_t *p;
476 
477     for(p = progs; p != NULL; p = p->next)
478 	fillin_program(p);
479 
480     remove_error_progs();
481     gen_specials_cache();
482     gen_output_cfile();
483     gen_output_makefile();
484     status("");
485     fprintf(stderr,
486 	    "Run \"make -f %s objs exe\" to build crunched binary.\n",
487 	    outmkname);
488 }
489 
490 
491 void fillin_program(prog_t *p)
492 {
493     char path[MAXPATHLEN];
494     char *srcparent;
495     strlst_t *s;
496 
497     (void)snprintf(line, sizeof(line), "filling in parms for %s", p->name);
498     status(line);
499 
500     if(!p->ident)
501 	p->ident = genident(p->name);
502     if(!p->srcdir) {
503 	srcparent = dir_search(p->name);
504 	if(srcparent) {
505 	    (void)snprintf(path, sizeof(path), "%s/%s", srcparent, p->name);
506 	    if(is_dir(path))
507 		p->srcdir = strdup(path);
508 	}
509     }
510     if(!p->objdir && p->srcdir) {
511 	(void)snprintf(path, sizeof(path), "%s/obj", p->srcdir);
512 	if(is_dir(path))
513 	    p->objdir = strdup(path);
514 	else {
515 	    (void)snprintf(path, sizeof(path), "%s/obj.%s", p->srcdir, machine);
516 	    if(is_dir(path))
517 		p->objdir = strdup(path);
518 	    else
519 	        p->objdir = p->srcdir;
520         }
521     }
522 
523     if(p->srcdir)
524 	(void)snprintf(path, sizeof(path), "%s/Makefile", p->srcdir);
525     if(!p->objs && p->srcdir && is_nonempty_file(path))
526 	fillin_program_objs(p, path);
527 
528     if(!p->objpaths && p->objdir && p->objs)
529 	for(s = p->objs; s != NULL; s = s->next) {
530 	    (void)snprintf(line, sizeof(line), "%s/%s", p->objdir, s->str);
531 	    add_string(&p->objpaths, line);
532 	}
533 
534     if(!p->srcdir && verbose)
535 	fprintf(stderr, "%s: %s: warning: could not find source directory.\n",
536 		infilename, p->name);
537     if(!p->objs && verbose)
538 	fprintf(stderr, "%s: %s: warning: could not find any .o files.\n",
539 		infilename, p->name);
540 
541     if(!p->objpaths) {
542 	fprintf(stderr,
543 		"%s: %s: error: no objpaths specified or calculated.\n",
544 		infilename, p->name);
545 	p->goterror = goterror = 1;
546     }
547 }
548 
549 void fillin_program_objs(prog_t *p, char *path)
550 {
551     char *obj, *cp;
552     int rc;
553     int fd;
554     FILE *f;
555 
556     /* discover the objs from the srcdir Makefile */
557 
558     if((fd = mkstemp(tempfname)) < 0) {
559 	perror(tempfname);
560 	exit(1);
561     }
562 
563     if((f = fdopen(fd, "w")) == NULL) {
564 	perror(tempfname);
565 	goterror = 1;
566 	return;
567     }
568 
569     fprintf(f, ".include \"%s\"\n", path);
570     fprintf(f, ".if defined(PROG) && !defined(OBJS)\n");
571     fprintf(f, "OBJS=${PROG}.o\n");
572     fprintf(f, ".endif\n");
573     fprintf(f, "crunchgen_objs:\n\t@echo 'OBJS= '${OBJS}\n");
574     fclose(f);
575 
576     (void)snprintf(line, sizeof(line), "make -f %s crunchgen_objs 2>&1",
577 	tempfname);
578     if((f = popen(line, "r")) == NULL) {
579 	perror("submake pipe");
580 	goterror = 1;
581 	return;
582     }
583 
584     while(fgets(line, MAXLINELEN, f)) {
585 	if(strncmp(line, "OBJS= ", 6)) {
586 	    if (strcmp(line,
587 	   	"sh: warning: running as root with dot in PATH\n") == 0)
588 		    continue;
589 	    fprintf(stderr, "make error: %s", line);
590 	    goterror = 1;
591 	    continue;
592 	}
593 	cp = line + 6;
594 	while(isspace(*cp)) cp++;
595 	while(*cp) {
596 	    obj = cp;
597 	    while(*cp && !isspace(*cp)) cp++;
598 	    if(*cp) *cp++ = '\0';
599 	    add_string(&p->objs, obj);
600 	    while(isspace(*cp)) cp++;
601 	}
602     }
603     if((rc=pclose(f)) != 0) {
604 	fprintf(stderr, "make error: make returned %d\n", rc);
605 	goterror = 1;
606     }
607     unlink(tempfname);
608 }
609 
610 void remove_error_progs(void)
611 {
612     prog_t *p1, *p2;
613 
614     p1 = NULL; p2 = progs;
615     while(p2 != NULL) {
616 	if(!p2->goterror)
617 	    p1 = p2, p2 = p2->next;
618 	else {
619 	    /* delete it from linked list */
620 	    fprintf(stderr, "%s: %s: ignoring program because of errors.\n",
621 		    infilename, p2->name);
622 	    if(p1) p1->next = p2->next;
623 	    else progs = p2->next;
624 	    p2 = p2->next;
625 	}
626     }
627 }
628 
629 void gen_specials_cache(void)
630 {
631     FILE *cachef;
632     prog_t *p;
633 
634     (void)snprintf(line, sizeof(line), "generating %s", cachename);
635     status(line);
636 
637     if((cachef = fopen(cachename, "w")) == NULL) {
638 	perror(cachename);
639 	goterror = 1;
640 	return;
641     }
642 
643     fprintf(cachef, "# %s - parm cache generated from %s by crunchgen %s\n\n",
644 	    cachename, infilename, CRUNCH_VERSION);
645 
646     for(p = progs; p != NULL; p = p->next) {
647 	fprintf(cachef, "\n");
648 	if(p->srcdir)
649 	    fprintf(cachef, "special %s srcdir %s\n", p->name, p->srcdir);
650 	if(p->objdir)
651 	    fprintf(cachef, "special %s objdir %s\n", p->name, p->objdir);
652 	if(p->objs) {
653 	    fprintf(cachef, "special %s objs", p->name);
654 	    output_strlst(cachef, p->objs);
655 	}
656 	fprintf(cachef, "special %s objpaths", p->name);
657 	output_strlst(cachef, p->objpaths);
658     }
659     fclose(cachef);
660 }
661 
662 
663 void gen_output_makefile(void)
664 {
665     prog_t *p;
666     FILE *outmk;
667 
668     (void)snprintf(line, sizeof(line), "generating %s", outmkname);
669     status(line);
670 
671     if((outmk = fopen(outmkname, "w")) == NULL) {
672 	perror(outmkname);
673 	goterror = 1;
674 	return;
675     }
676 
677     fprintf(outmk, "# %s - generated from %s by crunchgen %s\n\n",
678 	    outmkname, infilename, CRUNCH_VERSION);
679 
680     top_makefile_rules(outmk);
681 
682     for(p = progs; p != NULL; p = p->next)
683 	prog_makefile_rules(outmk, p);
684 
685     fprintf(outmk, "\n# ========\n");
686     fclose(outmk);
687 }
688 
689 
690 void gen_output_cfile(void)
691 {
692     extern char *crunched_skel[];
693     char **cp;
694     FILE *outcf;
695     prog_t *p;
696     strlst_t *s;
697 
698     (void)snprintf(line, sizeof(line), "generating %s", outcfname);
699     status(line);
700 
701     if((outcf = fopen(outcfname, "w")) == NULL) {
702 	perror(outcfname);
703 	goterror = 1;
704 	return;
705     }
706 
707     fprintf(outcf,
708 	  "/* %s - generated from %s by crunchgen %s */\n",
709 	    outcfname, infilename, CRUNCH_VERSION);
710 
711     fprintf(outcf, "#define EXECNAME \"%s\"\n", execfname);
712     for(cp = crunched_skel; *cp != NULL; cp++)
713 	fprintf(outcf, "%s\n", *cp);
714 
715     for(p = progs; p != NULL; p = p->next)
716 	fprintf(outcf, "extern int _crunched_%s_stub();\n", p->ident);
717 
718     fprintf(outcf, "\nstruct stub entry_points[] = {\n");
719     for(p = progs; p != NULL; p = p->next) {
720 	fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
721 		p->name, p->ident);
722 	for(s = p->links; s != NULL; s = s->next)
723 	    fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
724 		    s->str, p->ident);
725     }
726 
727     fprintf(outcf, "\t{ EXECNAME, crunched_main },\n");
728     fprintf(outcf, "\t{ NULL, NULL }\n};\n");
729     fclose(outcf);
730 }
731 
732 
733 char *genident(char *str)
734 {
735     char *n,*s,*d;
736 
737     /*
738      * generates a Makefile/C identifier from a program name, mapping '-' to
739      * '_' and ignoring all other non-identifier characters.  This leads to
740      * programs named "foo.bar" and "foobar" to map to the same identifier.
741      */
742 
743     if((n = strdup(str)) == NULL)
744 	return NULL;
745     for(d = s = n; *s != '\0'; s++) {
746 	if(*s == '-') *d++ = '_';
747 	else if(*s == '_' || isalnum(*s)) *d++ = *s;
748     }
749     *d = '\0';
750     return n;
751 }
752 
753 
754 char *dir_search(char *progname)
755 {
756     char path[MAXPATHLEN];
757     strlst_t *dir;
758 
759     for(dir=srcdirs; dir != NULL; dir=dir->next) {
760 	(void)snprintf(path, sizeof(path), "%s/%s", dir->str, progname);
761 	if(is_dir(path)) return dir->str;
762     }
763     return NULL;
764 }
765 
766 
767 void top_makefile_rules(FILE *outmk)
768 {
769     prog_t *p;
770 
771     fprintf(outmk, "STRIP=strip\n");
772     fprintf(outmk, "LIBS=");
773     fprintf(outmk, "-L%s ", libdir);
774     output_strlst(outmk, libs);
775 
776     fprintf(outmk, "CRUNCHED_OBJS=");
777     for(p = progs; p != NULL; p = p->next)
778 	fprintf(outmk, " %s.lo", p->name);
779     fprintf(outmk, "\n");
780 
781     fprintf(outmk, "SUBMAKE_TARGETS=");
782     for(p = progs; p != NULL; p = p->next)
783 	fprintf(outmk, " %s_make", p->ident);
784     fprintf(outmk, "\n\n");
785 
786     fprintf(outmk, "%s: %s.o $(CRUNCHED_OBJS)\n",
787 	    execfname, execfname);
788     fprintf(outmk, "\t$(CC) -static -o %s %s.o $(CRUNCHED_OBJS) $(LIBS)\n",
789 	    execfname, execfname);
790     fprintf(outmk, "\t$(STRIP) %s\n", execfname);
791     fprintf(outmk, "all: objs exe\nobjs: $(SUBMAKE_TARGETS)\n");
792     fprintf(outmk, "exe: %s\n", execfname);
793     fprintf(outmk, "clean:\n\trm -f %s *.lo *.o *_stub.c\n",
794 	    execfname);
795 }
796 
797 
798 void prog_makefile_rules(FILE *outmk, prog_t *p)
799 {
800     fprintf(outmk, "\n# -------- %s\n\n", p->name);
801 
802     if(p->srcdir && p->objs) {
803 	fprintf(outmk, "%s_SRCDIR=%s\n", p->ident, p->srcdir);
804 	fprintf(outmk, "%s_OBJS=", p->ident);
805 	output_strlst(outmk, p->objs);
806 	fprintf(outmk, "%s_make:\n", p->ident);
807 	fprintf(outmk, "\t(cd $(%s_SRCDIR); make $(%s_OBJS))\n\n",
808 		p->ident, p->ident);
809     }
810     else
811 	fprintf(outmk, "%s_make:\n\t@echo \"** cannot make objs for %s\"\n\n",
812 		p->ident, p->name);
813 
814     fprintf(outmk,   "%s_OBJPATHS=", p->ident);
815     output_strlst(outmk, p->objpaths);
816 
817     fprintf(outmk, "%s_stub.c:\n", p->name);
818     fprintf(outmk, "\techo \""
819 	           "int _crunched_%s_stub(int argc, char **argv, char **envp)"
820 	           "{return main(argc,argv,envp);}\" >%s_stub.c\n",
821 	    p->ident, p->name);
822     fprintf(outmk, "%s.lo: %s_stub.o $(%s_OBJPATHS)\n",
823 	    p->name, p->name, p->ident);
824     fprintf(outmk, "\t${LD} -dc -r -o %s.lo %s_stub.o $(%s_OBJPATHS)\n",
825 	    p->name, p->name, p->ident);
826     fprintf(outmk, "\tcrunchide -k _crunched_%s_stub %s.lo\n",
827 	    p->ident, p->name);
828 }
829 
830 void output_strlst(FILE *outf, strlst_t *lst)
831 {
832     for(; lst != NULL; lst = lst->next)
833 	fprintf(outf, " %s", lst->str);
834     fprintf(outf, "\n");
835 }
836 
837 
838 /*
839  * ========================================================================
840  * general library routines
841  *
842  */
843 
844 void status(char *str)
845 {
846     static int lastlen = 0;
847     int len, spaces;
848 
849     if(!verbose) return;
850 
851     len = strlen(str);
852     spaces = lastlen - len;
853     if(spaces < 1) spaces = 1;
854 
855     fprintf(stderr, " [%s]%*.*s\r", str, spaces, spaces, " ");
856     fflush(stderr);
857     lastlen = len;
858 }
859 
860 
861 void out_of_memory(void)
862 {
863     fprintf(stderr, "%s: %d: out of memory, stopping.\n", infilename, linenum);
864     exit(1);
865 }
866 
867 
868 void add_string(strlst_t **listp, char *str)
869 {
870     strlst_t *p1, *p2;
871 
872     /* add to end, but be smart about dups */
873 
874     for(p1 = NULL, p2 = *listp; p2 != NULL; p1 = p2, p2 = p2->next)
875 	if(!strcmp(p2->str, str)) return;
876 
877     p2 = malloc(sizeof(strlst_t));
878     if(p2) p2->str = strdup(str);
879     if(!p2 || !p2->str)
880 	out_of_memory();
881 
882     p2->next = NULL;
883     if(p1 == NULL) *listp = p2;
884     else p1->next = p2;
885 }
886 
887 
888 int is_dir(char *pathname)
889 {
890     struct stat buf;
891 
892     if(stat(pathname, &buf) == -1)
893 	return 0;
894     return S_ISDIR(buf.st_mode);
895 }
896 
897 int is_nonempty_file(char *pathname)
898 {
899     struct stat buf;
900 
901     if(stat(pathname, &buf) == -1)
902 	return 0;
903 
904     return S_ISREG(buf.st_mode) && buf.st_size > 0;
905 }
906