nrelease - fix/improve livecd
[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 /*
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
34 #include <sys/param.h>
35 #include <sys/stat.h>
36
37 #include <ctype.h>
38 #include <err.h>
39 #include <paths.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <libgen.h>
45
46 #define CRUNCH_VERSION  "0.3"
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
56 typedef 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
63 typedef 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 *realsrcdir;
69         char *objdir;
70         char *objvar;           /* Makefile variable to replace OBJS */
71         strlst_t *objs;
72         strlst_t *objpaths;
73         strlst_t *buildopts;
74         strlst_t *keeplist;
75         strlst_t *links;
76         strlst_t *libs;
77         strlst_t *libs_int;     /* internal libraries */
78         int goterror;
79 } prog_t;
80
81
82 /* global state */
83
84 static strlst_t *buildopts = NULL;
85 static strlst_t *linkopts  = NULL;
86 static strlst_t *srcdirs   = NULL;
87 static strlst_t *libs      = NULL;
88 static strlst_t *libs_so   = NULL;
89 static strlst_t *libs_int  = NULL;
90 static prog_t   *progs     = NULL;
91
92 static char confname[MAXPATHLEN - 32], infilename[MAXPATHLEN];
93 static char outmkname[MAXPATHLEN], outcfname[MAXPATHLEN], execfname[MAXPATHLEN];
94 static char tempfname[MAXPATHLEN], cachename[MAXPATHLEN], curfilename[MAXPATHLEN];
95 static char outhdrname[MAXPATHLEN]; /* user-supplied header for *.mk */
96 static char *objprefix;         /* where are the objects ? */
97 static char *path_make;
98 static int linenum = -1;
99 static int goterror = 0;
100
101 static int verbose, readcache;  /* options */
102 static int reading_cache;
103 static int makeobj = 0;         /* add 'make obj' rules to the makefile */
104
105 static int list_mode;
106
107 /* general routines */
108
109 static void status(const char *str);
110 static void out_of_memory(void) __dead2;
111 static void add_string(strlst_t **listp, char *str, int nodup);
112 static int is_dir(const char *pathname);
113 static int is_nonempty_file(const char *pathname);
114 static int subtract_strlst(strlst_t **lista, strlst_t **listb);
115 static int in_list(strlst_t **listp, char *str);
116 static void free_list(strlst_t *head);
117 static int iseq(const char *a, const char *b);
118
119 /* helper routines for main() */
120
121 static void usage(void) __dead2;
122 static void parse_conf_file(void);
123 static void gen_outputs(void);
124
125 extern char *crunched_skel[];
126
127
128 int
129 main(int argc, char **argv)
130 {
131         char *p;
132         int optc;
133
134         verbose = 1;
135         readcache = 1;
136         *outmkname = *outcfname = *execfname = '\0';
137
138         path_make = getenv("MAKE");
139         if (path_make == NULL || *path_make == '\0')
140                 path_make = "make";
141
142         p = getenv("MAKEOBJDIRPREFIX");
143         if (p == NULL || *p == '\0')
144                 objprefix = "/usr/obj"; /* default */
145         else if ((objprefix = strdup(p)) == NULL)
146                 out_of_memory();
147
148         while ((optc = getopt(argc, argv, "lh:m:c:e:p:foq")) != -1) {
149                 switch (optc) {
150                 case 'f':
151                         readcache = 0;
152                         break;
153                 case 'o':
154                         makeobj = 1;
155                         break;
156                 case 'q':
157                         verbose = 0;
158                         break;
159
160                 case 'm':
161                         strlcpy(outmkname, optarg, sizeof(outmkname));
162                         break;
163                 case 'p':
164                         if ((objprefix = strdup(optarg)) == NULL)
165                                 out_of_memory();
166                         break;
167
168                 case 'h':
169                         strlcpy(outhdrname, optarg, sizeof(outhdrname));
170                         break;
171                 case 'c':
172                         strlcpy(outcfname, optarg, sizeof(outcfname));
173                         break;
174                 case 'e':
175                         strlcpy(execfname, optarg, sizeof(execfname));
176                         break;
177
178                 case 'l':
179                         list_mode++;
180                         verbose = 0;
181                         break;
182
183                 case '?':
184                 default:
185                         usage();
186                 }
187         }
188
189         argc -= optind;
190         argv += optind;
191
192         if (argc != 1)
193                 usage();
194
195         /*
196          * generate filenames
197          */
198
199         strlcpy(infilename, argv[0], sizeof(infilename));
200
201         /* confname = `basename infilename .conf` */
202
203         if ((p = strrchr(infilename, '/')) != NULL)
204                 strlcpy(confname, p + 1, sizeof(confname));
205         else
206                 strlcpy(confname, infilename, sizeof(confname));
207
208         if ((p = strrchr(confname, '.')) != NULL && iseq(p, ".conf"))
209                 *p = '\0';
210
211         if (!*outmkname)
212                 snprintf(outmkname, sizeof(outmkname), "%s.mk", confname);
213         if (!*outcfname)
214                 snprintf(outcfname, sizeof(outcfname), "%s.c", confname);
215         if (!*execfname)
216                 snprintf(execfname, sizeof(execfname), "%s", confname);
217
218         snprintf(cachename, sizeof(cachename), "%s.cache", confname);
219         snprintf(tempfname, sizeof(tempfname), "%s/crunchgen_%sXXXXXX",
220                  getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP, confname);
221
222         parse_conf_file();
223         if (list_mode)
224                 exit(goterror);
225
226         gen_outputs();
227
228         exit(goterror);
229 }
230
231
232 static void
233 usage(void)
234 {
235         fprintf(stderr, "%s%s\n\t%s%s\n", "usage: crunchgen [-foq] ",
236             "[-h <makefile-header-name>] [-m <makefile>]",
237             "[-p <obj-prefix>] [-c <c-file-name>] [-e <exec-file>] ",
238             "<conffile>");
239         exit(1);
240 }
241
242
243 /*
244  * ========================================================================
245  * parse_conf_file subsystem
246  *
247  */
248
249 /* helper routines for parse_conf_file */
250
251 static void parse_one_file(char *filename);
252 static void parse_line(char *pline, int *fc, char **fv, int nf);
253 static void add_srcdirs(int argc, char **argv);
254 static void add_progs(int argc, char **argv);
255 static void add_link(int argc, char **argv);
256 static void add_libs(int argc, char **argv);
257 static void add_libs_so(int argc, char **argv);
258 static void add_libs_int(int argc, char **argv);
259 static void add_buildopts(int argc, char **argv);
260 static void add_linkopts(int argc, char **argv);
261 static void add_special(int argc, char **argv);
262
263 static prog_t *find_prog(char *str);
264 static void add_prog(char *progname);
265
266
267 static void
268 parse_conf_file(void)
269 {
270         if (!is_nonempty_file(infilename))
271                 errx(1, "fatal: input file \"%s\" not found", infilename);
272
273         parse_one_file(infilename);
274         if (readcache && is_nonempty_file(cachename)) {
275                 reading_cache = 1;
276                 parse_one_file(cachename);
277         }
278 }
279
280
281 static void
282 parse_one_file(char *filename)
283 {
284         char *fieldv[MAXFIELDS];
285         int fieldc;
286         void (*f)(int c, char **v);
287         FILE *cf;
288         char line[MAXLINELEN];
289
290         snprintf(line, sizeof(line), "reading %s", filename);
291         status(line);
292         strlcpy(curfilename, filename, sizeof(curfilename));
293
294         if ((cf = fopen(curfilename, "r")) == NULL) {
295                 warn("%s", curfilename);
296                 goterror = 1;
297                 return;
298         }
299
300         linenum = 0;
301         while (fgets(line, MAXLINELEN, cf) != NULL) {
302                 linenum++;
303                 parse_line(line, &fieldc, fieldv, MAXFIELDS);
304
305                 if (fieldc == 0) {
306                         continue;
307                 } else if (fieldc == 1) {
308                         warnx("%s:%d: %s %s",
309                             curfilename, linenum, fieldv[0],
310                             "command needs at least 1 argument, skipping");
311                         goterror = 1;
312                         continue;
313                 }
314
315                 if (iseq(fieldv[0], "srcdirs"))
316                         f = add_srcdirs;
317                 else if (iseq(fieldv[0], "progs"))
318                         f = add_progs;
319                 else if (iseq(fieldv[0], "ln"))
320                         f = add_link;
321                 else if (iseq(fieldv[0], "libs"))
322                         f = add_libs;
323                 else if (iseq(fieldv[0], "libs_so"))
324                         f = add_libs_so;
325                 else if (iseq(fieldv[0], "libs_int"))
326                         f = add_libs_int;
327                 else if (iseq(fieldv[0], "buildopts"))
328                         f = add_buildopts;
329                 else if (iseq(fieldv[0], "linkopts"))
330                         f = add_linkopts;
331                 else if (iseq(fieldv[0], "special"))
332                         f = add_special;
333                 else {
334                         warnx("%s:%d: skipping unknown command `%s'",
335                             curfilename, linenum, fieldv[0]);
336                         goterror = 1;
337                         continue;
338                 }
339
340                 f(fieldc, fieldv);
341         }
342
343         if (ferror(cf)) {
344                 warn("%s", curfilename);
345                 goterror = 1;
346         }
347         fclose(cf);
348 }
349
350
351 static void
352 parse_line(char *pline, int *fc, char **fv, int nf)
353 {
354         char *p;
355
356         p = pline;
357         *fc = 0;
358
359         for (;;) {
360                 while (isspace((unsigned char)*p))
361                         p++;
362
363                 if (*p == '\0' || *p == '#')
364                         break;
365
366                 if (*fc < nf)
367                         fv[(*fc)++] = p;
368
369                 while (*p && !isspace((unsigned char)*p) && *p != '#')
370                         p++;
371
372                 if (*p == '\0' || *p == '#')
373                         break;
374
375                 *p++ = '\0';
376         }
377
378         if (*p)
379                 *p = '\0';              /* needed for '#' case */
380 }
381
382
383 static void
384 add_srcdirs(int argc, char **argv)
385 {
386         int i;
387
388         for (i = 1; i < argc; i++) {
389                 if (is_dir(argv[i])) {
390                         add_string(&srcdirs, argv[i], 1);
391                 } else {
392                         warnx("%s:%d: `%s' is not a directory, skipping it",
393                             curfilename, linenum, argv[i]);
394                         goterror = 1;
395                 }
396         }
397 }
398
399
400 static void
401 add_progs(int argc, char **argv)
402 {
403         int i;
404
405         for (i = 1; i < argc; i++)
406                 add_prog(argv[i]);
407 }
408
409
410 static void
411 add_prog(char *progname)
412 {
413         prog_t *p1, *p2;
414
415         /* add to end, but be smart about dups */
416
417         for (p1 = NULL, p2 = progs; p2 != NULL; p1 = p2, p2 = p2->next)
418                 if (iseq(p2->name, progname))
419                         return;
420
421         p2 = malloc(sizeof(prog_t));
422         if (p2) {
423                 memset(p2, 0, sizeof(prog_t));
424                 p2->name = strdup(progname);
425         }
426         if (!p2 || !p2->name)
427                 out_of_memory();
428
429         p2->next = NULL;
430         if (p1 == NULL)
431                 progs = p2;
432         else
433                 p1->next = p2;
434
435         p2->ident = NULL;
436         p2->srcdir = NULL;
437         p2->realsrcdir = NULL;
438         p2->objdir = NULL;
439         p2->links = NULL;
440         p2->libs = NULL;
441         p2->objs = NULL;
442         p2->keeplist = NULL;
443         p2->buildopts = NULL;
444         p2->goterror = 0;
445
446         if (list_mode)
447                 printf("%s\n", progname);
448 }
449
450
451 static void
452 add_link(int argc, char **argv)
453 {
454         int i;
455         prog_t *p = find_prog(argv[1]);
456
457         if (p == NULL) {
458                 warnx("%s:%d: no prog %s previously declared, skipping link",
459                     curfilename, linenum, argv[1]);
460                 goterror = 1;
461                 return;
462         }
463
464         for (i = 2; i < argc; i++) {
465                 if (list_mode)
466                         printf("%s\n",argv[i]);
467
468                 add_string(&p->links, argv[i], 1);
469         }
470 }
471
472
473 static void
474 add_libs(int argc, char **argv)
475 {
476         int i;
477
478         for (i = 1; i < argc; i++) {
479                 add_string(&libs, argv[i], 1);
480                 if (in_list(&libs_so, argv[i]))
481                         warnx("%s:%d: "
482                                 "library `%s' specified as dynamic earlier",
483                                 curfilename, linenum, argv[i]);
484         }
485 }
486
487
488 static void
489 add_libs_so(int argc, char **argv)
490 {
491         int i;
492
493         for (i = 1; i < argc; i++) {
494                 add_string(&libs_so, argv[i], 1);
495                 if (in_list(&libs, argv[i]))
496                         warnx("%s:%d: "
497                                 "library `%s' specified as static earlier",
498                                 curfilename, linenum, argv[i]);
499         }
500 }
501
502
503 static void
504 add_libs_int(int argc, char **argv)
505 {
506         int i;
507
508         for (i = 1; i < argc; i++) {
509                 add_string(&libs_int, argv[i], 1);
510         }
511 }
512
513
514 static void
515 add_buildopts(int argc, char **argv)
516 {
517         int i;
518
519         for (i = 1; i < argc; i++)
520                 add_string(&buildopts, argv[i], 0);  /* allow duplicates */
521 }
522
523
524 static void
525 add_linkopts(int argc, char **argv)
526 {
527         int i;
528
529         for (i = 1; i < argc; i++)
530                 add_string(&linkopts, argv[i], 0);  /* allow duplicates */
531 }
532
533
534 static void
535 add_special(int argc, char **argv)
536 {
537         int i;
538         prog_t *p = find_prog(argv[1]);
539
540         if (p == NULL) {
541                 if (reading_cache)
542                         return;
543
544                 warnx("%s:%d: no prog %s previously declared, skipping special",
545                     curfilename, linenum, argv[1]);
546                 goterror = 1;
547                 return;
548         }
549
550         if (iseq(argv[2], "ident")) {
551                 if (argc != 4)
552                         goto argcount;
553                 if ((p->ident = strdup(argv[3])) == NULL)
554                         out_of_memory();
555         } else if (iseq(argv[2], "srcdir")) {
556                 if (argc != 4)
557                         goto argcount;
558                 if ((p->srcdir = strdup(argv[3])) == NULL)
559                         out_of_memory();
560         } else if (iseq(argv[2], "objdir")) {
561                 if (argc != 4)
562                         goto argcount;
563                 if ((p->objdir = strdup(argv[3])) == NULL)
564                         out_of_memory();
565         } else if (iseq(argv[2], "objs")) {
566                 p->objs = NULL;
567                 for (i = 3; i < argc; i++)
568                         add_string(&p->objs, argv[i], 1);
569         } else if (iseq(argv[2], "objpaths")) {
570                 p->objpaths = NULL;
571                 for (i = 3; i < argc; i++)
572                         add_string(&p->objpaths, argv[i], 1);
573         } else if (iseq(argv[2], "keep")) {
574                 p->keeplist = NULL;
575                 for (i = 3; i < argc; i++)
576                         add_string(&p->keeplist, argv[i], 1);
577         } else if (iseq(argv[2], "objvar")) {
578                 if (argc != 4)
579                         goto argcount;
580                 if ((p->objvar = strdup(argv[3])) == NULL)
581                         out_of_memory();
582         } else if (iseq(argv[2], "buildopts")) {
583                 p->buildopts = NULL;
584                 for (i = 3; i < argc; i++)
585                         add_string(&p->buildopts, argv[i], 0);
586         } else if (iseq(argv[2], "lib")) {
587                 for (i = 3; i < argc; i++)
588                         add_string(&p->libs, argv[i], 1);
589         } else if (iseq(argv[2], "lib_int")) {
590                 for (i = 3; i < argc; i++)
591                         add_string(&p->libs_int, argv[i], 1);
592         } else {
593                 warnx("%s:%d: bad parameter name `%s', skipping line",
594                     curfilename, linenum, argv[2]);
595                 goterror = 1;
596         }
597         return;
598
599 argcount:
600         warnx("%s:%d: too %s arguments, expected \"special %s %s <string>\"",
601             curfilename, linenum, argc < 4 ? "few" : "many", argv[1], argv[2]);
602         goterror = 1;
603 }
604
605
606 static prog_t *find_prog(char *str)
607 {
608         prog_t *p;
609
610         for (p = progs; p != NULL; p = p->next)
611                 if (iseq(p->name, str))
612                         return p;
613
614         return NULL;
615 }
616
617
618 /*
619  * ========================================================================
620  * gen_outputs subsystem
621  *
622  */
623
624 /* helper subroutines */
625
626 static void remove_error_progs(void);
627 static void fillin_program(prog_t *p);
628 static void gen_specials_cache(void);
629 static void gen_output_makefile(void);
630 static void gen_output_cfile(void);
631
632 static void fillin_program_objs(prog_t *p, char *path);
633 static void top_makefile_rules(FILE *outmk);
634 static void prog_makefile_rules(FILE *outmk, prog_t *p);
635 static void intlib_makefile_rules(FILE *outmk, char *path);
636 static void output_strlst(FILE *outf, strlst_t *lst);
637 static char *genident(char *str);
638 static char *dir_search(char *progname);
639 static void collect_internal_libs(strlst_t **listp);
640
641
642 static void
643 gen_outputs(void)
644 {
645         prog_t *p;
646
647         for (p = progs; p != NULL; p = p->next)
648                 fillin_program(p);
649
650         remove_error_progs();
651         gen_specials_cache();
652         gen_output_cfile();
653         gen_output_makefile();
654
655         status("");
656         fprintf(stderr,
657             "Run \"%s -f %s\" to build crunched binary.\n",
658             path_make, outmkname);
659 }
660
661 /*
662  * run the makefile for the program to find which objects are necessary
663  */
664 static void
665 fillin_program(prog_t *p)
666 {
667         char path[MAXPATHLEN];
668         char line[MAXLINELEN];
669
670         snprintf(line, MAXLINELEN, "filling in parms for %s", p->name);
671         status(line);
672
673         if (!p->ident)
674                 p->ident = genident(p->name);
675
676         /* look for the source directory if one wasn't specified by a special */
677         if (!p->srcdir) {
678                 p->srcdir = dir_search(p->name);
679         }
680
681         /* Determine the real srcdir (maybe symlinked). */
682         if (p->srcdir) {
683                 if ((realpath(p->srcdir, path)) == NULL)
684                         errx(1, "Can't get realpath on: %s\n", p->srcdir);
685                 p->realsrcdir = strdup(path);
686         }
687
688         /* Unless the option to make object files was specified the
689         * the objects will be built in the source directory unless
690         * an object directory already exists.
691         */
692         if (!makeobj && !p->objdir && p->srcdir) {
693                 snprintf(line, sizeof line, "%s/%s", objprefix, p->realsrcdir);
694                 if (is_dir(line)) {
695                         if ((p->objdir = strdup(line)) == NULL)
696                                 out_of_memory();
697                 } else {
698                         p->objdir = p->realsrcdir;
699                 }
700         }
701
702         /*
703         * XXX look for a Makefile.{name} in local directory first.
704         * This lets us override the original Makefile.
705         */
706         snprintf(path, sizeof(path), "Makefile.%s", p->name);
707         if (is_nonempty_file(path)) {
708                 snprintf(line, MAXLINELEN, "Using %s for %s", path, p->name);
709                 status(line);
710         } else if (p->srcdir) {
711                 snprintf(path, sizeof(path), "%s/Makefile", p->srcdir);
712         }
713
714         if (!p->objs && p->srcdir && is_nonempty_file(path))
715                 fillin_program_objs(p, path);
716
717         if (!p->srcdir && !p->objdir && verbose)
718                 warnx("%s: %s: %s",
719                     "warning: could not find source directory",
720                     infilename, p->name);
721         if (!p->objs && verbose)
722                 warnx("%s: %s: warning: could not find any .o files",
723                     infilename, p->name);
724
725         if ((!p->srcdir || !p->objdir) && !p->objs)
726                 p->goterror = 1;
727 }
728
729 static void
730 fillin_program_objs(prog_t *p, char *path)
731 {
732         char *obj, *cp;
733         int fd, rc;
734         FILE *f;
735         char *objvar = "OBJS";
736         strlst_t *s;
737         char line[MAXLINELEN];
738
739         /* discover the objs from the srcdir Makefile */
740
741         if ((fd = mkstemp(tempfname)) == -1) {
742                 perror(tempfname);
743                 exit(1);
744         }
745         if ((f = fdopen(fd, "w")) == NULL) {
746                 warn("%s", tempfname);
747                 goterror = 1;
748                 return;
749         }
750         if (p->objvar)
751                 objvar = p->objvar;
752
753         /*
754         * XXX include outhdrname (e.g. to contain Make variables)
755         */
756         if (outhdrname[0] != '\0')
757                 fprintf(f, ".include \"%s\"\n", outhdrname);
758         fprintf(f, ".include \"%s\"\n", path);
759         if (buildopts) {
760                 fprintf(f, "BUILDOPTS+=");
761                 output_strlst(f, buildopts);
762         }
763         fprintf(f, ".if defined(PROG)\n");
764         fprintf(f, "%s?= ${PROG}.o\n", objvar);
765         fprintf(f, ".endif\n");
766         fprintf(f, "loop:\n\t@echo 'OBJS= '${%s}\n", objvar);
767
768         fprintf(f, "crunchgen_objs:\n"
769             "\t@cd %s && %s -f %s ${BUILDOPTS} ${%s_OPTS}",
770             p->srcdir, path_make, tempfname, p->ident);
771         for (s = p->buildopts; s != NULL; s = s->next)
772                 fprintf(f, " %s", s->str);
773         fprintf(f, " loop\n");
774
775         fclose(f);
776
777         snprintf(line, MAXLINELEN, "cd %s && %s -f %s -B crunchgen_objs",
778              p->srcdir, path_make, tempfname);
779         if ((f = popen(line, "r")) == NULL) {
780                 warn("submake pipe");
781                 goterror = 1;
782                 unlink(tempfname);
783                 return;
784         }
785
786         while (fgets(line, MAXLINELEN, f)) {
787                 if (strncmp(line, "OBJS= ", 6)) {
788                         warnx("make error: %s", line);
789                         goterror = 1;
790                         continue;
791                 }
792
793                 cp = line + 6;
794                 while (isspace((unsigned char)*cp))
795                         cp++;
796
797                 while (*cp) {
798                         obj = cp;
799                         while (*cp && !isspace((unsigned char)*cp))
800                                 cp++;
801                         if (*cp)
802                                 *cp++ = '\0';
803                         add_string(&p->objs, obj, 1);
804                         while (isspace((unsigned char)*cp))
805                                 cp++;
806                 }
807         }
808
809         if ((rc = pclose(f)) != 0) {
810                 warnx("make error: make returned %d", rc);
811                 goterror = 1;
812         }
813
814         unlink(tempfname);
815 }
816
817 static void
818 remove_error_progs(void)
819 {
820         prog_t *p1, *p2;
821
822         p1 = NULL; p2 = progs;
823         while (p2 != NULL) {
824                 if (!p2->goterror) {
825                         p1 = p2, p2 = p2->next;
826                 } else {
827                         /* delete it from linked list */
828                         warnx("%s: %s: ignoring program because of errors",
829                             infilename, p2->name);
830                         if (p1)
831                                 p1->next = p2->next;
832                         else
833                                 progs = p2->next;
834                         p2 = p2->next;
835                 }
836         }
837 }
838
839 static void
840 gen_specials_cache(void)
841 {
842         FILE *cachef;
843         prog_t *p;
844         char line[MAXLINELEN];
845
846         snprintf(line, MAXLINELEN, "generating %s", cachename);
847         status(line);
848
849         if ((cachef = fopen(cachename, "w")) == NULL) {
850                 warn("%s", cachename);
851                 goterror = 1;
852                 return;
853         }
854
855         fprintf(cachef,
856             "# %s - parm cache generated from %s by crunchgen %s\n\n",
857             cachename, infilename, CRUNCH_VERSION);
858
859         for (p = progs; p != NULL; p = p->next) {
860                 fprintf(cachef, "\n");
861                 if (p->srcdir)
862                         fprintf(cachef, "special %s srcdir %s\n",
863                             p->name, p->srcdir);
864                 if (p->objdir)
865                         fprintf(cachef, "special %s objdir %s\n",
866                             p->name, p->objdir);
867                 if (p->objs) {
868                         fprintf(cachef, "special %s objs", p->name);
869                         output_strlst(cachef, p->objs);
870                 }
871                 if (p->objpaths) {
872                         fprintf(cachef, "special %s objpaths", p->name);
873                         output_strlst(cachef, p->objpaths);
874                 }
875         }
876         fclose(cachef);
877 }
878
879
880 static void
881 gen_output_makefile(void)
882 {
883         prog_t *p;
884         strlst_t *intlibs, *l;
885         FILE *outmk;
886         char line[MAXLINELEN];
887
888         snprintf(line, MAXLINELEN, "generating %s", outmkname);
889         status(line);
890
891         if ((outmk = fopen(outmkname, "w")) == NULL) {
892                 warn("%s", outmkname);
893                 goterror = 1;
894                 return;
895         }
896
897         fprintf(outmk, "# %s - generated from %s by crunchgen %s\n\n",
898             outmkname, infilename, CRUNCH_VERSION);
899
900         if (outhdrname[0] != '\0')
901                 fprintf(outmk, ".include \"%s\"\n", outhdrname);
902
903         top_makefile_rules(outmk);
904
905         intlibs = NULL;
906         collect_internal_libs(&intlibs);
907         for (l = intlibs; l != NULL; l = l->next)
908                 intlib_makefile_rules(outmk, l->str);
909         free_list(intlibs);
910
911         for (p = progs; p != NULL; p = p->next)
912                 prog_makefile_rules(outmk, p);
913
914         fprintf(outmk, "\n# ========\n");
915         fclose(outmk);
916 }
917
918
919 static void
920 gen_output_cfile(void)
921 {
922         char **cp;
923         FILE *outcf;
924         prog_t *p;
925         strlst_t *s;
926         char line[MAXLINELEN];
927
928         snprintf(line, MAXLINELEN, "generating %s", outcfname);
929         status(line);
930
931         if((outcf = fopen(outcfname, "w")) == NULL) {
932                 warn("%s", outcfname);
933                 goterror = 1;
934                 return;
935         }
936
937         fprintf(outcf,
938             "/* %s - generated from %s by crunchgen %s */\n",
939             outcfname, infilename, CRUNCH_VERSION);
940
941         fprintf(outcf, "#define EXECNAME \"%s\"\n", execfname);
942         for (cp = crunched_skel; *cp != NULL; cp++)
943                 fprintf(outcf, "%s\n", *cp);
944
945         for (p = progs; p != NULL; p = p->next)
946                 fprintf(outcf, "extern int _crunched_%s_stub();\n", p->ident);
947
948         fprintf(outcf, "\nstatic const struct stub entry_points[] = {\n");
949         for (p = progs; p != NULL; p = p->next) {
950                 fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
951                     p->name, p->ident);
952                 for (s = p->links; s != NULL; s = s->next)
953                         fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
954                             s->str, p->ident);
955         }
956
957         fprintf(outcf, "\t{ EXECNAME, crunched_main },\n");
958         fprintf(outcf, "\t{ NULL, NULL }\n};\n");
959         fclose(outcf);
960 }
961
962
963 static char *genident(char *str)
964 {
965         char *n, *s, *d;
966
967         /*
968          * generates a Makefile/C identifier from a program name,
969          * mapping '-' to '_' and ignoring all other non-identifier
970          * characters.  This leads to programs named "foo.bar" and
971          * "foobar" to map to the same identifier.
972          */
973
974         if ((n = strdup(str)) == NULL)
975                 return NULL;
976         for (d = s = n; *s != '\0'; s++) {
977                 if (*s == '-')
978                         *d++ = '_';
979                 else if (*s == '_' || isalnum((unsigned char)*s))
980                         *d++ = *s;
981         }
982         *d = '\0';
983         return n;
984 }
985
986
987 static char *dir_search(char *progname)
988 {
989         char path[MAXPATHLEN];
990         strlst_t *dir;
991         char *srcdir;
992
993         for (dir = srcdirs; dir != NULL; dir = dir->next) {
994                 snprintf(path, MAXPATHLEN, "%s/%s", dir->str, progname);
995                 if (!is_dir(path))
996                         continue;
997
998                 if ((srcdir = strdup(path)) == NULL)
999                         out_of_memory();
1000
1001                 return srcdir;
1002         }
1003         return NULL;
1004 }
1005
1006
1007 static void
1008 collect_internal_libs(strlst_t **listp)
1009 {
1010         strlst_t *l;
1011         prog_t *p;
1012
1013         for (l = libs_int; l != NULL; l = l->next)
1014                 add_string(listp, l->str, 1);
1015         for (p = progs; p != NULL; p = p->next) {
1016                 if (p->libs_int) {
1017                         for (l = p->libs_int; l != NULL; l = l->next)
1018                                 add_string(listp, l->str, 1);
1019                 }
1020         }
1021 }
1022
1023
1024 static void
1025 top_makefile_rules(FILE *outmk)
1026 {
1027         prog_t *p;
1028         strlst_t *intlibs, *l;
1029
1030         if (subtract_strlst(&libs, &libs_so))
1031                 fprintf(outmk, "# NOTE: Some LIBS declarations below overridden by LIBS_SO\n");
1032
1033         fprintf(outmk, "LIBS+=");
1034         output_strlst(outmk, libs);
1035
1036         fprintf(outmk, "LIBS_SO+=");
1037         output_strlst(outmk, libs_so);
1038
1039         fprintf(outmk, "LIBS_INT+=");
1040         output_strlst(outmk, libs_int);
1041
1042         if (makeobj) {
1043                 fprintf(outmk, "MAKEOBJDIRPREFIX?=%s\n", objprefix);
1044                 fprintf(outmk, "MAKEENV=env MAKEOBJDIRPREFIX=${MAKEOBJDIRPREFIX}\n");
1045                 fprintf(outmk, "CRUNCHMAKE=${MAKEENV} ${MAKE}\n");
1046         } else {
1047                 fprintf(outmk, "CRUNCHMAKE=${MAKE}\n");
1048         }
1049
1050         if (buildopts) {
1051                 fprintf(outmk, "BUILDOPTS+=");
1052                 output_strlst(outmk, buildopts);
1053         }
1054         if (linkopts) {
1055                 fprintf(outmk, "LINKOPTS+=");
1056                 output_strlst(outmk, linkopts);
1057         }
1058
1059         fprintf(outmk, "CRUNCHED_OBJS=");
1060         for (p = progs; p != NULL; p = p->next)
1061                 fprintf(outmk, " %s.lo", p->name);
1062         fprintf(outmk, "\n");
1063
1064         fprintf(outmk, "SUBMAKE_TARGETS=");
1065         for (p = progs; p != NULL; p = p->next)
1066                 fprintf(outmk, " %s_make", p->ident);
1067         fprintf(outmk, "\nSUBCLEAN_TARGETS=");
1068         for (p = progs; p != NULL; p = p->next)
1069                 fprintf(outmk, " %s_clean", p->ident);
1070         fprintf(outmk, "\n");
1071
1072         /* internal libraries */
1073         intlibs = NULL;
1074         collect_internal_libs(&intlibs);
1075         fprintf(outmk, "SUBMAKE_TARGETS+=");
1076         for (l = intlibs; l != NULL; l = l->next)
1077                 fprintf(outmk, " %s_make", basename(l->str));
1078         fprintf(outmk, "\nSUBCLEAN_TARGETS+=");
1079         for (l = intlibs; l != NULL; l = l->next)
1080                 fprintf(outmk, " %s_clean", basename(l->str));
1081         fprintf(outmk, "\n\n");
1082         free_list(intlibs);
1083
1084         fprintf(outmk, "all: objs exe\nobjs: ${SUBMAKE_TARGETS}\n");
1085         fprintf(outmk, "exe: %s\n", execfname);
1086         fprintf(outmk, "%s: %s.o ${CRUNCHED_OBJS} ${SUBMAKE_TARGETS}\n",
1087             execfname, execfname);
1088         fprintf(outmk, ".if defined(LIBS_SO) && !empty(LIBS_SO)\n");
1089         fprintf(outmk, "\t${CC} ${LINKOPTS} -o %s %s.o \\\n",
1090             execfname, execfname);
1091         fprintf(outmk, "\t\t${CRUNCHED_OBJS} ${LIBS_INT} \\\n");
1092         fprintf(outmk, "\t\t-Xlinker -Bstatic ${LIBS} \\\n");
1093         fprintf(outmk, "\t\t-Xlinker -Bdynamic ${LIBS_SO}\n");
1094         fprintf(outmk, ".else\n");
1095         fprintf(outmk, "\t${CC} ${LINKOPTS} -static -o %s %s.o \\\n",
1096             execfname, execfname);
1097         fprintf(outmk, "\t\t${CRUNCHED_OBJS} ${LIBS_INT} ${LIBS}\n");
1098         fprintf(outmk, ".endif\n");
1099         fprintf(outmk, "\tstrip %s\n", execfname);
1100         fprintf(outmk, "realclean: clean subclean\n");
1101         fprintf(outmk, "clean:\n\trm -f %s *.lo *.o *_stub.c\n", execfname);
1102         fprintf(outmk, "subclean: ${SUBCLEAN_TARGETS}\n");
1103 }
1104
1105
1106 static void
1107 prog_makefile_rules(FILE *outmk, prog_t *p)
1108 {
1109         strlst_t *lst;
1110
1111         fprintf(outmk, "\n# -------- %s\n\n", p->name);
1112
1113         fprintf(outmk, "%s_OBJDIR=", p->ident);
1114         if (p->objdir)
1115                 fprintf(outmk, "%s", p->objdir);
1116         else
1117                 fprintf(outmk, "${MAKEOBJDIRPREFIX}/${%s_REALSRCDIR}\n",
1118                     p->ident);
1119         fprintf(outmk, "\n");
1120
1121         fprintf(outmk, "%s_OBJPATHS=", p->ident);
1122         if (p->objpaths)
1123                 output_strlst(outmk, p->objpaths);
1124         else {
1125                 for (lst = p->objs; lst != NULL; lst = lst->next)
1126                         fprintf(outmk, " ${%s_OBJDIR}/%s", p->ident, lst->str);
1127                 fprintf(outmk, "\n");
1128         }
1129         fprintf(outmk, "${%s_OBJPATHS}: .NOMETA\n", p->ident);
1130
1131         if (p->srcdir && p->objs) {
1132                 fprintf(outmk, "%s_SRCDIR=%s\n", p->ident, p->srcdir);
1133                 fprintf(outmk, "%s_REALSRCDIR=%s\n", p->ident, p->realsrcdir);
1134
1135                 fprintf(outmk, "%s_OBJS=", p->ident);
1136                 output_strlst(outmk, p->objs);
1137                 if (p->buildopts != NULL) {
1138                         fprintf(outmk, "%s_OPTS+=", p->ident);
1139                         output_strlst(outmk, p->buildopts);
1140                 }
1141                 fprintf(outmk, "%s_make:\n", p->ident);
1142                 fprintf(outmk, "\t(cd ${%s_SRCDIR} && ", p->ident);
1143                 if (makeobj)
1144                         fprintf(outmk, "${CRUNCHMAKE} obj && ");
1145                 fprintf(outmk, "\\\n");
1146                 fprintf(outmk, "\t\t${CRUNCHMAKE} ${BUILDOPTS} ${%s_OPTS} depend && ",
1147                     p->ident);
1148                 fprintf(outmk, "\\\n");
1149                 fprintf(outmk, "\t\t${CRUNCHMAKE} ${BUILDOPTS} ${%s_OPTS} "
1150                     "${%s_OBJS})",
1151                     p->ident, p->ident);
1152                 fprintf(outmk, "\n");
1153                 fprintf(outmk, "%s_clean:\n", p->ident);
1154                 fprintf(outmk, "\t(cd ${%s_SRCDIR} && ${CRUNCHMAKE} ${BUILDOPTS} clean cleandepend)\n\n",
1155                     p->ident);
1156         } else {
1157                 fprintf(outmk, "%s_make:\n", p->ident);
1158                 fprintf(outmk, "\t@echo \"** cannot make objs for %s\"\n\n",
1159                     p->name);
1160         }
1161
1162         if (p->libs_int) {
1163                 fprintf(outmk, "%s_LIBS_INT=", p->ident);
1164                 for (lst = p->libs_int; lst != NULL; lst = lst->next)
1165                         fprintf(outmk, " ${%s_LIB}", basename(lst->str));
1166                 fprintf(outmk, "\n");
1167         }
1168         if (p->libs) {
1169                 fprintf(outmk, "%s_LIBS=", p->ident);
1170                 output_strlst(outmk, p->libs);
1171         }
1172
1173         fprintf(outmk, "%s_stub.c:\n", p->name);
1174         fprintf(outmk, "\techo \""
1175             "extern int main(int, char **, char **); "
1176             "int _crunched_%s_stub(int argc, char **argv, char **envp)"
1177             "{return main(argc,argv,envp);}\" >%s_stub.c\n",
1178             p->ident, p->name);
1179         fprintf(outmk, "%s_stub.o: %s_stub.c\n",
1180             p->name, p->name);
1181         fprintf(outmk, "\t${CC} ${CFLAGS:N-flto*} -c %s_stub.c -o %s_stub.o",
1182             p->name, p->name);
1183         fprintf(outmk, "\n");
1184         fprintf(outmk, "%s.lo: %s_stub.o ${%s_OBJPATHS}",
1185             p->name, p->name, p->ident);
1186         if (p->libs_int)
1187                 fprintf(outmk, " ${%s_LIBS_INT}", p->ident);
1188         if (p->libs)
1189                 fprintf(outmk, " ${%s_LIBS}", p->ident);
1190
1191         fprintf(outmk, "\n");
1192         fprintf(outmk, "\t${CC} -nostdlib -Wl,-dc -r "
1193             "-o %s.lo %s_stub.o ${%s_OBJPATHS}",
1194             p->name, p->name, p->ident);
1195         if (p->libs)
1196                 fprintf(outmk, " ${%s_LIBS}", p->ident);
1197         if (p->libs_int)
1198                 fprintf(outmk, " ${%s_LIBS_INT}", p->ident);
1199         fprintf(outmk, "\n");
1200         fprintf(outmk, "\tcrunchide -k _crunched_%s_stub ", p->ident);
1201         for (lst = p->keeplist; lst != NULL; lst = lst->next)
1202                 fprintf(outmk, "-k %s ", lst->str);
1203         fprintf(outmk, "%s.lo\n", p->name);
1204 }
1205
1206
1207 static void
1208 intlib_makefile_rules(FILE *outmk, char *path)
1209 {
1210         char *pathcopy, *libname, *srcdir, *objdir;
1211         char realsrcdir[MAXPATHLEN], line[MAXPATHLEN];
1212
1213         libname = basename(path);
1214         if ((pathcopy = strdup(path)) == NULL)
1215                 out_of_memory();
1216         srcdir = dirname(pathcopy);
1217         if ((realpath(srcdir, realsrcdir)) == NULL)
1218                 errx(1, "Can't get realpath on: %s\n", srcdir);
1219
1220         fprintf(outmk, "\n# -------- %s\n\n", libname);
1221         fprintf(outmk, "%s_SRCDIR=%s\n", libname, srcdir);
1222         fprintf(outmk, "%s_REALSRCDIR=%s\n", libname, realsrcdir);
1223
1224         snprintf(line, sizeof line, "%s/%s", objprefix, realsrcdir);
1225         if (is_dir(line)) {
1226                 if ((objdir = strdup(line)) == NULL)
1227                         out_of_memory();
1228         } else {
1229                 objdir = realsrcdir;
1230         }
1231         fprintf(outmk, "%s_OBJDIR=%s\n", libname, objdir);
1232         fprintf(outmk, "%s_LIB=${%s_OBJDIR}/%s\n", libname, libname, libname);
1233
1234         fprintf(outmk, "%s_make:\n", libname);
1235         fprintf(outmk, "\t(cd ${%s_SRCDIR} && ", libname);
1236         if (makeobj)
1237                 fprintf(outmk, "${CRUNCHMAKE} obj && ");
1238         fprintf(outmk, "\\\n");
1239         fprintf(outmk, "\t\t${CRUNCHMAKE} ${BUILDOPTS} ${%s_OPTS} depend && ",
1240             libname);
1241         fprintf(outmk, "\\\n");
1242         fprintf(outmk, "\t\t${CRUNCHMAKE} ${BUILDOPTS} ${%s_OPTS} %s)\n",
1243             libname, libname);
1244         fprintf(outmk, "%s_clean:\n", libname);
1245         fprintf(outmk, "\t(cd ${%s_SRCDIR} && ${CRUNCHMAKE} ${BUILDOPTS} clean cleandepend)\n",
1246             libname);
1247 }
1248
1249
1250 static void
1251 output_strlst(FILE *outf, strlst_t *lst)
1252 {
1253         for (; lst != NULL; lst = lst->next)
1254                 if ( strlen(lst->str) )
1255                         fprintf(outf, " %s", lst->str);
1256         fprintf(outf, "\n");
1257 }
1258
1259
1260 /*
1261  * ========================================================================
1262  * general library routines
1263  *
1264  */
1265
1266 static void
1267 status(const char *str)
1268 {
1269         static int lastlen = 0;
1270         int len, spaces;
1271
1272         if (!verbose)
1273                 return;
1274
1275         len = strlen(str);
1276         spaces = lastlen - len;
1277         if (spaces < 1)
1278                 spaces = 1;
1279
1280         fprintf(stderr, " [%s]%*.*s\r", str, spaces, spaces, " ");
1281         fflush(stderr);
1282         lastlen = len;
1283 }
1284
1285
1286 static void
1287 out_of_memory(void)
1288 {
1289         err(1, "%s: %d: out of memory, stopping", infilename, linenum);
1290 }
1291
1292
1293 static void
1294 add_string(strlst_t **listp, char *str, int nodup)
1295 {
1296         strlst_t *p1, *p2;
1297
1298         /* add to end, and avoid duplicate if nodup != 0 */
1299
1300         for (p1 = NULL, p2 = *listp; p2 != NULL; p1 = p2, p2 = p2->next)
1301                 if (nodup && iseq(p2->str, str))
1302                         return;
1303
1304         p2 = malloc(sizeof(strlst_t));
1305         if (p2) {
1306                 p2->next = NULL;
1307                 p2->str = strdup(str);
1308         }
1309         if (!p2 || !p2->str)
1310                 out_of_memory();
1311
1312         if (p1 == NULL)
1313                 *listp = p2;
1314         else
1315                 p1->next = p2;
1316 }
1317
1318 static int
1319 subtract_strlst(strlst_t **lista, strlst_t **listb)
1320 {
1321         int subtract_count = 0;
1322         strlst_t *p1;
1323
1324         for (p1 = *listb; p1 != NULL; p1 = p1->next)
1325                 if (in_list(lista, p1->str)) {
1326                         warnx("Will compile library `%s' dynamically", p1->str);
1327                         strcat(p1->str, "");
1328                         subtract_count++;
1329                 }
1330
1331         return subtract_count;
1332 }
1333
1334 static int
1335 in_list(strlst_t **listp, char *str)
1336 {
1337         strlst_t *p1;
1338
1339         for (p1 = *listp; p1 != NULL; p1 = p1->next)
1340                 if (iseq(p1->str, str))
1341                         return 1;
1342         return 0;
1343 }
1344
1345 static void
1346 free_list(strlst_t *head)
1347 {
1348         strlst_t *tmp;
1349
1350         while (head != NULL) {
1351                 tmp = head;
1352                 head = head->next;
1353                 free(tmp->str);
1354                 free(tmp);
1355         }
1356 }
1357
1358 static int
1359 is_dir(const char *pathname)
1360 {
1361         struct stat buf;
1362
1363         if (stat(pathname, &buf) == -1)
1364                 return 0;
1365
1366         return S_ISDIR(buf.st_mode);
1367 }
1368
1369 static int
1370 is_nonempty_file(const char *pathname)
1371 {
1372         struct stat buf;
1373
1374         if (stat(pathname, &buf) == -1)
1375                 return 0;
1376
1377         return S_ISREG(buf.st_mode) && buf.st_size > 0;
1378 }
1379
1380 static int
1381 iseq(const char *a, const char *b)
1382 {
1383         return (strcmp(a, b) == 0);
1384 }