xref: /illumos-gate/usr/src/test/libc-tests/tests/symbols/symbols_test.c (revision 9498083eeaed1aacdde41369b7fa6f3b84870791)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2015 Garrett D'Amore <garrett@damore.org>
14  */
15 
16 /*
17  * This program tests symbol visibility using the /usr/bin/c89 and
18  * /usr/bin/c99 programs.
19  */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <err.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <note.h>
30 #include <sys/wait.h>
31 #include "test_common.h"
32 
33 char *dname;
34 char *cfile;
35 char *ofile;
36 char *lfile;
37 char *efile;
38 
39 const char *sym = NULL;
40 
41 static int good_count = 0;
42 static int fail_count = 0;
43 static int full_count = 0;
44 static int extra_debug = 0;
45 static char *compilation = "compilation.cfg";
46 
47 #if defined(_LP64)
48 #define	MFLAG "-m64"
49 #elif defined(_ILP32)
50 #define	MFLAG "-m32"
51 #endif
52 
53 const char *compilers[] = {
54 	"cc",
55 	"gcc",
56 	"/opt/SUNWspro/bin/cc",
57 	"/opt/gcc/4.4.4/bin/gcc",
58 	"/opt/sunstudio12.1/bin/cc",
59 	"/opt/sfw/bin/gcc",
60 	"/usr/local/bin/gcc",
61 	NULL
62 };
63 
64 char *compiler = NULL;
65 const char *c89flags = NULL;
66 const char *c99flags = NULL;
67 
68 #define	MAXENV	64	/* maximum number of environments (bitmask width) */
69 #define	MAXHDR	10	/* maximum # headers to require to access symbol */
70 #define	MAXARG	20	/* maximum # of arguments */
71 
72 #define	WS	" \t"
73 
74 static int next_env = 0;
75 
76 struct compile_env {
77 	char		*ce_name;
78 	char		*ce_lang;
79 	char		*ce_defs;
80 	int		ce_index;
81 };
82 
83 static struct compile_env compile_env[MAXENV];
84 
85 struct env_group {
86 	char			*eg_name;
87 	uint64_t		eg_mask;
88 	struct env_group	*eg_next;
89 };
90 
91 typedef enum { SYM_TYPE, SYM_VALUE, SYM_FUNC } sym_type_t;
92 
93 struct sym_test {
94 	char			*st_name;
95 	sym_type_t		st_type;
96 	char			*st_hdrs[MAXHDR];
97 	char			*st_rtype;
98 	char			*st_atypes[MAXARG];
99 	uint64_t		st_test_mask;
100 	uint64_t		st_need_mask;
101 	char			*st_prog;
102 	struct sym_test		*st_next;
103 };
104 
105 struct env_group *env_groups = NULL;
106 
107 struct sym_test *sym_tests = NULL;
108 struct sym_test **sym_insert = &sym_tests;
109 
110 static char *
111 mystrdup(const char *s)
112 {
113 	char *r;
114 	if ((r = strdup(s)) == NULL) {
115 		perror("strdup");
116 		exit(1);
117 	}
118 	return (r);
119 }
120 
121 static void *
122 myzalloc(size_t sz)
123 {
124 	void *buf;
125 	if ((buf = calloc(1, sz)) == NULL) {
126 		perror("calloc");
127 		exit(1);
128 	}
129 	return (buf);
130 }
131 
132 static void
133 myasprintf(char **buf, const char *fmt, ...)
134 {
135 	int rv;
136 	va_list va;
137 	va_start(va, fmt);
138 	rv = vasprintf(buf, fmt, va);
139 	va_end(va);
140 	if (rv < 0) {
141 		perror("vasprintf");
142 		exit(1);
143 	}
144 }
145 
146 static void
147 append_sym_test(struct sym_test *st)
148 {
149 	*sym_insert = st;
150 	sym_insert = &st->st_next;
151 }
152 
153 static int
154 find_env_mask(const char *name, uint64_t *mask)
155 {
156 	for (int i = 0; i < MAXENV; i++) {
157 		if (compile_env[i].ce_name != NULL &&
158 		    strcmp(compile_env[i].ce_name, name) == 0) {
159 			*mask |= (1ULL << i);
160 			return (0);
161 		}
162 	}
163 
164 	for (struct env_group *eg = env_groups; eg != NULL; eg = eg->eg_next) {
165 		if (strcmp(name, eg->eg_name) == 0) {
166 			*mask |= eg->eg_mask;
167 			return (0);
168 		}
169 	}
170 	return (-1);
171 }
172 
173 
174 static int
175 expand_env(char *list, uint64_t *mask, char **erritem)
176 {
177 	char *item;
178 	for (item = strtok(list, WS); item != NULL; item = strtok(NULL, WS)) {
179 		if (find_env_mask(item, mask) < 0) {
180 			if (erritem != NULL) {
181 				*erritem = item;
182 			}
183 			return (-1);
184 		}
185 	}
186 	return (0);
187 }
188 
189 static int
190 expand_env_list(char *list, uint64_t *test, uint64_t *need, char **erritem)
191 {
192 	uint64_t mask = 0;
193 	int act;
194 	char *item;
195 	for (item = strtok(list, WS); item != NULL; item = strtok(NULL, WS)) {
196 		switch (item[0]) {
197 		case '+':
198 			act = 1;
199 			item++;
200 			break;
201 		case '-':
202 			act = 0;
203 			item++;
204 			break;
205 		default:
206 			act = 1;
207 			break;
208 		}
209 
210 		mask = 0;
211 		if (find_env_mask(item, &mask) < 0) {
212 			if (erritem != NULL) {
213 				*erritem = item;
214 			}
215 			return (-1);
216 		}
217 		*test |= mask;
218 		if (act) {
219 			*need |= mask;
220 		} else {
221 			*need &= ~(mask);
222 		}
223 	}
224 	return (0);
225 }
226 
227 static int
228 do_env(char **fields, int nfields, char **err)
229 {
230 	char *name;
231 	char *lang;
232 	char *defs;
233 
234 	if (nfields != 3) {
235 		myasprintf(err, "number of fields (%d) != 3", nfields);
236 		return (-1);
237 	}
238 
239 	if (next_env >= MAXENV) {
240 		myasprintf(err, "too many environments");
241 		return (-1);
242 	}
243 
244 	name = fields[0];
245 	lang = fields[1];
246 	defs = fields[2];
247 
248 	compile_env[next_env].ce_name = mystrdup(name);
249 	compile_env[next_env].ce_lang = mystrdup(lang);
250 	compile_env[next_env].ce_defs = mystrdup(defs);
251 	compile_env[next_env].ce_index = next_env;
252 	next_env++;
253 	return (0);
254 }
255 
256 static int
257 do_env_group(char **fields, int nfields, char **err)
258 {
259 	char *name;
260 	char *list;
261 	struct env_group *eg;
262 	uint64_t mask;
263 	char *item;
264 
265 	if (nfields != 2) {
266 		myasprintf(err, "number of fields (%d) != 2", nfields);
267 		return (-1);
268 	}
269 
270 	name = fields[0];
271 	list = fields[1];
272 	mask = 0;
273 
274 	if (expand_env(list, &mask, &item) < 0) {
275 		myasprintf(err, "reference to undefined env %s", item);
276 		return (-1);
277 	}
278 
279 	eg = myzalloc(sizeof (*eg));
280 	eg->eg_name = mystrdup(name);
281 	eg->eg_mask = mask;
282 	eg->eg_next = env_groups;
283 	env_groups = eg;
284 	return (0);
285 }
286 
287 static char *progbuf = NULL;
288 size_t proglen = 0;
289 size_t progsiz = 0;
290 
291 static void
292 addprogch(char c)
293 {
294 	while (progsiz <= (proglen + 1)) {
295 		progbuf = realloc(progbuf, progsiz + 4096);
296 		if (progbuf == NULL) {
297 			perror("realloc");
298 			exit(1);
299 		}
300 		progsiz += 1024;
301 	}
302 	progbuf[proglen++] = c;
303 	progbuf[proglen] = 0;
304 }
305 
306 static void
307 addprogstr(char *s)
308 {
309 	while (*s != NULL) {
310 		addprogch(*s);
311 		s++;
312 	}
313 }
314 
315 static void
316 addprogfmt(const char *fmt, ...)
317 {
318 	va_list va;
319 	char *buf = NULL;
320 	va_start(va, fmt);
321 	if (vasprintf(&buf, fmt, va) < 0) {
322 		perror("vasprintf");
323 		exit(1);
324 	}
325 	va_end(va);
326 	addprogstr(buf);
327 	free(buf);
328 }
329 
330 static void
331 mkprog(struct sym_test *st)
332 {
333 	char *s;
334 
335 	proglen = 0;
336 
337 	for (int i = 0; i < MAXHDR && st->st_hdrs[i] != NULL; i++) {
338 		addprogfmt("#include <%s>\n", st->st_hdrs[i]);
339 	}
340 
341 	for (s = st->st_rtype; *s; s++) {
342 		addprogch(*s);
343 		if (*s == '(') {
344 			s++;
345 			addprogch(*s);
346 			s++;
347 			break;
348 		}
349 	}
350 	addprogch(' ');
351 
352 	/* for function pointers, s is closing suffix, otherwise empty */
353 
354 	switch (st->st_type) {
355 	case SYM_TYPE:
356 		addprogstr("test_type;");
357 		break;
358 
359 	case SYM_VALUE:
360 		addprogfmt("test_value%s;\n", s);	/* s usually empty */
361 		addprogstr("void\ntest_func(void)\n{\n");
362 		addprogfmt("\ttest_value = %s;\n}", st->st_name);
363 		break;
364 
365 	case SYM_FUNC:
366 		addprogstr("\ntest_func(");
367 		for (int i = 0; st->st_atypes[i] != NULL && i < MAXARG; i++) {
368 			int didname = 0;
369 			if (i > 0) {
370 				addprogstr(", ");
371 			}
372 			if (strcmp(st->st_atypes[i], "void") == 0) {
373 				didname = 1;
374 			}
375 			if (strcmp(st->st_atypes[i], "") == 0) {
376 				didname = 1;
377 				addprogstr("void");
378 			}
379 
380 			/* print the argument list */
381 			for (char *a = st->st_atypes[i]; *a; a++) {
382 				if (*a == '(' && a[1] == '*' && !didname) {
383 					addprogfmt("(*a%d", i);
384 					didname = 1;
385 					a++;
386 				} else if (*a == '[' && !didname) {
387 					addprogfmt("a%d[", i);
388 					didname = 1;
389 				} else {
390 					addprogch(*a);
391 				}
392 			}
393 			if (!didname) {
394 				addprogfmt(" a%d", i);
395 			}
396 		}
397 
398 		if (st->st_atypes[0] == NULL) {
399 			addprogstr("void");
400 		}
401 
402 		/*
403 		 * Close argument list, and closing ")" for func ptrs.
404 		 * Note that for non-function pointers, s will be empty
405 		 * below, otherwise it points to the trailing argument
406 		 * list.
407 		 */
408 		addprogfmt(")%s\n{\n\t", s);
409 
410 		if (strcmp(st->st_rtype, "") != 0 &&
411 		    strcmp(st->st_rtype, "void") != 0) {
412 			addprogstr("return ");
413 		}
414 
415 		/* add the function call */
416 		addprogfmt("%s(", st->st_name);
417 		for (int i = 0; st->st_atypes[i] != NULL && i < MAXARG; i++) {
418 			if (strcmp(st->st_atypes[i], "") != 0 &&
419 			    strcmp(st->st_atypes[i], "void") != 0) {
420 				addprogfmt("%sa%d", i > 0 ? ", " : "", i);
421 			}
422 		}
423 
424 		addprogstr(");\n}");
425 		break;
426 	}
427 
428 	addprogch('\n');
429 
430 	st->st_prog = progbuf;
431 }
432 
433 static int
434 add_envs(struct sym_test *st, char *envs, char **err)
435 {
436 	char *item;
437 	if (expand_env_list(envs, &st->st_test_mask, &st->st_need_mask,
438 	    &item) < 0) {
439 		myasprintf(err, "bad env action %s", item);
440 		return (-1);
441 	}
442 	return (0);
443 }
444 
445 static int
446 add_headers(struct sym_test *st, char *hdrs, char **err)
447 {
448 	int i = 0;
449 
450 	for (char *h = strsep(&hdrs, ";"); h != NULL; h = strsep(&hdrs, ";")) {
451 		if (i >= MAXHDR) {
452 			myasprintf(err, "too many headers");
453 			return (-1);
454 		}
455 		test_trim(&h);
456 		st->st_hdrs[i++] = mystrdup(h);
457 	}
458 
459 	return (0);
460 }
461 
462 static int
463 add_arg_types(struct sym_test *st, char *atype, char **err)
464 {
465 	int i = 0;
466 	char *a;
467 	for (a = strsep(&atype, ";"); a != NULL; a = strsep(&atype, ";")) {
468 		if (i >= MAXARG) {
469 			myasprintf(err, "too many arguments");
470 			return (-1);
471 		}
472 		test_trim(&a);
473 		st->st_atypes[i++] = mystrdup(a);
474 	}
475 
476 	return (0);
477 }
478 
479 static int
480 do_type(char **fields, int nfields, char **err)
481 {
482 	char *decl;
483 	char *hdrs;
484 	char *envs;
485 	struct sym_test *st;
486 
487 	if (nfields != 3) {
488 		myasprintf(err, "number of fields (%d) != 3", nfields);
489 		return (-1);
490 	}
491 	decl = fields[0];
492 	hdrs = fields[1];
493 	envs = fields[2];
494 
495 	st = myzalloc(sizeof (*st));
496 	st->st_type = SYM_TYPE;
497 	st->st_name = mystrdup(decl);
498 	st->st_rtype = mystrdup(decl);
499 
500 	if ((add_envs(st, envs, err) < 0) ||
501 	    (add_headers(st, hdrs, err) < 0)) {
502 		return (-1);
503 	}
504 	append_sym_test(st);
505 
506 	return (0);
507 }
508 
509 static int
510 do_value(char **fields, int nfields, char **err)
511 {
512 	char *name;
513 	char *type;
514 	char *hdrs;
515 	char *envs;
516 	struct sym_test *st;
517 
518 	if (nfields != 4) {
519 		myasprintf(err, "number of fields (%d) != 4", nfields);
520 		return (-1);
521 	}
522 	name = fields[0];
523 	type = fields[1];
524 	hdrs = fields[2];
525 	envs = fields[3];
526 
527 	st = myzalloc(sizeof (*st));
528 	st->st_type = SYM_VALUE;
529 	st->st_name = mystrdup(name);
530 	st->st_rtype = mystrdup(type);
531 
532 	if ((add_envs(st, envs, err) < 0) ||
533 	    (add_headers(st, hdrs, err) < 0)) {
534 		return (-1);
535 	}
536 	append_sym_test(st);
537 
538 	return (0);
539 }
540 
541 static int
542 do_func(char **fields, int nfields, char **err)
543 {
544 	char *name;
545 	char *rtype;
546 	char *atype;
547 	char *hdrs;
548 	char *envs;
549 	struct sym_test *st;
550 
551 	if (nfields != 5) {
552 		myasprintf(err, "number of fields (%d) != 5", nfields);
553 		return (-1);
554 	}
555 	name = fields[0];
556 	rtype = fields[1];
557 	atype = fields[2];
558 	hdrs = fields[3];
559 	envs = fields[4];
560 
561 	st = myzalloc(sizeof (*st));
562 	st->st_type = SYM_FUNC;
563 	st->st_name = mystrdup(name);
564 	st->st_rtype = mystrdup(rtype);
565 
566 	if ((add_envs(st, envs, err) < 0) ||
567 	    (add_headers(st, hdrs, err) < 0) ||
568 	    (add_arg_types(st, atype, err) < 0)) {
569 		return (-1);
570 	}
571 	append_sym_test(st);
572 
573 	return (0);
574 }
575 
576 struct sym_test *
577 next_sym_test(struct sym_test *st)
578 {
579 	return (st == NULL ? sym_tests : st->st_next);
580 }
581 
582 const char *
583 sym_test_prog(struct sym_test *st)
584 {
585 	if (st->st_prog == NULL) {
586 		mkprog(st);
587 	}
588 	return (st->st_prog);
589 }
590 
591 const char *
592 sym_test_name(struct sym_test *st)
593 {
594 	return (st->st_name);
595 }
596 
597 /*
598  * Iterate through tests.  Pass in NULL for cenv to begin the iteration. For
599  * subsequent iterations, use the return value from the previous iteration.
600  * Returns NULL when there are no more environments.
601  */
602 struct compile_env *
603 sym_test_env(struct sym_test *st, struct compile_env *cenv, int *need)
604 {
605 	int i = cenv ? cenv->ce_index + 1: 0;
606 	uint64_t b = 1ULL << i;
607 
608 	while ((i < MAXENV) && (b != 0)) {
609 		cenv = &compile_env[i];
610 		if (b & st->st_test_mask) {
611 			*need = (st->st_need_mask & b) ? 1 : 0;
612 			return (cenv);
613 		}
614 		b <<= 1;
615 		i++;
616 	}
617 	return (NULL);
618 }
619 
620 const char *
621 env_name(struct compile_env *cenv)
622 {
623 	return (cenv->ce_name);
624 }
625 
626 const char *
627 env_lang(struct compile_env *cenv)
628 {
629 	return (cenv->ce_lang);
630 }
631 
632 const char *
633 env_defs(struct compile_env *cenv)
634 {
635 	return (cenv->ce_defs);
636 }
637 
638 static void
639 show_file(test_t t, const char *path)
640 {
641 	FILE *f;
642 	char *buf = NULL;
643 	size_t cap = 0;
644 	int line = 1;
645 
646 	f = fopen(path, "r");
647 	if (f == NULL) {
648 		test_debugf(t, "fopen(%s): %s", path, strerror(errno));
649 		return;
650 	}
651 
652 	test_debugf(t, "----->> begin (%s) <<------", path);
653 	while (getline(&buf, &cap, f) >= 0) {
654 		(void) strtok(buf, "\r\n");
655 		test_debugf(t, "%d: %s", line, buf);
656 		line++;
657 	}
658 	test_debugf(t, "----->> end (%s) <<------", path);
659 	(void) fclose(f);
660 }
661 
662 static void
663 cleanup(void)
664 {
665 	if (ofile != NULL) {
666 		(void) unlink(ofile);
667 		free(ofile);
668 		ofile = NULL;
669 	}
670 	if (lfile != NULL) {
671 		(void) unlink(lfile);
672 		free(lfile);
673 		lfile = NULL;
674 	}
675 	if (cfile != NULL) {
676 		(void) unlink(cfile);
677 		free(cfile);
678 		cfile = NULL;
679 	}
680 	if (efile != NULL) {
681 		(void) unlink(efile);
682 		free(efile);
683 		efile = NULL;
684 	}
685 	if (dname) {
686 		(void) rmdir(dname);
687 		free(dname);
688 		dname = NULL;
689 	}
690 }
691 
692 static int
693 mkworkdir(void)
694 {
695 	char b[32];
696 	char *d;
697 
698 	cleanup();
699 
700 	(void) strlcpy(b, "/tmp/symbols_testXXXXXX", sizeof (b));
701 	if ((d = mkdtemp(b)) == NULL) {
702 		perror("mkdtemp");
703 		return (-1);
704 	}
705 	dname = mystrdup(d);
706 	myasprintf(&cfile, "%s/compile_test.c", d);
707 	myasprintf(&ofile, "%s/compile_test.o", d);
708 	myasprintf(&lfile, "%s/compile_test.log", d);
709 	myasprintf(&efile, "%s/compile_test.exe", d);
710 	return (0);
711 }
712 
713 void
714 find_compiler(void)
715 {
716 	test_t t;
717 	int i;
718 	FILE *cf;
719 
720 	t = test_start("finding compiler");
721 
722 	if ((cf = fopen(cfile, "w+")) == NULL) {
723 		test_failed(t, "Unable to open %s for write: %s", cfile,
724 		    strerror(errno));
725 		return;
726 	}
727 	(void) fprintf(cf, "#include <stdio.h>\n");
728 	(void) fprintf(cf, "int main(int argc, char **argv) {\n");
729 	(void) fprintf(cf, "#if defined(__SUNPRO_C)\n");
730 	(void) fprintf(cf, "exit(51);\n");
731 	(void) fprintf(cf, "#elif defined(__GNUC__)\n");
732 	(void) fprintf(cf, "exit(52);\n");
733 	(void) fprintf(cf, "#else\n");
734 	(void) fprintf(cf, "exit(99)\n");
735 	(void) fprintf(cf, "#endif\n}\n");
736 	(void) fclose(cf);
737 
738 	for (i = 0; compilers[i] != NULL; i++) {
739 		char cmd[256];
740 		int rv;
741 
742 		(void) snprintf(cmd, sizeof (cmd),
743 		    "%s %s %s -o %s >/dev/null 2>&1",
744 		    compilers[i], MFLAG, cfile, efile);
745 		test_debugf(t, "trying %s", cmd);
746 		rv = system(cmd);
747 
748 		test_debugf(t, "result: %d", rv);
749 
750 		if ((rv < 0) || !WIFEXITED(rv) || WEXITSTATUS(rv) != 0)
751 			continue;
752 
753 		rv = system(efile);
754 		if (rv >= 0 && WIFEXITED(rv)) {
755 			rv = WEXITSTATUS(rv);
756 		} else {
757 			rv = -1;
758 		}
759 
760 		switch (rv) {
761 		case 51:	/* STUDIO */
762 			test_debugf(t, "Found Studio C");
763 			c89flags = "-Xc -errwarn=%all -v -xc99=%none " MFLAG;
764 			c99flags = "-Xc -errwarn=%all -v -xc99=%all " MFLAG;
765 			if (extra_debug) {
766 				test_debugf(t, "c89flags: %s", c89flags);
767 				test_debugf(t, "c99flags: %s", c99flags);
768 			}
769 			test_passed(t);
770 			break;
771 		case 52:	/* GCC */
772 			test_debugf(t, "Found GNU C");
773 			c89flags = "-Wall -Werror -std=c89 -nostdinc "
774 			    "-isystem /usr/include " MFLAG;
775 			c99flags = "-Wall -Werror -std=c99 -nostdinc "
776 			    "-isystem /usr/include " MFLAG;
777 			if (extra_debug) {
778 				test_debugf(t, "c89flags: %s", c89flags);
779 				test_debugf(t, "c99flags: %s", c99flags);
780 			}
781 			test_passed(t);
782 			break;
783 		case 99:
784 			test_debugf(t, "Found unknown (unsupported) compiler");
785 			continue;
786 		default:
787 			continue;
788 		}
789 		myasprintf(&compiler, "%s", compilers[i]);
790 		test_debugf(t, "compiler: %s", compiler);
791 		return;
792 	}
793 	test_failed(t, "No compiler found.");
794 }
795 
796 int
797 do_compile(test_t t, struct sym_test *st, struct compile_env *cenv, int need)
798 {
799 	char *cmd;
800 	FILE *logf;
801 	FILE *dotc;
802 	const char *prog;
803 
804 	full_count++;
805 
806 	if ((dotc = fopen(cfile, "w+")) == NULL) {
807 		test_failed(t, "fopen(%s): %s", cfile, strerror(errno));
808 		return (-1);
809 	}
810 	prog = sym_test_prog(st);
811 	if (fwrite(prog, 1, strlen(prog), dotc) < strlen(prog)) {
812 		test_failed(t, "fwrite: %s", strerror(errno));
813 		(void) fclose(dotc);
814 		return (-1);
815 	}
816 	if (fclose(dotc) < 0) {
817 		test_failed(t, "fclose: %s", strerror(errno));
818 		return (-1);
819 	}
820 
821 	(void) unlink(ofile);
822 
823 	myasprintf(&cmd, "%s %s %s -c %s -o %s >>%s 2>&1",
824 	    compiler, strcmp(env_lang(cenv), "c99") == 0 ? c99flags : c89flags,
825 	    env_defs(cenv), cfile, ofile, lfile);
826 
827 	if (extra_debug) {
828 		test_debugf(t, "command: %s", cmd);
829 	}
830 
831 	if ((logf = fopen(lfile, "w+")) == NULL) {
832 		test_failed(t, "fopen: %s", strerror(errno));
833 		return (-1);
834 	}
835 	(void) fprintf(logf, "===================\n");
836 	(void) fprintf(logf, "PROGRAM:\n%s\n", sym_test_prog(st));
837 	(void) fprintf(logf, "COMMAND: %s\n", cmd);
838 	(void) fprintf(logf, "EXPECT: %s\n", need ? "OK" : "FAIL");
839 	(void) fclose(logf);
840 
841 	switch (system(cmd)) {
842 	case -1:
843 		test_failed(t, "error compiling in %s: %s", env_name(cenv),
844 		    strerror(errno));
845 		return (-1);
846 	case 0:
847 		if (!need) {
848 			fail_count++;
849 			show_file(t, lfile);
850 			test_failed(t, "symbol visible in %s", env_name(cenv));
851 			return (-1);
852 		}
853 		break;
854 	default:
855 		if (need) {
856 			fail_count++;
857 			show_file(t, lfile);
858 			test_failed(t, "error compiling in %s", env_name(cenv));
859 			return (-1);
860 		}
861 		break;
862 	}
863 	good_count++;
864 	return (0);
865 }
866 
867 void
868 test_compile(void)
869 {
870 	struct sym_test *st;
871 	struct compile_env *cenv;
872 	test_t t;
873 	int need;
874 
875 	for (st = next_sym_test(NULL); st; st = next_sym_test(st)) {
876 		if ((sym != NULL) && strcmp(sym, sym_test_name(st))) {
877 			continue;
878 		}
879 		/* XXX: we really want a sym_test_desc() */
880 		for (cenv = sym_test_env(st, NULL, &need);
881 		    cenv != NULL;
882 		    cenv = sym_test_env(st, cenv, &need)) {
883 			t = test_start("%s : %c%s", sym_test_name(st),
884 			    need ? '+' : '-', env_name(cenv));
885 			if (do_compile(t, st, cenv, need) == 0) {
886 				test_passed(t);
887 			}
888 		}
889 	}
890 
891 	if (full_count > 0) {
892 		test_summary();
893 	}
894 }
895 
896 int
897 main(int argc, char **argv)
898 {
899 	int optc;
900 	int optC = 0;
901 
902 	while ((optc = getopt(argc, argv, "DdfCs:c:")) != EOF) {
903 		switch (optc) {
904 		case 'd':
905 			test_set_debug();
906 			break;
907 		case 'f':
908 			test_set_force();
909 			break;
910 		case 'D':
911 			test_set_debug();
912 			extra_debug++;
913 			break;
914 		case 'c':
915 			compilation = optarg;
916 			break;
917 		case 'C':
918 			optC++;
919 			break;
920 		case 's':
921 			sym = optarg;
922 			break;
923 		default:
924 			(void) fprintf(stderr, "Usage: %s [-df]\n", argv[0]);
925 			exit(1);
926 		}
927 	}
928 
929 	if (test_load_config(NULL, compilation,
930 	    "env", do_env, "env_group", do_env_group, NULL) < 0) {
931 		exit(1);
932 	}
933 
934 	while (optind < argc) {
935 		if (test_load_config(NULL, argv[optind++],
936 		    "type", do_type,
937 		    "value", do_value,
938 		    "func", do_func,
939 		    NULL) < 0) {
940 			exit(1);
941 		}
942 	}
943 
944 	if (atexit(cleanup) != 0) {
945 		perror("atexit");
946 		exit(1);
947 	}
948 
949 	if (mkworkdir() < 0) {
950 		perror("mkdir");
951 		exit(1);
952 	}
953 
954 	find_compiler();
955 	if (!optC)
956 		test_compile();
957 
958 	exit(0);
959 }
960