diff --git a/clite/commands.js b/clite/commands.js index dc41ea8..9be8f30 100644 --- a/clite/commands.js +++ b/clite/commands.js @@ -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; } diff --git a/clite/core.js b/clite/core.js index dde2156..83d77e7 100644 --- a/clite/core.js +++ b/clite/core.js @@ -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 diff --git a/clite/libclite.js b/clite/libclite.js index 6c7f8d9..f62e1e4 100644 --- a/clite/libclite.js +++ b/clite/libclite.js @@ -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; }, diff --git a/clite/libcurses.js b/clite/libcurses.js index 244359b..8ebb231 100644 --- a/clite/libcurses.js +++ b/clite/libcurses.js @@ -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'); diff --git a/clite/libstdio.js b/clite/libstdio.js index d131975..09a300c 100644 --- a/clite/libstdio.js +++ b/clite/libstdio.js @@ -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); }, diff --git a/clite/shell.js b/clite/shell.js index 157ad58..4422ef3 100644 --- a/clite/shell.js +++ b/clite/shell.js @@ -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') { + 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) diff --git a/readme.txt b/readme.txt index e832ea1..2902b33 100644 --- a/readme.txt +++ b/readme.txt @@ -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: