scc

simple c99 compiler
git clone git://git.simple-cc.org/scc
Log | Files | Refs | Submodules | README | LICENSE

cc.c (12426B)


      1 /*
      2  * FIXME: scc is a pure C99 source code, except this file that is
      3  * intended for a POSIX.2008 environment. This situation  creates
      4  * a problem where some systems require the macro _ANSI_SOURCE to
      5  * limit the namespace to a pure ISO namespace. NetBSD has a  bug
      6  * and using POSIX_C_SOURCE  and  _ANSI_SOURCE  at  the same time
      7  * generates a syntax  error in the system headers. A  patch  was
      8  * sent to NetBSD, but this temporary fix is added here until the
      9  * patch  arrives to the stable release.
     10  */
     11 #undef _POSIX_C_SOURCE
     12 #undef _ANSI_SOURCE
     13 #define _POSIX_C_SOURCE 200809L
     14 
     15 #include <fcntl.h>
     16 #include <sys/types.h>
     17 #include <sys/wait.h>
     18 #include <unistd.h>
     19 
     20 #include <assert.h>
     21 #include <errno.h>
     22 #include <limits.h>
     23 #include <signal.h>
     24 #include <stdio.h>
     25 #include <stdlib.h>
     26 #include <string.h>
     27 
     28 #include <scc/arg.h>
     29 #include <scc/config.h>
     30 #include <scc/scc.h>
     31 #include <scc/sys.h>
     32 
     33 enum {
     34 	CC1,
     35 	TEEIR,
     36 	CC2,
     37 	TEEQBE,
     38 	QBE,
     39 	TEEAS,
     40 	AS,
     41 	LD,
     42 	LAST_TOOL,
     43 };
     44 
     45 static struct tool {
     46 	char   cmd[PATH_MAX];
     47 	char   bin[32];
     48 	char  *outfile;
     49 	struct items args;
     50 	int    in, out;
     51 	pid_t  pid;
     52 } tools[] = {
     53 	[CC1]    = {.bin = "cc1"},
     54 	[TEEIR]  = {.bin = "tee"},
     55 	[CC2]    = {.bin = "cc2"},
     56 	[TEEQBE] = {.bin = "tee"},
     57 	[QBE]    = {.bin = "qbe"},
     58 	[TEEAS]  = {.bin = "tee"},
     59 	[AS]     = {.bin = ASBIN},
     60 	[LD]     = {.bin = LDBIN},
     61 };
     62 
     63 char *argv0;
     64 static char *arch, *sys, *abi, *format;
     65 static char *objfile, *outfile;
     66 static char *prefix, *libprefix;
     67 static char *tmpdir;
     68 static size_t tmpdirln;
     69 static struct items objtmp, objout;
     70 static struct items linkargs, cc1args;
     71 
     72 static int Mflag, Eflag, Sflag, Wflag,
     73            cflag, dflag, kflag, sflag, Qflag = 1, /* TODO: Remove Qflag */
     74            gflag;
     75 
     76 static int devnullfd = -1;
     77 
     78 extern int failure;
     79 
     80 static void
     81 terminate(void)
     82 {
     83 	unsigned i;
     84 
     85 	if (!kflag) {
     86 		for (i = 0; i < objtmp.n; ++i)
     87 			unlink(objtmp.s[i]);
     88 	}
     89 }
     90 
     91 static void
     92 sighandler(int sig)
     93 {
     94 	terminate();
     95 	_exit(1);
     96 }
     97 
     98 static void
     99 addarg(int tool, char *arg)
    100 {
    101 	struct tool *t = &tools[tool];
    102 	char *p, buff[FILENAME_MAX];
    103 	size_t len, cnt;
    104 	int n;
    105 
    106 	if (!arg) {
    107 		newitem(&t->args, NULL);
    108 		return;
    109 	}
    110 
    111 	if (arg[0] == '-') {
    112 		newitem(&t->args, xstrdup(arg));
    113 		return;
    114 	}
    115 
    116 	if (!strcmp(arg, "%c")) {
    117 		for (n = 0; n < linkargs.n; ++n)
    118 			newitem(&t->args, xstrdup(linkargs.s[n]));
    119 		return;
    120 	}
    121 
    122 	for (cnt = 0 ; *arg && cnt < FILENAME_MAX; ++arg) {
    123 		if (*arg != '%') {
    124 			buff[cnt++] = *arg;
    125 			continue;
    126 		}
    127 
    128 		switch (*++arg) {
    129 		case 'a':
    130 			p = arch;
    131 			break;
    132 		case 's':
    133 			p = sys;
    134 			break;
    135 		case 'p':
    136 			p = libprefix;
    137 			break;
    138 		case 'b':
    139 			p = abi;
    140 			break;
    141 		case 'o':
    142 			p = t->outfile;
    143 			break;
    144 		default:
    145 			buff[cnt++] = *arg;
    146 			continue;
    147 		}
    148 
    149 		len = strlen(p);
    150 		if (len + cnt >= FILENAME_MAX)
    151 			die("scc-cc: pathname too long");
    152 		memcpy(buff+cnt, p, len);
    153 		cnt += len;
    154 	}
    155 
    156 	if (cnt >= FILENAME_MAX)
    157 		abort();
    158 
    159 	buff[cnt] = '\0';
    160 	newitem(&t->args, xstrdup(buff));
    161 }
    162 
    163 static char *
    164 cc2fmt(int tool)
    165 {
    166 	if (Qflag && !strcmp(arch, "amd64") && !strcmp(abi, "sysv"))
    167 		return "%s/libexec/scc/%s-qbe_%s-%s";
    168 	return "%s/libexec/scc/%s-%s-%s";
    169 }
    170 
    171 static char *
    172 outfname(char *path, char *type)
    173 {
    174 	char *new, sep, *p;
    175 	size_t newsz, pathln;
    176 	int tmpfd, n;
    177 
    178 	if (path) {
    179 		sep = '.';
    180 		if (p = strrchr(path, '/'))
    181 			path = p + 1;
    182 		pathln = strlen(path);
    183 		if (p = strrchr(path, '.'))
    184 			pathln -= strlen(p);
    185 	} else {
    186 		sep = '/';
    187 		type = "scc-XXXXXX";
    188 		path = tmpdir;
    189 		pathln = tmpdirln;
    190 	}
    191 
    192 	newsz = pathln + 1 + strlen(type) + 1;
    193 	new = xmalloc(newsz);
    194 	n = snprintf(new, newsz, "%.*s%c%s", (int)pathln, path, sep, type);
    195 	if (n < 0 || n >= newsz)
    196 		die("scc-cc: wrong output filename");
    197 	if (sep == '/') {
    198 		if ((tmpfd = mkstemp(new)) < 0)
    199 			die("scc-cc: could not create output file '%s': %s",
    200 			    new, strerror(errno));
    201 		close(tmpfd);
    202 	}
    203 
    204 	return new;
    205 }
    206 
    207 static int
    208 settool(int tool, char *infile, int nexttool)
    209 {
    210 	struct tool *t = &tools[tool];
    211 	char *fmt;
    212 	int n, fds[2];
    213 	static int fdin = -1;
    214 
    215 	strcpy(t->cmd, t->bin);
    216 	assert(t->args.n == 0);
    217 	newitem(&t->args, xstrdup(t->bin));
    218 
    219 	switch (tool) {
    220 	case CC1:
    221 		if (Eflag)
    222 			addarg(tool, "-E");
    223 		if (Mflag)
    224 			addarg(tool, "-M");
    225 		if (Wflag)
    226 			addarg(tool, "-w");
    227 		for (n = 0; n < cc1args.n; ++n)
    228 			addarg(tool, cc1args.s[n]);
    229 		for (n = 0; sysincludes[n]; ++n) {
    230 			addarg(tool, "-I");
    231 			addarg(tool, sysincludes[n]);
    232 		}
    233 		fmt = "%s/libexec/scc/%s";
    234 		goto local_tool;
    235 	case CC2:
    236 		fmt = cc2fmt(tool);
    237 		goto local_tool;
    238 	case QBE:
    239 		fmt = "%s/libexec/scc/%s";
    240 	local_tool:
    241 		n = snprintf(t->cmd, sizeof(t->cmd),
    242 		             fmt, prefix, t->bin, arch, abi);
    243 		if (n < 0 || n >= sizeof(t->cmd))
    244 			die("scc-cc: target tool path is too long");
    245 		break;
    246 	case LD:
    247 		if (!outfile)
    248 			outfile = "a.out";
    249 		t->outfile = xstrdup(outfile);
    250 		if (gflag)
    251 			addarg(tool, "-g");
    252 		if (sflag)
    253 			addarg(tool, "-s");
    254 		for (n = 0; ldcmd[n]; n++)
    255 			addarg(tool, ldcmd[n]);
    256                 break;
    257 	case TEEIR:
    258 		if (Eflag && outfile)
    259 			t->outfile = xstrdup(outfile);
    260 		else
    261 			t->outfile = outfname(infile, "ir");
    262 		addarg(tool, t->outfile);
    263 		break;
    264 	case TEEQBE:
    265 		t->outfile = outfname(infile, "qbe");
    266 		addarg(tool, t->outfile);
    267 		break;
    268 	case TEEAS:
    269 		t->outfile = outfname(infile, "s");
    270 		addarg(tool, t->outfile);
    271 		break;
    272 	case AS:
    273 		if (cflag && outfile) {
    274 			objfile = xstrdup(outfile);
    275 		} else {
    276 			objfile = (cflag || kflag) ? infile : NULL;
    277 			objfile = outfname(objfile, "o");
    278 		}
    279 		t->outfile = xstrdup(objfile);
    280 		if (gflag)
    281 			addarg(tool, "-g");
    282 		for (n = 0; ascmd[n]; n++)
    283 			addarg(tool, ascmd[n]);
    284                 break;
    285 		break;
    286 	default:
    287 		break;
    288 	}
    289 
    290 	if (fdin > -1) {
    291 		t->in = fdin;
    292 		fdin = -1;
    293 	} else {
    294 		t->in = -1;
    295 		if (infile)
    296 			addarg(tool, infile);
    297 	}
    298 
    299 	if (nexttool < LAST_TOOL) {
    300 		if (pipe(fds))
    301 			die("scc-cc: pipe: %s", strerror(errno));
    302 		t->out = fds[1];
    303 		fdin = fds[0];
    304 	} else {
    305 		t->out = -1;
    306 	}
    307 
    308 	addarg(tool, NULL);
    309 
    310 	return tool;
    311 }
    312 
    313 /*
    314  * We cannot call exit() because it would call the atexit()
    315  * handlers. If it finishes correctly we already called
    316  * fflush() in the output buffers so it is not a problem
    317  * not following the normal exit path.
    318  */
    319 static void
    320 tee(char *fname)
    321 {
    322 	FILE *fp;
    323 	int ch;
    324 
    325 	if ((fp = fopen(fname, "w")) == NULL)
    326 		goto err;
    327 
    328 	while ((ch = getchar()) != EOF) {
    329 		putc(ch, stdout);
    330 		putc(ch, fp);
    331 	}
    332 
    333 	fflush(stdout);
    334 	fflush(fp);
    335 
    336 	if (ferror(stdin) || ferror(stdout) || ferror(fp))
    337 		goto err;
    338 	_exit(0);
    339 
    340 err:
    341 	fprintf(stderr,
    342 		"tee: teeing %s: %s\n",
    343 		fname,
    344 		strerror(errno));
    345 
    346 	_exit(1);
    347 }
    348 
    349 static void
    350 spawn(int tool)
    351 {
    352 	int i;
    353 	char **ap;
    354 	struct tool *t = &tools[tool];
    355 
    356 	switch (t->pid = fork()) {
    357 	case -1:
    358 		die("scc-cc: %s: %s", t->bin, strerror(errno));
    359 	case 0:
    360 		if (t->out > -1)
    361 			dup2(t->out, 1);
    362 		if (t->in > -1)
    363 			dup2(t->in, 0);
    364 
    365 		if (tool == TEEAS && Sflag)
    366 			dup2(devnullfd, 1);
    367 		if (!dflag && tool != CC1 && tool != LD)
    368 			dup2(devnullfd, 2);
    369 
    370 		if (dflag) {
    371 			fprintf(stderr, "%s", t->cmd);
    372 			for (ap = t->args.s+1; *ap; ap++)
    373 				fprintf(stderr, " %s", *ap);
    374 			putc('\n', stderr);
    375 		}
    376 		if (strcmp(t->cmd, "tee") == 0)
    377 			tee(t->outfile);
    378 		execvp(t->cmd, t->args.s);
    379 		if (dflag) {
    380 			fprintf(stderr,
    381 			        "scc-cc: execvp %s: %s\n",
    382 				t->cmd,
    383 			        strerror(errno));
    384 		}
    385 		abort();
    386 	default:
    387 		if (t->in > -1)
    388 			close(t->in);
    389 		if (t->out > -1)
    390 			close(t->out);
    391 
    392 		for (i = 0; i < t->args.n; i++)
    393 			free(t->args.s[i]);
    394 		t->args.n = 0;
    395 
    396 		break;
    397 	}
    398 }
    399 
    400 static int
    401 toolfor(char *file)
    402 {
    403 	char *dot = strrchr(file, '.');
    404 
    405 	if (Eflag)
    406 		return CC1;
    407 
    408 	if (dot) {
    409 		if (!strcmp(dot, ".c"))
    410 			return CC1;
    411 		if (!strcmp(dot, ".ir"))
    412 			return CC2;
    413 		if (!strcmp(dot, ".qbe"))
    414 			return QBE;
    415 		if (!strcmp(dot, ".s"))
    416 			return AS;
    417 		if (!strcmp(dot, ".o"))
    418 			return LD;
    419 		if (!strcmp(dot, ".a"))
    420 			return LD;
    421 	} else if (!strcmp(file, "-")) {
    422 		return CC1;
    423 	}
    424 
    425 	die("scc-cc: unrecognized filetype of %s", file);
    426 }
    427 
    428 static int
    429 valid(int tool, struct tool *t)
    430 {
    431 	int st;
    432 
    433 	if (waitpid(t->pid, &st, 0) == -1 || WIFSIGNALED(st))
    434 		goto internal;
    435 	if (WIFEXITED(st) && WEXITSTATUS(st) == 0)
    436 		return 1;
    437 	if (!failure && (tool == CC1 || tool == LD))
    438 		goto fail;
    439 
    440 internal:
    441 	if (!failure)
    442 		fprintf(stderr, "scc-cc:%s: internal error\n", t->bin);
    443 fail:
    444 	failure = 1;
    445 	return 0;
    446 }
    447 
    448 static int
    449 validatetools(void)
    450 {
    451 	struct tool *t;
    452 	unsigned i;
    453 	int tool, st, failed = LAST_TOOL;
    454 
    455 	for (tool = 0; tool < LAST_TOOL; ++tool) {
    456 		t = &tools[tool];
    457 		if (!t->pid)
    458 			continue;
    459 		if (!valid(tool, t))
    460 			failed = tool;
    461 		if (tool >= failed && t->outfile)
    462 			unlink(t->outfile);
    463 		free(t->outfile);
    464 		t->pid = 0;
    465 	}
    466 	if (failed < LAST_TOOL) {
    467 		unlink(objfile);
    468 		return 0;
    469 	}
    470 
    471 	return 1;
    472 }
    473 
    474 static int
    475 buildfile(char *file, int tool)
    476 {
    477 	int nexttool;
    478 
    479 	for (; tool < LAST_TOOL; tool = nexttool) {
    480 		switch (tool) {
    481 		case CC1:
    482 			if (Eflag && outfile)
    483 				nexttool = TEEIR;
    484 			else if (Eflag || Mflag)
    485 				nexttool = LAST_TOOL;
    486 			else
    487 				nexttool = kflag ? TEEIR : CC2;
    488 			break;
    489 		case TEEIR:
    490 			nexttool = (Eflag) ? LAST_TOOL : CC2;
    491 			break;
    492 		case CC2:
    493 			if (Qflag)
    494 				nexttool = kflag ? TEEQBE : QBE;
    495 			else
    496 				nexttool = (Sflag || kflag) ? TEEAS : AS;
    497 			break;
    498 		case TEEQBE:
    499 			nexttool = QBE;
    500 			break;
    501 		case QBE:
    502 			nexttool = (Sflag || kflag) ? TEEAS : AS;
    503 			break;
    504 		case TEEAS:
    505 			nexttool = Sflag ? LAST_TOOL : AS;
    506 			break;
    507 		case AS:
    508 			nexttool = LAST_TOOL;
    509 			break;
    510 		default:
    511 			nexttool = LAST_TOOL;
    512 			continue;
    513 		}
    514 
    515 		spawn(settool(tool, file, nexttool));
    516 	}
    517 
    518 	return validatetools();
    519 }
    520 
    521 static void
    522 build(struct items *chain, int link)
    523 {
    524 	int i, tool;
    525 
    526 	for (i = 0; i < chain->n; ++i) {
    527 		if (!strcmp(chain->s[i], "-l")) {
    528 			newitem(&linkargs, chain->s[i++]);
    529 			newitem(&linkargs, chain->s[i]);
    530 			continue;
    531 		}
    532 		tool = toolfor(chain->s[i]);
    533 		if (tool == LD) {
    534 			newitem(&linkargs, chain->s[i]);
    535 			continue;
    536 		}
    537 		if (buildfile(chain->s[i], tool)) {
    538 			newitem(&linkargs, objfile);
    539 			newitem((!link || kflag) ? &objout : &objtmp, objfile);
    540 		}
    541 	}
    542 }
    543 
    544 static void
    545 usage(void)
    546 {
    547 	fputs("usage: cc [options] file...\n", stderr);
    548 	exit(1);
    549 }
    550 
    551 int
    552 main(int argc, char *argv[])
    553 {
    554 	struct items linkchain = { .n = 0, };
    555 	int link, n;
    556 
    557 	atexit(terminate);
    558 	signal(SIGHUP, sighandler);
    559 	signal(SIGINT, sighandler);
    560 	signal(SIGTERM, sighandler);
    561 
    562 	if (!(arch = getenv("ARCH")))
    563 		arch = ARCH;
    564 	if (!(sys = getenv("SYS")))
    565 		sys = SYS;
    566 	if (!(abi = getenv("ABI")))
    567 		abi = ABI;
    568 	if (!(format = getenv("FORMAT")))
    569 		format = FORMAT;
    570 	if (!(prefix = getenv("SCCPREFIX")))
    571 		prefix = PREFIX;
    572 	if (!(libprefix = getenv("SCCLIBPREFIX")))
    573 		libprefix = LIBPREFIX;
    574 
    575 	ARGBEGIN {
    576 	case 'D':
    577 		newitem(&cc1args, "-D");
    578 		newitem(&cc1args, EARGF(usage()));
    579 		break;
    580 	case 'M':
    581 		Mflag = 1;
    582 		break;
    583 	case 'E':
    584 		Eflag = 1;
    585 		break;
    586 	case 'I':
    587 		newitem(&cc1args, "-I");
    588 		newitem(&cc1args, EARGF(usage()));
    589 		break;
    590 	case 'L':
    591 		newitem(&linkargs, "-L");
    592 		newitem(&linkargs, EARGF(usage()));
    593 		break;
    594 	case 'O':
    595 		EARGF(usage());
    596 		break;
    597 	case 'S':
    598 		Sflag = 1;
    599 		break;
    600 	case 'U':
    601 		newitem(&cc1args, "-U");
    602 		newitem(&cc1args, EARGF(usage()));
    603 		break;
    604 	case 'c':
    605 		cflag = 1;
    606 		break;
    607 	case 'd':
    608 		dflag = 1;
    609 		break;
    610 	case 'g':
    611 		gflag = 1;
    612 		break;
    613 	case 'k':
    614 		kflag = 1;
    615 		break;
    616 	case 'l':
    617 		newitem(&linkchain, "-l");
    618 		newitem(&linkchain, EARGF(usage()));
    619 		break;
    620 	case 'm':
    621 		arch = EARGF(usage());
    622 		break;
    623 	case 'o':
    624 		outfile = EARGF(usage());
    625 		break;
    626 	case 's':
    627 		sflag = 1;
    628 		break;
    629 	case 't':
    630 		sys = EARGF(usage());
    631 		break;
    632 	case 'w':
    633 		Wflag = 0;
    634 		break;
    635 	case 'W':
    636 		Wflag = 1;
    637 		break;
    638 	case 'q':
    639 		Qflag = 0;
    640 		break;
    641 	case 'Q':
    642 		Qflag = 1;
    643 		break;
    644 	case '-':
    645 		fprintf(stderr,
    646 		        "scc-cc: ignored parameter --%s\n", EARGF(usage()));
    647 		break;
    648 	default:
    649 		usage();
    650 	} ARGOPERAND {
    651 operand:
    652 		newitem(&linkchain, ARGOP());
    653 	} ARGEND
    654 
    655 	for (; *argv; --argc, ++argv)
    656 		goto operand;
    657 
    658 	if (Eflag && linkchain.n == 0)
    659 		newitem(&linkchain, "-");
    660 
    661 	if (Eflag && Mflag ||
    662             (Eflag || Mflag) && (Sflag || kflag) ||
    663 	    linkchain.n == 0 ||
    664 	    linkchain.n > 1 && cflag && outfile)
    665 		usage();
    666 
    667 	if (!dflag) {
    668 		if ((devnullfd = open("/dev/null", O_WRONLY)) < 0)
    669 			fputs("scc-cc: could not open /dev/null\n", stderr);
    670 	}
    671 
    672 	if (!(tmpdir = getenv("TMPDIR")) || !tmpdir[0])
    673 		tmpdir = ".";
    674 	tmpdirln = strlen(tmpdir);
    675 
    676 	build(&linkchain, (link = !(Mflag || Eflag || Sflag || cflag)));
    677 
    678 	if (!(link || cflag))
    679 		return failure;
    680 
    681 	if (link && !failure) {
    682 		spawn(settool(LD, NULL, LAST_TOOL));
    683 		validatetools();
    684 	}
    685 
    686 	return failure;
    687 }