implement Os;
include "sys.m";
sys: Sys;
include "draw.m";
include "string.m";
str: String;
include "ftrans.m";
ftrans: Ftrans;
include "env.m";
env: Env;
include "arg.m";
Os: module
{
init: fn(nil: ref Draw->Context, nil: list of string);
};
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
str = load String String->PATH;
if(str == nil)
fail(sys->sprint("cannot load %s: %r", String->PATH));
arg := load Arg Arg->PATH;
if(arg == nil)
fail(sys->sprint("cannot load %s: %r", Arg->PATH));
arg->init(args);
arg->setusage("os [-d dir] [-m mount] [-n] [-N nice] [-b] command [arg...]");
nice := 0;
nicearg: string;
workdir := "";
mntpoint := "";
foreground := 1;
translate := 0;
noexe := 0;
while((opt := arg->opt()) != 0) {
case opt {
'd' =>
workdir = arg->earg();
'E' =>
noexe = 1;
'm' =>
mntpoint = arg->earg();
'n' =>
nice = 1;
'N' =>
nice = 1;
nicearg = sys->sprint(" %q", arg->earg());
't' =>
translate = 1;
'T' =>
translate = 2;
'b' =>
foreground = 0;
* =>
arg->usage();
}
}
args = arg->argv();
if (args == nil)
arg->usage();
arg = nil;
if(translate){
if(workdir == nil)
workdir=".";
(workdir, args) = translatenames(args, workdir, translate>1);
}
if(noexe){
s := sys->sprint("os -d %q", workdir);
for(; args != nil; args = tl args)
s += sys->sprint(" %q", hd args);
sys->print("%s\n", s);
return;
}
sys->pctl(Sys->FORKNS, nil);
sys->bind("#p", "/prog", Sys->MREPL); # don't worry if it fails
if(mntpoint == nil){
mntpoint = "/cmd";
if(sys->stat(mntpoint+"/clone").t0 == -1)
if(sys->bind("#C", "/", Sys->MBEFORE) < 0)
fail(sys->sprint("bind #C /: %r"));
}
cfd := sys->open(mntpoint+"/clone", sys->ORDWR);
if(cfd == nil)
fail(sys->sprint("cannot open /cmd/clone: %r"));
buf := array[32] of byte;
if((n := sys->read(cfd, buf, len buf)) <= 0)
fail(sys->sprint("cannot read /cmd/clone: %r"));
dir := mntpoint+"/"+string buf[0:n];
wfd := sys->open(dir+"/wait", Sys->OREAD);
if(nice && sys->fprint(cfd, "nice%s", nicearg) < 0)
sys->fprint(sys->fildes(2), "os: warning: can't set nice priority: %r\n");
if(workdir != nil && sys->fprint(cfd, "dir %q", workdir) < 0)
fail(sys->sprint("cannot set cwd %q: %r", workdir));
if(foreground && sys->fprint(cfd, "killonclose") < 0)
sys->fprint(sys->fildes(2), "os: warning: cannot write killonclose: %r\n");
if(sys->fprint(cfd, "exec %s", str->quoted(args)) < 0)
fail(sys->sprint("cannot exec: %r"));
if(foreground){
if((tocmd := sys->open(dir+"/data", sys->OWRITE)) == nil)
fail(sys->sprint("canot open %s/data for writing: %r", dir));
if((fromcmd := sys->open(dir+"/data", sys->OREAD)) == nil)
fail(sys->sprint("cannot open %s/data for reading: %r", dir));
if((errcmd := sys->open(dir+"/stderr", sys->OREAD)) == nil)
sys->fprint(sys->fildes(2), "warning: cannot open %s/stderr for reading: %r\n", dir);
spawn copy(sync := chan of int, nil, sys->fildes(0), tocmd);
pid := <-sync;
tocmd = nil;
epid := -1;
if(errcmd != nil){
spawn copy(sync, nil, errcmd, sys->fildes(2));
epid = <-sync;
sync = nil;
errcmd = nil;
}
spawn copy(nil, done := chan of int, fromcmd, sys->fildes(1));
fromcmd = nil;
# cfd is still open, so if we're killgrp'ed and we're on a platform
# (e.g. windows) where the fromcmd read is uninterruptible,
# cfd will be closed, so the command will be killed (due to killonclose), and
# the fromcmd read should complete, allowing that process to be killed.
<-done;
kill(pid);
kill(epid);
}
if(wfd != nil){
status := array[1024] of byte;
n = sys->read(wfd, status, len status);
if(n < 0)
fail(sys->sprint("wait error: %r"));
s := string status[0:n];
if(s != nil){
# pid user sys real status
flds := str->unquoted(s);
if(len flds < 5)
fail(sys->sprint("wait error: odd status: %q", s));
s = hd tl tl tl tl flds;
if(0)
sys->fprint(sys->fildes(2), "WAIT: %q\n", s);
if(s != nil)
raise "fail:host: "+s;
}
}
}
translatenames(args: list of string, dir: string, all: int): (string, list of string)
{
ftrans = load Ftrans Ftrans->PATH;
if(ftrans == nil)
fail(sys->sprint("cannot load %s: %r", Ftrans->PATH));
env = load Env Env->PATH;
ftrans->init(nil, nil :: str->unquoted(env->getenv("ftrans")));
arg0 := translate1(hd args);
dir = translate1(dir);
if(all){
na: list of string;
for(args = tl args; args != nil; args = tl args){
a := hd args;
if(a != nil && (a[0] == '/' || len a > 1 && a[0:2] == "./" || (sys->stat(a).t0 != -1)))
a = translate1(a);
na = a :: na;
}
for(args = nil; na != nil; na = tl na)
args = hd na :: args;
}
return (dir, arg0 :: args);
}
translate1(p: string): string
{
if(p == nil)
return nil;
if(hasdriveletter(p) == 0){
(t, e) := ftrans->translate(p);
if(t == nil)
fail(sys->sprint("%s: %s", p, e));
if(prefix(t, "#")){
t = t[1:];
if(!prefix(t, "U"))
fail(p+": not in local filesystem space");
t = t[1:];
if(prefix(t, "/")){
# #U/... - rooted at $emuroot
root := str->unquoted(env->getenv("emuroot"));
if(len root != 1)
fail(sys->sprint("funny $emuroot %q", env->getenv("emuroot")));
t = hd root+t;
}else if(prefix(t, "*")){
# #U*/ - rooted at /
t = t[1:];
}else if(!hasdriveletter(t))
fail("unknown kind of dev: #U"+t);
}
p = t;
}
if(hasdriveletter(p)){
for(i := 0; i < len p; i++)
if(p[i] == '/')
p[i] = '\\';
}
return p;
}
hasdriveletter(p: string): int
{
return len p > 2 && (p[0] >= 'a' && p[0] <= 'z' || p[0] >= 'A' && p[0] <= 'Z') && p[1] == ':' && (p[2] == '/' || p[2] == '\\');
}
prefix(s, p: string): int
{
return len s >= len p && s[0:len p] == p;
}
copy(sync, done: chan of int, f, t: ref Sys->FD)
{
if(sync != nil)
sync <-= sys->pctl(0, nil);
buf := array[8192] of byte;
for(;;) {
r := sys->read(f, buf, len buf);
if(r <= 0)
break;
w := sys->write(t, buf, r);
if(w != r)
break;
}
if(done != nil)
done <-= 1;
}
kill(pid: int)
{
fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE);
sys->fprint(fd, "kill");
}
fail(msg: string)
{
sys->fprint(sys->fildes(2), "os: %s\n", msg);
raise "fail:"+msg;
}
|