shithub: scc

ref: c75555ca9951a5433703c0bb70f6a7ff6d0dfb1d
dir: /driver/posix/scc.c/

View raw version
/* See LICENSE file for copyright and license details. */
#define _POSIX_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../../inc/arg.h"
#include "../../inc/cc.h"

#define NARGS 64

enum {
	CC1,
	CC2,
	QBE,
	AS,
	TEE,
	NR_TOOLS,
};

static struct tool {
	char  cmd[PATH_MAX];
	char *args[NARGS];
	char  bin[16];
	char *outfile;
	int   nargs, in, out;
	pid_t pid;
} tools[NR_TOOLS] = {
	[CC1] = { .bin = "cc1", .cmd = PREFIX "/libexec/scc/", },
	[CC2] = { .bin = "cc2", .cmd = PREFIX "/libexec/scc/", },
	[QBE] = { .bin = "qbe", .cmd = "qbe", },
	[AS]  = { .bin = "as",  .cmd = "as", },
	[TEE] = { .bin = "tee", .cmd = "tee", },
};

char *argv0;
static char *arch;
static int failedtool = NR_TOOLS;
static int Eflag, Sflag, kflag;

static void
terminate(void)
{
	struct tool *t;
	int i;

	for (i = 0; i < NR_TOOLS; ++i) {
		t = &tools[i];
		if (t->pid)
			kill(t->pid, SIGTERM);
		if (i >= failedtool && t->outfile)
			unlink(t->outfile);
	}
}

static int
inittool(int tool)
{
	struct tool *t = &tools[tool];
	size_t binln;
	int n;

	if (!t->args[0]) {
		switch (tool) {
		case CC1:
		case CC2:
			binln = strlen(t->bin);
			if (arch) {
				n = snprintf(t->bin + binln,
					     sizeof(t->bin) - binln,
					     "-%s", arch);
				if (n < 0 || n >= sizeof(t->bin))
					die("scc: target tool bin too long");
				binln = strlen(t->bin);
			}

			if (strlen(t->cmd) + binln + 1 > sizeof(t->cmd))
				die("scc: target tool path too long");
			strcat(t->cmd, t->bin);
			break;
		case AS:
			t->nargs = 2;
			t->args[1] = "-o";
			break;
		default:
			break;
		}

		t->args[0] = t->bin;
	}

	return tool;
}

static char *
outfilename(char *path, char *ext)
{
	char *new, *name, *dot;
	size_t newsz, nameln;
	int n;

	if (!(name = strrchr(path, '/')))
		name = path;
	else
		++name;

	nameln = strlen(name);

	if (!(dot = strrchr(name, '.')))
		dot = &name[nameln];

	nameln = nameln - strlen(dot);
	newsz  = nameln + strlen(ext) + 1 + 1;

	new = xmalloc(newsz);

	n = snprintf(new, newsz, "%.*s.%s", nameln, name, ext);
	if (n < 0 || n >= newsz)
		die("scc: wrong output filename");

	return new;
}

static int
settool(int tool, char *input, int output)
{
	struct tool *t = &tools[tool];
	int fds[2], proxiedtool;
	char *ext;
	static int fdin;

	switch (tool) {
	case AS:
		t->outfile = outfilename(input, "o");
		t->args[2] = t->outfile;
		break;
	case TEE:
		switch (output) {
		case CC2:
			proxiedtool = CC1;
			ext = "ir"; break;
		case QBE:
			proxiedtool = CC2;
			ext = "qbe"; break;
		case NR_TOOLS:
		case AS:
			proxiedtool = CC2;
			ext = "as"; break;
		}
		tools[proxiedtool].outfile = outfilename(input, ext);
		t->args[1] = tools[proxiedtool].outfile;
		break;
	default:
		break;
	}

	if (fdin) {
		t->in = fdin;
		fdin = 0;
	} else {
		t->args[t->nargs + 1] = input;
	}

	if (output < NR_TOOLS) {
		if (pipe(fds))
			die("scc: pipe: %s", strerror(errno));
		t->out = fds[1];
		fdin = fds[0];
	}

	return tool;
}

static void
spawn(int t)
{
	struct tool *tool = &tools[t];

	switch (tool->pid = fork()) {
	case -1:
		die("scc: %s: %s", tool->bin, strerror(errno));
	case 0:
		if (tool->out)
			dup2(tool->out, 1);
		if (tool->in)
			dup2(tool->in, 0);
		execvp(tool->cmd, tool->args);
		fprintf(stderr, "scc: execp %s: %s\n",
		        tool->cmd, strerror(errno));
		_exit(1);
	default:
		if (tool->in)
			close(tool->in);
		if (tool->out)
			close(tool->out);
		break;
	}
}
static int
toolfor(char *file)
{
	char *dot = strrchr(file, '.');

	if (dot) {
		if (!strcmp(dot, ".c"))
			return CC1;
		if (!strcmp(dot, ".ir"))
			return CC2;
		if (!strcmp(dot, ".qbe"))
			return QBE;
		if (!strcmp(dot, ".as"))
			return AS;
	}

	die("scc: do not recognize filetype of %s", file);
}

static void
checktool(int tool)
{
	struct tool *t = &tools[tool];
	int st;

	if (!t->pid)
		return;

	if (waitpid(t->pid, &st, 0) < 0 ||
	    !WIFEXITED(st) || WEXITSTATUS(st) != 0) {
		failedtool = tool;
		exit(-1);
	}

	t->pid = 0;
}

static void
build(char *file)
{
	int i, tool, out, keepfile;
	static int preout;

	for (tool = toolfor(file); tool < NR_TOOLS; tool = out) {
		keepfile = 0;

		switch (tool) {
		case CC1:
			out = Eflag ? NR_TOOLS : CC2;
			if (!Eflag)
				keepfile = kflag;
			break;
		case CC2:
			if (!arch || strcmp(arch, "qbe")) {
				out = Sflag ? NR_TOOLS : AS;
				keepfile = (Sflag || kflag);
			} else {
				out = QBE;
				keepfile = kflag;
			}
			break;
		case QBE:
			out = Sflag ? NR_TOOLS : AS;
			keepfile = (Sflag || kflag);
			break;
		case AS:
			out = NR_TOOLS;
			break;
		case TEE:
			out = preout;
			break;
		default:
			break;
		}

		if (keepfile) {
			preout = out;
			out = TEE;
		}

		spawn(settool(inittool(tool), file, out));
	}

	for (i = 0; i < NR_TOOLS; ++i)
		checktool(i);

	for (i = 0; i < NR_TOOLS; ++i) {
		free(tools[i].outfile);
		tools[i].outfile = NULL;
	}
}

static void
addarg(int tool, char *arg) {
	struct tool *t = &tools[tool];

	if (t->nargs >= NARGS - 3) /* 3: argv0, filename, NULL terminator */
		die("scc: too many parameters given");

	t->args[++t->nargs] = arg;
}

static void
usage(void)
{
	die("usage: %s [-E|-kS] [-m arch] [-D macro[=val]]... "
	    "[-I dir]... file ...", argv0);
}

int
main(int argc, char *argv[])
{
	atexit(terminate);

	arch = getenv("ARCH");

	ARGBEGIN {
	case 'D':
		addarg(CC1, "-D");
		addarg(CC1, EARGF(usage()));
		break;
	case 'E':
		Eflag = 1;
		addarg(CC1, "-E");
		break;
	case 'I':
		addarg(CC1, "-I");
		addarg(CC1, EARGF(usage()));
		break;
	case 'S':
		Sflag = 1;
		break;
	case 'k':
		kflag = 1;
		break;
	case 'm':
		arch = EARGF(usage());
		break;
	case '-':
		fprintf(stderr,
		        "scc: ignored parameter --%s\n", EARGF(usage()));
		break;
	default:
		usage();
	} ARGEND

	if (Eflag && (Sflag || kflag))
		usage();

	if (!argc)
		die("scc: fatal error: no input file");

	for (; *argv; ++argv)
		build(*argv);

	return 0;
}