diff --git a/clite/commands.js b/clite/commands.js index 462f529..9fd0517 100644 --- a/clite/commands.js +++ b/clite/commands.js @@ -1,9 +1,135 @@ clite.commands.data = function() { // insert commands below this line -clite.commands.load('ls',function(args) { - clite.log.write('hello world (l)'); - clite.shell.writeLine('hello world (s)'); +clite.commands.load('help',function(args,env,io) { + io.write('yeah, I should probably implement this'); + return 0; +}); + +clite.commands.load('ls',function(args,env,io) { + var dirs = []; + var long = false; + function help() { + io.write(` +Usage: ls [OPTION] [FILE] + +Options: +-? Print this help information +-l Display data in long format + `); + } + + function writeNodeData(node) { + if (long) { + io.write(node.perms+'\t'+node.uid+'\t'+node.gid+'\t'+node.name); + }else{ + io.write(node.name); + } + } + + for (var i=1; i 1) + io.write(dir+':'); + var fd = clite.io.open(dir); + if (!fd) { + io.error('cannot open directory: '+dir); + return; + } + if (!fd.node.data.isdir) { + writeNodeData(fd.node); + clite.io.close(fd); + return; + } + var e; + while ((e = clite.io.read(fd)) != null) { + writeNodeData(e); + } + clite.io.close(fd); + }); + + return 0; +}); + +clite.commands.load('cat',function(args,env,io) { + var files = []; + var pending = 0; + + for (var i=1; i -1) + return true; + return false; + } + function setLogin(user,uid,gid,groups) { + udata.name = user; + udata.uid = uid; + udata.group = gid; + udata.groups = groups; + clite.shell.env.HOME = '/usr/home/'+user; + clite.shell.env.PWD = '/usr/home/'+user; + clite.shell.env.USER = user; + clite.shell.prompt.generate(); + clite.user.hasLogin = function() { + return true; + } + } + clite.user.genGuest = function() { + clite.term.clear(); + vfs.mkDir('/usr/home/guest'); + var n = vfs.getNode('/usr/home/guest'); + if (n) { + n.perms = '-rwx------'; + n.uid = 1; + n.gid = 1; + } + setLogin('guest',1,1,[]); + clite.events.refocus(); + } return false; }, getUID:function() { // get the user's user id @@ -185,7 +238,11 @@ clite.user = { }, checkGID:function(gid) { // check if the user is in a group return true; - } + }, + hasLogin:function() { // check if we're in a user session + return false; + }, + genGuest:null }; clite.io = { @@ -223,7 +280,7 @@ clite.io = { } }; - function getFileDes(path,link) { + function getFileDes(path,link,cb) { var n = vfsapi.getNode(path); if (!n) return null; @@ -238,19 +295,24 @@ clite.io = { canexec:false, remote:{ ispending:p, - callback:null + callback:cb } }); - if (p) + if (p) { clite.core.load.file(n.data.remote,function(d) { n.data.content = d; + fd.remote.ispending = false; try{ fd.remote.callback(fd); } catch(err) {} }); + } fd.canread = perms.checkReadable(fd.node.perms,fd.node.uid,fd.node.gid); fd.canwrite = perms.checkWritable(fd.node.perms,fd.node.uid,fd.node.gid); fd.canexec = perms.checkExecutable(fd.node.perms,fd.node.uid,fd.node.gid); + if (!p && cb != null) { + clite.core.execSafeAsync(function() {cb(fd);}); + } return fd; } clite.io.creat = function(path,type) { @@ -265,8 +327,8 @@ clite.io = { return vfsapi.mkFile(path); } } - clite.io.open = function(path) { - return getFileDes(path,false); + clite.io.open = function(path,cb) { + return getFileDes(path,false,cb); } clite.io.close = function(fd) { try{ @@ -299,6 +361,9 @@ clite.io = { fd.pos += l.length; return l; } + clite.io.readAll = function(fd) { + return fd.node.data.content; + } clite.io.write = function(fd,data) { if (!fd.canwrite) return false; @@ -327,7 +392,7 @@ clite.io = { return true; } clite.io.truncate = function(path,len) { - var fd = getFileDes(path,false); + var fd = getFileDes(path,false,null); if (!fd) return false; var r = clite.io.ftruncate(fd,len); @@ -347,15 +412,15 @@ clite.io = { return fd.pos; } clite.io.remove = function(path) { - var fd = getFileDes(path,true); + var fd = getFileDes(path,true,null); if (!fd || !fd.canwrite) return false; return vfsapi.remove(path); } clite.io.link = function(path,target) { - if (!getFileDes(target,false)) + if (!getFileDes(target,false,null)) return false; - var fd = getFileDes(path,true); + var fd = getFileDes(path,true,null); if (fd) { if (!fd.canwrite) return false; @@ -370,6 +435,7 @@ clite.io = { close:null, read:null, readLine:null, + readAll:null, write:null, ftruncate:null, truncate:null, @@ -394,14 +460,20 @@ clite.vfs = { } } function checkCanOpen(node) { - return true; + if (node.perms[7] == 'r') + return true; + if (clite.user.checkGID(node.gid) && node.perms[4] == 'r') + return true; + if (node.uid == clite.user.getUID() && node.perms[1] == 'r') + return true; + return false; } function mkNode() { var fsnode = { name: '', uid: 0, gid: 0, - perms:'-rw-r-----', + perms:'-rw-r--r--', data:{ remote:null, isdir:false, @@ -421,8 +493,9 @@ clite.vfs = { vfsdata.fs.perms[0] = 'd'; clite.vfs.getApi = function() { - // TODO: only allow for the root user - return vfsdata.api; + if (clite.user.getUID() == 0) + return vfsdata.api; + return null; } vfsdata.api.getNode = function(path) { @@ -467,8 +540,9 @@ clite.vfs = { n.data.parent = parent; n.data.content = []; n.data.isdir = true; - n.perms[0] = 'd'; - // TODO: set uid/gid/permissions + n.perms = 'drwxr-xr-x'; + n.uid = clite.user.getUID(); + n.gid = clite.user.getGID(); parent.data.content.push(n); return true; } @@ -487,7 +561,9 @@ clite.vfs = { n.name = name; n.data.parent = parent; n.data.content = ''; - // TODO: set uid/gid/permissions + n.perms = '-rw-r--r--'; + n.uid = clite.user.getUID(); + n.gid = clite.user.getGID(); parent.data.content.push(n); return true; } @@ -506,9 +582,10 @@ clite.vfs = { n.name = name; n.data.parent = parent; n.data.islink = true; - n.perms[0] = 'l'; n.data.content = target; - // TODO: set uid/gid/permissions + n.perms = 'lrwxr-xr-x'; + n.uid = clite.user.getUID(); + n.gid = clite.user.getGID(); parent.data.content.push(n); return true; } @@ -539,14 +616,11 @@ clite.vfs = { clite.log = { init:function(vfs) { clite.log.write = function(txt) { - // TODO: after login don't write to term or shell - try { - if (clite.term.hasInput()) { - clite.shell.writeLine(txt); - }else{ + if (!clite.user.hasLogin()) { + try { clite.term.writeLine(txt); - } - } catch(e) {} + } catch(e) {} + } var n = vfs.getNode('/var/logs'); if (n) n.data.content += txt+'\n'; @@ -648,24 +722,39 @@ clite.term = { }; clite.shell = { + exit:function(v) { + clite.shell.prompt.pop(); + clite.events.refocus(); + }, readLine:function(cb) { }, writeLine:function(txt) { clite.term.writeLine(txt); clite.events.refocus(); }, - exec:function(txt) { - // TODO: split this better - var args = txt.split(' '); - // TODO: resolve using PATH - var path = '/bin/'+args[0]; + resolvePath:function(txt) { + // TODO: resolve using PATH (check each colon separated path) + return clite.lib.resolvePath(txt,clite.shell.env.PATH) + }, + exec:function(args,io) { + var path = clite.shell.resolvePath(args[0]); var fd = clite.io.open(path); + // TODO: should errors here write to stderr? (io.error) if (!fd) { - clite.shell.writeLine('Shell: unknown command: '+args[0]); + clite.term.writeLine('Shell: unknown command: '+args[0]); + io.exit(-1); return; } - if (!clite.core.execSafe(function() {fd.node.data.content(args)})) - clite.shell.writeLine('Shell: error in command:'+args[0]); + + var r = clite.core.execSafe(function() { + var rr = fd.node.data.content(args,Object.create(clite.shell.env),io); + if (typeof rr == 'number') + io.exit(rr); + }); + if (!r) { + clite.term.writeLine('Shell: error in command:'+args[0]); + io.exit(-1); + } clite.io.close(fd); }, parse:function(txt) { @@ -676,13 +765,108 @@ clite.shell = { clite.shell.history.add(txt); clite.shell.history.resetCurrent(); clite.shell.writeLine(clite.shell.prompt.getHTML()+txt); + clite.shell.prompt.push(''); + clite.term.preventInput(); + // ready stdio (stdout,stdin) + var stdio = { + error:clite.term.writeLine, + write:clite.term.writeLine, + read:clite.shell.readLine, + exit:clite.shell.exit, + istty:true + }; // check for a macro + var args = clite.lib.strToArgs(txt); // then either run the macro or expand the program to be executed via PATH - clite.shell.exec(txt); + if (typeof clite.shell.macro[args[0]] != 'undefined') { + var r = clite.shell.macro[args[0]](args,stdio); + clite.shell.exit(r); + }else{ + clite.shell.exec(args,stdio); + } + }, + env:{ + USER:'root', + PWD:'/', + HOME:'/', + PATH:'/bin' + }, + macro:{ + clear:function(args,io) { + clite.term.clear(); + }, + cd:function(args,io) { + var dir = clite.shell.env.HOME; + if (args.length > 1) { + dir = clite.lib.resolvePath(args[1]); + } + var fd = clite.io.open(dir); + if (!fd) { + io.error('invalid directory: '+dir); + return; + } + clite.io.close(fd); + clite.shell.env.PWD = dir; + clite.shell.prompt.generate(); + }, + pwd:function(args,io) { + io.write(clite.shell.env.PWD); + }, + echo:function(args,io) { + args.shift(); + var txt = args.join(' '); + io.write(txt); + }, + which:function(args,io) { + if (args.length <2) + return; + if (typeof clite.shell.macro[args[1]] != 'undefined') { + return; + }else{ + var path = clite.shell.resolvePath(args[1]); + var fd = clite.io.open(path); + if (!fd) + return; + clite.io.close(fd); + io.write(args[1]+' is '+path); + } + }, + type:function(args,io) { + if (args.length <2) + return; + if (typeof clite.shell.macro[args[1]] != 'undefined') { + io.write(args[1]+' is a shell builtin'); + }else{ + var path = clite.shell.resolvePath(args[1]); + var fd = clite.io.open(path); + if (!fd) + return; + clite.io.close(fd); + io.write(args[1]+' is '+path); + } + }, + whoami:function(args,io) { + io.write(clite.shell.env.USER); + }, + alias:function(args,io) { + io.write('unimplemented'); + }, + export:function(args,io) { + if (args.length == 1) { + Object.keys(clite.shell.env,function(key) { + io.write(key+'='+clite.shell.env[key]); + }); + return; + } + var parts = args[1].split('='); + if (parts.length != 2) + return; + clite.shell.env[parts[0]] = parts[1]; + } }, prompt:{ list:[], - data:'$ ', + data:'# ', set:function(txt) { clite.shell.prompt.list = []; clite.shell.prompt.data = txt; @@ -694,6 +878,15 @@ clite.shell = { pop:function() { clite.shell.prompt.data = clite.shell.prompt.list.pop(); }, + generate:function() { + var p = clite.shell.env.USER+':'+clite.shell.env.PWD; + if (clite.user.getUID() == 0) { + p += '# '; + }else{ + p += '$ '; + } + clite.shell.prompt.set(p); + }, get:function() { return clite.shell.prompt.data; }, @@ -759,5 +952,61 @@ clite.lib = { var parts = txt.split('/'); parts.pop(); return parts.join('/'); + }, + // returns an absolute path: ('foo','/etc') -> '/etc/foo': ('foo','bar') -> $PWD/bar/foo + resolvePath:function(txt,base) { + if (txt[0] == '/') + return txt; + if (txt[0] == '~') + return clite.shell.env.HOME+txt.substring(1); + if (typeof base != 'string') + base = clite.shell.env.PWD; + if (base[0] != '/') + base = clite.shell.env.PWD+'/'+base; + return base+'/'+txt; + }, + // convert a string into an argument array: 'ls /etc' -> ['ls',/etc] + strToArgs:function(txt) { + // TODO: split this properly (escape strings and insert env variables) + var parts = []; + var s = ''; + var e = false; + var q = false; + for (var i=0; i 0) + parts.push(s); + return parts;//txt.split(' '); } } diff --git a/data/intro b/data/intro index bd5f6c4..9627a0f 100644 --- a/data/intro +++ b/data/intro @@ -1,6 +1,4 @@ -BEGINDATAWelcome, this site runs on Clite, the command line site management system. +Welcome, this site runs on Clite, the command line site management system. Clite works like a Unix terminal, a guest login will be automatically generated for you. Clite has many Unix-like commands for navigating the website, simply type in a command and press enter to run it. Not sure how this works? Use the `help' command at any time. - -Press Enter to begin.