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