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 <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/param.h>
38 
39 #include <ctype.h>
40 #include <err.h>
41 #include <paths.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 #define CRUNCH_VERSION	"0.2"
48 
49 #define MAXLINELEN	16384
50 #define MAXFIELDS 	 2048
51 
52 
53 /* internal representation of conf file: */
54 
55 /* simple lists of strings suffice for most parms */
56 
57 typedef struct strlst {
58 	struct strlst *next;
59 	char *str;
60 } strlst_t;
61 
62 /* progs have structure, each field can be set with "special" or calculated */
63 
64 typedef struct prog {
65 	struct prog *next;	/* link field */
66 	char *name;		/* program name */
67 	char *ident;		/* C identifier for the program name */
68 	char *srcdir;
69 	char *realsrcdir;
70 	char *objdir;
71 	char *objvar;		/* Makefile variable to replace OBJS */
72 	strlst_t *objs, *objpaths;
73 	strlst_t *buildopts;
74 	strlst_t *keeplist;
75 	strlst_t *links;
76 	strlst_t *libs;
77 	strlst_t *libs_so;
78 	int goterror;
79 } prog_t;
80 
81 
82 /* global state */
83 
84 strlst_t *buildopts = NULL;
85 strlst_t *srcdirs   = NULL;
86 strlst_t *libs      = NULL;
87 strlst_t *libs_so   = NULL;
88 prog_t   *progs     = NULL;
89 
90 char confname[MAXPATHLEN], infilename[MAXPATHLEN];
91 char outmkname[MAXPATHLEN], outcfname[MAXPATHLEN], execfname[MAXPATHLEN];
92 char tempfname[MAXPATHLEN], cachename[MAXPATHLEN], curfilename[MAXPATHLEN];
93 char outhdrname[MAXPATHLEN] ;	/* user-supplied header for *.mk */
94 char *objprefix;		/* where are the objects ? */
95 int linenum = -1;
96 int goterror = 0;
97 
98 int verbose, readcache;		/* options */
99 int reading_cache;
100 int makeobj = 0;		/* add 'make obj' rules to the makefile */
101 
102 int list_mode;
103 
104 /* general library routines */
105 
106 void status(char *str);
107 void out_of_memory(void);
108 void add_string(strlst_t **listp, char *str);
109 int is_dir(char *pathname);
110 int is_nonempty_file(char *pathname);
111 int subtract_strlst(strlst_t **lista, strlst_t **listb);
112 int in_list(strlst_t **listp, char *str);
113 
114 /* helper routines for main() */
115 
116 void usage(void);
117 void parse_conf_file(void);
118 void gen_outputs(void);
119 
120 
121 int main(int argc, char **argv)
122 {
123 	char *p;
124 	int optc;
125 
126 	verbose = 1;
127 	readcache = 1;
128 	*outmkname = *outcfname = *execfname = '\0';
129 
130 	p = getenv("MAKEOBJDIRPREFIX");
131 	if (p == NULL || *p == '\0')
132 		objprefix = "/usr/obj"; /* default */
133 	else
134 		if ((objprefix = strdup(p)) == NULL)
135 			out_of_memory();
136 
137 	while((optc = getopt(argc, argv, "lh:m:c:e:p:foq")) != -1) {
138 		switch(optc) {
139 		case 'f':
140 			readcache = 0;
141 			break;
142 		case 'o':
143 			makeobj = 1;
144 			break;
145 		case 'q':
146 			verbose = 0;
147 			break;
148 
149 		case 'm':
150 			strlcpy(outmkname, optarg, sizeof(outmkname));
151 			break;
152 		case 'p':
153 			if ((objprefix = strdup(optarg)) == NULL)
154 				out_of_memory();
155 			break;
156 
157 		case 'h':
158 			strlcpy(outhdrname, optarg, sizeof(outhdrname));
159 			break;
160 		case 'c':
161 			strlcpy(outcfname, optarg, sizeof(outcfname));
162 			break;
163 		case 'e':
164 			strlcpy(execfname, optarg, sizeof(execfname));
165 			break;
166 
167 		case 'l':
168 			list_mode++;
169 			verbose = 0;
170 			break;
171 
172 		case '?':
173 		default:
174 			usage();
175 		}
176 	}
177 
178 	argc -= optind;
179 	argv += optind;
180 
181 	if (argc != 1)
182 		usage();
183 
184 	/*
185 	 * generate filenames
186 	 */
187 
188 	strlcpy(infilename, argv[0], sizeof(infilename));
189 
190 	/* confname = `basename infilename .conf` */
191 
192 	if ((p=strrchr(infilename, '/')) != NULL)
193 		strlcpy(confname, p + 1, sizeof(confname));
194 	else
195 		strlcpy(confname, infilename, sizeof(confname));
196 
197 	if ((p=strrchr(confname, '.')) != NULL && !strcmp(p, ".conf"))
198 		*p = '\0';
199 
200 	if (!*outmkname)
201 		snprintf(outmkname, sizeof(outmkname), "%s.mk", confname);
202 	if (!*outcfname)
203 		snprintf(outcfname, sizeof(outcfname), "%s.c", confname);
204 	if (!*execfname)
205 		snprintf(execfname, sizeof(execfname), "%s", confname);
206 
207 	snprintf(cachename, sizeof(cachename), "%s.cache", confname);
208 	snprintf(tempfname, sizeof(tempfname), "%s/crunchgen_%sXXXXXX",
209 	getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP, confname);
210 
211 	parse_conf_file();
212 	if (list_mode)
213 		exit(goterror);
214 
215 	gen_outputs();
216 
217 	exit(goterror);
218 }
219 
220 
221 void usage(void)
222 {
223 	fprintf(stderr, "%s%s\n\t%s%s\n", "usage: crunchgen [-foq] ",
224 	    "[-h <makefile-header-name>] [-m <makefile>]",
225 	    "[-p <obj-prefix>] [-c <c-file-name>] [-e <exec-file>] ",
226 	    "<conffile>");
227 	exit(1);
228 }
229 
230 
231 /*
232  * ========================================================================
233  * parse_conf_file subsystem
234  *
235  */
236 
237 /* helper routines for parse_conf_file */
238 
239 void parse_one_file(char *filename);
240 void parse_line(char *line, int *fc, char **fv, int nf);
241 void add_srcdirs(int argc, char **argv);
242 void add_progs(int argc, char **argv);
243 void add_link(int argc, char **argv);
244 void add_libs(int argc, char **argv);
245 void add_libs_so(int argc, char **argv);
246 void add_buildopts(int argc, char **argv);
247 void add_special(int argc, char **argv);
248 
249 prog_t *find_prog(char *str);
250 void add_prog(char *progname);
251 
252 
253 void parse_conf_file(void)
254 {
255 	if (!is_nonempty_file(infilename))
256 		errx(1, "fatal: input file \"%s\" not found", infilename);
257 
258 	parse_one_file(infilename);
259 	if (readcache && is_nonempty_file(cachename)) {
260 		reading_cache = 1;
261 		parse_one_file(cachename);
262 	}
263 }
264 
265 
266 void parse_one_file(char *filename)
267 {
268 	char *fieldv[MAXFIELDS];
269 	int fieldc;
270 	void (*f)(int c, char **v);
271 	FILE *cf;
272 	char line[MAXLINELEN];
273 
274 	snprintf(line, sizeof(line), "reading %s", filename);
275 	status(line);
276 	strlcpy(curfilename, filename, sizeof(curfilename));
277 
278 	if ((cf = fopen(curfilename, "r")) == NULL) {
279 		warn("%s", curfilename);
280 		goterror = 1;
281 		return;
282 	}
283 
284 	linenum = 0;
285 	while (fgets(line, MAXLINELEN, cf) != NULL) {
286 		linenum++;
287 		parse_line(line, &fieldc, fieldv, MAXFIELDS);
288 
289 		if (fieldc < 1)
290 			continue;
291 
292 		if (!strcmp(fieldv[0], "srcdirs"))
293 			f = add_srcdirs;
294 		else if(!strcmp(fieldv[0], "progs"))
295 			f = add_progs;
296 		else if(!strcmp(fieldv[0], "ln"))
297 			f = add_link;
298 		else if(!strcmp(fieldv[0], "libs"))
299 			f = add_libs;
300 		else if(!strcmp(fieldv[0], "libs_so"))
301 			f = add_libs_so;
302 		else if(!strcmp(fieldv[0], "buildopts"))
303 			f = add_buildopts;
304 		else if(!strcmp(fieldv[0], "special"))
305 			f = add_special;
306 		else {
307 			warnx("%s:%d: skipping unknown command `%s'",
308 			    curfilename, linenum, fieldv[0]);
309 			goterror = 1;
310 			continue;
311 		}
312 
313 		if (fieldc < 2) {
314 			warnx("%s:%d: %s %s",
315 			    curfilename, linenum, fieldv[0],
316 			    "command needs at least 1 argument, skipping");
317 			goterror = 1;
318 			continue;
319 		}
320 
321 		f(fieldc, fieldv);
322 	}
323 
324 	if (ferror(cf)) {
325 		warn("%s", curfilename);
326 		goterror = 1;
327 	}
328 	fclose(cf);
329 }
330 
331 
332 void parse_line(char *line, int *fc, char **fv, int nf)
333 {
334 	char *p;
335 
336 	p = line;
337 	*fc = 0;
338 
339 	while (1) {
340 		while (isspace(*p))
341 			p++;
342 
343 		if (*p == '\0' || *p == '#')
344 			break;
345 
346 		if (*fc < nf)
347 			fv[(*fc)++] = p;
348 
349 		while (*p && !isspace(*p) && *p != '#')
350 			p++;
351 
352 		if (*p == '\0' || *p == '#')
353 			break;
354 
355 		*p++ = '\0';
356 	}
357 
358 	if (*p)
359 		*p = '\0';		/* needed for '#' case */
360 }
361 
362 
363 void add_srcdirs(int argc, char **argv)
364 {
365 	int i;
366 
367 	for (i = 1; i < argc; i++) {
368 		if (is_dir(argv[i]))
369 			add_string(&srcdirs, argv[i]);
370 		else {
371 			warnx("%s:%d: `%s' is not a directory, skipping it",
372 			    curfilename, linenum, argv[i]);
373 			goterror = 1;
374 		}
375 	}
376 }
377 
378 
379 void add_progs(int argc, char **argv)
380 {
381 	int i;
382 
383 	for (i = 1; i < argc; i++)
384 		add_prog(argv[i]);
385 }
386 
387 
388 void add_prog(char *progname)
389 {
390 	prog_t *p1, *p2;
391 
392 	/* add to end, but be smart about dups */
393 
394 	for (p1 = NULL, p2 = progs; p2 != NULL; p1 = p2, p2 = p2->next)
395 		if (!strcmp(p2->name, progname))
396 			return;
397 
398 	p2 = malloc(sizeof(prog_t));
399 	if(p2) {
400 		memset(p2, 0, sizeof(prog_t));
401 		p2->name = strdup(progname);
402 	}
403 	if (!p2 || !p2->name)
404 		out_of_memory();
405 
406 	p2->next = NULL;
407 	if (p1 == NULL)
408 		progs = p2;
409 	else
410 		p1->next = p2;
411 
412 	p2->ident = NULL;
413 	p2->srcdir = NULL;
414 	p2->realsrcdir = NULL;
415 	p2->objdir = NULL;
416 	p2->links = NULL;
417 	p2->libs = NULL;
418 	p2->libs_so = NULL;
419 	p2->objs = NULL;
420 	p2->keeplist = NULL;
421 	p2->buildopts = NULL;
422 	p2->goterror = 0;
423 
424 	if (list_mode)
425 		printf("%s\n",progname);
426 }
427 
428 
429 void add_link(int argc, char **argv)
430 {
431 	int i;
432 	prog_t *p = find_prog(argv[1]);
433 
434 	if (p == NULL) {
435 		warnx("%s:%d: no prog %s previously declared, skipping link",
436 		    curfilename, linenum, argv[1]);
437 		goterror = 1;
438 		return;
439 	}
440 
441 	for (i = 2; i < argc; i++) {
442 		if (list_mode)
443 			printf("%s\n",argv[i]);
444 
445 		add_string(&p->links, argv[i]);
446 	}
447 }
448 
449 
450 void add_libs(int argc, char **argv)
451 {
452 	int i;
453 
454 	for(i = 1; i < argc; i++) {
455 		add_string(&libs, argv[i]);
456 		if ( in_list(&libs_so, argv[i]) )
457 			warnx("%s:%d: "
458 				"library `%s' specified as dynamic earlier",
459 				curfilename, linenum, argv[i]);
460 	}
461 }
462 
463 
464 void add_libs_so(int argc, char **argv)
465 {
466 	int i;
467 
468 	for(i = 1; i < argc; i++) {
469 		add_string(&libs_so, argv[i]);
470 		if ( in_list(&libs, argv[i]) )
471 			warnx("%s:%d: "
472 				"library `%s' specified as static earlier",
473 				curfilename, linenum, argv[i]);
474 	}
475 }
476 
477 
478 void add_buildopts(int argc, char **argv)
479 {
480 	int i;
481 
482 	for (i = 1; i < argc; i++)
483 		add_string(&buildopts, argv[i]);
484 }
485 
486 
487 void add_special(int argc, char **argv)
488 {
489 	int i;
490 	prog_t *p = find_prog(argv[1]);
491 
492 	if (p == NULL) {
493 		if (reading_cache)
494 			return;
495 
496 		warnx("%s:%d: no prog %s previously declared, skipping special",
497 		    curfilename, linenum, argv[1]);
498 		goterror = 1;
499 		return;
500 	}
501 
502 	if (!strcmp(argv[2], "ident")) {
503 		if (argc != 4)
504 			goto argcount;
505 		if ((p->ident = strdup(argv[3])) == NULL)
506 			out_of_memory();
507 	} else if (!strcmp(argv[2], "srcdir")) {
508 		if (argc != 4)
509 			goto argcount;
510 		if ((p->srcdir = strdup(argv[3])) == NULL)
511 			out_of_memory();
512 	} else if (!strcmp(argv[2], "objdir")) {
513 		if(argc != 4)
514 			goto argcount;
515 		if((p->objdir = strdup(argv[3])) == NULL)
516 			out_of_memory();
517 	} else if (!strcmp(argv[2], "objs")) {
518 		p->objs = NULL;
519 		for (i = 3; i < argc; i++)
520 			add_string(&p->objs, argv[i]);
521 	} else if (!strcmp(argv[2], "objpaths")) {
522 		p->objpaths = NULL;
523 		for (i = 3; i < argc; i++)
524 			add_string(&p->objpaths, argv[i]);
525 	} else if (!strcmp(argv[2], "keep")) {
526 		p->keeplist = NULL;
527 		for(i = 3; i < argc; i++)
528 			add_string(&p->keeplist, argv[i]);
529 	} else if (!strcmp(argv[2], "objvar")) {
530 		if(argc != 4)
531 			goto argcount;
532 		if ((p->objvar = strdup(argv[3])) == NULL)
533 			out_of_memory();
534 	} else if (!strcmp(argv[2], "buildopts")) {
535 		p->buildopts = NULL;
536 		for (i = 3; i < argc; i++)
537 			add_string(&p->buildopts, argv[i]);
538 	} else if (!strcmp(argv[2], "lib")) {
539 		for (i = 3; i < argc; i++)
540 			add_string(&p->libs, argv[i]);
541 	} else {
542 		warnx("%s:%d: bad parameter name `%s', skipping line",
543 		    curfilename, linenum, argv[2]);
544 		goterror = 1;
545 	}
546 	return;
547 
548  argcount:
549 	warnx("%s:%d: too %s arguments, expected \"special %s %s <string>\"",
550 	    curfilename, linenum, argc < 4? "few" : "many", argv[1], argv[2]);
551 	goterror = 1;
552 }
553 
554 
555 prog_t *find_prog(char *str)
556 {
557 	prog_t *p;
558 
559 	for (p = progs; p != NULL; p = p->next)
560 		if (!strcmp(p->name, str))
561 			return p;
562 
563 	return NULL;
564 }
565 
566 
567 /*
568  * ========================================================================
569  * gen_outputs subsystem
570  *
571  */
572 
573 /* helper subroutines */
574 
575 void remove_error_progs(void);
576 void fillin_program(prog_t *p);
577 void gen_specials_cache(void);
578 void gen_output_makefile(void);
579 void gen_output_cfile(void);
580 
581 void fillin_program_objs(prog_t *p, char *path);
582 void top_makefile_rules(FILE *outmk);
583 void prog_makefile_rules(FILE *outmk, prog_t *p);
584 void output_strlst(FILE *outf, strlst_t *lst);
585 char *genident(char *str);
586 char *dir_search(char *progname);
587 
588 
589 void gen_outputs(void)
590 {
591 	prog_t *p;
592 
593 	for (p = progs; p != NULL; p = p->next)
594 		fillin_program(p);
595 
596 	remove_error_progs();
597 	gen_specials_cache();
598 	gen_output_cfile();
599 	gen_output_makefile();
600 	status("");
601 	fprintf(stderr,
602 	    "Run \"make -f %s\" to build crunched binary.\n", outmkname);
603 }
604 
605 /*
606  * run the makefile for the program to find which objects are necessary
607  */
608 void fillin_program(prog_t *p)
609 {
610 	char path[MAXPATHLEN];
611 	char line[MAXLINELEN];
612 	FILE *f;
613 
614 	snprintf(line, MAXLINELEN, "filling in parms for %s", p->name);
615 	status(line);
616 
617 	if (!p->ident)
618 		p->ident = genident(p->name);
619 
620 	/* look for the source directory if one wasn't specified by a special */
621 	if (!p->srcdir) {
622 		p->srcdir = dir_search(p->name);
623 	}
624 
625 	/* Determine the actual srcdir (maybe symlinked). */
626 	if (p->srcdir) {
627 		snprintf(line, MAXLINELEN, "cd %s && echo -n `/bin/pwd`",
628 		    p->srcdir);
629 		f = popen(line,"r");
630 		if (!f)
631 			errx(1, "Can't execute: %s\n", line);
632 
633 		path[0] = '\0';
634 		fgets(path, sizeof path, f);
635 		if (pclose(f))
636 			errx(1, "Can't execute: %s\n", line);
637 
638 		if (!*path)
639 			errx(1, "Can't perform pwd on: %s\n", p->srcdir);
640 
641 		p->realsrcdir = strdup(path);
642 	}
643 
644 	/* Unless the option to make object files was specified the
645 	* the objects will be built in the source directory unless
646 	* an object directory already exists.
647 	*/
648 	if (!makeobj && !p->objdir && p->srcdir) {
649 		snprintf(line, sizeof line, "%s/%s", objprefix, p->realsrcdir);
650 		if (is_dir(line)) {
651 			if ((p->objdir = strdup(line)) == NULL)
652 			out_of_memory();
653 		} else
654 			p->objdir = p->realsrcdir;
655 	}
656 
657 	/*
658 	* XXX look for a Makefile.{name} in local directory first.
659 	* This lets us override the original Makefile.
660 	*/
661 	snprintf(path, sizeof(path), "Makefile.%s", p->name);
662 	if (is_nonempty_file(path)) {
663 		snprintf(line, MAXLINELEN, "Using %s for %s", path, p->name);
664 		status(line);
665 	} else
666 		if (p->srcdir)
667 			snprintf(path, sizeof(path), "%s/Makefile", p->srcdir);
668 	if (!p->objs && p->srcdir && is_nonempty_file(path))
669 		fillin_program_objs(p, path);
670 
671 	if (!p->srcdir && !p->objdir && verbose)
672 		warnx("%s: %s: %s",
673 		    "warning: could not find source directory",
674 		    infilename, p->name);
675 	if (!p->objs && verbose)
676 		warnx("%s: %s: warning: could not find any .o files",
677 		    infilename, p->name);
678 
679 	if ((!p->srcdir || !p->objdir) && !p->objs)
680 		p->goterror = 1;
681 }
682 
683 void fillin_program_objs(prog_t *p, char *path)
684 {
685 	char *obj, *cp;
686 	int fd, rc;
687 	FILE *f;
688 	char *objvar="OBJS";
689 	strlst_t *s;
690 	char line[MAXLINELEN];
691 
692 	/* discover the objs from the srcdir Makefile */
693 
694 	if ((fd = mkstemp(tempfname)) == -1) {
695 		perror(tempfname);
696 		exit(1);
697 	}
698 	if ((f = fdopen(fd, "w")) == NULL) {
699 		warn("%s", tempfname);
700 		goterror = 1;
701 		return;
702 	}
703 	if (p->objvar)
704 		objvar = p->objvar;
705 
706 	/*
707 	* XXX include outhdrname (e.g. to contain Make variables)
708 	*/
709 	if (outhdrname[0] != '\0')
710 		fprintf(f, ".include \"%s\"\n", outhdrname);
711 	fprintf(f, ".include \"%s\"\n", path);
712 	if (buildopts) {
713 		fprintf(f, "BUILDOPTS+=");
714 		output_strlst(f, buildopts);
715 	}
716 	fprintf(f, ".if defined(PROG) && !defined(%s)\n", objvar);
717 	fprintf(f, "%s=${PROG}.o\n", objvar);
718 	fprintf(f, ".endif\n");
719 	fprintf(f, "loop:\n\t@echo 'OBJS= '${%s}\n", objvar);
720 
721 	fprintf(f, "crunchgen_objs:\n"
722 	    "\t@cd %s && make -f %s $(BUILDOPTS) $(%s_OPTS)",
723 	    p->srcdir, tempfname, p->ident);
724 	for (s = p->buildopts; s != NULL; s = s->next)
725 		fprintf(f, " %s", s->str);
726 	fprintf(f, " loop\n");
727 
728 	fclose(f);
729 
730 	snprintf(line, MAXLINELEN, "cd %s && make -f %s crunchgen_objs",
731 	    p->srcdir, tempfname);
732 	if ((f = popen(line, "r")) == NULL) {
733 		warn("submake pipe");
734 		goterror = 1;
735 		return;
736 	}
737 
738 	while(fgets(line, MAXLINELEN, f)) {
739 		if (strncmp(line, "OBJS= ", 6)) {
740 			warnx("make error: %s", line);
741 			goterror = 1;
742 			continue;
743 		}
744 
745 		cp = line + 6;
746 		while (isspace(*cp))
747 			cp++;
748 
749 		while(*cp) {
750 			obj = cp;
751 			while (*cp && !isspace(*cp))
752 				cp++;
753 			if (*cp)
754 				*cp++ = '\0';
755 			add_string(&p->objs, obj);
756 			while (isspace(*cp))
757 				cp++;
758 		}
759 	}
760 
761 	if ((rc=pclose(f)) != 0) {
762 		warnx("make error: make returned %d", rc);
763 		goterror = 1;
764 	}
765 
766 	unlink(tempfname);
767 }
768 
769 void remove_error_progs(void)
770 {
771 	prog_t *p1, *p2;
772 
773 	p1 = NULL; p2 = progs;
774 	while (p2 != NULL) {
775 		if (!p2->goterror)
776 			p1 = p2, p2 = p2->next;
777 		else {
778 			/* delete it from linked list */
779 			warnx("%s: %s: ignoring program because of errors",
780 			    infilename, p2->name);
781 			if (p1)
782 				p1->next = p2->next;
783 			else
784 				progs = p2->next;
785 			p2 = p2->next;
786 		}
787 	}
788 }
789 
790 void gen_specials_cache(void)
791 {
792 	FILE *cachef;
793 	prog_t *p;
794 	char line[MAXLINELEN];
795 
796 	snprintf(line, MAXLINELEN, "generating %s", cachename);
797 	status(line);
798 
799 	if ((cachef = fopen(cachename, "w")) == NULL) {
800 		warn("%s", cachename);
801 		goterror = 1;
802 		return;
803 	}
804 
805 	fprintf(cachef, "# %s - parm cache generated from %s by crunchgen "
806 	    " %s\n\n",
807 	    cachename, infilename, CRUNCH_VERSION);
808 
809 	for (p = progs; p != NULL; p = p->next) {
810 		fprintf(cachef, "\n");
811 		if (p->srcdir)
812 			fprintf(cachef, "special %s srcdir %s\n",
813 			    p->name, p->srcdir);
814 		if (p->objdir)
815 			fprintf(cachef, "special %s objdir %s\n",
816 			    p->name, p->objdir);
817 		if (p->objs) {
818 			fprintf(cachef, "special %s objs", p->name);
819 			output_strlst(cachef, p->objs);
820 		}
821 		if (p->objpaths) {
822 			fprintf(cachef, "special %s objpaths", p->name);
823 			output_strlst(cachef, p->objpaths);
824 		}
825 	}
826 	fclose(cachef);
827 }
828 
829 
830 void gen_output_makefile(void)
831 {
832 	prog_t *p;
833 	FILE *outmk;
834 	char line[MAXLINELEN];
835 
836 	snprintf(line, MAXLINELEN, "generating %s", outmkname);
837 	status(line);
838 
839 	if ((outmk = fopen(outmkname, "w")) == NULL) {
840 		warn("%s", outmkname);
841 		goterror = 1;
842 		return;
843 	}
844 
845 	fprintf(outmk, "# %s - generated from %s by crunchgen %s\n\n",
846 	    outmkname, infilename, CRUNCH_VERSION);
847 
848 	if (outhdrname[0] != '\0')
849 		fprintf(outmk, ".include \"%s\"\n", outhdrname);
850 
851 	top_makefile_rules(outmk);
852 	for (p = progs; p != NULL; p = p->next)
853 		prog_makefile_rules(outmk, p);
854 
855 	fprintf(outmk, "\n# ========\n");
856 	fclose(outmk);
857 }
858 
859 
860 void gen_output_cfile(void)
861 {
862 	extern char *crunched_skel[];
863 	char **cp;
864 	FILE *outcf;
865 	prog_t *p;
866 	strlst_t *s;
867 	char line[MAXLINELEN];
868 
869 	snprintf(line, MAXLINELEN, "generating %s", outcfname);
870 	status(line);
871 
872 	if((outcf = fopen(outcfname, "w")) == NULL) {
873 		warn("%s", outcfname);
874 		goterror = 1;
875 		return;
876 	}
877 
878 	fprintf(outcf,
879 	    "/* %s - generated from %s by crunchgen %s */\n",
880 	    outcfname, infilename, CRUNCH_VERSION);
881 
882 	fprintf(outcf, "#define EXECNAME \"%s\"\n", execfname);
883 	for (cp = crunched_skel; *cp != NULL; cp++)
884 		fprintf(outcf, "%s\n", *cp);
885 
886 	for (p = progs; p != NULL; p = p->next)
887 		fprintf(outcf, "extern int _crunched_%s_stub();\n", p->ident);
888 
889 	fprintf(outcf, "\nstruct stub entry_points[] = {\n");
890 	for (p = progs; p != NULL; p = p->next) {
891 		fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
892 		    p->name, p->ident);
893 		for (s = p->links; s != NULL; s = s->next)
894 			fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
895 			    s->str, p->ident);
896 	}
897 
898 	fprintf(outcf, "\t{ EXECNAME, crunched_main },\n");
899 	fprintf(outcf, "\t{ NULL, NULL }\n};\n");
900 	fclose(outcf);
901 }
902 
903 
904 char *genident(char *str)
905 {
906 	char *n, *s, *d;
907 
908 	/*
909 	 * generates a Makefile/C identifier from a program name,
910 	 * mapping '-' to '_' and ignoring all other non-identifier
911 	 * characters.  This leads to programs named "foo.bar" and
912 	 * "foobar" to map to the same identifier.
913 	 */
914 
915 	if ((n = strdup(str)) == NULL)
916 		return NULL;
917 	for (d = s = n; *s != '\0'; s++) {
918 		if (*s == '-')
919 			*d++ = '_';
920 		else if (*s == '_' || isalnum(*s))
921 			*d++ = *s;
922 	}
923 	*d = '\0';
924 	return n;
925 }
926 
927 
928 char *dir_search(char *progname)
929 {
930 	char path[MAXPATHLEN];
931 	strlst_t *dir;
932 	char *srcdir;
933 
934 	for (dir = srcdirs; dir != NULL; dir = dir->next) {
935 		snprintf(path, MAXPATHLEN, "%s/%s", dir->str, progname);
936 		if (!is_dir(path))
937 			continue;
938 
939 		if ((srcdir = strdup(path)) == NULL)
940 			out_of_memory();
941 
942 		return srcdir;
943 	}
944 	return NULL;
945 }
946 
947 
948 void top_makefile_rules(FILE *outmk)
949 {
950 	prog_t *p;
951 
952 	if ( subtract_strlst(&libs, &libs_so) )
953 		fprintf(outmk, "# NOTE: Some LIBS declarations below overridden by LIBS_SO\n");
954 
955 	fprintf(outmk, "LIBS+=");
956 	output_strlst(outmk, libs);
957 
958 	fprintf(outmk, "LIBS_SO+=");
959 	output_strlst(outmk, libs_so);
960 
961 	if (makeobj) {
962 		fprintf(outmk, "MAKEOBJDIRPREFIX?=%s\n", objprefix);
963 		fprintf(outmk, "MAKEENV=env MAKEOBJDIRPREFIX=$(MAKEOBJDIRPREFIX)\n");
964 		fprintf(outmk, "CRUNCHMAKE=$(MAKEENV) $(MAKE)\n");
965 	} else {
966 		fprintf(outmk, "CRUNCHMAKE=$(MAKE)\n");
967 	}
968 
969 	if (buildopts) {
970 		fprintf(outmk, "BUILDOPTS+=");
971 		output_strlst(outmk, buildopts);
972 	}
973 
974 	fprintf(outmk, "CRUNCHED_OBJS=");
975 	for (p = progs; p != NULL; p = p->next)
976 		fprintf(outmk, " %s.lo", p->name);
977 	fprintf(outmk, "\n");
978 
979 	fprintf(outmk, "SUBMAKE_TARGETS=");
980 	for (p = progs; p != NULL; p = p->next)
981 		fprintf(outmk, " %s_make", p->ident);
982 	fprintf(outmk, "\nSUBCLEAN_TARGETS=");
983 	for (p = progs; p != NULL; p = p->next)
984 		fprintf(outmk, " %s_clean", p->ident);
985 	fprintf(outmk, "\n\n");
986 
987 	fprintf(outmk, "all: objs exe\nobjs: $(SUBMAKE_TARGETS)\n");
988 	fprintf(outmk, "exe: %s\n", execfname);
989 	fprintf(outmk, "%s: %s.o $(CRUNCHED_OBJS) $(SUBMAKE_TARGETS)\n", execfname, execfname);
990 	fprintf(outmk, ".if defined(LIBS_SO) && !empty(LIBS_SO)\n");
991 	fprintf(outmk, "\t$(CC) -o %s %s.o $(CRUNCHED_OBJS) \\\n",
992 	    execfname, execfname);
993 	fprintf(outmk, "\t\t-Xlinker -Bstatic $(LIBS) \\\n");
994 	fprintf(outmk, "\t\t-Xlinker -Bdynamic $(LIBS_SO)\n");
995 	fprintf(outmk, ".else\n");
996 	fprintf(outmk, "\t$(CC) -static -o %s %s.o $(CRUNCHED_OBJS) $(LIBS)\n",
997 	    execfname, execfname);
998 	fprintf(outmk, ".endif\n");
999 	fprintf(outmk, "\tstrip %s\n", execfname);
1000 	fprintf(outmk, "realclean: clean subclean\n");
1001 	fprintf(outmk, "clean:\n\trm -f %s *.lo *.o *_stub.c\n", execfname);
1002 	fprintf(outmk, "subclean: $(SUBCLEAN_TARGETS)\n");
1003 }
1004 
1005 
1006 void prog_makefile_rules(FILE *outmk, prog_t *p)
1007 {
1008 	strlst_t *lst;
1009 
1010 	fprintf(outmk, "\n# -------- %s\n\n", p->name);
1011 
1012 	fprintf(outmk, "%s_OBJDIR=", p->ident);
1013 	if (p->objdir)
1014 		fprintf(outmk, "%s", p->objdir);
1015 	else
1016 		fprintf(outmk, "$(MAKEOBJDIRPREFIX)/$(%s_REALSRCDIR)\n",
1017 		    p->ident);
1018 	fprintf(outmk, "\n");
1019 
1020 	fprintf(outmk, "%s_OBJPATHS=", p->ident);
1021 	if (p->objpaths)
1022 		output_strlst(outmk, p->objpaths);
1023 	else {
1024 		for (lst = p->objs; lst != NULL; lst = lst->next) {
1025 			fprintf(outmk, " $(%s_OBJDIR)/%s", p->ident, lst->str);
1026 		}
1027 		fprintf(outmk, "\n");
1028 	}
1029 
1030 	if (p->srcdir && p->objs) {
1031 		fprintf(outmk, "%s_SRCDIR=%s\n", p->ident, p->srcdir);
1032 		fprintf(outmk, "%s_REALSRCDIR=%s\n", p->ident, p->realsrcdir);
1033 
1034 		fprintf(outmk, "%s_OBJS=", p->ident);
1035 		output_strlst(outmk, p->objs);
1036 		if (p->buildopts != NULL) {
1037 			fprintf(outmk, "%s_OPTS+=", p->ident);
1038 			output_strlst(outmk, p->buildopts);
1039 		}
1040 #if 0
1041 		fprintf(outmk, "$(%s_OBJPATHS): %s_make\n\n", p->ident, p->ident);
1042 #endif
1043 		fprintf(outmk, "%s_make:\n", p->ident);
1044 		fprintf(outmk, "\t(cd $(%s_SRCDIR) && ", p->ident);
1045 		if (makeobj)
1046 			fprintf(outmk, "$(CRUNCHMAKE) obj && ");
1047 		fprintf(outmk, "\\\n");
1048 		fprintf(outmk, "\t\t$(CRUNCHMAKE) $(BUILDOPTS) $(%s_OPTS) depend &&",
1049 		    p->ident);
1050 		fprintf(outmk, "\\\n");
1051 		fprintf(outmk, "\t\t$(CRUNCHMAKE) $(BUILDOPTS) $(%s_OPTS) "
1052 		    "$(%s_OBJS))",
1053 		    p->ident, p->ident);
1054 		fprintf(outmk, "\n");
1055 		fprintf(outmk, "%s_clean:\n", p->ident);
1056 		fprintf(outmk, "\t(cd $(%s_SRCDIR) && $(CRUNCHMAKE) $(BUILDOPTS) clean cleandepend)\n\n",
1057 		    p->ident);
1058 	} else {
1059 		fprintf(outmk, "%s_make:\n", p->ident);
1060 		fprintf(outmk, "\t@echo \"** cannot make objs for %s\"\n\n",
1061 		    p->name);
1062 	}
1063 
1064 	if (p->libs) {
1065 		fprintf(outmk, "%s_LIBS=", p->ident);
1066 		output_strlst(outmk, p->libs);
1067 	}
1068 
1069 	fprintf(outmk, "%s_stub.c:\n", p->name);
1070 	fprintf(outmk, "\techo \""
1071 	    "int _crunched_%s_stub(int argc, char **argv, char **envp)"
1072 	    "{return main(argc,argv,envp);}\" >%s_stub.c\n",
1073 	    p->ident, p->name);
1074 	fprintf(outmk, "%s.lo: %s_stub.o $(%s_OBJPATHS)",
1075 	    p->name, p->name, p->ident);
1076 	if (p->libs)
1077 		fprintf(outmk, " $(%s_LIBS)", p->ident);
1078 
1079 	fprintf(outmk, "\n");
1080 	fprintf(outmk, "\tld -dc -r -o %s.lo %s_stub.o $(%s_OBJPATHS)",
1081 	    p->name, p->name, p->ident);
1082 	if (p->libs)
1083 		fprintf(outmk, " $(%s_LIBS)", p->ident);
1084 	fprintf(outmk, "\n");
1085 	fprintf(outmk, "\tcrunchide -k _crunched_%s_stub ", p->ident);
1086 	for (lst = p->keeplist; lst != NULL; lst = lst->next)
1087 		fprintf(outmk, "-k _%s ", lst->str);
1088 	fprintf(outmk, "%s.lo\n", p->name);
1089 }
1090 
1091 void output_strlst(FILE *outf, strlst_t *lst)
1092 {
1093 	for (; lst != NULL; lst = lst->next)
1094 		if ( strlen(lst->str) )
1095 			fprintf(outf, " %s", lst->str);
1096 	fprintf(outf, "\n");
1097 }
1098 
1099 
1100 /*
1101  * ========================================================================
1102  * general library routines
1103  *
1104  */
1105 
1106 void status(char *str)
1107 {
1108 	static int lastlen = 0;
1109 	int len, spaces;
1110 
1111 	if (!verbose)
1112 		return;
1113 
1114 	len = strlen(str);
1115 	spaces = lastlen - len;
1116 	if (spaces < 1)
1117 		spaces = 1;
1118 
1119 	fprintf(stderr, " [%s]%*.*s\r", str, spaces, spaces, " ");
1120 	fflush(stderr);
1121 	lastlen = len;
1122 }
1123 
1124 
1125 void out_of_memory(void)
1126 {
1127 	err(1, "%s: %d: out of memory, stopping", infilename, linenum);
1128 }
1129 
1130 
1131 void add_string(strlst_t **listp, char *str)
1132 {
1133 	strlst_t *p1, *p2;
1134 
1135 	/* add to end, but be smart about dups */
1136 
1137 	for (p1 = NULL, p2 = *listp; p2 != NULL; p1 = p2, p2 = p2->next)
1138 		if (!strcmp(p2->str, str))
1139 			return;
1140 
1141 	p2 = malloc(sizeof(strlst_t));
1142 	if (p2) {
1143 		p2->next = NULL;
1144 		p2->str = strdup(str);
1145     	}
1146 	if (!p2 || !p2->str)
1147 		out_of_memory();
1148 
1149 	if (p1 == NULL)
1150 		*listp = p2;
1151 	else
1152 		p1->next = p2;
1153 }
1154 
1155 int subtract_strlst(strlst_t **lista, strlst_t **listb)
1156 {
1157 	int subtract_count = 0;
1158 	strlst_t *p1;
1159 	for (p1 = *listb; p1 != NULL; p1 = p1->next)
1160 		if ( in_list(lista, p1->str) ) {
1161 			warnx("Will compile library `%s' dynamically", p1->str);
1162 			strcat(p1->str, "");
1163 			subtract_count++;
1164 		}
1165 	return subtract_count;
1166 }
1167 
1168 int in_list(strlst_t **listp, char *str)
1169 {
1170 	strlst_t *p1;
1171 	for (p1 = *listp; p1 != NULL; p1 = p1->next)
1172 		if (!strcmp(p1->str, str))
1173 			return 1;
1174 	return 0;
1175 }
1176 
1177 int is_dir(char *pathname)
1178 {
1179 	struct stat buf;
1180 
1181 	if (stat(pathname, &buf) == -1)
1182 		return 0;
1183 
1184 	return S_ISDIR(buf.st_mode);
1185 }
1186 
1187 int is_nonempty_file(char *pathname)
1188 {
1189 	struct stat buf;
1190 
1191 	if (stat(pathname, &buf) == -1)
1192 		return 0;
1193 
1194 	return S_ISREG(buf.st_mode) && buf.st_size > 0;
1195 }
1196