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