code: purgatorio

ref: 82b046f36f8084a22bbb5d71edd0edd9179561eb
dir: /appl/cmd/mash/exec.b/

View raw version
#
#	Manage the execution of a command.
#

srv:	string;		# srv file proto
nsrv:	int = 0;	# srv file unique id

#
#	Return error string.
#
errstr(): string
{
	return sys->sprint("%r");
}

#
#	Server thread for servefd.
#
server(c: ref Sys->FileIO, fd: ref Sys->FD, write: int)
{
	a: array of byte;
	if (!write)
		a = array[Sys->ATOMICIO] of byte;
	for (;;) {
		alt {
		(nil, b, nil, wc) := <- c.write =>
			if (wc == nil)
				return;
			if (!write) {
				wc <- = (0, EPIPE);
				return;
			}
			r := sys->write(fd, b, len b);
			if (r < 0) {
				wc <- = (0, errstr());
				return;
			}
			wc <- = (r, nil);
		(nil, n, nil, rc) := <- c.read =>
			if (rc == nil)
				return;
			if (write) {
				rc <- = (array[0] of byte, nil);
				return;
			}
			if (n > Sys->ATOMICIO)
				n = Sys->ATOMICIO;
			r := sys->read(fd, a, n);
			if (r < 0) {
				rc <- = (nil, errstr());
				return;
			}
			rc <- = (a[0:r], nil);
		}
	}
}

#
#	Serve FD as a #s file.  Used to implement generators.
#
Env.servefd(e: self ref Env, fd: ref Sys->FD, write: int): string
{
	(s, c) := e.servefile(nil);
	spawn server(c, fd, write);
	return s;
}

#
#	Generate name and FileIO adt for a served filed.
#
Env.servefile(e: self ref Env, n: string): (string, ref Sys->FileIO)
{
	c: ref Sys->FileIO;
	s: string;
	if (srv == nil) {
		(ok, d) := sys->stat(CHAN);
		if (ok < 0)
			e.couldnot("stat", CHAN);
		if (d.dtype != 's') {
			if (sys->bind("#s", CHAN, Sys->MBEFORE) < 0)
				e.couldnot("bind", CHAN);
		}
		srv = "mash." + string sys->pctl(0, nil);
	}
	retry := 0;
	for (;;) {
		if (retry || n == nil)
			s = srv + "." + string nsrv++;
		else
			s = n;
		c = sys->file2chan(CHAN, s);
		s = CHAN + "/" + s;
		if (c == nil) {
			if (retry || n == nil || errstr() != EEXISTS)
				e.couldnot("file2chan", s);
			retry = 1;
			continue;
		}
		break;
	}
	if (n != nil)
		n = CHAN + "/" + n;
	else
		n = s;
	if (retry && sys->bind(s, n, Sys->MREPL) < 0)
		e.couldnot("bind", n);
	return (n, c);
}

#
#	Shorthand for string output.
#
Env.output(e: self ref Env, s: string)
{
	if (s == nil)
		return;
	out := e.outfile();
	if (out == nil)
		return;
	out.puts(s);
	out.close();
}

#
#	Return Iobuf for stdout.
#
Env.outfile(e: self ref Env): ref Bufio->Iobuf
{
	fd := e.out;
	if (fd == nil)
		fd = sys->fildes(1);
	out := bufio->fopen(fd, Bufio->OWRITE);
	if (out == nil)
		e.report(sys->sprint("fopen failed: %r"));
	return out;
}

#
#	Return FD for /dev/null.
#
Env.devnull(e: self ref Env): ref Sys->FD
{
	fd := sys->open(DEVNULL, Sys->OREAD);
	if (fd == nil)
		e.couldnot("open", DEVNULL);
	return fd;
}

#
#	Make a pipe.
#
Env.pipe(e: self ref Env): array of ref Sys->FD
{
	fds := array[2] of ref Sys->FD;
	if (sys->pipe(fds) < 0) {
		e.report(sys->sprint("pipe failed: %r"));
		return nil;
	}
	return fds;
}

#
#	Open wait file for an env.
#
waitfd(e: ref Env)
{
	w := "#p/" + string sys->pctl(0, nil) + "/wait";
	fd := sys->open(w, sys->OREAD);
	if (fd == nil)
		e.couldnot("open", w);
	e.wait = fd;
}

#
#	Wait for a thread.  Perhaps propagate exception or exit.
#
waitfor(e: ref Env, pid: int, wc: chan of int, ec, xc: chan of string)
{
	if (ec != nil || xc != nil) {
		spawn waiter(e, pid, wc);
		if (ec == nil)
			ec = chan of string;
		if (xc == nil)
			xc = chan of string;
		alt {
		<-wc =>
			return;
		x := <-ec =>
			<-wc;
			exitmash();
		x := <-xc =>
			<-wc;
			s := x;
			if (len s < FAILLEN || s[0:FAILLEN] != FAIL)
				s = FAIL + s;
			raise s;
		}
	} else
		waiter(e, pid, nil);
}

#
#	Wait for a specific pid.
#
waiter(e: ref Env, pid: int, wc: chan of int)
{
	buf := array[sys->WAITLEN] of byte;
	for(;;) {
		n := sys->read(e.wait, buf, len buf);
		if (n < 0) {
			e.report(sys->sprint("read wait: %r\n"));
			break;
		}
		status := string buf[0:n];
		if (status[len status - 1] != ':')
			sys->fprint(e.stderr, "%s\n", status);
		who := int status;
		if (who != 0 && who == pid)
			break;
	}
	if (wc != nil)
		wc <-= 0;
}

#
#	Preparse IO for a new thread.
#	Make a new FD group and redirect stdin/stdout.
#
prepareio(in, out: ref sys->FD): (int, ref Sys->FD)
{
	fds := list of { 0, 1, 2};
	if (in != nil)
		fds = in.fd :: fds;
	if (out != nil)
		fds = out.fd :: fds;
	pid := sys->pctl(sys->NEWFD, fds);
	console := sys->fildes(2);
	if (in != nil) {
		sys->dup(in.fd, 0);
		in = nil;
	}
	if (out != nil) {
		sys->dup(out.fd, 1);
		out = nil;
	}
	return (pid, console);
}

#
#	Add ".dis" to a command if missing.
#
dis(s: string): string
{
	if (len s < 4 || s[len s - 4:] != ".dis")
		return s + ".dis";
	return s;
}

#
#	Load a builtin.
#
Env.doload(e: self ref Env, s: string)
{
	file := dis(s);
	l := load Mashbuiltin file;
	if (l == nil) {
		err := errstr();
		if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") {
			l = load Mashbuiltin LIB + file;
			if (l == nil)
				err = errstr();
		}
		if (l == nil) {
			e.report(s + ": " + err);
			return;
		}
	}
	l->mashinit("load" :: s :: nil, lib, l, e);
}

#
#	Execute a spawned thread (dis module or builtin).
#
mkprog(args: list of string, e: ref Env, in, out: ref Sys->FD, wc: chan of int, ec, xc: chan of string)
{
	(pid, console) := prepareio(in, out);
	wc <-= pid;
	if (pid < 0)
		return;
	cmd := hd args;
	{
		b := e.builtin(cmd);
		if (b != nil) {
			e = e.copy();
			e.in = in;
			e.out = out;
			e.stderr = console;
			e.wait = nil;
			b->mashcmd(e, args);
		} else {
			file := dis(cmd);
			c := load Command file;
			if (c == nil) {
				err := errstr();
				if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") {
					c = load Command "/dis/" + file;
					if (c == nil)
						err = errstr();
				}
				if (c == nil) {
					sys->fprint(console, "%s: %s\n", file, err);
					return;
				}
			}
			c->init(gctxt, args);
		}
	}exception x{
	FAILPAT =>
		if (xc != nil)
			xc <-= x;
		# the command failure should be propagated silently to
		# a higher level, where $status can be set.. - wrtp.
		#else
		#	sys->fprint(console, "%s: %s\n", cmd, x.name);
		exit;
	EPIPE =>
		if (xc != nil)
			xc <-= x;
		#else
		#	sys->fprint(console, "%s: %s\n", cmd, x.name);
		exit;
	EXIT =>
		if (ec != nil)
			ec <-= x;
		exit;
	}
}

#
#	Open/create files for redirection.
#
redirect(e: ref Env, f: array of string, in, out: ref Sys->FD): (int, ref Sys->FD, ref Sys->FD)
{
	s: string;
	err := 0;
	if (f[Rinout] != nil) {
		s = f[Rinout];
		in = sys->open(s, Sys->ORDWR);
		if (in == nil) {
			sys->fprint(e.stderr, "%s: %r\n", s);
			err = 1;
		}
		out = in;
	} else if (f[Rin] != nil) {
		s = f[Rin];
		in = sys->open(s, Sys->OREAD);
		if (in == nil) {
			sys->fprint(e.stderr, "%s: %r\n", s);
			err = 1;
		}
	}
	if (f[Rout] != nil || f[Rappend] != nil) {
		if (f[Rappend] != nil) {
			s = f[Rappend];
			out = sys->open(s, Sys->OWRITE);
			if (out != nil)
				sys->seek(out, big 0, Sys->SEEKEND);
		} else {
			s = f[Rout];
			out = nil;
		}
		if (out == nil) {
			out = sys->create(s, Sys->OWRITE, 8r666);
			if (out == nil) {
				sys->fprint(e.stderr, "%s: %r\n", s);
				err = 1;
			}
		}
	}
	if (err)
		return (0, nil, nil);
	return (1, in, out);
}

#
#	Spawn a command and maybe wait for it.
#
exec(a: list of string, e: ref Env, infd, outfd: ref Sys->FD, wait: int)
{
	if (wait && e.wait == nil)
		waitfd(e);
	wc := chan of int;
	if (wait && (e.flags & ERaise))
		xc := chan of string;
	if (wait && (e.flags & ETop))
		ec := chan of string;
	spawn mkprog(a, e, infd, outfd, wc, ec, xc);
	pid := <-wc;
	if (wait)
		waitfor(e, pid, wc, ec, xc);
}