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