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