3e20625a8e1d444950e85df9a28c95a21e8bb0a6
[dragonfly.git] / usr.bin / crunch / crunchgen / crunchgen.c
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 }