var clite = { state:{ version:'0.8.0', isinit:false, runlevel:1, bios:null, cookiesAccepted:null }, includes:{ // a list of program/command files to load prog:['commands.js','shell.js','vi.js','user.js','build.js'], // a list of library files to load libs:['libclite.js','libcurses.js','libstd.js','libstdio.js','libterm.js','libtime.js','libauth.js'] }, time:{ sec:function() { return parseInt(Date.now()/1000.0,10); }, msec:function() { return Date.now(); }, nsec:function() { return parseInt(Date.now()*1000,10); } }, core:{ execSafe:function(f) { try{ f(); } catch(e) { clite.log.write(e.message); return false; } return true; }, execSafeAsync:function(f,delay) { if (typeof delay !== 'number') delay = 10; try{ setTimeout(f,delay); } catch(e) { clite.log.write(e.message); return false; } return true; }, download:function(name,data) { var link = document.createElement("a"); link.target = '_blank'; link.download = name; var blob = new Blob([data], {type: "text/plain"}); link.href = URL.createObjectURL(blob); document.body.appendChild(link); link.click(); URL.revokeObjectURL(link.href); document.body.removeChild(link); }, reboot:function() { clite.log.write('The system is rebooting'); setTimeout(function() { clite.console.clear(); clite.log.write('Bringing down the system'); clite.log.write('Resetting system core'); var b = clite.state.bios; delete clite; clite = null; b.io.write(4,true); },10); }, shutdown:function() { clite.log.write('The system is shutting down'); setTimeout(function() { clite.console.clear(); clite.log.write('Bringing down the system'); delete clite; clite = null; b.io.write(4,false); },10); }, hostname:function() { if (window.location.protocol != 'file:') return window.location.hostname; return 'localhost'; } }, init:function(bios) { if (clite.state.isinit) return; clite.state.isinit = true; clite.state.bios = bios; clite.state.bios.io.write(0,'CLIte '+clite.state.version); clite.core.execSafeAsync(function() { clite.console.init(); var vfsapi = null; function defaultDevices() { // writing to /dev/local downloads data {name:'filename to save',data:'file content'} { vfsapi.mkFile(0,'/dev/local'); let n = vfsapi.getNode(0,'/dev/local'); if (!n) { clite.log.write('local device failure'); return false; } n.data.content = { read:function(cb) { clite.state.bios.core.load.localfile('',cb); return true; }, write:function(obj) { if (typeof obj === 'string') { var n = 'data.txt'; var d = obj; var ind = obj.indexOf(String.fromCharCode(2)); if (ind > -1) { n = obj.substring(0,ind); d = obj.substring(ind+1); } clite.core.download(n,d); }else if (typeof obj.name === 'string' && typeof obj.data === 'string') { clite.core.download(obj.name,obj.data); }else{ return false; } return true; } }; n.mode = clite.lib.modestr('crw-rw-rw-'); n.data.isdev = true; } // /dev/null - writing to it goes nowhere, reading from it is always null { vfsapi.mkFile(0,'/dev/null'); let n = vfsapi.getNode(0,'/dev/null'); if (!n) { clite.log.write('null device failure'); return false; } n.data.content = { read:function() { return null; }, write:function(v) { return true; } }; n.mode = clite.lib.modestr('crw-rw-rw-'); n.data.isdev = true; } // /dev/random - writing to it goes nowhere, reading from it returns a stringified random number (integer) { vfsapi.mkFile(0,'/dev/random'); let n = vfsapi.getNode(0,'/dev/random'); if (!n) { clite.log.write('random device failure'); return false; } n.data.content = { read:function() { return Math.floor(Math.random() * (Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER + 1) + Number.MIN_SAFE_INTEGER).toString(); }, write:null }; n.mode = clite.lib.modestr('crw-rw-rw-'); n.data.isdev = true; } // /dev/initctl - writing to it sets the runlevel, reading from it returns the current runlevel { vfsapi.mkFile(0,'/dev/initctl'); let n = vfsapi.getNode(0,'/dev/initctl'); if (!n) { clite.log.write('initctl device failure'); return false; } n.data.content = { data:1, read:function() { return n.data.content.data.toString(); }, write:function(rl) { var rli = parseInt(rl); switch (rli) { case 0: clite.state.runlevel = rli; n.data.content.data = 0; clite.core.shutdown(); break; case 1: // essentially the booting runlevel clite.state.runlevel = rli; n.data.content.data = 1; break; case 3: // really just a status change, doesn't "do" anything clite.state.runlevel = rli; clite.init = null; n.data.content.data = 3; break; case 6: clite.state.runlevel = rli; n.data.content.data = 6; clite.core.reboot(); break; default:; } return false; } }; n.mode = clite.lib.modestr('crw-rw-rw-'); n.data.isdev = true; } // /dev/console - an interface for the system console - required by posix/sus, currently no op { vfsapi.mkFile(0,'/dev/console'); let n = vfsapi.getNode(0,'/dev/console'); if (!n) { clite.log.write('console device failure'); return false; } n.data.content = { read:clite.console.read, write:clite.console.write }; n.mode = clite.lib.modestr('crw-rw-rw-'); n.data.isdev = true; } // /dev/tty - virtual device that always acts like the controlling terminal { vfsapi.mkFile(0,'/dev/tty'); let n = vfsapi.getNode(0,'/dev/tty'); if (!n) { clite.log.write('tty failure'); return false; } n.data.content = { read:clite.tty.read, write:clite.tty.write }; n.mode = clite.lib.modestr('crw-rw-rw-'); n.data.isdev = true; n.data.istty = true; } // /dev/time - reading from it returns the current unix epoch time UTC { vfsapi.mkFile(0,'/dev/time'); let n = vfsapi.getNode(0,'/dev/time'); if (!n) { clite.log.write('time device failure'); return false; } n.data.content = { read:function() { return clite.time.msec().toString(); }, write:null }; n.mode = clite.lib.modestr('crw-rw-rw-'); n.data.isdev = true; } return true; } function defaultConfig() { // /etc/env contains the default environment variables { vfsapi.mkFile(0,'/etc/env'); let n = vfsapi.getNode(0,'/etc/env'); if (!n) { clite.log.write('environment failure'); return false; } n.data.content = ` PATH=/bin EDITOR=/bin/vi `; n.mode = clite.lib.modestr('-rw-r--r--'); } // /etc/passwd contains user account details { vfsapi.mkFile(0,'/etc/passwd'); let n = vfsapi.getNode(0,'/etc/passwd'); if (!n) { clite.log.write('access config failure'); return false; } n.data.content = ` root:$-7a1c0437:0:0:root:/root:/bin/sh guest:x:1:1:guest:/usr/home/guest:/bin/sh `; n.mode = clite.lib.modestr('-rw-------'); } // /etc/group contains group details { vfsapi.mkFile(0,'/etc/group'); let n = vfsapi.getNode(0,'/etc/group'); if (!n) { clite.log.write('group config failure'); return false; } n.data.content = ` root:x:0:root guest:x:1:guest `; n.mode = clite.lib.modestr('-rw-r--r--'); } // /etc/greeting is displayed by the shell after login { vfsapi.mkFile(0,'/etc/greeting'); let n = vfsapi.getNode(0,'/etc/greeting'); if (!n) { clite.log.write('config failure'); return false; } n.data.content =` _____ _ _____ _ / ____| | |_ _| | | | | | | | | |_ ___ | | | | | | | __/ _ \\ | |____| |____ _| |_| || __/ \\_____|______|_____|\\__\\___| `; n.mode = clite.lib.modestr('-rw-r--r--'); } // /etc/shrc shell startup file { vfsapi.mkFile(0,'/etc/shrc'); let n = vfsapi.getNode(0,'/etc/shrc'); if (!n) { clite.log.write('shell config failure'); return false; } n.data.content =`#!/bin/sh cat /etc/greeting `; n.mode = clite.lib.modestr('-rwxr-xr-x'); } } function loadCommands(nextfn) { clite.commands = { data:null, load:function(name,fn,suid,sgid) { vfsapi.mkFile(0,'/bin/'+name); var f = vfsapi.getNode(0,'/bin/'+name); if (!f) return; f.mode = clite.lib.modestr('-rwxr-xr-x'); if (typeof fn === 'string') { f.data.content = new Function('args','env','io',fn+';return main(args.length,args);'); }else{ f.data.content = fn; } if (typeof suid === 'boolean' && suid == true) f.mode |= clite.io.modes.S_ISUID; if (typeof sgid === 'boolean' && sgid == true) f.mode |= clite.io.modes.S_ISGID; vfsapi.mkFile(0,'/usr/src/'+name+'.js'); f = vfsapi.getNode(0,'/usr/src/'+name+'.js'); if (!f) return; f.mode = clite.lib.modestr('-rw-rw-r--'); if (typeof fn === 'string') { f.data.content = fn; }else{ f.data.content = fn.toString().substring(23); var e = f.data.content.lastIndexOf('return main('); if (e > -1) f.data.content = f.data.content.substring(0,e); } } } var index = 0; function cb() { if (clite.commands.data) clite.core.execSafe(clite.commands.data); clite.commands.data = null; if (index >= clite.includes.prog.length) { clite.commands = null; clite.core.execSafeAsync(nextfn); return; } var f = clite.includes.prog[index]; index++; clite.state.bios.core.load.script('clite/'+f,cb); } cb(); } function loadLibs(nextfn) { clite.libs = { index:[], data:null, load:function(name,header,fn) { vfsapi.mkFile(0,'/lib/'+name+'.so'); var f = vfsapi.getNode(0,'/lib/'+name+'.so'); if (!f) return; f.mode = clite.lib.modestr('-rw-r--r--'); f.data.content = fn; vfsapi.mkFile(0,'/usr/src/libs/'+name+'.js'); f = vfsapi.getNode(0,'/usr/src/libs/'+name+'.js'); if (!f) return; f.mode = clite.lib.modestr('-rw-rw-r--'); f.data.content = 'function init'+fn.toString().substring(8); clite.libs.index.push({header:header,file:name}); } } var index = 0; function cb() { if (clite.libs.data) clite.core.execSafe(clite.libs.data); clite.libs.data = null; if (index >= clite.includes.libs.length) { clite.libs.load = null; clite.core.execSafeAsync(nextfn); return; } var f = clite.includes.libs[index]; index++; clite.state.bios.core.load.script('clite/libs/'+f,cb); } cb(); } function init1() { clite.log.write('CLIte Version '+clite.state.version); // setup vfs clite.log.write('Setting up VFS'); clite.vfs.init(); vfsapi = clite.vfs.getApi(); clite.log.write('Setting up TTY'); clite.tty.init(vfsapi); clite.log.init(vfsapi); clite.log.write('Mounting wfs on /'); // mount core (root) filesystem using data/filesys.txt if (window.location.protocol == 'file:') { // work around the file: problems by just not loading files, here's a temporary filesystem var d = ` clite/core.js:/usr/clite/core.js:0:0:-rw-r--r-- clite/core.css:/usr/clite/web/core.css:0:0:-rw-r--r-- data/intro.txt:/usr/share/introduction:0:0:-rw-rw-r-- data/about.txt:/usr/share/site/about:0:0:-rw-rw-r-- readme.txt:/usr/clite/readme:0:0:-rw-r--r-- license.txt:/etc/license:0:0:-rw-r--r--`; init2(d); return; } clite.state.bios.core.load.file('data/filesys.txt',init2); } function init2(data) { if (data == null) { clite.log.write('No Filesystem Found'); return; } vfsapi.mkFile(0,'/dev/wfs'); var n = vfsapi.getNode(0,'/dev/wfs'); if (!n) { clite.log.write('wfs device failure'); return; } n.data.content = data; n.mode = clite.lib.modestr('cr--r--r--'); n.data.isdev = true; var fd = clite.io.open(0,'/dev/wfs',clite.io.flags.O_RDONLY|clite.io.flags.O_SYNC); var l; while ((l = clite.io.readLine(0,fd)) != null) { if (l.length <1 || l[0] == '#') continue; var parts = l.split(':'); if (parts.length != 5) continue; var url = parts[0]; var path = parts[1]; var uid = parseInt(parts[2]); var gid = parseInt(parts[3]); var perms = parts[4]; var fn = vfsapi.getNode(0,path); if (!fn) { if (perms[0] == 'd') { vfsapi.mkPath(0,path); }else{ var dir = clite.lib.dirname(path); vfsapi.mkPath(0,dir); vfsapi.mkFile(0,path); } fn = vfsapi.getNode(0,path); } if (!fn) continue; fn.mode = clite.lib.modestr(perms); fn.uid = uid; fn.gid = gid; fn.name = clite.lib.basename(path); fn.data.remote = url; fn.data.content = null; } clite.io.close(0,fd); // populate /etc (mostly for environment) clite.log.write('Populating /etc'); defaultConfig(); var b = clite.state.bios.io.read(2); if (b) { let list = b.getList(); // if we're restoring data, assume cookies were accepted previously if (list.length > 0) clite.state.cookiesAccepted = true; list.forEach(function(fn) { clite.vfs.restoreFile(fn); }); } // populate /dev (data/filesys.txt is /dev/wfs already) clite.log.write('Populating /dev'); defaultDevices(); // populate /bin clite.log.write('Populating /bin'); loadCommands(init3); } function init3() { clite.log.write('Populating /lib'); loadLibs(init4); } function init4() { clite.log.write('Bringing up the process manager'); if (!clite.proc.init(vfsapi)) { clite.log.write('failed'); return; } var fd = clite.io.open(0,'/dev/initctl',clite.io.flags.O_WRONLY); if (fd) { clite.io.write(0,fd,'3'); clite.io.close(0,fd); } // check cookies for login clite.log.write('Checking for user session'); clite.user.init(vfsapi); { // create the login environment // spawns /bin/login on pid 1, optionally with default guest login var env = clite.user.getEnv(0); var io = { pid:0, stdin:clite.io.open(0,'/dev/tty0',clite.io.flags.O_RDONLY), stdout:clite.io.open(0,'/dev/tty0',clite.io.flags.O_WRONLY), stderr:clite.io.open(0,'/dev/tty0',clite.io.flags.O_WRONLY), exit:null, // filled in by fork() include:null, // filled in by fork() }; var pid = clite.lib.fork(env,io,init5); } } function init5(env,io) { clite.tty.clear(0); var args = ['login']; // check for user-created account with password set if (!clite.user.hasAltLogin()) args.push('guest'); clite.lib.exec('/bin/login',args,env,io); } init1(); }); } }; clite.proc = { init:function(vfs) { var vfsapi = vfs; var data = { nextid:1, procs:[], groups:[] }; function getProc(pid) { var proc = null; data.procs.forEach(function(p,i) { if (proc) return; if (p.pid == pid) proc = p; }); return proc; } function getProcIndex(pid) { var index = -1; if (pid < 0) return data.procs.length-1; data.procs.forEach(function(p,i) { if (index > -1) return; if (p.pid == pid) index = i; }); return index; } function mapCPID(cpid) { //clite.log.write('mapCPID('+cpid+') -> '+data.groups.length); if (data.groups.length < 1) { addGroup(1); return 1; } var parent = getProc(cpid); if (parent && parent.gpid != cpid) return parent.gpid; return cpid; } function getGroup(cpid) { for (var i=0; i 0) return true; group.waitAll.forEach(function(w) { w(pid,ev); }); group.waitAll = []; return true; } clite.proc.add = function(cpid,fn) { if (typeof fn !== 'function') return 0; var proc = Object.create({ ruid:0, // the real user id - owner of the process rgid:0, // the real group id - owner of the process uid:0, // the (effective) user id this process is running as gid:0, // the (effective) group id this process is running as gpid:mapCPID(cpid), // the group pid (parent process id) of this process pid:data.nextid++, // the pid (process id) of this process ctty:0, // the id of the controlling tty for this process obj:fn, // the function this process executes waits:[] // array of wait() calls pending for this process on exit }); var parent = getProc(proc.gpid); if (parent) { proc.uid = parent.uid; proc.gid = parent.gid; proc.ruid = parent.ruid; proc.rgid = parent.rgid; proc.ctty = parent.ctty; } data.procs.push(proc); vfsapi.mkFile(0,'/proc/'+proc.pid); var n = vfsapi.getNode(0,'/proc/'+proc.pid); n.data.content = 'pid: '+proc.pid+'\n'; return proc.pid; } clite.proc.update = function(pid,cl,obj) { var proc = getProc(pid); if (!proc) return false; var n = vfsapi.getNode(0,'/proc/'+proc.pid); if (!n) return false; proc.obj = obj; n.data.content += 'command: '+cl+'\n'; return true; } clite.proc.exit = function(pid,ev) { var i = getProcIndex(pid); if (i<0) return false; var proc = getProc(pid); if (!proc) return false; data.procs.splice(i,1); vfsapi.remove(0,'/proc/'+pid); proc.waits.forEach(function(w) { clite.core.execSafeAsync(function() {w(pid,ev)}); }); exitGroup(proc.gpid,pid,ev); // special case, reboot if pid 1 exits if (pid == 1) { clite.core.execSafeAsync(function() { var fd = clite.io.open(0,'/dev/initctl',clite.io.flags.O_WRONLY); if (!fd) { clite.core.reboot(); return; } clite.io.write(0,fd,6); clite.io.close(0,fd); }); } return true; } clite.proc.count = function() { return data.procs.length; } clite.proc.addWait = function(pid,w) { var proc = getProc(pid); if (!proc) { clite.core.execSafeAsync(function() {w(pid)}); return false; } proc.waits.push(w); return true; } clite.proc.addWaitGroupAny = function(pid,w) { var cpid = mapCPID(pid); var group = getGroup(cpid); if (!group) { clite.core.execSafeAsync(function() {w(pid)}); return true; } group.waitAny.push(w); return true; } clite.proc.addWaitGroupAll = function(pid,w) { var cpid = mapCPID(pid); var group = getGroup(cpid); if (!group) { clite.core.execSafeAsync(function() {w(pid)}); return false; } group.waitAll.push(w); return true; } clite.proc.addGroup = function(pid) { var proc = getProc(pid); if (!proc) return false; if (proc.gpid == pid) return true; proc.gpid = pid; if (getGroup(pid)) return true; addGroup(pid); return true; } clite.proc.getUID = function(pid) { var proc = getProc(pid); if (!proc) return 0; return proc.uid; } clite.proc.getGID = function(pid) { var proc = getProc(pid); if (!proc) return 0; return proc.gid; } clite.proc.getRUID = function(pid) { var proc = getProc(pid); if (!proc) return 0; return proc.ruid; } clite.proc.getRGID = function(pid) { var proc = getProc(pid); if (!proc) return 0; return proc.rgid; } clite.proc.setUID = function(pid,uid,force) { var proc = getProc(pid); if (!proc) return false; if (!clite.user.getPWData(uid)) return false; if (!force && proc.uid != 0) return false; proc.uid = uid; return true; } clite.proc.setGID = function(pid,gid,force) { var proc = getProc(pid); if (!proc) return false; if (!clite.user.getGRData(gid)) return false; if (!force && gid == 0 && clite.proc.getUID(pid) != 0) return false; proc.gid = gid; return true; } clite.proc.setRUID = function(pid,uid,force) { var proc = getProc(pid); if (!proc) return false; if (!clite.user.getPWData(uid)) return false; if (!force && proc.uid != 0) return false; addGroup(pid); proc.gpid = pid; proc.ruid = uid; return true; } clite.proc.setRGID = function(pid,gid,force) { var proc = getProc(pid); if (!proc) return false; if (!clite.user.getGRData(gid)) return false; if (!force && gid == 0 && proc.uid != 0) return false; proc.rgid = gid; return true; } clite.proc.getTTY = function(pid) { var proc = getProc(pid); if (!proc) return -1; return proc.ctty; } clite.proc.setLogin = function(pid,uid) { var proc = getProc(pid); if (!proc) return false; proc.ruid = uid; proc.uid = uid; proc.rgid = clite.user.getGID(uid); proc.gid = proc.rgid; return true; } clite.proc.dump = function() { var r = ''; data.procs.forEach(function(p) { r += '{\n'; r += ' ruid:'+p.ruid+',\n'; r += ' rgid:'+p.rgid+',\n'; r += ' uid:'+p.uid+',\n'; r += ' gid:'+p.gid+',\n'; r += ' gpid:'+p.gpid+',\n'; r += ' pid:'+p.pid+',\n'; r += ' ctty:'+p.ctty+'\n'; r += '}\n'; }); return r; } clite.proc.init = null; return true; }, add:function(cl,fn) {return 0;}, update:function(cl) {return false;}, exit:function(pid) {}, count:function() {return 0;}, addWait:function() {return false;}, addWaitGroupAny:function() {return false;}, addWaitGroupAll:function() {return false;}, addGroup:function() {return false;}, getUID:function(pid) {return 0;}, getGID:function(pid) {return 0;}, getTTY:function(pid) {return 0;}, setLogin:function(pid,uid) {return false;} }; clite.user = { init:function(vfsapi) { // returns true if there's a user login or false for guest var data = { users:[{ name:'root', pass:'', uid:0, gid:0, groups:[], env:{ HOME:'/', USER:this.name, } }], groups:[{ name:'root', gid:0, users:[] }] }; var env = {}; function getNextUid() { let id = data.users.length; let accepted = false; while (!accepted) { accepted = true; for (var i=0; i -1) return true; return false; } clite.user.getGID = function(uid) { var udata = getUser(uid); if (!udata) return 0; return udata.gid; } clite.user.genGuest = function() { vfsapi.mkDir(0,'/usr/home/guest'); var n = vfsapi.getNode(0,'/usr/home/guest'); if (n) { n.mode = clite.lib.modestr('drwx------'); n.uid = 1; n.gid = 1; } vfsapi.mkLink(1,'/usr/home/guest/web','/usr/share/site'); vfsapi.mkFile(1,'/usr/home/guest/.shrc'); n = vfsapi.getNode(1,'/usr/home/guest/.shrc'); if (!n) return; n.mode = clite.lib.modestr('-rwx------'); n.uid = 1; n.gid = 1; n.data.content = `#!/bin/sh cat -l /usr/share/introduction `; } clite.user.getEnv = function(uid) { var udata = getUser(uid); if (!udata) return structuredClone(env); return structuredClone(udata.env); } clite.user.getPWData = function(uid) { var udata = getUser(uid); if (!udata) return null; return Object.create({ pw_name:udata.name, // User's login name. pw_uid:udata.uid, // Numerical user ID. pw_gid:udata.gid, // Numerical group ID. pw_dir:udata.env.HOME, // Initial working directory. pw_shell:udata.env.SHELL // Program to use as shell. }); } clite.user.getPWDataInd = function(i) { if (i >= data.user.length) return null; var udata = data.users[i]; return Object.create({ pw_name:udata.name, // User's login name. pw_uid:udata.uid, // Numerical user ID. pw_gid:udata.gid, // Numerical group ID. pw_dir:udata.env.HOME, // Initial working directory. pw_shell:udata.env.SHELL // Program to use as shell. }); } clite.user.removePW = function(pid,uid) { if (clite.proc.getRUID(pid) != 0) return false; loadUsers(); var ind = -1; for (var i=0; i -1) return false; var udata = {}; udata.name = pw.pw_name; udata.pass = 'x'; udata.uid = getNextUid(); udata.gid = pw.pw_gid; udata.groups = []; udata.env = {}; udata.env.HOME = pw.pw_dir; udata.env.SHELL = pw.pw_shell; udata.env.USER = udata.name; udata.env.PWD = udata.env.HOME; data.users.push(udata); saveUsers(); loadUsers(); return true; } var uid = clite.proc.getUID(pid); if (pw.pw_uid != uid && uid != 0) return false; var udata = getUser(pw.pw_uid); if (!udata) return false; udata.name = pw.pw_name; udata.uid = pw.pw_uid; udata.gid = pw.pw_gid; udata.env.HOME = pw.pw_dir; udata.env.SHELL = pw.pw_shell; udata.env.USER = udata.name; udata.env.PWD = udata.env.HOME; saveUsers(); loadUsers(); return true; } clite.user.getGRData = function(gid) { var gdata = getGroup(gid); if (!gdata) return null; return Object.create({ gr_name:gdata.name, // The name of the group. gr_gid:gdata.gid, // Numerical group ID. gr_mem:gdata.users // array of member names. }); } clite.user.getGRDataInd = function(i) { if (i >= data.groups.length) return null; var gdata = data.groups[i]; return Object.create({ gr_name:gdata.name, // The name of the group. gr_gid:gdata.gid, // Numerical group ID. gr_mem:gdata.users // array of member names. }); } clite.user.removeGR = function(pid,gid) { if (clite.proc.getRUID(pid) != 0) return false; loadUsers(); var ind = -1; for (var i=0; i -1) return false; var gdata = {}; gdata.name = gr.gr_name; gdata.gid = getNextGid(); gdata.users = Array.from(gr.gr_mem); data.groups.push(gdata); saveUsers(); loadUsers(); return true; } var uid = clite.proc.getUID(pid); if (!clite.user.checkGID(uid,gr.gr_gid) && uid != 0) return false; var gdata = getGroup(gr.gr_gid); if (!gdata) return false; gdata.name = gr.gr_name; gdata.gid = gr.gr_gid; gdata.users = Array.from(gr.gr_mem); saveUsers(); loadUsers(); return true; } clite.user.mapUserName = function(name) { for (var i=0; i= fd.node.data.content.length) return null; if (fd.node.data.isdir) { var e = fd.node.data.content[fd.pos++]; return e.name; } return fd.node.data.content[fd.pos++]; } clite.io.readLine = function(pid,fd,cb) { if (fd.node.data.content == null) return null; if (!fd.canread) return null; if (fd.node.data.istty) { try{ if (typeof cb === 'function') { if (fd.node.data.content.read.length == 2) { var ctty = clite.proc.getTTY(pid); fd.node.data.content.read(ctty,cb); }else{ fd.node.data.content.read(cb); } return true; } } catch(err) {} return false; }else if (fd.node.data.isdev) { if (typeof fd.node.data.content !== 'string') { try{ return fd.node.data.content.read(); } catch(err) { return null; } } } if (fd.pos >= fd.node.data.content.length) return null; if (typeof fd.node.data.content != 'string') return null; var e = fd.node.data.content.indexOf('\n',fd.pos); var l = ''; if (e < 0) { l = fd.node.data.content.substring(fd.pos); }else{ l = fd.node.data.content.substring(fd.pos,e); fd.pos++; } fd.pos += l.length; return l; } clite.io.readAll = function(pid,fd) { if (fd.node.data.istty) return false; if (fd.node.data.content == null) return null; if (!fd.canread) return null; if (fd.node.data.isdev) { if (typeof fd.node.data.content !== 'string') { try{ return fd.node.data.content.read(); } catch(err) { return null; } } } return fd.node.data.content; } clite.io.write = function(pid,fd,data) { if (!fd.canwrite) return false; if (fd.node.data.isdev) { if (typeof fd.node.data.content != 'string') { fd.node.time.modify = clite.time.sec(); try{ if (fd.node.data.content.write.length == 2) { var ctty = clite.proc.getTTY(pid); return fd.node.data.content.write(ctty,data); }else{ return fd.node.data.content.write(data); } } catch(err) { return false; } } } if (typeof data === 'function') { if (typeof fd.node.data.content !== 'string' && typeof fd.node.data.content !== 'function') return false; fd.node.data.content = data; fd.pos = 0; }else{ if (typeof fd.node.data.content != 'string') return false; if (typeof data != 'string') return false; if (fd.pos + data.length >= fd.node.data.content.length) { fd.node.data.content = fd.node.data.content.substring(0,fd.pos)+data; fd.pos = fd.node.data.content.length; }else{ var b = fd.node.data.content.substring(0,fd.pos); var e = fd.node.data.content.substring(fd.pos+data.length); fd.node.data.content = b+data+e; fd.pos += data.length; } } fd.node.time.modify = clite.time.sec(); clite.vfs.saveFile(fd.path); return true; } clite.io.ftruncate = function(pid,fd,len) { if (!fd.canwrite) return false; if (typeof fd.data.content != 'string') return false; fd.data.content = fd.data.content.substring(0,len); if (fd.pos > len) fd.pos = len; fd.node.time.modify = clite.time.sec(); clite.vfs.saveFile(fd.path); return true; } clite.io.truncate = function(pid,path,len) { var fd = getFileDes(pid,path,false,null); if (!fd) return false; var r = clite.io.ftruncate(pid,fd,len); close(fd); return r; } clite.io.seek = function(pid,fd,pos) { if (fd.node.data.content == null) return 0; if (fd.node.data.content.length < 1) return 0; if (pos<0) return fd.pos; fd.pos = pos; if (fd.pos >= fd.node.data.content.length) fd.pos = fd.node.data.content.length-1; return fd.pos; } clite.io.remove = function(pid,path) { var fd = getFileDes(pid,path,true,null); if (!fd || !fd.canwrite || fd.node.data.isdev) return false; var uid = clite.proc.getUID(pid); clite.vfs.saveFile(fd.path); var r = vfsapi.remove(uid,path); if (r) clite.vfs.saveFile(path); return r; } clite.io.link = function(pid,path,target) { if (!getFileDes(pid,target,false,null)) return false; var fd = getFileDes(pid,path,true,null); if (fd) { if (!fd.canwrite) return false; fd.node.data.content = target; clite.vfs.saveFile(path); return true; } var uid = clite.proc.getUID(pid); var r = vfsapi.mkLink(uid,path,target); if (r) clite.vfs.saveFile(path); return r; }, clite.io.fstatat = function(pid,fd,flags) { if (!fd || !fd.node || !fd.canread) return null; if (fd.node.islink && (flags&clite.io.flags.AT_SYMLINK_NOFOLLOW) == 0) fd = getFileDesPre(pid,n.node.data.content,true); if (!fd || !fd.node || !fd.canread) return null; var stat = Object.create({ name:fd.node.name, type:clite.lib.getFileType(fd), st_dev:0, st_ino:0, st_mode:fd.node.mode, st_nlink:0, st_uid:fd.node.uid, st_gid:fd.node.gid, st_size:0, st_atim:{ tv_sec:fd.node.time.access, tv_nsec:0 }, st_mtim:{ tv_sec:fd.node.time.modify, tv_nsec:0 }, st_ctim:{ tv_sec:fd.node.time.change, tv_nsec:0 } }); if (stat.type == clite.io.types.FT_TEXT || stat.type == clite.io.types.FT_SCRIPT) stat.st_size = fd.node.data.content.length; return stat; }, clite.io.fchmod = function(pid,fd,mode) { if (typeof mode != 'number') return false; var uid = clite.proc.getUID(pid); if (fd.node.uid != uid && uid != 0) return false; mode &= ~clite.io.modes.S_IFMT; mode |= (fd.node.mode&clite.io.modes.S_IFMT); fd.node.mode = mode; fd.node.time.change = clite.time.sec(); clite.vfs.saveFile(fd.path); return true; } clite.io.chmod = function(pid,path,mode) { var fd = getFileDesPre(pid,path,true); return clite.io.fchmod(pid,fd,mode); } clite.io.chown = function(pid,path,uid,gid) { var fd = getFileDesPre(pid,path,true); if (!fd) return false; var euid = clite.proc.getUID(pid); if (euid != 0) { if (fd.node.uid != euid) return false; if (uid > -1 && uid != euid) return false; var egid = clite.proc.getGID(pid); if (gid > -1 && gid != egid) return false; } if (uid > -1) fd.node.uid = uid; if (gid > -1) fd.node.gid = gid; clite.vfs.saveFile(path); return true; } clite.io.lchown = function(pid,path,uid,gid) { var fd = getFileDesPre(pid,path,false); if (!fd) return false; var euid = clite.proc.getUID(pid); if (euid != 0) { if (fd.node.uid != euid) return false; if (uid > -1 && uid != euid) return false; var egid = clite.proc.getGID(pid); if (gid > -1 && gid != egid) return false; } if (uid > -1) fd.node.uid = uid; if (gid > -1) fd.node.gid = gid; clite.vfs.saveFile(path); return true; } clite.io.isatty = function(pid,fd) { return fd.node.data.istty; } clite.io.mkdir = function(pid,path,mode) { if (typeof mode != 'number' || mode == 0) { mode = clite.io.modes.S_IRWXU|clite.io.modes.S_IRGRP|clite.io.modes.S_IXGRP; } mode &= ~clite.io.modes.S_IFMT; mode |= clite.io.modes.S_IFDIR; var uid = clite.proc.getUID(pid); var r = vfsapi.mkDir(uid,path); if (!r) return false; var n = vfsapi.getNode(uid,path); if (!n) return false; n.mode = mode; clite.vfs.saveFile(path); return true; } clite.io.vfprintf = function(pid,fd,fmt,args) { var str = ''; var parts = fmt.replace(/%%/g,'%').split('%'); var i = 0; var d = false; parts.forEach(function(p,k) { var fs = p; if (!k || d) { str += p; d = false; return; } if (p == '' && j -1) { var pp = fs.substring(0,ind); var pi = parseInt(pp); if (pp == pi.toString()) { param = pi; nparam = false; fs = fs.substring(ind+1); } } //clite.log.write('(param='+param+')'+fs); // TODO: support multiple flags var flag = false; if (fs[0] == '-' || fs[0] == '+' || fs[0] == ' ' || fs[0] == '0' || fs[0] == "'" || fs[0] == '#') { flag = fs[0]; fs = fs.substring(1); } //clite.log.write('(param='+param+')(flag='+flag+')'+fs); var width = 0; if (fs[0] == '*') { if (param >= args.length) param = args.length-1; width = parseInt(args[param]); param++; }else{ ind = -1; for (var j=0; j -1 && v < 10) { ind = j; }else{ break; } } if (ind > -1) { var pp = fs.substring(0,ind+1); var pi = parseInt(pp); if (pi.toString() == pp) { width = pi; fs = fs.substring(ind+1); } } } //clite.log.write('(param='+param+')(flag='+flag+')(width='+width+')'+fs); var precision = 0; if (fs[0] == '.') { fs = fs.substring(1); ind = -1; for (var j=0; j -1 && v < 10) { ind = j; }else{ break; } } if (ind > -1) { var pp = fs.substring(0,ind+1); var pi = parseInt(pp); if (pp.toString() == pp) { precision = pi; fs = fs.substring(ind+1); } } } // ignore length if (fs.substring(0,2) == 'hh' || fs.substring(0,2) == 'll') { fs = fs.substring(2); }else if (['h','l','L','z','j','t'].indexOf(fs[0]) > -1) { fs = fs.substring(1); } if (param >= args.length) return false; //clite.log.write('(param='+param+')(flag='+flag+')(width='+width+')'+fs); var type = fs[0]; fs = fs.substring(1); //clite.log.write('(param='+param+')(flag='+flag+')(width='+width+')(type='+type+')'+fs); switch (type) { case 'd': case 'i': // signed int var val = parseInt(args[param],10); if (typeof flag === 'string' && val > 0) { if (flag == '+') str += '+'; if (flag == ' ') str += ' '; } if (width > 0) { var c = ' '; if (flag == '0') c = '0'; if (flag == '-') { str += val.toFixed(0).padEnd(width,c); }else{ str += val.toFixed(0).padStart(width,c); } }else{ str += val.toFixed(0); } break; case 'u': // unsigned int var sval = args[param].toString(10); if (sval[0] == '-') sval = sval.substring(1); var val = parseInt(sval,10); if (width > 0) { var c = ' '; if (flag == '0') c = '0'; if (flag == '-') { str += val.toFixed(0).padEnd(width,c); }else{ str += val.toFixed(0).padStart(width,c); } }else{ str += val.toFixed(0); } break; case 'f': // float var val = parseFloat(args[param].toString(10)); if (isNaN(val)) { str += 'nan'; }else if (!Number.isFinite(val)) { str += 'inf'; }else{ if (precision == 0) { var s = String(val).split('.'); if (s.length > 1) precision = s[1].length; } var s = val.toFixed(precision).toLowerCase(); if (precision == 0) s += '.'; if (typeof flag === 'string' && val > 0) { if (flag == '+') s = '+'+s; if (flag == ' ') { clite.log.write('pad space'); s = ' '+s; } } if (width > 0) { var c = ' '; if (flag == '0') c = '0'; if (flag == '-') { s = s.padEnd(width,c); }else{ s = s.padStart(width,c); } } str += s; } break; case 'F': // FLOAT var val = parseFloat(args[param].toString(10)); if (isNaN(val)) { str += 'NAN'; }else if (!Number.isFinite(val)) { str += 'INF'; }else{ if (precision == 0) { var s = String(val).split('.'); if (s.length > 1) precision = s[1].length; } var s = val.toFixed(precision).toUpperCase(); if (precision == 0) s += '.'; if (typeof flag === 'string' && val > 0) { if (flag == '+') s = '+'+s; if (flag == ' ') s = ' '+s; } if (width > 0) { var c = ' '; if (flag == '0') c = '0'; if (flag == '-') { s = s.padEnd(width,c); }else{ s = s.padStart(width,c); } } str += s; } break; case 'e': // float with exponent var val = parseFloat(args[param].toString(10)); if (isNaN(val)) { str += 'NAN'; }else if (!Number.isFinite(val)) { str += 'INF'; }else{ str += val.toExponential().toLowerCase(); } break; case 'E': // FLOAT with exponent var val = parseFloat(args[param].toString(10)); if (isNaN(val)) { str += 'NAN'; }else if (!Number.isFinite(val)) { str += 'INF'; }else{ str += val.toExponential().toUpperCase(); } break; case 'g': // either float or float with exponent var val = parseFloat(args[param].toString(10)); if (isNaN(val)) { str += 'nan'; }else if (!Number.isFinite(val)) { str += 'inf'; }else{ str += val.toString(10).toLowerCase(); } break; case 'G': // either FLOAT or FLOAT with exponent var val = parseFloat(args[param].toString(10)); if (isNaN(val)) { str += 'NAN'; }else if (!Number.isFinite(val)) { str += 'INF'; }else{ str += val.toString(10).toUpperCase(); } break; case 'x': // hexadecimal var val = parseInt(args[param]); var s = ''; if (flag == '#') s += '0x'; s += val.toString(16).toLowerCase(); if (width > 0) { if (flag == '-') { s = s.padEnd(width,' '); }else{ s = s.padStart(width,' '); } } str += s; break; case 'X': // HEXADECIMAL var val = parseInt(args[param]); var s = ''; if (flag == '#') s += '0X'; s += val.toString(16).toUpperCase(); if (width > 0) { if (flag == '-') { s = s.padEnd(width,' '); }else{ s = s.padStart(width,' '); } } str += s; break; case 'o': // octal var val = parseInt(args[param],10); var s = ''; if (flag == '#') s += '0'; s += val.toString(8); if (width > 0) { if (flag == '-') { s = s.padEnd(width,' '); }else{ s = s.padStart(width,' '); } } str += s; break; case 's': // string var s = args[param].toString(); if (width > 0) { if (flag == '-') { s = s.padEnd(width,' '); }else{ s = s.padStart(width,' '); } } if (precision > 0) s = s.substring(0,precision); str += s; break; case 'c': // character if (width > 0) { if (flag == '-') { str += args[param].toString()[0].padEnd(width,' '); }else{ str += args[param].toString()[0].padStart(width,' '); } }else{ str += args[param].toString()[0]; } break; case 'p': // pointer (NULL) if (width > 0) { if (flag == '-') { str += 'NULL'.padEnd(width,' '); }else{ str += 'NULL'.padStart(width,' '); } }else{ str += 'NULL'; } break; case 'a': // hexfloat var val = parseFloat(args[param]); str += val.toString(16).toLowerCase(); break; case 'A': // HEXFLOAT var val = parseFloat(args[param]); str += val.toString(16).toUpperCase(); break; case 'n': // number of chars written so far returned args[param].value = str.length; break; default:; } if (nparam) i = param+1; str += fs; }) str = str.replace(/%/g,'%'); return clite.io.write(pid,fd,str); } }, // 0: unknown // 1: text file // 2: executable/binary (function) // 3: directory // 4: link // 5: device // 6: unloaded remote file // 7: shell script // 8: image // 9: shared object (library) types:{ FT_UNKNOWN:0, FT_TEXT:1, FT_BINARY:2, FT_DIR:3, FT_LINK:4, FT_DEV:5, FT_REMOTE:6, FT_SCRIPT:7, FT_IMAGE:8, FT_LIBRARY:9 }, flags:{ // at least one of these O_EXEC: parseInt('0x010000',16), // open for executing only, no op on directories O_RDONLY: parseInt('0x020000',16), // open for reading only O_RDWR: parseInt('0x060000',16), // open for reading and writing O_SEARCH: parseInt('0x080000',16), // open for directory search only, no op on non-directories O_WRONLY: parseInt('0x040000',16), // open for writing only // any of these O_APPEND: parseInt('0x000001',16), // append to the file, sets pos to content.length, requires O_RDONLY or O_RDWR O_CLOEXEC: parseInt('0x000002',16), // close on exec O_CREAT: parseInt('0x000004',16), // create if file does not exist O_DIRECTORY: parseInt('0x000008',16), // only open if directory O_EXCL: parseInt('0x000010',16), // if O_CREAT is set, fail if file exists O_NOCTTY: parseInt('0x0',16), // no op O_NOFOLLOW: parseInt('0x000020',16), // if file is a symlink, don't follow it AT_SYMLINK_NOFOLLOW: parseInt('0x000020',16), // if file is a symlink, don't follow it O_NONBLOCK: parseInt('0x000040',16), // no callbacks, return immediately O_SYNC: parseInt('0x000080',16), // write immediately (may fail on remote data) O_TRUNC: parseInt('0x000100',16), // set file size to 0 before writing, requires O_RDWR or O_WRONLY O_TTY_INIT: parseInt('0x0',16) // no op }, modes:{ // permissions S_IRWXU: parseInt('00700',8), // Read, write, execute/search by owner. S_IRUSR: parseInt('00400',8), // Read permission, owner. S_IWUSR: parseInt('00200',8), // Write permission, owner. S_IXUSR: parseInt('00100',8), // Execute/search permission, owner. S_IRWXG: parseInt('00070',8), // Read, write, execute/search by group. S_IRGRP: parseInt('00040',8), // Read permission, group. S_IWGRP: parseInt('00020',8), // Write permission, group. S_IXGRP: parseInt('00010',8), // Execute/search permission, group. S_IRWXO: parseInt('00007',8), // Read, write, execute/search by others. S_IROTH: parseInt('00004',8), // Read permission, others. S_IWOTH: parseInt('00002',8), // Write permission, others. S_IXOTH: parseInt('00001',8), // Execute/search permission, others. S_ISUID: parseInt('04000',8), // Set-user-ID on execution. S_ISGID: parseInt('02000',8), // Set-group-ID on execution. S_ISVTX: parseInt('01000',8), // On directories, restricted deletion flag. // file types S_IFMT: parseInt('110000',8), // format (file type) mask S_IFBLK: parseInt('010000',8), // Block special S_IFCHR: parseInt('020000',8), // Character special S_IFIFO: parseInt('040000',8), // FIFO special S_IFREG: parseInt('000000',8), // Regular file S_IFDIR: parseInt('100000',8), // Directory S_IFLNK: parseInt('200000',8), // Symbolic link S_IFSOCK: parseInt('400000',8), // Socket }, creat:null, open:null, close:null, read:null, readLine:null, readAll:null, write:null, ftruncate:null, truncate:null, seek:null, remove:null, link:null, stat:null, fstat:null, chmod:null, fchmod:null, isatty:null, mkdir:null, vfprintf:null }; clite.vfs = { isinit:false, init:function() { var vfsdata = { fs:{}, api:{} }; function findNodeChild(node,name) { for (var i=0; i 0) { if (!checkCanOpen(uid,n)) return null; if (n.data.islink) { n = vfsdata.api.getNode(uid,n.data.content); } if (!n.data.isdir) return null; var p = parts.shift(); if (!p || p == '') continue; var nn = findNodeChild(n,p); if (!nn) return null; n = nn; } if (!checkCanOpen(uid,n)) return null; n.time.access = clite.time.sec(); return n; } vfsdata.api.getFile = function(uid,path) { var n = vfsdata.api.getNode(uid,path); if (!n) return null; if (n.data.islink) // get the actual file data, not the link data return vfsdata.api.getFile(uid,n.data.content); return n.data.content; } vfsdata.api.mkDir = function(uid,path) { if (vfsdata.api.getNode(0,path) != null) return false; var parts = path.split('/'); var name = parts.pop(); var dir = parts.join('/'); if (dir.length < 1) dir = '/'; var parent = vfsdata.api.getNode(uid,dir); if (!parent || !parent.data.isdir) return false; if (!checkCanWrite(uid,parent)) return false; var n = mkNode(); n.name = name; n.data.parent = parent; n.data.content = []; n.data.isdir = true; n.mode = clite.lib.modestr('drwxr-xr-x'); n.uid = uid; n.gid = clite.user.getGID(uid); parent.data.content.push(n); parent.time.modify = clite.time.sec(); return true; } vfsdata.api.mkFile = function(uid,path) { if (vfsdata.api.getNode(uid,path) != null) return false; var parts = path.split('/'); var name = parts.pop(); var dir = parts.join('/'); if (dir.length < 1) dir = '/'; var parent = vfsdata.api.getNode(0,dir); if (!parent || !parent.data.isdir) return false; if (!checkCanWrite(uid,parent)) return false; var n = mkNode(); n.name = name; n.data.parent = parent; n.data.content = ''; n.mode = clite.lib.modestr('-rw-r--r--'); n.uid = uid; n.gid = clite.user.getGID(uid); parent.data.content.push(n); parent.time.modify = clite.time.sec(); return true; } vfsdata.api.mkLink = function(uid,path,target) { if (vfsdata.api.getNode(uid,path) != null) return false; var parts = path.split('/'); var name = parts.pop(); var dir = parts.join('/'); if (dir.length < 1) dir = '/'; var parent = vfsdata.api.getNode(uid,dir); if (!parent || !parent.data.isdir) return false; if (!checkCanWrite(uid,parent)) return false; var n = mkNode(); n.name = name; n.data.parent = parent; n.data.islink = true; n.data.content = target; n.mode = clite.lib.modestr('lrw-r--r--'); n.uid = uid; n.gid = clite.user.getGID(uid); parent.data.content.push(n); parent.time.modify = clite.time.sec(); return true; } vfsdata.api.mkPath = function(uid,path) { var parts = path.split('/'); var p = ''; parts.forEach(function(part) { if (part == '') return; p += '/'+part; vfsdata.api.mkDir(uid,p); }); if (vfsdata.api.getNode(uid,path)) return true; return false; } vfsdata.api.remove = function(uid,path) { var n = vfsdata.api.getNode(uid,path); if (!n || (n.data.isdir && n.data.content.length > 0)) return false; if (!checkCanWrite(uid,n)) return false; var dir = clite.lib.dirname(path); var parent = vfsdata.api.getNode(0,dir); if (!parent) return false; if (!checkCanWrite(uid,parent)) return false; var c = []; parent.data.content.forEach(function(nn) { if (nn == n) return; c.push(nn); }); parent.data.content = c; return true; } clite.vfs.getSavedFile = function(path) { let n = vfsdata.api.getNode(0,path); if (!n) return null; var str = n.uid.toString(10)+':'+n.gid.toString(10)+':'; str += n.mode.toString(10)+':'+n.time.access.toString(10)+':'; str += n.time.modify.toString(10)+':'+n.time.change.toString(10)+':'; let t = clite.lib.getFileType({node:n}); str += t.toString(10)+':'; if (typeof n.data.content === 'string' && (t == clite.io.types.FT_LINK || t == clite.io.types.FT_SCRIPT || t == clite.io.types.FT_TEXT)) str += n.data.content.length.toString(10)+':'+n.data.content; return str } clite.vfs.setSavedFile = function(path,data) { let ind = data.indexOf(':'); if (ind < 0) return false; let v = data.substring(0,ind); let str = data.substring(ind+1); let uid = parseInt(v,10); ind = str.indexOf(':'); if (ind < 0) return false; v = str.substring(0,ind); str = str.substring(ind+1); let gid = parseInt(v,10); ind = str.indexOf(':'); if (ind < 0) return false; v = str.substring(0,ind); str = str.substring(ind+1); let mode = parseInt(v,10) ind = str.indexOf(':'); if (ind < 0) return false; v = str.substring(0,ind); str = str.substring(ind+1); let access = parseInt(v,10); ind = str.indexOf(':'); if (ind < 0) return false; v = str.substring(0,ind); str = str.substring(ind+1); let modify = parseInt(v,10); ind = str.indexOf(':'); if (ind < 0) return false; v = str.substring(0,ind); str = str.substring(ind+1); let change = parseInt(v,10); ind = str.indexOf(':'); if (ind < 0) return false; v = str.substring(0,ind); str = str.substring(ind+1); let type = parseInt(v,10); let size = 0; let content = null; ind = str.indexOf(':'); if (ind > -1) { v = str.substring(0,ind); content = str.substring(ind+1); size = parseInt(v,10); } let n = vfsdata.api.getNode(0,path); if (!n) { var dir = clite.lib.dirname(path); vfsdata.api.mkPath(0,dir); switch (type) { case clite.io.types.FT_DIR: vfsdata.api.mkDir(0,path); break; case clite.io.types.FT_LINK: vfsdata.api.mkLink(0,path,content); break; case clite.io.types.FT_SCRIPT: case clite.io.types.FT_TEXT: vfsdata.api.mkFile(0,path); break; default: return false; } n = vfsdata.api.getNode(0,path); if (!n) return false; } let ntype = clite.lib.getFileType({node:n}); if (ntype != type) { if (!( (ntype == clite.io.types.FT_SCRIPT || ntype == clite.io.types.FT_TEXT) && (type == clite.io.types.FT_SCRIPT || type == clite.io.types.FT_TEXT) )) return false; } n.uid = uid; n.gid = gid; n.mode = mode; n.time.access = access; n.time.modify = modify; n.time.change = change; if (content != null) n.data.content = content; return true; } clite.vfs.saveFile = function(path) { if (!clite.state.cookiesAccepted) return true; var b = clite.state.bios.io.read(2); if (!b) return false; var str = clite.vfs.getSavedFile(path); if (!str) { b.delFile(path); return true; } b.addFile(path,str); return true; } clite.vfs.restoreFile = function(path) { var b = clite.state.bios.io.read(2); if (!b) return false; var d = b.getFile(path); if (!d) return false; return clite.vfs.setSavedFile(path,d); } // make some directories vfsdata.api.mkDir(0,'/bin'); vfsdata.api.mkDir(0,'/dev'); vfsdata.api.mkDir(0,'/etc'); vfsdata.api.mkDir(0,'/lib'); vfsdata.api.mkDir(0,'/proc'); vfsdata.api.mkDir(0,'/root'); vfsdata.api.mkDir(0,'/tmp'); vfsdata.api.mkDir(0,'/usr'); vfsdata.api.mkDir(0,'/usr/clite'); vfsdata.api.mkDir(0,'/usr/clite/web'); vfsdata.api.mkDir(0,'/usr/home'); vfsdata.api.mkDir(0,'/usr/share'); vfsdata.api.mkDir(0,'/usr/share/docs'); vfsdata.api.mkDir(0,'/usr/share/site'); vfsdata.api.mkDir(0,'/usr/src'); vfsdata.api.mkDir(0,'/usr/src/libs'); vfsdata.api.mkDir(0,'/var'); vfsdata.api.mkFile(0,'/var/logs'); var n = vfsdata.api.getNode(0,'/root'); if (n) n.mode = clite.lib.modestr('drwx------'); n = vfsdata.api.getNode(0,'/tmp'); if (n) n.mode = clite.lib.modestr('drwxrwxrwt'); clite.io.init(); vfsdata.api.isinit = true; clite.core.execSafeAsync(function() {clite.vfs.init = null;}); }, getSavedFile:function(path) { return null; }, setSavedFile:function(path,data) { return false; }, getApi:null }; clite.log = { logs:[], init:function(vfsapi) { clite.log.write = function(txt) { if (clite.state.runlevel != 3) { try { clite.console.write(txt+'\n'); } catch(e) {} } var n = vfsapi.getNode(0,'/var/logs'); if (n) n.data.content += txt+'\n'; console.log(txt); } var n = vfsapi.getNode(0,'/var/logs'); if (n) n.data.content = clite.log.logs.join('\n')+'\n'; clite.core.execSafeAsync(function(){clite.log.init = null;}); }, write:function(txt) { clite.log.logs.push(txt); try { clite.console.write(txt+'\n'); } catch(e) {} console.log(txt); } }; clite.tty = { data:{ active:null, activeID:-1, ttys:[], control:false }, internal:{ genTTY:function() {return null;}, write:function(id,ch) { var cc = ch.charCodeAt(0); var updateLine = false; var updateAll = false; var t = clite.tty.data.ttys[id]; var updateX = t.x; var updateY = t.y; switch (cc) { case 8: //BS t.x--; updateLine = true; break; case 10: //LF t.y++; break; case 13: //CR t.x = 0; updateLine = true; break; default: t.data[updateY][updateX].ch = ch; t.x++; } if (t.x < 0) { t.y--; t.x = 79; } if (t.y < 0) t.y = 0; if (t.x > 79) { t.y++; t.x = 0; } // theoretically this should be fine, in practice it may not if (t.y > 24) { var l = t.data.shift() for (var i=0; i<80; i++) { l[i].ch = ' '; l[i].fg = t.fg; l[i].bg = t.bg; } t.y = 24; t.data.push(l); updateAll = true; } if (clite.tty.data.activeID != id) return; if (updateAll) { clite.console.drawBuff(t.data); }else if (updateLine) { clite.console.drawLine(updateY,t.data[updateY]); }else{ clite.console.drawAt(updateX,updateY,t.data[updateY][updateX]); } clite.console.setCursor(t.x,t.y); }, popLine:function(t) { var off = -1; var l = ''; for (var i=0; i 126) { var k = t.buff.splice(i,1); return k.code; } l += t.buff[i].char; } if (off < 0) return null; t.buff = t.buff.slice(off); return l; }, getControl:function(ch) { var key = { down:false, char:0, lchar:0, code:0 }; var ind = '-abcdefghijklmnopqrstuvwxyz[\\]^_?'; var i = ind.indexOf(ch); if (i>0) { key.char = String.fromCharCode(i); key.lchar = key.char; key.code = i; return key; } return null; } }, init:function(vfsapi) { clite.tty.internal.genTTY = function() { var l = []; for (var y=0; y<25; y++) { l[y] = []; for (var x=0; x<80; x++) { l[y].push({bg:0,fg:15,bk:false,ch:' '}); } } var id = clite.tty.data.ttys.length; var t = { id:id, x:0, y:0, fg:15, bg:0, data:l, rcb:null, buff:[], raw:false, echo:true }; clite.tty.data.ttys.push(t); vfsapi.mkFile(0,'/dev/tty'+id); let n = vfsapi.getNode(0,'/dev/tty'+id); if (!n) { clite.log.write('tty creation failure (/dev/tty'+id+')'); return null; } n.data.content = { ttyid:id, read:function(cb) {return clite.tty.read(n.data.content.ttyid,cb);}, write:function(data) {return clite.tty.write(n.data.content.ttyid,data);} }; n.mode = clite.lib.modestr('crw-rw-rw-'); n.data.isdev = true; n.data.istty = true; clite.tty.data.ttys.push(t); return t; } clite.tty.data.active = clite.tty.internal.genTTY(); if (!clite.tty.data.active) return false; clite.tty.data.activeID = clite.tty.data.active.id; return true; }, input:function(key) { if (key.down) { if (key.code == -1) clite.tty.data.control = true; return; } if (key.code < 0) { if (key.code == -1) { clite.tty.data.control = false; return; }else{ if (clite.tty.data.active && typeof clite.tty.data.active.rcb === 'function') { var f = clite.tty.data.active.rcb; clite.tty.data.active.rcb = null; f(key.code); return; } } } if (clite.tty.data.control) { var k = clite.tty.internal.getControl(key.char); if (k) key = k; } if (!clite.tty.data.active) return; var t = clite.tty.data.active; // TODO: this hides control characters from echoing... do we want that? if (t.echo && ((key.code > 31 && key.code < 127) || key.code == 10 || key.code == 9)) clite.tty.write(t.id,key.char); if (t.raw) { if (typeof t.rcb === 'function') { var f = t.rcb; t.rcb = null; if ((key.code > 31 && key.code < 127) || key.code == 10 || key.code == 9) { f(key.char); }else{ f(key.code); } }else{ t.buff.push(key); } return; } // Backspace if (key.code == 8) { if (t.buff.pop() && t.echo) { clite.tty.write(t.id,String.fromCharCode(8)); clite.tty.write(t.id,' '); clite.tty.write(t.id,String.fromCharCode(8)); } return; } t.buff.push(key); if (typeof t.rcb !== 'function') return; if (key.code != 10) return; var l = clite.tty.internal.popLine(t); if (l == null) return; var f = t.rcb; t.rcb = null; f(l); }, fake:function(str) { for (var i=0; i=clite.tty.data.ttys.length) return false; var t = clite.tty.data.ttys[id]; if (t.raw) { if (t.buff.length > 0) { var k = t.buff.shift(); cb(k.char); return true; } }else{ var l = clite.tty.internal.popLine(t); if (l != null) { cb(l); return true; } } clite.tty.data.ttys[id].rcb = cb; return true; }, write:function(id,str) { if (id<0 || id>=clite.tty.data.ttys.length) return; for (var i=0; i 126) { switch (cc) { case 8: clite.tty.internal.write(id,String.fromCharCode(8)); break; case 9: { var cr = clite.tty.getCursor(id); if (Array.isArray(cr)) { var m = 4-(cr[0]%4); for (var j=0; j=clite.tty.data.ttys.length) return; var t = clite.tty.data.ttys[id]; for (var y=0; y<25; y++) { t.data[y] = []; for (var x=0; x<80; x++) { t.data[y].push({bg:t.bg,fg:t.fg,bk:false,ch:' '}); } } t.x = 0; t.y = 0; if (id != clite.tty.data.activeID) return; clite.console.drawBuff(t.data); clite.console.setCursor(0,0); }, ttyctrl:function(id,fn,v) { if (id<0 || id>=clite.tty.data.ttys.length) return; var t = clite.tty.data.ttys[id]; switch (fn) { case 'raw': if (typeof v === 'boolean') t.raw = v; return t.raw; break; case 'atton': break; case 'attoff': break; case 'echo': if (typeof v === 'boolean') t.echo = v; return t.echo; break; case 'cols': return t.data[0].length; break; case 'rows': return t.data.length; break; case 'sane': t.raw = false; t.echo = true; return true; break; default: return false; } }, getCursor:function(id) { if (id<0 || id>=clite.tty.data.ttys.length) return; var t = clite.tty.data.ttys[id]; return [t.x,t.y]; }, setCursor:function(id,x,y) { if (id<0 || id>=clite.tty.data.ttys.length) return; var t = clite.tty.data.ttys[id]; if (x > -1 && x < 80) t.x = x; if (y > -1 && y < 25) t.y = y; if (id == clite.tty.data.activeID) clite.console.setCursor(x,y); } }; clite.console = { data:{ vgaio:null, state:0, size:[80,25], cursor:[0,0] }, init:function() { // set graphics to text mode, and get the VGA api clite.state.bios.io.write(1,1); clite.console.data.vgaio = clite.state.bios.io.read(1); // set a keyboard handler, then flush the buffer clite.state.bios.io.write(2,clite.tty.input); clite.state.bios.io.write(2); clite.console.data.state = 1; clite.console.clear(); }, clear:function() { if (clite.console.data.state == 1) { for (var y=0; y<25; y++) { for (var x=0; x<80; x++) { clite.console.data.vgaio.write(x,y,' '); clite.console.data.vgaio.writeSet(0,x,y,0); clite.console.data.vgaio.writeSet(1,x,y,15); clite.console.data.vgaio.writeSet(2,x,y,false); } } } }, drawBuff:function(buff) { if (clite.console.data.state == 1) { for (var y=0; y<25; y++) { for (var x=0; x<80; x++) { clite.console.data.vgaio.write(x,y,buff[y][x].ch); clite.console.data.vgaio.writeSet(0,x,y,buff[y][x].bg); clite.console.data.vgaio.writeSet(1,x,y,buff[y][x].fg); clite.console.data.vgaio.writeSet(2,x,y,buff[y][x].bk); } } } }, drawLine:function(y,buff) { if (clite.console.data.state == 1) { for (var x=0; x<80; x++) { clite.console.data.vgaio.write(x,y,buff[x].ch); clite.console.data.vgaio.writeSet(0,x,y,buff[x].bg); clite.console.data.vgaio.writeSet(1,x,y,buff[x].fg); clite.console.data.vgaio.writeSet(2,x,y,buff[x].bk); } } }, drawAt:function(x,y,data) { if (clite.console.data.state == 1) { if (x<0 || x>79 || y<0 || y>24) return; clite.console.data.vgaio.write(x,y,data.ch); clite.console.data.vgaio.writeSet(0,x,y,data.bg); clite.console.data.vgaio.writeSet(1,x,y,data.fg); clite.console.data.vgaio.writeSet(2,x,y,data.bk); } }, moveCursor:function() { clite.console.data.vgaio.writeSet(3,clite.console.data.cursor[0],clite.console.data.cursor[1],true); }, setCursor:function(x,y) { clite.console.data.cursor[0] = x; clite.console.data.cursor[1] = y; clite.console.moveCursor(); }, read:function() { }, write:function(str) { var d = { ch:'', bg:0, fg:15, bk:false }; clite.console.moveCursor(); var x = clite.console.data.cursor[0]; var y = clite.console.data.cursor[1]; for (var i=0; i= 80) { x = 0; y++; } d.ch = str[i]; clite.console.drawAt(x,y,d); x++; } clite.console.setCursor(x,y); } } clite.lib = { // makes text html safe htmlEncode:function(txt) { var rtxt = txt.replace(/&/g, '&').replace(/>/g, '>').replace(/ 0 && rtxt[rtxt.length-1] == ' ') return rtxt.substring(0,rtxt.length-1)+' '; return rtxt; }, // returns the file name of a path /tmp/test/foo = foo basename:function(txt) { var parts = txt.split('/'); return parts[parts.length-1]; }, // returns the directory name of a path /tmp/test/foo = /tmp/test dirname:function(txt) { var parts = txt.split('/'); parts.pop(); return parts.join('/'); }, // returns an int identifier for the file type getFileType:function(fd) { if (fd.node.data.content == null) { if (fd.node.data.remote != null) return clite.io.types.FT_REMOTE; return clite.io.types.FT_UNKNOWN; } if (fd.node.data.islink) return clite.io.types.FT_LINK; if (fd.node.data.isdir) return clite.io.types.FT_DIR; if (fd.node.data.isdev) return clite.io.types.FT_DEV; const type = typeof fd.node.data.content; switch (type) { case 'string': // text file if (fd.node.data.content.substring(0,2) == '#!') return clite.io.types.FT_SCRIPT; return clite.io.types.FT_TEXT; break; case 'function': // executable if (fd.node.data.content.length == 2) { return clite.io.types.FT_LIBRARY; }else if (fd.node.data.content.length == 3) { return clite.io.types.FT_BINARY; }else{ return clite.io.types.FT_UNKNOWN; } break; case 'object': if (Array.isArray(fd.node.data.content)) // directory return clite.io.types.FT_DIR; if (fd.node.data.content instanceof HTMLImageElement) return clite.io.types.FT_IMAGE; case 'symbol': case 'boolean': case 'number': case 'bigint': case 'undefined': default: return clite.io.types.FT_UNKNOWN; } return clite.io.types.FT_UNKNOWN; }, checkNodeReadable:function(node,pid) { var uid = clite.proc.getUID(pid); if (uid == 0) return true; if ((node.mode&clite.io.modes.S_IROTH) == clite.io.modes.S_IROTH) return true; if (clite.user.checkGID(uid,node.gid) && (node.mode&clite.io.modes.S_IRGRP) == clite.io.modes.S_IRGRP) return true; if (node.uid == uid && (node.mode&clite.io.modes.S_IRUSR) == clite.io.modes.S_IRUSR) return true; return false; }, checkNodeWritable:function(node,pid) { var uid = clite.proc.getUID(pid); if (uid == 0) return true; if (node.uid == uid && (node.mode&clite.io.modes.S_IWUSR) == clite.io.modes.S_IWUSR) return true; // check for directory sticky bit on parent if (node.parent && (node.parent.mode&clite.io.modes.S_ISVTX) == clite.io.modes.S_ISVTX && node.uid != uid) return false; if ((node.mode&clite.io.modes.S_IWOTH) == clite.io.modes.S_IWOTH) return true; if (clite.user.checkGID(uid,node.gid) && (node.mode&clite.io.modes.S_IWGRP) == clite.io.modes.S_IWGRP) return true; return false; }, checkNodeExecutable:function(node,pid) { var uid = clite.proc.getUID(pid); if (uid == 0) return true; if ((node.mode&clite.io.modes.S_IXOTH) == clite.io.modes.S_IXOTH) return true; if (clite.user.checkGID(uid,node.gid) && (node.mode&clite.io.modes.S_IXGRP) == clite.io.modes.S_IXGRP) return true; if (node.uid == uid && (node.mode&clite.io.modes.S_IXUSR) == clite.io.modes.S_IXUSR) return true; return false; }, // really basic hashing function hash:function(str) { let hash = 0; if (str.length == 0) return hash; for (var i=0; i -rwxrwxrwx) strmode:function(mode) { var str = mode.toString(8); var t = '0'; var s = '0'; var su = false; var sg = false; var sd = false; var p = ''; var perms = ['---','---','---']; str = str.padStart(3,'0'); if (str.length > 4) { t = str.substring(0,str.length-4); str = str.substring(t.length); } t = t.padStart(2,'0'); if (str.length == 4) { s = str.substring(0,1); str = str.substring(1); } if (s != '0') { var si = parseInt(s.padEnd(4,'0'),8); if ((si & clite.io.modes.S_ISUID) == clite.io.modes.S_ISUID) su = true; if ((si & clite.io.modes.S_ISGID) == clite.io.modes.S_ISGID) sg = true; if ((si & clite.io.modes.S_ISVTX) == clite.io.modes.S_ISVTX) sd = true; } switch (t) { case '01': p = 'b'; break; case '02': p = 'c'; break; case '04': p = 'f'; break; case '10': p = 'd'; break; case '20': p = 'l'; break; case '40': p = 's'; break; case '00': default: p = '-'; } for (var i=0; i 0777) modestr:function(perms) { var m = 0; perms = perms.padStart(10,'-'); if (perms[0] != '-') { switch (perms[0]) { case 'b': m |= clite.io.modes.S_IFBLK; break; case 'c': m |= clite.io.modes.S_IFCHR; break; case 'f': m |= clite.io.modes.S_IFIFO; break; case 'd': m |= clite.io.modes.S_IFDIR; break; case 'l': m |= clite.io.modes.S_IFLNK; break; case 's': m |= clite.io.modes.S_IFSOCK; break; default:; } } if (perms[1] == 'r') m |= clite.io.modes.S_IRUSR; if (perms[2] == 'w') m |= clite.io.modes.S_IWUSR; if (perms[3] == 'x') m |= clite.io.modes.S_IXUSR; if (perms[3] == 'S') m |= clite.io.modes.S_ISUID; if (perms[3] == 's') { m |= clite.io.modes.S_IXUSR; m |= clite.io.modes.S_ISUID; } if (perms[4] == 'r') m |= clite.io.modes.S_IRGRP; if (perms[5] == 'w') m |= clite.io.modes.S_IWGRP; if (perms[6] == 'x') m |= clite.io.modes.S_IXGRP; if (perms[6] == 'S') m |= clite.io.modes.S_ISGID; if (perms[6] == 's') { m |= clite.io.modes.S_IXGRP; m |= clite.io.modes.S_ISGID; } if (perms[7] == 'r') m |= clite.io.modes.S_IROTH; if (perms[8] == 'w') m |= clite.io.modes.S_IWOTH; if (perms[9] == 'x') m |= clite.io.modes.S_IXOTH; if (perms[9] == 'T') m |= clite.io.modes.S_ISVTX; if (perms[9] == 't') { m |= clite.io.modes.S_IXOTH; m |= clite.io.modes.S_ISVTX; } return m; }, fork:function(env,io,call) { var pid = clite.proc.add(io.pid,call); if (!pid) return 0; var nio = Object.create(io); nio.pid = pid; nio.exit = function(v) { clite.lib.exit(pid,v); } var nenv = structuredClone(env); // this is esssentially the dynamic linker nio.include = function(name) { var file = ''; clite.libs.index.forEach(function(e) { if (e.header == name) file = e.file; }); if (file == '') return null; var fd = clite.io.open(0,'/lib/'+file+'.so',clite.io.flags.O_RDONLY); if (!fd) return null; var lib = null; try{ lib = fd.node.data.content(nio,nenv); }catch(err) {} clite.io.close(0,fd); if (lib) return lib; return null; } clite.core.execSafeAsync(function() { call(nenv,nio); }); return pid; }, exec:function(path,args,env,io) { var fd = clite.io.open(io.pid,path,clite.io.flags.O_RDONLY|clite.io.flags.O_EXEC|clite.io.flags.O_SYNC); if (!fd) return -1; if (!fd.canexec) { clite.io.close(io.pid,fd); return -3; } var ft = clite.lib.getFileType(fd); if (ft == clite.io.types.FT_SCRIPT) { var fl = clite.io.readLine(io.pid,fd); clite.io.close(fd); if (!fl) return -5; var e = fl.substring(2); fd = clite.io.open(io.pid,e,clite.io.flags.O_RDONLY|clite.io.flags.O_EXEC|clite.io.flags.O_SYNC); if (!fd) return -6; ft = clite.lib.getFileType(fd); } if (ft != clite.io.types.FT_BINARY) { clite.io.close(io.pid,fd); return -4; } if ((fd.node.mode&clite.io.modes.S_ISGID) == clite.io.modes.S_ISGID) { if (!clite.proc.setGID(io.pid,fd.node.gid,true)) return -7; } if ((fd.node.mode&clite.io.modes.S_ISUID) == clite.io.modes.S_ISUID) { if (!clite.proc.setUID(io.pid,fd.node.uid,true)) return -8; } io.include = function(name) { var file = ''; clite.libs.index.forEach(function(e) { if (e.header == name) file = e.file; }); if (file == '') return null; var fd = clite.io.open(0,'/lib/'+file+'.so',clite.io.flags.O_RDONLY); if (!fd) return null; var lib = null; try{ lib = fd.node.data.content(io,env); }catch(err) {} clite.io.close(0,fd); if (lib) return lib; return null; } clite.proc.update(io.pid,args.join(' '),fd.node.data.content); var r = clite.core.execSafe(function() { var rr = fd.node.data.content(args,env,io); if (typeof rr == 'number') io.exit(rr); }); clite.io.close(io.pid,fd); if (!r) { io.exit(-2); return -2; } return 0; }, exit:function(pid,v) { if (pid > -1) clite.proc.exit(pid,v); } } window.BIOSentry(clite.init);