clite.commands.data = function() { // insert commands below this line clite.commands.load('sh',function(args,env,io) { var stdio = io.include('stdio'); var stdlib = io.include('stdlib'); var term = io.include('term'); var clite = io.include('clite'); var has_exited = false; var macro = { clear:function(args,io) { term.clear(); return 0; }, cd:function(args,io) { var dir = env.HOME; if (args.length > 1) dir = clite.resolvePath(args[1]); var fd = stdio.open(dir,stdio.flags.O_SEARCH|stdio.flags.O_DIRECTORY); if (!fd) { stdio.fprintf(io.stderr,'invalid directory: %s\n',dir); return 1; } stdio.close(fd); env.PWD = dir; return 0; }, pwd:function(args,io) { stdio.fprintf(io.stdout,'%s\n',env.PWD); return 0; }, echo:function(args,io) { args.shift(); var txt = args.join(' ')+'\n'; stdio.write(io.stdout,txt); return 0; }, which:function(args,io) { if (args.length <2) return 1; if (typeof macro[args[1]] != 'undefined') { return 1; }else{ var path = resolvePATH(args[1]); if (!path) return 1; stdio.fprintf(io.stdout,'%s is %s\n',args[1],path); } return 0; }, type:function(args,io) { if (args.length <2) return 1; if (typeof macro[args[1]] != 'undefined') { stdio.fprintf(io.stdout,'%s is a shell builtin\n',args[1]); }else{ var path = resolvePATH(args[1]); if (!path) return 1; stdio.fprintf(io.stdout,'%s is %s\n',args[1],path); } return 0; }, whoami:function(args,io) { stdio.fprintf(io.stdout,'%s\n',env.USER); return 0; }, alias:function(args,io) { stdio.fprintf(io.stdout,'unimplemented\n'); return 0; }, export:function(args,io) { if (args.length == 1) { Object.keys(env).forEach(function(key) { stdio.fprintf(io.stdout,'%s=%s\n',key,env[key]); }); return 0; } var parts = args[1].split('='); if (parts.length != 2) return 0; env[parts[0]] = parts[1]; return 0; }, true:function(args,io) { return 0; }, false:function(args,io) { return 1; }, exit:function(args,io) { var ev = 0; if (args.length > 1) ev = parseInt(args[1]); doShellExit(ev); return 0; } }; var history = { data:[], current:'', index:0, load:function() { let path = clite.resolvePath('~/.shhistory'); let fd = stdio.open(path,stdio.flags.O_RDONLY|stdio.flags.O_SYNC); if (!fd) return; var e; while ((e = stdio.readLine(fd)) != null) { history.add(e); } stdio.close(fd); }, save:function() { let path = clite.resolvePath('~/.shhistory'); let fd = stdio.open(path,stdio.flags.O_WRONLY|stdio.flags.O_TRUNC|stdio.flags.O_CREAT|stdio.flags.O_SYNC); if (!fd) return; history.data.forEach(function(l) { stdio.write(fd,l+'\n'); }); stdio.close(fd); }, add:function(txt) { if (txt.length < 1) return; if (history.data.length > 0 && history.data[history.data.length-1] == txt) return; history.data.push(txt); history.index = history.data.length; }, up:function() { if (history.index < 1) return null; history.index--; return history.getCurrent(); }, down:function() { if (history.index >= history.data.length) return null; history.index++; return history.getCurrent(); }, setCurrent:function(txt) { history.current = txt; }, getCurrent:function() { if (history.index >= history.data.length) return history.current; return history.data[history.index]; }, resetCurrent:function() { history.current = ''; history.index = history.data.length; }, clear:function() { history.data = []; history.current = ''; history.index = 0; } }; var parser = { files:[], callback:null, queue:function(file) { parser.files.push(file); }, internal:{ lines:[], scrpt:null, parseFile:function(f) { var fn = clite.resolvePath(f); if (fn == null) { parser.run(); return; } var st = stdio.stat(fn); if (st == null || st.type != stdio.types.FT_SCRIPT) { parser.run(); return; } var fd = stdio.open(fn,stdio.flags.O_RDONLY,parser.internal.prepFile); if (!fd) parser.run(); }, prepFile:function(fd) { var data = stdio.readAll(fd); if (fd != io.stdin) stdio.close(fd); if (!data) { parser.run(); return; } parser.internal.script = ''; parser.internal.lines = data.split('\n'); if (parser.internal.lines.length < 1) { parser.run(); return; } parser.internal.parseLine(); }, parseLine:function() { var l = parser.internal.lines.shift(); if (l == null) { parser.internal.exec(); return; } if (l[0] != '#') parser.internal.script += l+'; '; parser.internal.parseLine(); }, exec:function() { exec.setCommand(parser.internal.script); exec.run(parser.run); } }, exit:function() { 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) { if (typeof cb === 'function') parser.callback = cb; var f = parser.files.shift(); if (!f) { parser.exit(); return; } parser.internal.parseFile(f); }, stdin:function() { parser.callback = function() { io.exit(0); } parser.internal.prepFile(io.stdin); } }; var exec = { callback:null, cmds:[], active:null, bufferout:null, bufferin:null, skipto:null, cfg:{closout:false,closin:false,closerr:false}, last:0, io:{}, internal:{ conditionals:[], loops:[], doCommand:function(cmd) { exec.active = cmd; if ((cmd.onlastsuccess && exec.last != 0) || (cmd.onlastfail && exec.last == 0)) { exec.run(); return; } switch (cmd.args[0]) { case 'if': exec.internal.conditionals.push(cmd); exec.run(); return; break; case 'then': if (exec.last == 0) { exec.internal.conditionals[exec.internal.conditionals.length-1].conditional = true; }else{ exec.internal.conditionals[exec.internal.conditionals.length-1].conditional = false; exec.skipto = 'else'; } exec.run(); return; break; case 'else': if (exec.internal.conditionals[exec.internal.conditionals.length-1].conditional != false) { exec.skipto = 'fi'; } exec.run(); return; break; case 'fi': exec.internal.conditionals.pop(); exec.run(); return; break; case 'while': case 'do': case 'done': exec.run(); return; break; default:; } 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 == -7 || r == -8) { stdio.fprintf(io.stderr,'Shell: failed to set uid/gid for: %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, conditional:false }; var c = structuredClone(cmd); exec.cmds = []; var fault = false; var conds = []; var loops = []; for (var i=0; i 0) { exec.cmds.push(c); c = structuredClone(cmd); } continue; } if (args[i] == 'if') { if (c.args.length > 0) { exec.cmds.push(c); c = structuredClone(cmd); } c.args.push(args[i]); exec.cmds.push(c); conds.push(c); c = structuredClone(cmd); continue; } if (args[i] == 'then') { if (c.args.length > 0) { exec.cmds.push(c); c = structuredClone(cmd); } c.args.push(args[i]); exec.cmds.push(c); if (conds.length < 1) { fault = true; }else{ conds[conds.length-1].args.push(c); } c = structuredClone(cmd); continue; } if (args[i] == 'else') { if (c.args.length > 0) { exec.cmds.push(c); c = structuredClone(cmd); } c.args.push(args[i]); exec.cmds.push(c); if (conds.length < 1) { fault = true; }else{ conds[conds.length-1].args.push(c); } c = structuredClone(cmd); continue; } if (args[i] == 'fi') { if (c.args.length > 0) { exec.cmds.push(c); c = structuredClone(cmd); } c.args.push(args[i]); exec.cmds.push(c); if (conds.length < 1) { fault = true; }else{ conds[conds.length-1].args.push(c); conds.pop(); } c = structuredClone(cmd); continue; } if (args[i] == 'while') { if (c.args.length > 0) { exec.cmds.push(c); c = structuredClone(cmd); } c.args.push(args[i]); exec.cmds.push(c); loops.push(c); c = structuredClone(cmd); continue; } if (args[i] == 'do') { if (c.args.length > 0) { exec.cmds.push(c); c = structuredClone(cmd); } c.args.push(args[i]); exec.cmds.push(c); if (loops.length < 1) { fault = true; }else{ loops[loops.length-1].args.push(c); } c = structuredClone(cmd); continue; } if (args[i] == 'done') { if (c.args.length > 0) { exec.cmds.push(c); c = structuredClone(cmd); } c.args.push(args[i]); exec.cmds.push(c); if (loops.length < 1) { fault = true; }else{ loops[loops.length-1].args.push(c); loops.pop(); } c = structuredClone(cmd); continue; } 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 (conds.length > 0) fault = true; if (loops.length > 0) fault = true; if (c.args.length > 0) exec.cmds.push(c); exec.cmds.forEach(function(c,i) { if (c.args.length < 1) { fault = true; return; } c.args[0] = c.args[0].trim(); c.path = resolvePATH(c.args[0]); }); if (fault) { stdio.write(io.stderr,'Shell: unable to parse 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.closout = 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; if (exec.skipto !== null) { if (exec.skipto == 'fi') { while ((c = exec.cmds.shift()) != null) { if (c.args[0] == 'fi') break; } }else if (exec.skipto == 'else') { while ((c = exec.cmds.shift()) != null) { if (c.args[0] == 'else' || c.args[0] == 'fi') break; } } exec.skipto = null; }else{ c = exec.cmds.shift(); } if (!c) { exec.exit(); return; } exec.internal.doCommand(c); } }; function help() { stdio.write(io.stdout,` sh - command interpreter (shell) Usage: sh [OPTION] [FILE] Options: -? Print this help information `); } function doShellExit(v) { history.save(); stdio.write(io.stdout,'\n'); has_exited = true; io.exit(v); } function resolvePATH(txt) { // special case of test command if (txt == '[') txt = 'test'; if (typeof env.PATH === 'undefined') env.PATH = '/bin'; var paths = env.PATH.split(':'); var path = null; paths.forEach(function(p) { if (path) return; var rp = clite.resolvePath(txt,p); var st = stdio.stat(rp); if (st) { path = rp; } }); return path; } function preprocess(txt) { var parts = txt.split('$'); var pptxt = ''; if (parts.length == 1) return txt; parts.forEach(function(p,i) { if (i > 0) { var b = p; var e = ''; if (b[0] == '{') { var pi = b.indexOf('}'); e = b.substring(pi+1); b = b.substring(1,pi); }else{ var pi = b.indexOf(' '); if (pi > 0) { e = b.substring(pi); b = b.substring(0,pi); } } if (typeof env[b] === 'string') { b = env[b]; }else{ b = ''; } pptxt += b+e; return; } pptxt += p; }); txt = pptxt; parts = txt.split('`'); pptxt = ''; if (parts.length == 1) return txt; parts.forEach(function(p,i) { if (i > 0) { var b = p; var e = ''; var pi = b.indexOf('`'); if (pi > 0) { e = b.substring(pi); b = b.substring(0,pi); } // TODO: replace b with the output of the command in b (command substitution) pptxt = b+e; return; } pptxt += p; }); return pptxt; } function tabfill(c) { var parts = clite.strToArgs(c); var ai = parts.length-1; if (ai < 0) return null; var a = parts[ai]; var add = ''; if (ai == 0) { // test for shell macros/builtins Object.keys(macro).forEach(function(key) { if (add == '' && key.substring(0,a.length) == a) { add = key.substring(a.length); } }); if (add == '') { // test for commands var pparts = env.PATH.split(':'); pparts.forEach(function(pp) { if (add != '') return; var fd = stdio.open('/bin',stdio.flags.O_SEARCH|stdio.flags.O_DIRECTORY); var f; while (add == '' && (f = stdio.read(fd)) != null) { if (f.substring(0,a.length) == a) { add = f.substring(a.length)+' '; } } stdio.close(fd); }); } }else{ // TODO: path fill } if (add == '') return null; stdio.write(io.stdout,'\b'+add); return c+add; } function writePrompt() { // generate the prompt var p = env.PS1; if (typeof p === 'undefined') p = '${USER}:${PWD}' var prompt = preprocess(p); if (stdlib.getuid() == 0) { prompt += '# '; }else{ prompt += '$ '; } stdio.write(io.stdout,prompt); } function inputRead() { if (has_exited) return; var c = ''; var cc = 0; function inputProcess(str) { switch (str) { case String.fromCharCode(4): case 4: term.ttyctrl('raw',false); doShellExit(0); break; case '\t': // tab case 9: var ct = tabfill(c); if (ct != null) { c = ct; cc = c.length; } break; case '\n': // enter term.ttyctrl('raw',false); history.add(c); history.resetCurrent(); exec.setCommand(c); exec.run(inputRead); c = ''; cc = 0; return; break; case '\b': case 8: if (c.length > 0) { cc--; c = c.substring(0,cc); stdio.write(io.stdout,'\b \b'); } break; case -3: // down arrow var ct = history.down(); if (ct != null) { for (var i=0; i