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