scc

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

cc.c (12708B)


      1 /*
      2  * 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 had a  bug
      6  * and using POSIX_C_SOURCE  and  _ANSI_SOURCE  at  the same time
      7  * generates a syntax  error in the system headers. The bug was
      8  * already fixed in NetBSD but keeping the workaround here does
      9  * not hurt and it helps to keep back compatibility.
     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)
    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 	local_tool:
    238 		n = snprintf(t->cmd, sizeof(t->cmd),
    239 		             fmt, prefix, t->bin, arch, abi);
    240 		if (n < 0 || n >= sizeof(t->cmd))
    241 			die("scc-cc: target tool path is too long");
    242 		break;
    243 	case QBE:
    244 		strcpy(t->cmd, "qbe");
    245 
    246 		if (!strcmp(arch, "amd64") && !strcmp(abi, "sysv"))
    247 			fmt = "amd64_sysv";
    248 		else if (!strcmp(arch, "arm64") && !strcmp(abi, "sysv"))
    249 			fmt = "arm64";
    250 		else if (!strcmp(arch, "riscv64") && !strcmp(abi, "sysv"))
    251 			fmt = "rv64";
    252 		else
    253 			die("not supported arch-abi (%s-%s) for qbe", arch, abi);
    254 
    255 		addarg(tool, "-t");
    256 		addarg(tool, fmt);
    257 		break;
    258 	case LD:
    259 		if (!outfile)
    260 			outfile = "a.out";
    261 		t->outfile = xstrdup(outfile);
    262 		if (gflag)
    263 			addarg(tool, "-g");
    264 		if (sflag)
    265 			addarg(tool, "-s");
    266 		for (n = 0; ldcmd[n]; n++)
    267 			addarg(tool, ldcmd[n]);
    268                 break;
    269 	case TEEIR:
    270 		if (Eflag && outfile)
    271 			t->outfile = xstrdup(outfile);
    272 		else
    273 			t->outfile = outfname(infile, "ir");
    274 		addarg(tool, t->outfile);
    275 		break;
    276 	case TEEQBE:
    277 		t->outfile = outfname(infile, "qbe");
    278 		addarg(tool, t->outfile);
    279 		break;
    280 	case TEEAS:
    281 		t->outfile = outfname(infile, "s");
    282 		addarg(tool, t->outfile);
    283 		break;
    284 	case AS:
    285 		if (cflag && outfile) {
    286 			objfile = xstrdup(outfile);
    287 		} else {
    288 			objfile = (cflag || kflag) ? infile : NULL;
    289 			objfile = outfname(objfile, "o");
    290 		}
    291 		t->outfile = xstrdup(objfile);
    292 		if (gflag)
    293 			addarg(tool, "-g");
    294 		for (n = 0; ascmd[n]; n++)
    295 			addarg(tool, ascmd[n]);
    296                 break;
    297 		break;
    298 	default:
    299 		break;
    300 	}
    301 
    302 	if (fdin > -1) {
    303 		t->in = fdin;
    304 		fdin = -1;
    305 	} else {
    306 		t->in = -1;
    307 		if (infile)
    308 			addarg(tool, infile);
    309 	}
    310 
    311 	if (nexttool < LAST_TOOL) {
    312 		if (pipe(fds))
    313 			die("scc-cc: pipe: %s", strerror(errno));
    314 		t->out = fds[1];
    315 		fdin = fds[0];
    316 	} else {
    317 		t->out = -1;
    318 	}
    319 
    320 	addarg(tool, NULL);
    321 
    322 	return tool;
    323 }
    324 
    325 /*
    326  * We cannot call exit() because it would call the atexit()
    327  * handlers. If it finishes correctly we already called
    328  * fflush() in the output buffers so it is not a problem
    329  * not following the normal exit path.
    330  */
    331 static void
    332 tee(char *fname)
    333 {
    334 	FILE *fp;
    335 	int ch;
    336 
    337 	if ((fp = fopen(fname, "w")) == NULL)
    338 		goto err;
    339 
    340 	while ((ch = getchar()) != EOF) {
    341 		putc(ch, stdout);
    342 		putc(ch, fp);
    343 	}
    344 
    345 	fflush(stdout);
    346 	fflush(fp);
    347 
    348 	if (ferror(stdin) || ferror(stdout) || ferror(fp))
    349 		goto err;
    350 	_exit(0);
    351 
    352 err:
    353 	fprintf(stderr,
    354 		"tee: teeing %s: %s\n",
    355 		fname,
    356 		strerror(errno));
    357 
    358 	_exit(1);
    359 }
    360 
    361 static void
    362 spawn(int tool)
    363 {
    364 	int i;
    365 	char **ap;
    366 	struct tool *t = &tools[tool];
    367 
    368 	switch (t->pid = fork()) {
    369 	case -1:
    370 		die("scc-cc: %s: %s", t->bin, strerror(errno));
    371 	case 0:
    372 		if (t->out > -1)
    373 			dup2(t->out, 1);
    374 		if (t->in > -1)
    375 			dup2(t->in, 0);
    376 
    377 		if (tool == TEEAS && Sflag)
    378 			dup2(devnullfd, 1);
    379 		if (!dflag && tool != CC1 && tool != LD)
    380 			dup2(devnullfd, 2);
    381 
    382 		if (dflag) {
    383 			fprintf(stderr, "%s", t->cmd);
    384 			for (ap = t->args.s+1; *ap; ap++)
    385 				fprintf(stderr, " %s", *ap);
    386 			putc('\n', stderr);
    387 		}
    388 		if (strcmp(t->cmd, "tee") == 0)
    389 			tee(t->outfile);
    390 		execvp(t->cmd, t->args.s);
    391 		if (dflag) {
    392 			fprintf(stderr,
    393 			        "scc-cc: execvp %s: %s\n",
    394 				t->cmd,
    395 			        strerror(errno));
    396 		}
    397 		abort();
    398 	default:
    399 		if (t->in > -1)
    400 			close(t->in);
    401 		if (t->out > -1)
    402 			close(t->out);
    403 
    404 		for (i = 0; i < t->args.n; i++)
    405 			free(t->args.s[i]);
    406 		t->args.n = 0;
    407 
    408 		break;
    409 	}
    410 }
    411 
    412 static int
    413 toolfor(char *file)
    414 {
    415 	char *dot = strrchr(file, '.');
    416 
    417 	if (Eflag)
    418 		return CC1;
    419 
    420 	if (dot) {
    421 		if (!strcmp(dot, ".c"))
    422 			return CC1;
    423 		if (!strcmp(dot, ".ir"))
    424 			return CC2;
    425 		if (!strcmp(dot, ".qbe"))
    426 			return QBE;
    427 		if (!strcmp(dot, ".s"))
    428 			return AS;
    429 		if (!strcmp(dot, ".o"))
    430 			return LD;
    431 		if (!strcmp(dot, ".a"))
    432 			return LD;
    433 	} else if (!strcmp(file, "-")) {
    434 		return CC1;
    435 	}
    436 
    437 	die("scc-cc: unrecognized filetype of %s", file);
    438 }
    439 
    440 static int
    441 valid(int tool, struct tool *t)
    442 {
    443 	int st;
    444 
    445 	if (waitpid(t->pid, &st, 0) == -1 || WIFSIGNALED(st))
    446 		goto internal;
    447 	if (WIFEXITED(st) && WEXITSTATUS(st) == 0)
    448 		return 1;
    449 	if (!failure && (tool == CC1 || tool == LD))
    450 		goto fail;
    451 
    452 internal:
    453 	if (!failure)
    454 		fprintf(stderr, "scc-cc:%s: internal error\n", t->bin);
    455 fail:
    456 	failure = 1;
    457 	return 0;
    458 }
    459 
    460 static int
    461 validatetools(void)
    462 {
    463 	struct tool *t;
    464 	unsigned i;
    465 	int tool, st, failed = LAST_TOOL;
    466 
    467 	for (tool = 0; tool < LAST_TOOL; ++tool) {
    468 		t = &tools[tool];
    469 		if (!t->pid)
    470 			continue;
    471 		if (!valid(tool, t))
    472 			failed = tool;
    473 		if (tool >= failed && t->outfile)
    474 			unlink(t->outfile);
    475 		free(t->outfile);
    476 		t->pid = 0;
    477 	}
    478 	if (failed < LAST_TOOL) {
    479 		unlink(objfile);
    480 		return 0;
    481 	}
    482 
    483 	return 1;
    484 }
    485 
    486 static int
    487 buildfile(char *file, int tool)
    488 {
    489 	int nexttool;
    490 
    491 	for (; tool < LAST_TOOL; tool = nexttool) {
    492 		switch (tool) {
    493 		case CC1:
    494 			if (Eflag && outfile)
    495 				nexttool = TEEIR;
    496 			else if (Eflag || Mflag)
    497 				nexttool = LAST_TOOL;
    498 			else
    499 				nexttool = kflag ? TEEIR : CC2;
    500 			break;
    501 		case TEEIR:
    502 			nexttool = (Eflag) ? LAST_TOOL : CC2;
    503 			break;
    504 		case CC2:
    505 			if (Qflag)
    506 				nexttool = kflag ? TEEQBE : QBE;
    507 			else
    508 				nexttool = (Sflag || kflag) ? TEEAS : AS;
    509 			break;
    510 		case TEEQBE:
    511 			nexttool = QBE;
    512 			break;
    513 		case QBE:
    514 			nexttool = (Sflag || kflag) ? TEEAS : AS;
    515 			break;
    516 		case TEEAS:
    517 			nexttool = Sflag ? LAST_TOOL : AS;
    518 			break;
    519 		case AS:
    520 			nexttool = LAST_TOOL;
    521 			break;
    522 		default:
    523 			nexttool = LAST_TOOL;
    524 			continue;
    525 		}
    526 
    527 		spawn(settool(tool, file, nexttool));
    528 	}
    529 
    530 	return validatetools();
    531 }
    532 
    533 static void
    534 build(struct items *chain, int link)
    535 {
    536 	int i, tool;
    537 
    538 	for (i = 0; i < chain->n; ++i) {
    539 		if (!strcmp(chain->s[i], "-l")) {
    540 			newitem(&linkargs, chain->s[i++]);
    541 			newitem(&linkargs, chain->s[i]);
    542 			continue;
    543 		}
    544 		tool = toolfor(chain->s[i]);
    545 		if (tool == LD) {
    546 			newitem(&linkargs, chain->s[i]);
    547 			continue;
    548 		}
    549 		if (buildfile(chain->s[i], tool)) {
    550 			newitem(&linkargs, objfile);
    551 			newitem((!link || kflag) ? &objout : &objtmp, objfile);
    552 		}
    553 	}
    554 }
    555 
    556 static void
    557 usage(void)
    558 {
    559 	fputs("usage: cc [options] file...\n", stderr);
    560 	exit(1);
    561 }
    562 
    563 int
    564 main(int argc, char *argv[])
    565 {
    566 	struct items linkchain = { .n = 0, };
    567 	int link, n;
    568 
    569 	atexit(terminate);
    570 	signal(SIGHUP, sighandler);
    571 	signal(SIGINT, sighandler);
    572 	signal(SIGTERM, sighandler);
    573 
    574 	if (!(arch = getenv("ARCH")))
    575 		arch = ARCH;
    576 	if (!(sys = getenv("SYS")))
    577 		sys = SYS;
    578 	if (!(abi = getenv("ABI")))
    579 		abi = ABI;
    580 	if (!(format = getenv("FORMAT")))
    581 		format = FORMAT;
    582 	if (!(prefix = getenv("SCCPREFIX")))
    583 		prefix = PREFIX;
    584 	if (!(libprefix = getenv("SCCLIBPREFIX")))
    585 		libprefix = LIBPREFIX;
    586 
    587 	ARGBEGIN {
    588 	case 'D':
    589 		newitem(&cc1args, "-D");
    590 		newitem(&cc1args, EARGF(usage()));
    591 		break;
    592 	case 'M':
    593 		Mflag = 1;
    594 		break;
    595 	case 'E':
    596 		Eflag = 1;
    597 		break;
    598 	case 'I':
    599 		newitem(&cc1args, "-I");
    600 		newitem(&cc1args, EARGF(usage()));
    601 		break;
    602 	case 'L':
    603 		newitem(&linkargs, "-L");
    604 		newitem(&linkargs, EARGF(usage()));
    605 		break;
    606 	case 'O':
    607 		EARGF(usage());
    608 		break;
    609 	case 'S':
    610 		Sflag = 1;
    611 		break;
    612 	case 'U':
    613 		newitem(&cc1args, "-U");
    614 		newitem(&cc1args, EARGF(usage()));
    615 		break;
    616 	case 'c':
    617 		cflag = 1;
    618 		break;
    619 	case 'd':
    620 		dflag = 1;
    621 		break;
    622 	case 'g':
    623 		gflag = 1;
    624 		break;
    625 	case 'k':
    626 		kflag = 1;
    627 		break;
    628 	case 'l':
    629 		newitem(&linkchain, "-l");
    630 		newitem(&linkchain, EARGF(usage()));
    631 		break;
    632 	case 'm':
    633 		arch = EARGF(usage());
    634 		break;
    635 	case 'o':
    636 		outfile = EARGF(usage());
    637 		break;
    638 	case 's':
    639 		sflag = 1;
    640 		break;
    641 	case 't':
    642 		sys = EARGF(usage());
    643 		break;
    644 	case 'w':
    645 		Wflag = 0;
    646 		break;
    647 	case 'W':
    648 		Wflag = 1;
    649 		break;
    650 	case 'q':
    651 		Qflag = 0;
    652 		break;
    653 	case 'Q':
    654 		Qflag = 1;
    655 		break;
    656 	case '-':
    657 		fprintf(stderr,
    658 		        "scc-cc: ignored parameter --%s\n", EARGF(usage()));
    659 		break;
    660 	default:
    661 		usage();
    662 	} ARGOPERAND {
    663 operand:
    664 		newitem(&linkchain, ARGOP());
    665 	} ARGEND
    666 
    667 	for (; *argv; --argc, ++argv)
    668 		goto operand;
    669 
    670 	if (Eflag && linkchain.n == 0)
    671 		newitem(&linkchain, "-");
    672 
    673 	if (Eflag && Mflag ||
    674             (Eflag || Mflag) && (Sflag || kflag) ||
    675 	    linkchain.n == 0 ||
    676 	    linkchain.n > 1 && cflag && outfile)
    677 		usage();
    678 
    679 	if (!dflag) {
    680 		if ((devnullfd = open("/dev/null", O_WRONLY)) < 0)
    681 			fputs("scc-cc: could not open /dev/null\n", stderr);
    682 	}
    683 
    684 	if (!(tmpdir = getenv("TMPDIR")) || !tmpdir[0])
    685 		tmpdir = ".";
    686 	tmpdirln = strlen(tmpdir);
    687 
    688 	build(&linkchain, (link = !(Mflag || Eflag || Sflag || cflag)));
    689 
    690 	if (!(link || cflag))
    691 		return failure;
    692 
    693 	if (link && !failure) {
    694 		spawn(settool(LD, NULL, LAST_TOOL));
    695 		validatetools();
    696 	}
    697 
    698 	return failure;
    699 }