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