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