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