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