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; let builtins = ['clear','cd','pwd','echo','which','type','whoami','alias','export','true','false','exit']; let builtins_func = [builtin_clear,builtin_cd,builtin_pwd,builtin_echo,builtin_which,builtin_type,builtin_whoami,builtin_alias,builtin_export,builtin_true,builtin_false,builtin_exit]; let history_data = []; let history_current = ''; let history_index = 0; let parser_files = []; let parser_callback = null; let parser_lines = []; let parser_script = null; let exec_callback = null; let exec_cmds = []; let exec_active = null; let exec_bufferout = null; let exec_bufferin = null; let exec_skipto = null; let exec_closout = false; let exec_closin = false; let exec_closerr = false; let exec_last = 0; let exec_io = {}; let exec_conditionals = []; let exec_loops = []; function builtin_clear(args,io) { term.clear(); return 0; } function builtin_cd(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; } function builtin_pwd(args,io) { stdio.fprintf(io.stdout,'%s\n',env.PWD); return 0; } function builtin_echo(args,io) { args.shift(); var txt = args.join(' ')+'\n'; stdio.write(io.stdout,txt); return 0; } function builtin_which(args,io) { if (args.length <2) return 1; if (builtins.indexOf(args[1]) > -1) { 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; } function builtin_type(args,io) { if (args.length <2) return 1; if (builtins.indexOf(args[1]) > -1) { 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; } function builtin_whoami(args,io) { stdio.fprintf(io.stdout,'%s\n',env.USER); return 0; } function builtin_alias(args,io) { stdio.fprintf(io.stdout,'unimplemented\n'); return 0; } function builtin_export(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; } function builtin_true(args,io) { return 0; } function builtin_false(args,io) { return 1; } function builtin_exit(args,io) { var ev = 0; if (args.length > 1) ev = parseInt(args[1]); doShellExit(ev); return 0; } function history_load() { 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); } function history_save() { 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); } function history_add(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; } function history_up() { if (history_index < 1) return null; history_index--; return history_getCurrent(); } function history_down() { if (history_index >= history_data.length) return null; history_index++; return history_getCurrent(); } function history_setCurrent(txt) { history_current = txt; } function history_getCurrent() { if (history_index >= history_data.length) return history_current; return history_data[history_index]; } function history_resetCurrent() { history_current = ''; history_index = history_data.length; } function history_clear() { history_data = []; history_current = ''; history_index = 0; } function parser_queue(file) { parser_files.push(file); } function parser_parseFile(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_prepFile); if (!fd) parser_run(); } function parser_prepFile(fd) { var data = stdio.readAll(fd); if (fd != io.stdin) stdio.close(fd); if (!data) { parser_run(); return; } parser_script = ''; parser_lines = data.split('\n'); if (parser_lines.length < 1) { parser_run(); return; } parser_parseLine(); } function parser_parseLine() { var l = parser_lines.shift(); if (l == null) { parser_exec(); return; } if (l[0] != '#') parser_script += l+'; '; parser_parseLine(); } function parser_exec() { exec_setCommand(parser_script); exec_run(parser_run); } function parser_exit() { 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; } } function parser_run(cb) { if (typeof cb === 'function') parser_callback = cb; var f = parser_files.shift(); if (!f) { parser_exit(); return; } parser_parseFile(f); } function parser_stdin() { parser_callback = function() { io.exit(0); } parser_prepFile(io.stdin); } function exec_doCommand(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_conditionals.push(cmd); exec_run(); return; break; case 'then': if (exec_last == 0) { exec_conditionals[exec_conditionals.length-1].conditional = true; }else{ exec_conditionals[exec_conditionals.length-1].conditional = false; exec_skipto = 'else'; } exec_run(); return; break; case 'else': if (exec_conditionals[exec_conditionals.length-1].conditional != false) { exec_skipto = 'fi'; } exec_run(); return; break; case 'fi': exec_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_closin) { stdio.close(exec_io.stdin); exec_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_closout) { stdio.close(exec_io.stdout); exec_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_closerr) { stdio.close(exec_io.stderr); exec_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 (builtins.indexOf(cmd.args[0]) > -1) { exec_io.exit = io.exit; let fn = builtins.indexOf(cmd.args[0]); let r = builtins_func[fn](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_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(); }); } function exec_doExec(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); } } function exec_setLast(v) { exec_last = parseInt(v); env['?'] = exec_last.toString(); } function exec_setCommand(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; } function exec_stdin_setFile(path) { if (exec_closin) { stdio.close(exec_io.stdin); exec_closin = false; } exec_io.stdin = stdio.open(path,stdio.flags.O_RDONLY); exec_closin = true; } function exec_stdin_setNull() { if (exec_closin) { stdio.close(exec_io.stdin); exec_closin = false; } exec_io.stdin = stdio.open('/dev/null',stdio.flags.O_RDONLY); exec_closin = true; } function exec_stdin_setOut() { if (exec_closin) { stdio.close(exec_io.stdin); exec_closin = false; } if (exec_bufferout) { exec_io.stdout = null; exec_closout = false; exec_bufferin = exec_bufferout; exec_bufferout = null; exec_io.stdin = exec_bufferin; stdio.seek(exec_io.stdin,0); exec_closin = true; }else{ exec_stdin_setNull(); } } function exec_stdout_setBuffered() { if (exec_closout) { stdio.close(exec_io.stdout); exec_closout = false; if (exec_bufferout) exec_bufferout = null; } exec_bufferout = stdio.mkstemp('/tmp/shell-XXXXXX'); exec_io.stdout = exec_bufferout; exec_closout = false; } function exec_stdout_setFile(path) { if (exec_closout) { stdio.close(exec_io.stdout); exec_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_closout = true; } function exec_stdout_setNull() { if (exec_closout) { stdio.close(exec_io.stdout); exec_closout = false; } exec_io.stdout = stdio.open('/dev/null',stdio.flags.O_WRONLY); exec_closout = true; } function exec_stderr_setFile(path) { if (exec_closerr) { stdio.close(exec_io.stderr); exec_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_closerr = true; } function exec_stderr_setNull() { if (exec_closerr) { stdio.close(exec_io.stderr); exec_closerr = false; } exec_io.stderr = stdio.open('/dev/null',stdio.flags.O_WRONLY); exec_closerr = true; } function exec_stderr_setOut() { if (exec_closerr) { stdio.close(exec_io.stderr); exec_closerr = false; } exec_io.stderr = exec_io.stdout; exec_closerr = exec_closout; } function exec_exit() { if (exec_closin) { stdio.close(exec_io.stdin); exec_closin = false; } var out = ''; if (exec_bufferout) out = stdio.readAll(exec_bufferout); if (exec_closout) { stdio.close(exec_io.stdout); exec_closout = false; } if (exec_closerr) { stdio.close(exec_io.stderr); exec_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; } } function exec_run(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_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 builtins.forEach(function(key) { if (add == '' && key.substring(0,a.length) == a) { add = key.substring(a.length); } }); if (add == '') { // test for commands let pparts = env.PATH.split(':'); pparts.forEach(function(pp) { if (add != '') return; let fd = stdio.open('/bin',stdio.flags.O_SEARCH|stdio.flags.O_DIRECTORY); let f = null; while (add == '' && (f = stdio.read(fd)) != null) { if (f.substring(0,a.length) == a) { add = f.substring(a.length)+' '; } } stdio.close(fd); }); } }else if (a != '') { let p = clite.resolvePath(a); if (p[0] == '/') { let d = stdlib.dirname(p); let b = stdlib.basename(p); if (d == '') d = '/'; let fd = stdio.open(d,stdio.flags.O_SEARCH|stdio.flags.O_DIRECTORY); if (fd) { let f = null; while (add == '' && (f = stdio.read(fd)) != null) { if (f.substring(0,b.length) == b) { add = f.substring(b.length); let fp = d+'/'+f; let st = stdio.stat(fp); if (st && st.type != stdio.types.FT_DIR) add += ' '; } } stdio.close(fd); } } } 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 inputProcess(str) { let c = history_getCurrent(); 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; }else{ stdio.write(io.stdout,'\b \b'); } break; case '\n': // enter term.ttyctrl('raw',false); history_add(c); history_resetCurrent(); exec_setCommand(c); exec_run(inputRead); return; break; case '\b': case 8: if (c.length > 0) { c = c.substring(0,c.length-1); stdio.write(io.stdout,'\b \b'); } break; case -3: // down arrow var ct = history_down(); if (ct != null) { for (var i=0; i