shithub: purgatorio

ref: d3da2e1b89f30f404c3d11053680098f1b7bf677
dir: /appl/tiny/sh.b/

View raw version
implement Sh;

include "sys.m";
	sys: Sys;
	FD: import Sys;

include "draw.m";
	Context: import Draw;

include "filepat.m";
	filepat: Filepat;
	nofilepat := 0;			# true if load Filepat has failed.

include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;

include "env.m";
	env: Env;

stdin: ref FD;
stderr: ref FD;
waitfd: ref FD;

Quoted: con '\uFFF0';
stringQuoted: con "\uFFF0";

Sh: module
{
	init: fn(ctxt: ref Context, argv: list of string);
};

Command: adt
{
	args: list of string;
	inf, outf: string;
	append: int;
};

Async, Seq: con iota;

Pipeline: adt
{
	cmds: list of ref Command;
	term: int;
};

usage()
{
	sys->fprint(stderr, "Usage: sh [-n] [-c cmd] [file]\n");
}

init(ctxt: ref Context, argv: list of string)
{
	n: int;
	arg: list of string;
	buf := array[1024] of byte;

	sys = load Sys Sys->PATH;
	env = load Env Env->PATH;

	stderr = sys->fildes(2);

	waitfd = sys->open("#p/"+string sys->pctl(0, nil)+"/wait", sys->OREAD);
	if(waitfd == nil){
		sys->fprint(stderr, "sh: open wait: %r\n");
		return;
	}

	eflag := nflag := lflag := 0;
	cmd: string;
	if(argv != nil)
		argv = tl argv;
	for(; argv != nil && len hd argv && (hd argv)[0]=='-'; argv = tl argv)
		case hd argv {
		"-e" =>
			eflag = 1;
		"-n" =>
			nflag = 1;
		"-l" =>
			lflag = 1;
		"-c" =>
			argv = tl argv;
			if(len argv != 1){
				usage();
				return;
			}
			cmd = hd argv;
		* =>
			usage();
			return;
		}
	
	if (lflag)
		startup(ctxt);

	if(eflag == 0)
		sys->pctl(sys->FORKENV, nil);
	if(nflag == 0)
		sys->pctl(sys->FORKNS, nil);
	if(cmd != nil){
		arg = tokenize(cmd+"\n");
		if(arg != nil)
			runit(ctxt, parseit(arg));
		return;
	}
	if(argv != nil){
		script(ctxt, hd argv);
		return;
	}

	stdin = sys->fildes(0);

	prompt := sysname() + "$ ";
	for(;;){
		sys->print("%s", prompt);
		n = sys->read(stdin, buf, len buf);
		if(n <= 0)
			break;
		arg = tokenize(string buf[0:n]);
		if(arg != nil)
			runit(ctxt, parseit(arg));
	}
}

rev(arg: list of string): list of string
{
	ret: list of string;

	while(arg != nil){
		ret = hd arg :: ret;
		arg = tl arg;
	}
	return ret;
}

waitfor(pid: int)
{
	if(pid <= 0)
		return;
	buf := array[sys->WAITLEN] of byte;
	status := "";
	for(;;){
		n := sys->read(waitfd, buf, len buf);
		if(n < 0){
			sys->fprint(stderr, "sh: read wait: %r\n");
			return;
		}
		status = string buf[0:n];
		if(status[len status-1] != ':')
			sys->fprint(stderr, "%s\n", status);
		who := int status;
		if(who != 0){
			if(who == pid)
				return;
		}
	}
}

mkprog(ctxt: ref Context, arg: list of string, infd, outfd: ref Sys->FD, waitpid: chan of int)
{
	fds := list of {0, 1, 2};
	if(infd != nil)
		fds = infd.fd :: fds;
	if(outfd != nil)
		fds = outfd.fd :: fds;
	pid := sys->pctl(sys->NEWFD, fds);
	console := sys->fildes(2);

	if(infd != nil){
		sys->dup(infd.fd, 0);
		infd = nil;
	}
	if(outfd != nil){
		sys->dup(outfd.fd, 1);
		outfd = nil;
	}

	waitpid <-= pid;

	if(pid < 0 || arg == nil)
		return;

	{
		exec(ctxt, arg, console);
	}exception{
	"fail:*" =>
		#sys->fprint(console, "%s:%s\n", hd arg, e.name[5:]);
		exit;
	"write on closed pipe" =>
		#sys->fprint(console, "%s: %s\n", hd arg, e.name);
		exit;
	}
}

exec(ctxt: ref Context, args: list of string, console: ref Sys->FD)
{
	if (args == nil)
		return;
	cmd := hd args;
	file := cmd;
		
	if(len file<4 || file[len file-4:]!=".dis")
		file += ".dis";

	c := load Sh file;
	if(c == nil) {
		err := sys->sprint("%r");
		if(err != "permission denied" && err != "access permission denied" && file[0]!='/' && file[0:2]!="./"){
			c = load Sh "/dis/"+file;
			if(c == nil)
				err = sys->sprint("%r");
		}
		if(c == nil){
			sys->fprint(console, "%s: %s\n", cmd, err);
			return;
		}
	}

	c->init(ctxt, args);
}

script(ctxt: ref Context, src: string)
{
	bufio = load Bufio Bufio->PATH;
	if(bufio == nil){
		sys->fprint(stderr, "sh: load bufio: %r\n");
		return;
	}

	f := bufio->open(src, Bufio->OREAD);
	if(f == nil){
		sys->fprint(stderr, "sh: open %s: %r\n", src);
		return;
	}
	for(;;){
		s := f.gets('\n');
		if(s == nil)
			break;
		arg := tokenize(s);
		if(arg != nil)
			runit(ctxt, parseit(arg));
	}
}

sysname(): string
{
	fd := sys->open("#c/sysname", sys->OREAD);
	if(fd == nil)
		return "anon";
	buf := array[128] of byte;
	n := sys->read(fd, buf, len buf);
	if(n < 0) 
		return "anon";
	return string buf[0:n];
}

# Lexer.

tokenize(s: string): list of string
{
	tok: list of string;
	token := "";
	instring := 0;

loop:
	for(i:=0; i<len s; i++) {
		if(instring) {
			if(s[i] != '\'')
				token = addchar(token, s[i]);
			else if(i == len s-1 || s[i+1] != '\'') {
				if(i == len s-1 || s[i+1] == ' ' || s[i+1] == '\t' || s[i+1] == '\n'){
					tok = token :: tok;
					token = "";
				}
				instring = 0;
			} else {
				token[len token] = '\'';
				i++;
			}
			continue;
		}
		case s[i] {
		' ' or '\t' or '\n' or '#' or
		'\'' or '|' or '&' or ';' or
		'>' or '<' or '\r' =>
			if(token != "" && s[i]!='\''){
				tok = token :: tok;
				token = "";
			}
			case s[i] {
			'#' =>
				break loop;
			'\'' =>
				instring = 1;
			'>' =>
				ss := "";
				ss[0] = s[i];
				if(i<len s-1 && s[i+1]==s[i])
					ss[1] = s[i++];
				tok = ss :: tok;
			'|' or '&' or ';' or '<' =>
				ss := "";
				ss[0] = s[i];
				tok = ss :: tok;
			}
		* =>
			token[len token] = s[i];
		}
	}
	if(instring){
		sys->fprint(stderr, "sh: unmatched quote\n");
		return nil;
	}
	return rev(tok);
}

ismeta(char: int): int
{
	case char {
	'*'  or '[' or '?' or
	'#'  or '\'' or '|' or
	'&' or ';' or '>' or
	'<'  =>
		return 1;
	}
	return 0;
}

addchar(token: string, char: int): string
{
	if(ismeta(char) && (len token==0 || token[0]!=Quoted))
		token = stringQuoted + token;
	token[len token] = char;
	return token;
}

# Parser.

getcommand(words: list of string): (ref Command, list of string)
{
	args: list of string;
	word: string;
	si, so: string;
	append := 0;

gather:
	do {
		word = hd words;

		case word {
		">" or ">>" =>
			if(so != nil)
				return (nil, nil);

			words = tl words;

			if(words == nil)
				return (nil, nil);

			so = hd words;
			if(len so>0 && so[0]==Quoted)
				so = so[1:];
			if(word == ">>")
				append = 1;
		"<" =>
			if(si != nil)
				return (nil, nil);

			words = tl words;

			if(words == nil)
				return (nil, nil);

			si = hd words;
			if(len si>0 && si[0]==Quoted)
				si = si[1:];
		"|" or ";" or "&" =>
			break gather;
		* =>
			files := doexpand(word);
			while(files != nil){
				args = hd files :: args;
				files = tl files;
			}
		}

		words = tl words;
	} while (words != nil);

	return (ref Command(rev(args), si, so, append), words);
}

doexpand(file: string): list of string
{
	if(file == nil)
		return file :: nil;
	if(len file>0 && file[0]==Quoted)
		return file[1:] :: nil;
	if (nofilepat)
		return file :: nil;
	for(i:=0; i<len file; i++)
		if(file[i]=='*' || file[i]=='[' || file[i]=='?'){
			if(filepat == nil) {
				if ((filepat = load Filepat Filepat->PATH) == nil) {
					sys->fprint(stderr, "sh: warning: cannot load %s: %r\n",
							Filepat->PATH);
					nofilepat = 1;
					return file :: nil;
				}
			}
			files := filepat->expand(file);
			if(files != nil)
				return files;
			break;
		}
	return file :: nil;
}

revc(arg: list of ref Command): list of ref Command
{
	ret: list of ref Command;
	while(arg != nil) {
		ret = hd arg :: ret;
		arg = tl arg;
	}
	return ret;
}

getpipe(words: list of string): (ref Pipeline, list of string)
{
	cmds: list of ref Command;
	cur: ref Command;
	word: string;

	term := Seq;
gather:
	while(words != nil) {
		word = hd words;

		if(word == "|")
			return (nil, nil);

		(cur, words) = getcommand(words);

		if(cur == nil)
			return (nil, nil);

		cmds = cur :: cmds;

		if(words == nil)
			break gather;

		word = hd words;
		words = tl words;

		case word {
		";" =>
			break gather;
		"&" =>
			term = Async;
			break gather;
		"|" =>
			continue gather;
		}
		return (nil, nil);
	}

	if(word == "|")
		return (nil, nil);

	return (ref Pipeline(revc(cmds), term), words);
}

revp(arg: list of ref Pipeline): list of ref Pipeline
{
	ret: list of ref Pipeline;

	while(arg != nil) {
		ret = hd arg :: ret;
		arg = tl arg;
	}
	return ret;
}

parseit(words: list of string): list of ref Pipeline
{
	ret: list of ref Pipeline;
	cur: ref Pipeline;

	while(words != nil) {
		(cur, words) = getpipe(words);
		if(cur == nil){
			sys->fprint(stderr, "sh: syntax error\n");
			return nil;
		}
		ret = cur :: ret;
	}
	return revp(ret);
}

# Runner.

runpipeline(ctx: ref Context, pipeline: ref Pipeline)
{
	if(pipeline.term == Async)
		sys->pctl(sys->NEWPGRP, nil);
	pid := startpipeline(ctx, pipeline);
	if(pid < 0)
		return;
	if(pipeline.term == Seq)
		waitfor(pid);
}

startpipeline(ctx: ref Context, pipeline: ref Pipeline): int
{
	pid := 0;
	cmds := pipeline.cmds;
	first := 1;
	inpipe, outpipe: ref Sys->FD;
	while(cmds != nil) {
		last := tl cmds == nil;
		cmd := hd cmds;

		infd: ref Sys->FD;
		if(!first)
			infd = inpipe;
		else if(cmd.inf != nil){
			infd = sys->open(cmd.inf, Sys->OREAD);
			if(infd == nil){
				sys->fprint(stderr, "sh: can't open %s: %r\n", cmd.inf);
				return -1;
			}
		}

		outfd: ref Sys->FD;
		if(!last){
			fds := array[2] of ref Sys->FD;
			if(sys->pipe(fds) < 0){
				sys->fprint(stderr, "sh: can't make pipe: %r\n");
				return -1;
			}
			outpipe = fds[0];
			outfd = fds[1];
			fds = nil;
		}else if(cmd.outf != nil){
			if(cmd.append){
				outfd = sys->open(cmd.outf, Sys->OWRITE);
				if(outfd != nil)
					sys->seek(outfd, big 0, Sys->SEEKEND);
			}
			if(outfd == nil)
				outfd = sys->create(cmd.outf, Sys->OWRITE, 8r666);
			if(outfd == nil){
				sys->fprint(stderr, "sh: can't open %s: %r\n", cmd.outf);
				return -1;
			}
		}

		rpid := chan of int;
		spawn mkprog(ctx, cmd.args, infd, outfd, rpid);
		pid = <-rpid;
		infd = nil;
		outfd = nil;

		inpipe = outpipe;
		outpipe = nil;

		first = 0;
		cmds = tl cmds;
	}
	return pid;
}

runit(ctx: ref Context, pipes: list of ref Pipeline)
{
	while(pipes != nil) {
		pipeline := hd pipes;
		pipes = tl pipes;
		if(pipeline.term == Seq)
			runpipeline(ctx, pipeline);
		else
			spawn runpipeline(ctx, pipeline);
	}
}

strchr(s: string, c: int): int
{
	ln := len s;
	for (i := 0; i < ln; i++)
		if (s[i] == c)
			return i;
	return -1;
}

# PROFILE: con "/lib/profile";
PROFILE: con "/lib/infernoinit";

startup(ctxt: ref Context)
{
	if (env == nil)
		return;
	# if (env->getenv("home") != nil)
	#	return;
	home := gethome();
	env->setenv("home", home);
	escript(ctxt, PROFILE);
	escript(ctxt, home + PROFILE);
}

escript(ctxt: ref Context, file: string)
{
	fd := sys->open(file, Sys->OREAD);
	if (fd != nil)
		script(ctxt, file);
}

gethome(): string
{
	fd := sys->open("/dev/user", sys->OREAD);
  	if(fd == nil)
    		return "/";
  	buf := array[128] of byte;
  	n := sys->read(fd, buf, len buf);
  	if(n < 0)
    		return "/";
  	return "/usr/" + string buf[0:n];
}