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 	fprintf(f, ".POSIX:\n");
713 	if (buildopts) {
714 		fprintf(f, "BUILDOPTS+=");
715 		output_strlst(f, buildopts);
716 	}
717 	fprintf(f, ".if defined(PROG)\n");
718 	fprintf(f, "%s?=${PROG}.o\n", objvar);
719 	fprintf(f, ".endif\n");
720 	fprintf(f, "loop:\n\t@echo 'OBJS= '${%s}\n", objvar);
721 
722 	fprintf(f, "crunchgen_objs:\n"
723 	    "\t@cd %s && make -f %s $(BUILDOPTS) $(%s_OPTS)",
724 	    p->srcdir, tempfname, p->ident);
725 	for (s = p->buildopts; s != NULL; s = s->next)
726 		fprintf(f, " %s", s->str);
727 	fprintf(f, " loop\n");
728 
729 	fclose(f);
730 
731 	snprintf(line, MAXLINELEN, "cd %s && make -f %s -B crunchgen_objs",
732 	    p->srcdir, tempfname);
733 	if ((f = popen(line, "r")) == NULL) {
734 		warn("submake pipe");
735 		goterror = 1;
736 		return;
737 	}
738 
739 	while(fgets(line, MAXLINELEN, f)) {
740 		if (strncmp(line, "OBJS= ", 6)) {
741 			warnx("make error: %s", line);
742 			goterror = 1;
743 			continue;
744 		}
745 
746 		cp = line + 6;
747 		while (isspace(*cp))
748 			cp++;
749 
750 		while(*cp) {
751 			obj = cp;
752 			while (*cp && !isspace(*cp))
753 				cp++;
754 			if (*cp)
755 				*cp++ = '\0';
756 			add_string(&p->objs, obj);
757 			while (isspace(*cp))
758 				cp++;
759 		}
760 	}
761 
762 	if ((rc=pclose(f)) != 0) {
763 		warnx("make error: make returned %d", rc);
764 		goterror = 1;
765 	}
766 
767 	unlink(tempfname);
768 }
769 
770 void remove_error_progs(void)
771 {
772 	prog_t *p1, *p2;
773 
774 	p1 = NULL; p2 = progs;
775 	while (p2 != NULL) {
776 		if (!p2->goterror)
777 			p1 = p2, p2 = p2->next;
778 		else {
779 			/* delete it from linked list */
780 			warnx("%s: %s: ignoring program because of errors",
781 			    infilename, p2->name);
782 			if (p1)
783 				p1->next = p2->next;
784 			else
785 				progs = p2->next;
786 			p2 = p2->next;
787 		}
788 	}
789 }
790 
791 void gen_specials_cache(void)
792 {
793 	FILE *cachef;
794 	prog_t *p;
795 	char line[MAXLINELEN];
796 
797 	snprintf(line, MAXLINELEN, "generating %s", cachename);
798 	status(line);
799 
800 	if ((cachef = fopen(cachename, "w")) == NULL) {
801 		warn("%s", cachename);
802 		goterror = 1;
803 		return;
804 	}
805 
806 	fprintf(cachef, "# %s - parm cache generated from %s by crunchgen "
807 	    " %s\n\n",
808 	    cachename, infilename, CRUNCH_VERSION);
809 
810 	for (p = progs; p != NULL; p = p->next) {
811 		fprintf(cachef, "\n");
812 		if (p->srcdir)
813 			fprintf(cachef, "special %s srcdir %s\n",
814 			    p->name, p->srcdir);
815 		if (p->objdir)
816 			fprintf(cachef, "special %s objdir %s\n",
817 			    p->name, p->objdir);
818 		if (p->objs) {
819 			fprintf(cachef, "special %s objs", p->name);
820 			output_strlst(cachef, p->objs);
821 		}
822 		if (p->objpaths) {
823 			fprintf(cachef, "special %s objpaths", p->name);
824 			output_strlst(cachef, p->objpaths);
825 		}
826 	}
827 	fclose(cachef);
828 }
829 
830 
831 void gen_output_makefile(void)
832 {
833 	prog_t *p;
834 	FILE *outmk;
835 	char line[MAXLINELEN];
836 
837 	snprintf(line, MAXLINELEN, "generating %s", outmkname);
838 	status(line);
839 
840 	if ((outmk = fopen(outmkname, "w")) == NULL) {
841 		warn("%s", outmkname);
842 		goterror = 1;
843 		return;
844 	}
845 
846 	fprintf(outmk, "# %s - generated from %s by crunchgen %s\n\n",
847 	    outmkname, infilename, CRUNCH_VERSION);
848 
849 	if (outhdrname[0] != '\0')
850 		fprintf(outmk, ".include \"%s\"\n", outhdrname);
851 
852 	top_makefile_rules(outmk);
853 	for (p = progs; p != NULL; p = p->next)
854 		prog_makefile_rules(outmk, p);
855 
856 	fprintf(outmk, "\n# ========\n");
857 	fclose(outmk);
858 }
859 
860 
861 void gen_output_cfile(void)
862 {
863 	extern char *crunched_skel[];
864 	char **cp;
865 	FILE *outcf;
866 	prog_t *p;
867 	strlst_t *s;
868 	char line[MAXLINELEN];
869 
870 	snprintf(line, MAXLINELEN, "generating %s", outcfname);
871 	status(line);
872 
873 	if((outcf = fopen(outcfname, "w")) == NULL) {
874 		warn("%s", outcfname);
875 		goterror = 1;
876 		return;
877 	}
878 
879 	fprintf(outcf,
880 	    "/* %s - generated from %s by crunchgen %s */\n",
881 	    outcfname, infilename, CRUNCH_VERSION);
882 
883 	fprintf(outcf, "#define EXECNAME \"%s\"\n", execfname);
884 	for (cp = crunched_skel; *cp != NULL; cp++)
885 		fprintf(outcf, "%s\n", *cp);
886 
887 	for (p = progs; p != NULL; p = p->next)
888 		fprintf(outcf, "extern int _crunched_%s_stub();\n", p->ident);
889 
890 	fprintf(outcf, "\nstruct stub entry_points[] = {\n");
891 	for (p = progs; p != NULL; p = p->next) {
892 		fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
893 		    p->name, p->ident);
894 		for (s = p->links; s != NULL; s = s->next)
895 			fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
896 			    s->str, p->ident);
897 	}
898 
899 	fprintf(outcf, "\t{ EXECNAME, crunched_main },\n");
900 	fprintf(outcf, "\t{ NULL, NULL }\n};\n");
901 	fclose(outcf);
902 }
903 
904 
905 char *genident(char *str)
906 {
907 	char *n, *s, *d;
908 
909 	/*
910 	 * generates a Makefile/C identifier from a program name,
911 	 * mapping '-' to '_' and ignoring all other non-identifier
912 	 * characters.  This leads to programs named "foo.bar" and
913 	 * "foobar" to map to the same identifier.
914 	 */
915 
916 	if ((n = strdup(str)) == NULL)
917 		return NULL;
918 	for (d = s = n; *s != '\0'; s++) {
919 		if (*s == '-')
920 			*d++ = '_';
921 		else if (*s == '_' || isalnum(*s))
922 			*d++ = *s;
923 	}
924 	*d = '\0';
925 	return n;
926 }
927 
928 
929 char *dir_search(char *progname)
930 {
931 	char path[MAXPATHLEN];
932 	strlst_t *dir;
933 	char *srcdir;
934 
935 	for (dir = srcdirs; dir != NULL; dir = dir->next) {
936 		snprintf(path, MAXPATHLEN, "%s/%s", dir->str, progname);
937 		if (!is_dir(path))
938 			continue;
939 
940 		if ((srcdir = strdup(path)) == NULL)
941 			out_of_memory();
942 
943 		return srcdir;
944 	}
945 	return NULL;
946 }
947 
948 
949 void top_makefile_rules(FILE *outmk)
950 {
951 	prog_t *p;
952 
953 	if ( subtract_strlst(&libs, &libs_so) )
954 		fprintf(outmk, "# NOTE: Some LIBS declarations below overridden by LIBS_SO\n");
955 
956 	fprintf(outmk, "LIBS+=");
957 	output_strlst(outmk, libs);
958 
959 	fprintf(outmk, "LIBS_SO+=");
960 	output_strlst(outmk, libs_so);
961 
962 	if (makeobj) {
963 		fprintf(outmk, "MAKEOBJDIRPREFIX?=%s\n", objprefix);
964 		fprintf(outmk, "MAKEENV=env MAKEOBJDIRPREFIX=$(MAKEOBJDIRPREFIX)\n");
965 		fprintf(outmk, "CRUNCHMAKE=$(MAKEENV) $(MAKE)\n");
966 	} else {
967 		fprintf(outmk, "CRUNCHMAKE=$(MAKE)\n");
968 	}
969 
970 	if (buildopts) {
971 		fprintf(outmk, "BUILDOPTS+=");
972 		output_strlst(outmk, buildopts);
973 	}
974 
975 	fprintf(outmk, "CRUNCHED_OBJS=");
976 	for (p = progs; p != NULL; p = p->next)
977 		fprintf(outmk, " %s.lo", p->name);
978 	fprintf(outmk, "\n");
979 
980 	fprintf(outmk, "SUBMAKE_TARGETS=");
981 	for (p = progs; p != NULL; p = p->next)
982 		fprintf(outmk, " %s_make", p->ident);
983 	fprintf(outmk, "\nSUBCLEAN_TARGETS=");
984 	for (p = progs; p != NULL; p = p->next)
985 		fprintf(outmk, " %s_clean", p->ident);
986 	fprintf(outmk, "\n\n");
987 
988 	fprintf(outmk, "all: objs exe\nobjs: $(SUBMAKE_TARGETS)\n");
989 	fprintf(outmk, "exe: %s\n", execfname);
990 	fprintf(outmk, "%s: %s.o $(CRUNCHED_OBJS) $(SUBMAKE_TARGETS)\n", execfname, execfname);
991 	fprintf(outmk, ".if defined(LIBS_SO) && !empty(LIBS_SO)\n");
992 	fprintf(outmk, "\t$(CC) -o %s %s.o $(CRUNCHED_OBJS) \\\n",
993 	    execfname, execfname);
994 	fprintf(outmk, "\t\t-Xlinker -Bstatic $(LIBS) \\\n");
995 	fprintf(outmk, "\t\t-Xlinker -Bdynamic $(LIBS_SO)\n");
996 	fprintf(outmk, ".else\n");
997 	fprintf(outmk, "\t$(CC) -static -o %s %s.o $(CRUNCHED_OBJS) $(LIBS)\n",
998 	    execfname, execfname);
999 	fprintf(outmk, ".endif\n");
1000 	fprintf(outmk, "\tstrip %s\n", execfname);
1001 	fprintf(outmk, "realclean: clean subclean\n");
1002 	fprintf(outmk, "clean:\n\trm -f %s *.lo *.o *_stub.c\n", execfname);
1003 	fprintf(outmk, "subclean: $(SUBCLEAN_TARGETS)\n");
1004 }
1005 
1006 
1007 void prog_makefile_rules(FILE *outmk, prog_t *p)
1008 {
1009 	strlst_t *lst;
1010 
1011 	fprintf(outmk, "\n# -------- %s\n\n", p->name);
1012 
1013 	fprintf(outmk, "%s_OBJDIR=", p->ident);
1014 	if (p->objdir)
1015 		fprintf(outmk, "%s", p->objdir);
1016 	else
1017 		fprintf(outmk, "$(MAKEOBJDIRPREFIX)/$(%s_REALSRCDIR)\n",
1018 		    p->ident);
1019 	fprintf(outmk, "\n");
1020 
1021 	fprintf(outmk, "%s_OBJPATHS=", p->ident);
1022 	if (p->objpaths)
1023 		output_strlst(outmk, p->objpaths);
1024 	else {
1025 		for (lst = p->objs; lst != NULL; lst = lst->next) {
1026 			fprintf(outmk, " $(%s_OBJDIR)/%s", p->ident, lst->str);
1027 		}
1028 		fprintf(outmk, "\n");
1029 	}
1030 
1031 	if (p->srcdir && p->objs) {
1032 		fprintf(outmk, "%s_SRCDIR=%s\n", p->ident, p->srcdir);
1033 		fprintf(outmk, "%s_REALSRCDIR=%s\n", p->ident, p->realsrcdir);
1034 
1035 		fprintf(outmk, "%s_OBJS=", p->ident);
1036 		output_strlst(outmk, p->objs);
1037 		if (p->buildopts != NULL) {
1038 			fprintf(outmk, "%s_OPTS+=", p->ident);
1039 			output_strlst(outmk, p->buildopts);
1040 		}
1041 #if 0
1042 		fprintf(outmk, "$(%s_OBJPATHS): %s_make\n\n", p->ident, p->ident);
1043 #endif
1044 		fprintf(outmk, "%s_make:\n", p->ident);
1045 		fprintf(outmk, "\t(cd $(%s_SRCDIR) && ", p->ident);
1046 		if (makeobj)
1047 			fprintf(outmk, "$(CRUNCHMAKE) obj && ");
1048 		fprintf(outmk, "\\\n");
1049 		fprintf(outmk, "\t\t$(CRUNCHMAKE) $(BUILDOPTS) $(%s_OPTS) depend &&",
1050 		    p->ident);
1051 		fprintf(outmk, "\\\n");
1052 		fprintf(outmk, "\t\t$(CRUNCHMAKE) $(BUILDOPTS) $(%s_OPTS) "
1053 		    "$(%s_OBJS))",
1054 		    p->ident, p->ident);
1055 		fprintf(outmk, "\n");
1056 		fprintf(outmk, "%s_clean:\n", p->ident);
1057 		fprintf(outmk, "\t(cd $(%s_SRCDIR) && $(CRUNCHMAKE) $(BUILDOPTS) clean cleandepend)\n\n",
1058 		    p->ident);
1059 	} else {
1060 		fprintf(outmk, "%s_make:\n", p->ident);
1061 		fprintf(outmk, "\t@echo \"** cannot make objs for %s\"\n\n",
1062 		    p->name);
1063 	}
1064 
1065 	if (p->libs) {
1066 		fprintf(outmk, "%s_LIBS=", p->ident);
1067 		output_strlst(outmk, p->libs);
1068 	}
1069 
1070 	fprintf(outmk, "%s_stub.c:\n", p->name);
1071 	fprintf(outmk, "\techo \""
1072 	    "int _crunched_%s_stub(int argc, char **argv, char **envp)"
1073 	    "{return main(argc,argv,envp);}\" >%s_stub.c\n",
1074 	    p->ident, p->name);
1075 	fprintf(outmk, "%s.lo: %s_stub.o $(%s_OBJPATHS)",
1076 	    p->name, p->name, p->ident);
1077 	if (p->libs)
1078 		fprintf(outmk, " $(%s_LIBS)", p->ident);
1079 
1080 	fprintf(outmk, "\n");
1081 	fprintf(outmk, "\tld -dc -r -o %s.lo %s_stub.o $(%s_OBJPATHS)",
1082 	    p->name, p->name, p->ident);
1083 	if (p->libs)
1084 		fprintf(outmk, " $(%s_LIBS)", p->ident);
1085 	fprintf(outmk, "\n");
1086 	fprintf(outmk, "\tcrunchide -k _crunched_%s_stub ", p->ident);
1087 	for (lst = p->keeplist; lst != NULL; lst = lst->next)
1088 		fprintf(outmk, "-k _%s ", lst->str);
1089 	fprintf(outmk, "%s.lo\n", p->name);
1090 }
1091 
1092 void output_strlst(FILE *outf, strlst_t *lst)
1093 {
1094 	for (; lst != NULL; lst = lst->next)
1095 		if ( strlen(lst->str) )
1096 			fprintf(outf, " %s", lst->str);
1097 	fprintf(outf, "\n");
1098 }
1099 
1100 
1101 /*
1102  * ========================================================================
1103  * general library routines
1104  *
1105  */
1106 
1107 void status(char *str)
1108 {
1109 	static int lastlen = 0;
1110 	int len, spaces;
1111 
1112 	if (!verbose)
1113 		return;
1114 
1115 	len = strlen(str);
1116 	spaces = lastlen - len;
1117 	if (spaces < 1)
1118 		spaces = 1;
1119 
1120 	fprintf(stderr, " [%s]%*.*s\r", str, spaces, spaces, " ");
1121 	fflush(stderr);
1122 	lastlen = len;
1123 }
1124 
1125 
1126 void out_of_memory(void)
1127 {
1128 	err(1, "%s: %d: out of memory, stopping", infilename, linenum);
1129 }
1130 
1131 
1132 void add_string(strlst_t **listp, char *str)
1133 {
1134 	strlst_t *p1, *p2;
1135 
1136 	/* add to end, but be smart about dups */
1137 
1138 	for (p1 = NULL, p2 = *listp; p2 != NULL; p1 = p2, p2 = p2->next)
1139 		if (!strcmp(p2->str, str))
1140 			return;
1141 
1142 	p2 = malloc(sizeof(strlst_t));
1143 	if (p2) {
1144 		p2->next = NULL;
1145 		p2->str = strdup(str);
1146     	}
1147 	if (!p2 || !p2->str)
1148 		out_of_memory();
1149 
1150 	if (p1 == NULL)
1151 		*listp = p2;
1152 	else
1153 		p1->next = p2;
1154 }
1155 
1156 int subtract_strlst(strlst_t **lista, strlst_t **listb)
1157 {
1158 	int subtract_count = 0;
1159 	strlst_t *p1;
1160 	for (p1 = *listb; p1 != NULL; p1 = p1->next)
1161 		if ( in_list(lista, p1->str) ) {
1162 			warnx("Will compile library `%s' dynamically", p1->str);
1163 			strcat(p1->str, "");
1164 			subtract_count++;
1165 		}
1166 	return subtract_count;
1167 }
1168 
1169 int in_list(strlst_t **listp, char *str)
1170 {
1171 	strlst_t *p1;
1172 	for (p1 = *listp; p1 != NULL; p1 = p1->next)
1173 		if (!strcmp(p1->str, str))
1174 			return 1;
1175 	return 0;
1176 }
1177 
1178 int is_dir(char *pathname)
1179 {
1180 	struct stat buf;
1181 
1182 	if (stat(pathname, &buf) == -1)
1183 		return 0;
1184 
1185 	return S_ISDIR(buf.st_mode);
1186 }
1187 
1188 int is_nonempty_file(char *pathname)
1189 {
1190 	struct stat buf;
1191 
1192 	if (stat(pathname, &buf) == -1)
1193 		return 0;
1194 
1195 	return S_ISREG(buf.st_mode) && buf.st_size > 0;
1196 }
1197