shell piping and io redirects

This commit is contained in:
Lisa Milne 2023-12-15 00:44:44 +10:00
parent 55b557be42
commit 9ec6e0e26a
7 changed files with 473 additions and 121 deletions

View file

@ -445,6 +445,10 @@ Options:
return 1;
}
fd = io.stdin;
io.stdin = stdio.open('/dev/tty',stdio.flags.O_RDONLY|stdio.flags.O_SYNC);
stdlib.sleep(2,function() {
prepFile(fd);
});
}else{
fd = stdio.open(file,stdio.flags.O_RDONLY,prepFile);
if (!fd) {
@ -1360,6 +1364,10 @@ Options:
}
if (file == null) {
if (!stdio.isatty(io.stdin)) {
writeFile(io.stdin);
return null;
}
stdio.write(io.stderr,'no file specified\n');
return 1;
}
@ -1474,6 +1482,10 @@ Options:
}
if (file == null) {
if (!stdio.isatty(io.stdin)) {
writeFile(io.stdin);
return null;
}
stdio.write(io.stderr,'no file specified\n');
return 1;
}

View file

@ -1,6 +1,6 @@
var clite = {
state:{
version:'0.5.1',
version:'0.5.2',
isinit:false,
runlevel:1,
bios:null,
@ -1106,6 +1106,10 @@ clite.io = {
read = true;
if ((flags&clite.io.flags.O_WRONLY) == clite.io.flags.O_WRONLY)
write = true;
if ((flags&clite.io.flags.O_RDWR) == clite.io.flags.O_RDWR) {
read = true;
write = true;
}
if ((flags&clite.io.flags.O_EXEC) == clite.io.flags.O_EXEC)
exec = true;
if ((flags&clite.io.flags.O_SEARCH) == clite.io.flags.O_SEARCH)
@ -1116,7 +1120,7 @@ clite.io = {
return null;
if ((flags&clite.io.flags.O_APPEND) == clite.io.flags.O_APPEND) {
if (!read)
if (!read && !write)
return null;
append = true;
}
@ -1180,6 +1184,8 @@ clite.io = {
return null; // cannot truncate unloaded remote data
}
}
if (append && write)
fd.pos = fd.node.data.content.length;
return getFileDesPost(fd,cb);
}
@ -2674,9 +2680,6 @@ clite.lib = {
nio.pid = pid;
nio.exit = function(v) {
clite.lib.exit(pid,v);
try{
io.exit(pid,v);
} catch(err) {}
}
var nenv = structuredClone(env);
// this is esssentially the dynamic linker

View file

@ -96,7 +96,6 @@ return Object.create({
}
if (s.length > 0)
parts.push(s);
// TODO: resolve all paths passed as arguments? - catches ~ and * etc
return parts;
},

View file

@ -42,7 +42,7 @@ return Object.create({
initscr:function() {
// curses only works on a tty
if (!clite.io.isatty(io.pid,io.stdin))
if (!clite.io.isatty(io.pid,io.stdin) || !clite.io.isatty(io.pid,io.stdout))
return false;
this.ctty = clite.proc.getTTY(io.pid);
this.size.rows = clite.tty.ttyctrl(this.ctty,'rows');

View file

@ -1,6 +1,10 @@
clite.libs.data = function() {
clite.libs.load('libio','stdio',function(io,env) {
function randChar() {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
return characters.charAt(Math.floor(Math.random() * characters.length));
}
return Object.create({
@ -93,6 +97,32 @@ return Object.create({
return clite.io.mkdir(io.pid,path,mode);
},
mkstemp:function(template,rc) {
if (typeof template === 'undefined')
template = '/tmp/tmpfileXXXXXX';
if (typeof rc === 'undefined')
rc = 0;
if (rc > 4 || template[0] != '/')
return null;
var path = template;
var ind;
while ((ind = path.lastIndexOf('X')) > 4) {
var t = path.substring(0,ind)+randChar()+path.substring(ind+1);
path = t;
}
if (path == template)
return this.mkstemp(template+'XXXXXX',rc+1);
var fd = clite.io.open(io.pid,path,clite.io.flags.O_RDWR|clite.io.flags.O_CREAT|clite.io.flags.O_EXCL|clite.io.flags.O_SYNC);
if (!fd)
return this.mkstemp(template,rc+1);
return fd;
},
vfprintf:function(fd,fmt,args) {
return clite.io.vfprintf(io.pid,fd,fmt,args);
},

View file

@ -183,18 +183,19 @@ clite.commands.load('sh',function(args,env,io) {
parser.internal.parseLine();
return;
}
run(l,parser.internal.parseLine);
exec.setCommand(l);
exec.run(parser.internal.parseLine);
}
},
exit:function() {
if (typeof parser.callback === 'function') {
try{
parser.callback();
} catch(err) {
stdio.write(io.stderr,'Shell: internal error in parser\n');
io.exit(1);
return;
}
if (typeof parser.callback !== 'function')
return;
try{
parser.callback();
} catch(err) {
stdio.write(io.stderr,'Shell: internal error in parser\n');
io.exit(1);
return;
}
},
run:function(cb) {
@ -215,6 +216,411 @@ clite.commands.load('sh',function(args,env,io) {
}
};
var exec = {
callback:null,
cmds:[],
active:null,
bufferout:null,
bufferin:null,
cfg:{closout:false,closin:false,closerr:false},
last:0,
io:{},
internal:{
doCommand:function(cmd) {
exec.active = cmd;
if ((cmd.onlastsuccess && exec.last != 0) || (cmd.onlastfail && exec.last == 0)) {
exec.run();
return;
}
var usebi = false;
var usebo = false;
if (cmd.stdin != '&0') {
switch (cmd.stdin) {
case '|':
exec.stdin.setOut();
usebi = true;
break;
case null:
exec.stdin.setNull();
break;
default:
exec.stdin.setFile(cmd.stdin);
break;
}
}else{
if (exec.cfg.closin) {
stdio.close(exec.io.stdin);
exec.cfg.closin = false;
}
exec.io.stdin = io.stdin;
}
if (cmd.stdout != '&1') {
switch (cmd.stdout) {
case '|':
exec.stdout.setBuffered();
usebo = true;
break;
case null:
exec.stdout.setNull();
break;
default:
exec.stdout.setFile(cmd.stdout);
break;
}
}else{
if (exec.cfg.closout) {
stdio.close(exec.io.stdout);
exec.cfg.closout = false;
}
exec.io.stdout = io.stdout;
}
if (cmd.stderr != '&2') {
switch (cmd.stderr) {
case '&1':
exec.stderr.setOut();
break;
case null:
exec.stderr.setNull();
break;
default:
exec.stderr.setFile(cmd.stderr);
break;
}
}else{
if (exec.cfg.closerr) {
stdio.close(exec.io.stderr);
exec.cfg.closerr = false;
}
exec.io.stderr = io.stderr;
}
if (exec.io.stdin == null) {
stdio.write(io.stderr,'could not open standard input for reading\n');
exec.setLast(1);
exec.run();
return;
}
if (exec.io.stdout == null) {
stdio.write(io.stderr,'could not open standard output for writing\n');
exec.setLast(1);
exec.run();
return;
}
if (exec.io.stderr == null) {
stdio.write(io.stderr,'could not open standard error for writing\n');
exec.setLast(1);
exec.run();
return;
}
if (typeof macro[cmd.args[0]] != 'undefined') {
exec.io.exit = io.exit;
var r = macro[cmd.args[0]](cmd.args,exec.io);
exec.setLast(r);
exec.run();
return;
}
if (!cmd.path) {
stdio.fprintf(io.stderr,'Shell: unknown command: %s\n',cmd.args[0]);
exec.setLast(1);
exec.run();
return;
}
var pid = stdlib.fork(env,exec.io,exec.internal.doExec);
if (pid == 0) {
stdio.write(io.stderr,'Shell: internal error\n');
exec.setLast(1);
exec.run();
return;
}
stdlib.waitpid(pid,function(pid,ev) {
exec.setLast(ev);
exec.run();
});
},
doExec:function(env,io) {
var r = stdlib.exec(exec.active.path,exec.active.args,env,io);
if (r > 0)
return;
if (r == -1) {
stdlib.fprintf(io.stderr,'Shell: unknown command: %s\n',exec.active.args[0]);
io.exit(-1);
return;
}
if (r == -2) {
stdio.fprintf(io.stderr,'Shell: error in command: %s\n',exec.active.args[0]);
io.exit(-1);
return;
}
if (r == -3 || r == -4) {
stdio.fprintf(io.stderr,'Shell: not an executable file: %s\n',exec.active.args[0]);
io.exit(-3);
return;
}
if (r == -5) {
stdio.fprintf(io.stderr,'Shell: invalid parser for file: %s\n',exec.active.args[0]);
io.exit(-3);
return;
}
if (r == -6) {
stdio.fprintf(io.stderr,'Shell: no valid parser for file: %s\n',exec.active.args[0]);
io.exit(-3);
return;
}
if (r<0) {
stdio.fprintf(io.stderr,'Shell: could not exec file: %s\n',exec.active.args[0]);
io.exit(-3);
}
}
},
setLast:function(v) {
exec.last = parseInt(v);
env['?'] = exec.last.toString();
},
setCommand:function(txt) {
var args = clite.strToArgs(txt);
if (args.length < 1)
return;
args.forEach(function(val,i) {
args[i] = preprocess(val);
});
var cmd = {
args:[],
path:null,
stdin:'&0',
stdout:'&1',
stderr:'&2',
onlastsuccess:false,
onlastfail:false
};
var c = structuredClone(cmd);
exec.cmds = [];
for (var i=0; i<args.length; i++) {
if (args[i] == '|') {
c.stdout = '|';
exec.cmds.push(c);
c = structuredClone(cmd);
c.stdin = '|';
continue;
}
if (args[i] == '||') {
exec.cmds.push(c);
c = structuredClone(cmd);
c.onlastfail = true;
continue;
}
if (args[i] == '&&') {
exec.cmds.push(c);
c = structuredClone(cmd);
c.onlastsuccess = true;
continue;
}
if (args[i] == '>') {
i++;
c.stdout = clite.resolvePath(args[i]);
if (c.stdout == '/dev/null')
c.stdout = null;
continue;
}
if (args[i] == '2>&1') {
c.stderr = '&1';
continue;
}
if (args[i] == '>>') {
i++;
c.stdout = clite.resolvePath(args[i]);
if (c.stdout == '/dev/null') {
c.stdout = null;
}else{
c.stdout = '+'+c.stdout;
}
continue;
}
if (args[i] == '<') {
i++;
c.stdin = clite.resolvePath(args[i]);
if (c.stdin == '/dev/null')
c.stdin = null;
continue;
}
c.args.push(args[i]);
}
if (c.args.length > 0)
exec.cmds.push(c);
var fault = false;
exec.cmds.forEach(function(c) {
if (c.args.length < 1) {
fault = true;
return;
}
c.path = resolvePATH(c.args[0]);
});
if (fault) {
stdio.write(io.stderr,'Shell: unable to pass command\n');
exec.exit();
return;
}
exec.io.pid = io.pid;
exec.io.stdin = io.stdin;
exec.io.stdout = io.stdout;
exec.io.stderr = io.stderr;
exec.io.exit = null; // filled in by fork()
exec.io.include = null; // filled in by fork()
exec.last = 0;
},
stdin:{
setFile:function(path) {
if (exec.cfg.closin) {
stdio.close(exec.io.stdin);
exec.cfg.closin = false;
}
exec.io.stdin = stdio.open(path,stdio.flags.O_RDONLY);
exec.cfg.closin = true;
},
setNull:function() {
if (exec.cfg.closin) {
stdio.close(exec.io.stdin);
exec.cfg.closin = false;
}
exec.io.stdin = stdio.open('/dev/null',stdio.flags.O_RDONLY);
exec.cfg.closin = true;
},
setOut:function() {
if (exec.cfg.closin) {
stdio.close(exec.io.stdin);
exec.cfg.closin = false;
}
if (exec.bufferout) {
exec.io.stdout = null;
exec.cfg.closout = false;
exec.bufferin = exec.bufferout;
exec.bufferout = null;
exec.io.stdin = exec.bufferin;
stdio.seek(exec.io.stdin,0);
exec.cfg.closin = true;
}else{
exec.stdin.setNull();
}
}
},
stdout:{
setBuffered:function() {
if (exec.cfg.closout) {
stdio.close(exec.io.stdout);
exec.cfg.closout = false;
if (exec.bufferout)
exec.bufferout = null;
}
exec.bufferout = stdio.mkstemp('/tmp/shell-XXXXXX');
exec.io.stdout = exec.bufferout;
exec.cfg.closout = false;
},
setFile:function(path) {
if (exec.cfg.closout) {
stdio.close(exec.io.stdout);
exec.cfg.closout = false;
}
var flags = stdio.flags.O_WRONLY|stdio.flags.O_CREAT|stdio.flags.O_SYNC;
var p = path;
if (path[0] == '+') {
flags |= stdio.flags.O_APPEND;
p = path.substring(1);
}else{
flags |= stdio.flags.O_TRUNC;
}
exec.io.stdout = stdio.open(p,flags);
exec.cfg.closout = true;
},
setNull:function() {
if (exec.cfg.closout) {
stdio.close(exec.io.stdout);
exec.cfg.closout = false;
}
exec.io.stdout = stdio.open('/dev/null',stdio.flags.O_WRONLY);
exec.cfg.closin = true;
}
},
stderr:{
setFile:function(path) {
if (exec.cfg.closerr) {
stdio.close(exec.io.stderr);
exec.cfg.closerr = false;
}
var flags = stdio.flags.O_WRONLY|stdio.flags.O_CREAT|stdio.flags.O_SYNC;
var p = path;
if (path[0] == '+') {
flags |= stdio.flags.O_APPEND;
p = path.substring(1);
}else{
flags |= stdio.flags.O_TRUNC;
}
exec.io.stderr = stdio.open(p,flags);
exec.cfg.closerr = true;
},
setNull:function() {
if (exec.cfg.closerr) {
stdio.close(exec.io.stderr);
exec.cfg.closerr = false;
}
exec.io.stderr = stdio.open('/dev/null',stdio.flags.O_WRONLY);
exec.cfg.closerr = true;
},
setOut:function() {
if (exec.cfg.closerr) {
stdio.close(exec.io.stderr);
exec.cfg.closerr = false;
}
exec.io.stderr = exec.io.stdout;
exec.cfg.closerr = exec.cfg.closout;
}
},
exit:function() {
if (exec.cfg.closin) {
stdio.close(exec.io.stdin);
exec.cfg.closin = false;
}
var out = '';
if (exec.bufferout)
out = stdio.readAll(exec.bufferout);
if (exec.cfg.closout) {
stdio.close(exec.io.stdout);
exec.cfg.closout = false;
}
if (exec.cfg.closerr) {
stdio.close(exec.io.stderr);
exec.cfg.closerr = false;
}
exec.active = null;
exec.cmds = [];
if (typeof exec.callback !== 'function')
return;
try{
exec.callback(out);
} catch(err) {
console.log(err.fileName+':'+err.lineNumber+':'+err.columnNumber+':'+err.message);
stdio.write(io.stderr,'Shell: internal error in command execution\n');
io.exit(1);
return;
}
},
run:function(cb) {
if (typeof cb === 'function')
exec.callback = cb;
var c = exec.cmds.shift();
if (!c) {
exec.exit();
return;
}
exec.internal.doCommand(c);
}
};
function help() {
stdio.write(io.stdout,`
sh - command interpreter (shell)
@ -235,7 +641,7 @@ Options:
if (path)
return;
var rp = clite.resolvePath(txt,p);
var st = stdio.stat(rp);//open(rp,stdio.flags.O_RDONLY|stdio.flags.O_SYNC);
var st = stdio.stat(rp);
if (st) {
path = rp;
}
@ -335,108 +741,6 @@ Options:
return c+add;
}
function run(txt,cb) {
if (txt.length < 1) {
if (typeof cb === 'function')
cb();
return;
}
history.add(txt);
history.resetCurrent();
// split command line into arguments
var args = clite.strToArgs(txt);
args.forEach(function(val,i) {
args[i] = preprocess(val);
});
// prepare io (stdin,stdout,stderr)
// TODO: check for io redirects and piping, then setup stdio accordingly
var fio = {
pid:io.pid,
stdin:io.stdin,
stdout:io.stdout,
stderr:io.stderr,
exit:null, // filled in by fork()
include:null, // filled in by fork()
};
// check for a macro
// then either run the macro or expand the program to be executed via PATH
if (typeof macro[args[0]] != 'undefined') {
fio.exit = io.exit;
var r = macro[args[0]](args,fio);
env['?'] = r.toString();
if (typeof cb === 'function')
cb();
return;
}
var path = resolvePATH(args[0]);
if (!path) {
stdio.fprintf(io.stderr,'Shell: unknown command: %s\n',args[0]);
env['?'] = '1';
if (typeof cb === 'function')
cb();
return;
}
var shenv = env;
function execFunc(env,io) {
var r = stdlib.exec(path,args,env,io);
if (r > 0)
return;
shenv['?'] = '1';
if (r == -1) {
stdlib.fprintf(io.stderr,'Shell: unknown command: %s\n',args[0]);
io.exit(-1);
return;
}
if (r == -2) {
stdio.fprintf(io.stderr,'Shell: error in command: %s\n',args[0]);
io.exit(-1);
return;
}
if (r == -3 || r == -4) {
stdio.fprintf(io.stderr,'Shell: not an executable file: %s\n',args[0]);
io.exit(-3);
return;
}
if (r == -5) {
stdio.fprintf(io.stderr,'Shell: invalid parser for file: %s\n',args[0]);
io.exit(-3);
return;
}
if (r == -6) {
stdio.fprintf(io.stderr,'Shell: no valid parser for file: %s\n',args[0]);
io.exit(-3);
return;
}
if (r<0) {
stdio.fprintf(io.stderr,'Shell: could not exec file: %s\n',args[0]);
io.exit(-3);
}
}
var pid = stdlib.fork(env,fio,execFunc);
if (pid == 0) {
stdio.write(io.stderr,'Shell: internal error\n');
env['?'] = '1';
if (typeof cb === 'function')
cb();
return;
}
if (typeof cb === 'function')
stdlib.waitpid(pid,function(pid,ev) {
env['?'] = ev.toString();
cb();
});
}
function writePrompt() {
// generate the prompt
var p = env.PS1;
@ -472,7 +776,10 @@ Options:
break;
case '\n': // enter
term.ttyctrl('raw',false);
run(c,inputRead);
history.add(c);
history.resetCurrent();
exec.setCommand(c);
exec.run(inputRead);
c = '';
cc = 0;
return;
@ -551,6 +858,7 @@ Options:
}
// TODO: hacks r uglee
// if args[0] isn't the shell itself, then we're running a shell script
if (!f && args[0] != '/bin/sh' && args[0] != 'sh') {
f = clite.resolvePath(args[0]);
if (!f)

View file

@ -66,7 +66,7 @@ The shell supports arguments in "quotes" and 'single quotes', as well as
dot (.) and dot dot (..) for current and parent directories. The
asterisk (*) wildcard is not currently supported
The shell maintains a command history, use up and down arrows to access.
Piping and io redirects are not currently supported, yet.
Piping and io redirects are supported, however they may be buggy.
Minimal shell scripting is supported, essentially a line parser, no
conditional or loop support (yet).
The following shell builtin commands or macros are available: