mirror of
https://codeberg.org/TicklishHoneyBee/CLIte.git
synced 2026-03-11 09:04:37 +00:00
1834 lines
44 KiB
JavaScript
1834 lines
44 KiB
JavaScript
var clite = {
|
|
state:{
|
|
version:'0.2-2',
|
|
isinit:false
|
|
},
|
|
core:{
|
|
execSafe:function(f) {
|
|
try{
|
|
f();
|
|
} catch(e) {
|
|
clite.log.write(e.message);
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
execSafeAsync:function(f) {
|
|
try{
|
|
setTimeout(f,10);
|
|
} catch(e) {
|
|
clite.log.write(e.message);
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
load:{
|
|
script:function(name,callback) {
|
|
var head = document.getElementsByTagName('head')[0];
|
|
var script = document.createElement('script');
|
|
script.type = 'text/javascript';
|
|
script.src = name+'?d='+(new Date).getTime();
|
|
script.onload = callback;
|
|
head.appendChild(script);
|
|
},
|
|
file:function(name,callback) {
|
|
if (window.location.protocol == 'file:') { // this is a dirty hack and I hate it, just let me open a file: path!
|
|
clite.term.setCustom({type:'file',callback:callback});
|
|
//clite.shell.writeLine('open file: '+name);
|
|
return;
|
|
}
|
|
|
|
fetch(name+'?d='+(new Date).getTime())
|
|
.then(response => response.text())
|
|
.then((data) => {
|
|
callback(data);
|
|
});
|
|
}
|
|
},
|
|
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();
|
|
document.body.removeChild(link);
|
|
},
|
|
reboot:function() {
|
|
clite.log.write('The system is rebooting');
|
|
setTimeout(function() {
|
|
clite.term.preventInput();
|
|
clite.log.write('Bringing down the system');
|
|
var l = clite.core.load.script;
|
|
var s = Array.from(document.getElementsByTagName('src'));
|
|
s.forEach(function(el,i) {
|
|
el.parentNode.removeChild(el);
|
|
});
|
|
clite.term.preventInput();
|
|
clite.log.write('Resetting system core');
|
|
delete clite;
|
|
clite = null;
|
|
l('clite/core.js',function() {setTimeout('clite.init();',500)});
|
|
},10);
|
|
},
|
|
hostname:function() {
|
|
if (window.location.protocol != 'file:')
|
|
return window.location.hostname;
|
|
return 'localhost';
|
|
}
|
|
},
|
|
init:function() {
|
|
if (this.state.isinit)
|
|
return;
|
|
this.state.isinit = true;
|
|
|
|
this.core.execSafeAsync(function() {
|
|
clite.term.clear();
|
|
var vfsapi = null;
|
|
|
|
function defaultDevices() {
|
|
// writing to /dev/local downloads data {name:'filename to save',data:'file content'}
|
|
vfsapi.mkFile('/dev/local');
|
|
var n = vfsapi.getNode('/dev/local');
|
|
if (!n) {
|
|
clite.log.write('local device failure');
|
|
return false;
|
|
}
|
|
n.data.content = {
|
|
read:null,
|
|
write:function(obj) {
|
|
if (typeof obj === 'string') {
|
|
clite.core.download('data.txt',obj);
|
|
}else if (typeof obj.name === 'string' && typeof obj.data === 'string') {
|
|
clite.core.download(obj.name,obj.data);
|
|
}else{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
n.perms = 'crw-rw-rw-';
|
|
n.data.isdev = true;
|
|
// /dev/null - writing to it goes nowhere, reading from it is always null
|
|
vfsapi.mkFile('/dev/null');
|
|
n = vfsapi.getNode('/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.perms = '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('/dev/random');
|
|
n = vfsapi.getNode('/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.perms = 'crw-rw-rw-';
|
|
n.data.isdev = true;
|
|
// /dev/initctl - writing to it sets the runlevel, reading from it returns the current runlevel
|
|
vfsapi.mkFile('/dev/initctl');
|
|
n = vfsapi.getNode('/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:
|
|
n.data.content.data = 0;
|
|
setTimeout(function() {
|
|
var s = Array.from(document.getElementsByTagName('src'));
|
|
s.forEach(function(el) {
|
|
el.parentNode.removeChild(el);
|
|
});
|
|
clite.term.preventInput();
|
|
delete clite;
|
|
clite = null;
|
|
},10);
|
|
break;
|
|
case 1: // essentially the booting runlevel
|
|
n.data.content.data = 1;
|
|
break;
|
|
case 3: // really just a status change, doesn't "do" anything
|
|
clite.init = 3;
|
|
n.data.content.data = 3;
|
|
break;
|
|
case 6:
|
|
clite.init = 6;
|
|
n.data.content.data = 6;
|
|
clite.core.reboot();
|
|
break;
|
|
default:;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
n.perms = 'crw-rw-rw-';
|
|
n.data.isdev = true;
|
|
// /dev/tty - the main tty device, writing to it prints to the terminal, reading from it reads keyboard input
|
|
vfsapi.mkFile('/dev/tty');
|
|
n = vfsapi.getNode('/dev/tty');
|
|
if (!n) {
|
|
clite.log.write('tty failure');
|
|
return false;
|
|
}
|
|
n.data.content = {
|
|
receive:{
|
|
callback:null,
|
|
buffer:[],
|
|
input:function(str) {
|
|
if (n.data.content.receive.callback != null) {
|
|
var fn = n.data.content.receive.callback;
|
|
clite.core.execSafeAsync(function() {
|
|
fn(str);
|
|
});
|
|
n.data.content.receive.callback = null;
|
|
return;
|
|
}
|
|
n.data.content.receive.buffer.push(str);
|
|
}
|
|
},
|
|
read:function(cb) {
|
|
if (n.data.content.receive.buffer.length > 0) {
|
|
var str = n.data.content.receive.buffer.shift();
|
|
cb(str);
|
|
return true;
|
|
}
|
|
n.data.content.receive.callback = cb;
|
|
clite.term.genForm();
|
|
clite.term.data.field.focus();
|
|
return true;
|
|
},
|
|
write:clite.term.writeLine
|
|
};
|
|
clite.term.data.handler = n.data.content.receive.input;
|
|
n.perms = 'crw-rw-rw-';
|
|
n.data.isdev = true;
|
|
n.data.istty = true;
|
|
return true;
|
|
}
|
|
|
|
function defaultConfig() {
|
|
// /etc/env contains the default environment variables
|
|
vfsapi.mkFile('/etc/env');
|
|
var n = vfsapi.getNode('/etc/env');
|
|
if (!n) {
|
|
clite.log.write('environment failure');
|
|
return false;
|
|
}
|
|
n.data.content = `
|
|
PATH=/bin
|
|
`;
|
|
n.perms = '-rw-r--r--';
|
|
// /etc/passwd contains user account details
|
|
vfsapi.mkFile('/etc/passwd');
|
|
var n = vfsapi.getNode('/etc/passwd');
|
|
if (!n) {
|
|
clite.log.write('access config failure');
|
|
return false;
|
|
}
|
|
n.data.content = `
|
|
root:x:0:0:root:/root:/bin/sh
|
|
guest:x:1:1:guest:/usr/home/guest:/bin/sh
|
|
`;
|
|
// TODO: check cookies for a local user
|
|
n.perms = '-rw-r--r--';
|
|
// /etc/greeting is displayed by the shell after login
|
|
vfsapi.mkFile('/etc/greeting');
|
|
var n = vfsapi.getNode('/etc/greeting');
|
|
if (!n) {
|
|
clite.log.write('config failure');
|
|
return false;
|
|
}
|
|
n.data.content =`
|
|
_____ _ _____ _
|
|
/ ____| | |_ _| |
|
|
| | | | | | | |_ ___
|
|
| | | | | | | __/ _ \\
|
|
| |____| |____ _| |_| || __/
|
|
\\_____|______|_____|\\__\\___|`;
|
|
n.perms = '-rw-r--r--';
|
|
// /etc/shrc shell startup file
|
|
vfsapi.mkFile('/etc/shrc');
|
|
var n = vfsapi.getNode('/etc/shrc');
|
|
if (!n) {
|
|
clite.log.write('shell config failure');
|
|
return false;
|
|
}
|
|
n.data.content =`
|
|
#!/bin/sh
|
|
cat /etc/greeting
|
|
`;
|
|
n.perms = '-rwxr-xr-x';
|
|
}
|
|
|
|
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.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--`;
|
|
init2(d);
|
|
return;
|
|
}
|
|
clite.core.load.file('data/filesys.txt',init2);
|
|
}
|
|
function init2(data) {
|
|
if (data == null) {
|
|
clite.log.write('No Filesystem Found');
|
|
return;
|
|
}
|
|
vfsapi.mkFile('/dev/wfs');
|
|
var n = vfsapi.getNode('/dev/wfs');
|
|
if (!n) {
|
|
clite.log.write('wfs device failure');
|
|
return;
|
|
}
|
|
n.data.content = data;
|
|
n.perms = 'cr--r--r--';
|
|
n.data.isdev = true;
|
|
var fd = clite.io.open('/dev/wfs');
|
|
var l;
|
|
while ((l = clite.io.readLine(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(path);
|
|
if (!fn) {
|
|
if (perms[0] == 'd') {
|
|
vfsapi.mkPath(path);
|
|
}else{
|
|
var dir = clite.lib.dirname(path);
|
|
vfsapi.mkPath(dir);
|
|
vfsapi.mkFile(path);
|
|
}
|
|
fn = vfsapi.getNode(path);
|
|
}
|
|
if (!fn)
|
|
continue;
|
|
fn.perms = perms;
|
|
fn.uid = uid;
|
|
fn.gid = gid;
|
|
fn.name = clite.lib.basename(path);
|
|
fn.data.remote = url;
|
|
fn.data.content = null;
|
|
}
|
|
clite.io.close(fd);
|
|
// populate /etc (mostly for environment)
|
|
clite.log.write('Populating /etc');
|
|
defaultConfig();
|
|
// populate /dev (data/filesys.txt is /dev/wfs already)
|
|
clite.log.write('Populating /dev');
|
|
defaultDevices();
|
|
// populate /bin
|
|
clite.log.write('Populating /bin');
|
|
// create a temporary function to load in commands
|
|
clite.commands = {
|
|
data:null,
|
|
load:function(name,fn) {
|
|
vfsapi.mkFile('/bin/'+name);
|
|
var f = vfsapi.getNode('/bin/'+name);
|
|
if (!f)
|
|
return;
|
|
f.perms = '-r-xr-xr-x';
|
|
f.data.content = fn;
|
|
vfsapi.mkFile('/usr/src/'+name+'.js');
|
|
f = vfsapi.getNode('/usr/src/'+name+'.js');
|
|
if (!f)
|
|
return;
|
|
f.perms = '-rw-rw-r--';
|
|
f.data.content = 'function main'+fn.toString().substring(8);
|
|
}
|
|
}
|
|
clite.core.load.script('clite/commands.js',init3);
|
|
}
|
|
function init3(data) {
|
|
clite.core.execSafe(clite.commands.data);
|
|
clite.core.load.script('clite/shell.js',init4);
|
|
}
|
|
function init4(data) {
|
|
clite.core.execSafe(clite.commands.data);
|
|
clite.commands = null;
|
|
clite.log.write('Bringing up the process manager');
|
|
if (!clite.proc.init(vfsapi)) {
|
|
clite.log.write('failed');
|
|
return;
|
|
}
|
|
|
|
var fd = clite.io.open('/dev/initctl',false);
|
|
if (fd) {
|
|
clite.io.write(fd,'3');
|
|
clite.io.close(fd);
|
|
}
|
|
// check cookies for login
|
|
clite.log.write('Checking for user session');
|
|
if (clite.user.init(vfsapi)) {// if logged in:
|
|
clite.log.write('found user session?');
|
|
// create user session
|
|
}else{// if new user:
|
|
clite.log.write('Generating guest session');
|
|
clite.user.genGuest();
|
|
// create the login shell
|
|
// should really have init/getty exec the shell with the env data etc
|
|
var env = clite.user.getEnv();
|
|
var io = {
|
|
pid:0,
|
|
stdin:clite.io.open('/dev/tty',false),
|
|
stdout:clite.io.open('/dev/tty',false),
|
|
stderr:clite.io.open('/dev/tty',false),
|
|
exit:null, // filled in by fork()
|
|
include:null, // filled in by fork()
|
|
};
|
|
clite.lib.fork(env,io,init5);
|
|
}
|
|
}
|
|
function init5(env,io) {
|
|
clite.lib.exec('/bin/sh',['sh'],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<data.groups.length; i++) {
|
|
if (data.groups[i].gpid == cpid)
|
|
return data.groups[i];
|
|
}
|
|
return null;
|
|
}
|
|
function addGroup(cpid) {
|
|
if (getGroup(cpid))
|
|
return;
|
|
data.groups.push({gpid:cpid,waitAny:[],waitAll:[]});
|
|
}
|
|
function exitGroup(cpid,pid) {
|
|
var group = getGroup(cpid);
|
|
if (!group)
|
|
return false;
|
|
group.waitAny.forEach(function(w) {
|
|
w(pid);
|
|
});
|
|
var c = 0;
|
|
data.procs.forEach(function(p) {
|
|
// we want a count of child processes, so ignore the controlling process
|
|
if (p.gpid == cpid && p.pid != cpid)
|
|
c++;
|
|
});
|
|
if (c > 0)
|
|
return true;
|
|
group.waitAll.forEach(function(w) {
|
|
w(pid);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
clite.proc.add = function(cpid,fn) {
|
|
if (typeof fn !== 'function')
|
|
return 0;
|
|
var proc = Object.create({
|
|
gpid:mapCPID(cpid),
|
|
pid:data.nextid++,
|
|
func:fn,
|
|
waits:[]
|
|
});
|
|
data.procs.push(proc);
|
|
vfsapi.mkFile('/proc/'+proc.pid);
|
|
var n = vfsapi.getNode('/proc/'+proc.pid);
|
|
n.data.content = 'pid: '+proc.pid+'\n';
|
|
return proc.pid;
|
|
}
|
|
clite.proc.update = function(cl) {
|
|
var i = getProcIndex(-1);
|
|
if (i<0)
|
|
return false;
|
|
var proc = data.procs[i];
|
|
var n = vfsapi.getNode('/proc/'+proc.pid);
|
|
if (!n)
|
|
return false;
|
|
n.data.content += 'command: '+cl+'\n';
|
|
return true;
|
|
}
|
|
clite.proc.exit = function(pid) {
|
|
var i = getProcIndex(pid);
|
|
if (i<0)
|
|
return false;
|
|
var proc = getProc(pid);
|
|
if (!proc)
|
|
return false;
|
|
data.procs.splice(i,1);
|
|
vfsapi.remove('/proc/'+pid);
|
|
proc.waits.forEach(function(w) {
|
|
clite.core.execSafeAsync(function() {w(pid)});
|
|
});
|
|
exitGroup(proc.gpid,pid);
|
|
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.init = null;
|
|
return true;
|
|
},
|
|
add:function(cl,fn) {return 0;},
|
|
update:function(cl) {return false;},
|
|
exit:function(id) {},
|
|
count:function() {return 0;}
|
|
};
|
|
|
|
clite.user = {
|
|
init:function(vfs) { // returns true if there's a user login or false for guest
|
|
var udata = {
|
|
name:'root',
|
|
uid:0,
|
|
gid:0,
|
|
groups:[],
|
|
env:{
|
|
HOME:'/',
|
|
USER:this.name,
|
|
}
|
|
};
|
|
clite.user.getUID = function() {
|
|
return udata.uid;
|
|
}
|
|
clite.user.getGID = function() {
|
|
return udata.gid;
|
|
}
|
|
clite.user.checkGID = function(gid) {
|
|
if (gid == udata.gid)
|
|
return true;
|
|
if (udata.groups.indexOf(gid) > -1)
|
|
return true;
|
|
return false;
|
|
}
|
|
function setLogin(uid) {
|
|
var found = false;
|
|
|
|
var n = vfs.getFile('/etc/passwd');
|
|
if (!n)
|
|
return false;
|
|
var lines = n.split('\n');
|
|
lines.forEach(function(line) {
|
|
if (found)
|
|
return;
|
|
if (line[0] == '#')
|
|
return;
|
|
var sects = line.split(':');
|
|
// 0 username
|
|
// 1 null
|
|
// 2 uid
|
|
// 3 gid
|
|
// 4 real name
|
|
// 5 home dir
|
|
// 6 shell
|
|
if (sects.length != 7)
|
|
return;
|
|
if (parseInt(sects[2]) != uid)
|
|
return;
|
|
found = true;
|
|
udata.name = sects[0];
|
|
udata.uid = uid;
|
|
udata.gid = parseInt(sects[3]);
|
|
udata.env.HOME = sects[5];
|
|
udata.env.SHELL = sects[6];
|
|
udata.env.USER = udata.name;
|
|
udata.env.PWD = udata.env.HOME;
|
|
});
|
|
|
|
if (!found)
|
|
return false;
|
|
|
|
n = vfs.getFile('/etc/env');
|
|
if (!n)
|
|
return false;
|
|
lines = n.split('\n');
|
|
lines.forEach(function(line) {
|
|
if (line[0] == '#')
|
|
return;
|
|
var sects = line.split('=');
|
|
if (sects.length != 2)
|
|
return;
|
|
udata.env[sects[0].trim()] = sects[1].trim();
|
|
});
|
|
|
|
// TODO: read in data from /etc/groups
|
|
udata.groups = [];
|
|
|
|
clite.user.hasLogin = function() {
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
vfs.mkLink('/usr/home/guest/web','/usr/share/site');
|
|
|
|
vfs.mkFile('/usr/home/guest/.shrc');
|
|
n = vfs.getNode('/usr/home/guest/.shrc');
|
|
if (n) {
|
|
n.perms = '-rwx------';
|
|
n.uid = 1;
|
|
n.gid = 1;
|
|
n.data.content = `
|
|
#!/bin/sh
|
|
cat -l /usr/share/introduction
|
|
`;
|
|
}
|
|
|
|
setLogin(1);
|
|
}
|
|
clite.user.getEnv = function() {
|
|
return structuredClone(udata.env);
|
|
}
|
|
return false;
|
|
},
|
|
getUID:function() { // get the user's user id
|
|
return 0;
|
|
},
|
|
getGID:function() { // get the user's group id
|
|
return 0;
|
|
},
|
|
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;
|
|
},
|
|
getEnv:function() { // get the current environment (variables)
|
|
return {};
|
|
},
|
|
genGuest:null
|
|
};
|
|
|
|
clite.io = {
|
|
init:function() {
|
|
var vfsapi = clite.vfs.getApi();
|
|
|
|
var perms = {
|
|
// -rwxrwxrwx user1-3,group4-6,other7-9
|
|
checkReadable:function(p,uid,gid) {
|
|
if (p[7] == 'r')
|
|
return true;
|
|
if (clite.user.checkGID(gid) && p[4] == 'r')
|
|
return true;
|
|
if (uid == clite.user.getUID() && p[1] == 'r')
|
|
return true;
|
|
return false;
|
|
},
|
|
checkWritable:function(p,uid,gid) {
|
|
if (p[8] == 'w')
|
|
return true;
|
|
if (clite.user.checkGID(gid) && p[5] == 'w')
|
|
return true;
|
|
if (uid == clite.user.getUID() && p[2] == 'w')
|
|
return true;
|
|
return false;
|
|
},
|
|
checkExecutable:function(p,uid,gid) {
|
|
if (p[9] == 'x')
|
|
return true;
|
|
if (clite.user.checkGID(gid) && p[6] == 'x')
|
|
return true;
|
|
if (uid == clite.user.getUID() && p[3] == 'x')
|
|
return true;
|
|
return false;
|
|
}
|
|
};
|
|
|
|
function getFileDes(path,link,cb) {
|
|
var n = vfsapi.getNode(path);
|
|
if (!n)
|
|
return null;
|
|
if (n.data.islink && !link) // if link is false, follow links
|
|
n = vfsapi.getNode(n.data.content);
|
|
var p = (n.data.remote && n.data.content == null);
|
|
var fd = Object.create({
|
|
node:n,
|
|
pos:0,
|
|
canread:false,
|
|
canwrite:false,
|
|
canexec:false,
|
|
remote:{
|
|
ispending:p,
|
|
callback:cb
|
|
}
|
|
});
|
|
if (cb == false) {
|
|
fd.remote.callback = null;
|
|
}else 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 && fd.remote.callback != null) {
|
|
clite.core.execSafeAsync(function() {fd.remote.callback(fd);});
|
|
}
|
|
return fd;
|
|
}
|
|
clite.io.creat = function(path,type) {
|
|
switch (type) {
|
|
case 'd':
|
|
return vfsapi.mkDir(path);
|
|
break;
|
|
case 'l':
|
|
return vfsapi.mkLink(path);
|
|
break;
|
|
default:
|
|
return vfsapi.mkFile(path);
|
|
}
|
|
}
|
|
clite.io.open = function(path,cb,open_link) {
|
|
if (typeof cb === 'undefined')
|
|
cb = false;
|
|
if (typeof open_link === 'undefined')
|
|
open_link = false;
|
|
return getFileDes(path,open_link,cb);
|
|
}
|
|
clite.io.close = function(fd) {
|
|
try{
|
|
fd.node = null;
|
|
delete fd;
|
|
} catch(err) {}
|
|
}
|
|
clite.io.read = function(fd,cb) {
|
|
if (fd.node.data.content == null)
|
|
return null;
|
|
if (fd.node.data.istty) {
|
|
try{
|
|
if (typeof cb === 'function') {
|
|
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 (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(fd,cb) {
|
|
if (fd.node.data.content == null)
|
|
return null;
|
|
if (fd.node.data.istty) {
|
|
try{
|
|
if (typeof cb === 'function') {
|
|
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(fd) {
|
|
if (fd.node.data.istty)
|
|
return false;
|
|
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(fd,data) {
|
|
if (!fd.canwrite)
|
|
return false;
|
|
if (fd.node.data.isdev) {
|
|
if (typeof fd.node.data.content != 'string') {
|
|
try{
|
|
return fd.node.data.content.write(data);
|
|
} catch(err) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (typeof fd.node.data.content != 'string')
|
|
return false;
|
|
if (typeof data != 'string')
|
|
return false;
|
|
if (fd.pos + data.length >= fd.data.content.length) {
|
|
fd.data.content = fd.data.content.substring(0,fd.pos)+data;
|
|
fd.pos = fd.data.content.length;
|
|
}else{
|
|
var b = fd.data.content.substring(0,fd.pos);
|
|
var e = fd.data.content.substring(fd.pos+data.length);
|
|
fd.data.content = b+data+e;
|
|
}
|
|
return true;
|
|
}
|
|
clite.io.ftruncate = function(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;
|
|
return true;
|
|
}
|
|
clite.io.truncate = function(path,len) {
|
|
var fd = getFileDes(path,false,null);
|
|
if (!fd)
|
|
return false;
|
|
var r = clite.io.ftruncate(fd,len);
|
|
close(fd);
|
|
return r;
|
|
}
|
|
clite.io.seek = function(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(path) {
|
|
var fd = getFileDes(path,true,null);
|
|
if (!fd || !fd.canwrite || fd.node.data.isdev)
|
|
return false;
|
|
return vfsapi.remove(path);
|
|
}
|
|
clite.io.link = function(path,target) {
|
|
if (!getFileDes(target,false,null))
|
|
return false;
|
|
var fd = getFileDes(path,true,null);
|
|
if (fd) {
|
|
if (!fd.canwrite)
|
|
return false;
|
|
fd.node.data.content = target;
|
|
return true;
|
|
}
|
|
return vfsapi.mkLink(path,target);
|
|
},
|
|
clite.io.fstat = function(fd) {
|
|
if (!fd || !fd.node)
|
|
return null;
|
|
|
|
var stat = Object.create({
|
|
name:fd.node.name,
|
|
type:clite.lib.getFileType(fd),
|
|
uid:fd.node.uid,
|
|
gid:fd.node.gid,
|
|
size:0,
|
|
perms:fd.node.perms
|
|
});
|
|
|
|
if (stat.type == 1 || stat.type == 7)
|
|
stat.size = fd.node.data.content.length;
|
|
|
|
return stat;
|
|
},
|
|
clite.io.stat = function(path) {
|
|
var fd = getFileDes(path,true,false);
|
|
return clite.io.fstat(fd);
|
|
},
|
|
clite.io.fchmod = function(fd,mode) {
|
|
if (fd.node.uid != clite.user.getUID())
|
|
return false;
|
|
if (typeof mode !== 'string')
|
|
return false;
|
|
if (mode.length == 9) {
|
|
var c = fd.node.perms[0];
|
|
fd.node.perms = c+mode;
|
|
}else if (mode.length != 10) {
|
|
return false;
|
|
}
|
|
if (fd.node.data.isdev) {
|
|
fd.node.perms = 'c'+mode.substring(1);
|
|
}else if (fd.node.data.isdir) {
|
|
fd.node.perms = 'd'+mode.substring(1);
|
|
}else if (fd.node.data.islink) {
|
|
fd.node.perms = 'l'+mode.substring(1);
|
|
}else{
|
|
fd.node.perms = '-'+mode.substring(0);
|
|
}
|
|
return true;
|
|
}
|
|
clite.io.chmod = function(path,mode) {
|
|
var fd = getFileDes(path,true,false);
|
|
return clite.io.fchmod(fd,mode);
|
|
}
|
|
clite.io.isatty = function(fd) {
|
|
return fd.node.data.istty;
|
|
}
|
|
clite.io.fprintf = function(fd,fmt) {
|
|
// TODO: actually support formatting
|
|
return clite.io.write(fd,fmt);
|
|
}
|
|
},
|
|
// 0: unknown
|
|
// 1: text file
|
|
// 2: executable/binary (function)
|
|
// 3: directory
|
|
// 4: link
|
|
// 5: device
|
|
// 6: unloaded remote file
|
|
// 7: shell script
|
|
// 8: image
|
|
types:{
|
|
FT_UNKOWN: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
|
|
},
|
|
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,
|
|
fprintf:null
|
|
};
|
|
|
|
clite.vfs = {
|
|
isinit:false,
|
|
init:function() {
|
|
var vfsdata = {
|
|
fs:{},
|
|
api:{}
|
|
};
|
|
|
|
function findNodeChild(node,name) {
|
|
for (var i=0; i<node.data.content.length; i++) {
|
|
var nn = node.data.content[i];
|
|
if (nn.name == name)
|
|
return nn;
|
|
}
|
|
}
|
|
function checkCanOpen(node) {
|
|
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;
|
|
if (clite.user.getUID() == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
function mkNode() {
|
|
var fsnode = {
|
|
name: '',
|
|
uid: 0,
|
|
gid: 0,
|
|
perms:'-rw-r--r--',
|
|
data:{
|
|
remote:null,
|
|
isdir:false,
|
|
islink:false,
|
|
isdev:false,
|
|
istty:false,
|
|
parent:null,
|
|
content:null
|
|
}
|
|
};
|
|
return Object.create(fsnode);
|
|
}
|
|
|
|
// set up the root node
|
|
vfsdata.fs = mkNode();
|
|
vfsdata.fs.data.content = [];
|
|
vfsdata.fs.data.isdir = true;
|
|
vfsdata.fs.perms = 'drwxr-xr-x';
|
|
|
|
clite.vfs.getApi = function() {
|
|
if (clite.user.getUID() == 0)
|
|
return vfsdata.api;
|
|
return null;
|
|
}
|
|
|
|
vfsdata.api.getNode = function(path) {
|
|
var n = vfsdata.fs;
|
|
var parts = path.split('/');
|
|
while (parts.length > 0) {
|
|
if (!checkCanOpen(n))
|
|
return null;
|
|
if (n.data.islink) {
|
|
n = vfsdata.api.getNode(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(n))
|
|
return null;
|
|
return n;
|
|
}
|
|
vfsdata.api.getFile = function(path) {
|
|
var n = vfsdata.api.getNode(path);
|
|
if (!n)
|
|
return null;
|
|
if (n.data.islink) // get the actual file data, not the link data
|
|
return vfsdata.api.getFile(n.data.content);
|
|
return n.data.content;
|
|
}
|
|
vfsdata.api.mkDir = function(path) {
|
|
if (vfsdata.api.getNode(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(dir);
|
|
if (!parent || !parent.data.isdir)
|
|
return false;
|
|
var n = mkNode();
|
|
n.name = name;
|
|
n.data.parent = parent;
|
|
n.data.content = [];
|
|
n.data.isdir = true;
|
|
n.perms = 'drwxr-xr-x';
|
|
n.uid = clite.user.getUID();
|
|
n.gid = clite.user.getGID();
|
|
parent.data.content.push(n);
|
|
return true;
|
|
}
|
|
vfsdata.api.mkFile = function(path) {
|
|
if (vfsdata.api.getNode(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(dir);
|
|
if (!parent || !parent.data.isdir)
|
|
return false;
|
|
var n = mkNode();
|
|
n.name = name;
|
|
n.data.parent = parent;
|
|
n.data.content = '';
|
|
n.perms = '-rw-r--r--';
|
|
n.uid = clite.user.getUID();
|
|
n.gid = clite.user.getGID();
|
|
parent.data.content.push(n);
|
|
return true;
|
|
}
|
|
vfsdata.api.mkLink = function(path,target) {
|
|
if (vfsdata.api.getNode(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(dir);
|
|
if (!parent || !parent.data.isdir)
|
|
return false;
|
|
var n = mkNode();
|
|
n.name = name;
|
|
n.data.parent = parent;
|
|
n.data.islink = true;
|
|
n.data.content = target;
|
|
n.perms = 'lrw-r--r--';
|
|
n.uid = clite.user.getUID();
|
|
n.gid = clite.user.getGID();
|
|
parent.data.content.push(n);
|
|
return true;
|
|
}
|
|
vfsdata.api.mkPath = function(path) {
|
|
var parts = path.split('/');
|
|
var p = '';
|
|
parts.forEach(function(part) {
|
|
if (part == '')
|
|
return;
|
|
p += '/'+part;
|
|
vfsdata.api.mkDir(p);
|
|
});
|
|
if (vfsdata.api.getNode(path))
|
|
return true;
|
|
return false;
|
|
}
|
|
vfsdata.api.remove = function(path) {
|
|
var n = vfsdata.api.getNode(path);
|
|
if (!n || (n.data.isdir && n.data.content.length > 0))
|
|
return false;
|
|
var dir = clite.lib.dirname(path);
|
|
var parent = vfsdata.api.getNode(dir);
|
|
if (!parent)
|
|
return false;
|
|
var c = [];
|
|
parent.data.content.forEach(function(nn) {
|
|
if (nn == n)
|
|
return;
|
|
c.push(nn);
|
|
});
|
|
parent.data.content = c;
|
|
return true;
|
|
}
|
|
|
|
// make some directories
|
|
vfsdata.api.mkDir('/bin');
|
|
vfsdata.api.mkDir('/dev');
|
|
vfsdata.api.mkDir('/etc');
|
|
vfsdata.api.mkDir('/proc');
|
|
vfsdata.api.mkDir('/usr');
|
|
vfsdata.api.mkDir('/usr/clite');
|
|
vfsdata.api.mkDir('/usr/clite/web');
|
|
vfsdata.api.mkDir('/usr/home');
|
|
vfsdata.api.mkDir('/usr/share');
|
|
vfsdata.api.mkDir('/usr/share/site');
|
|
vfsdata.api.mkDir('/usr/src');
|
|
vfsdata.api.mkDir('/var');
|
|
vfsdata.api.mkFile('/var/logs');
|
|
|
|
clite.io.init();
|
|
vfsdata.api.isinit = true;
|
|
|
|
clite.core.execSafeAsync(function() {clite.vfs.init = null;});
|
|
},
|
|
getApi:null
|
|
};
|
|
|
|
clite.log = {
|
|
init:function(vfs) {
|
|
clite.log.write = function(txt) {
|
|
if (!clite.user.hasLogin() || (typeof clite.init == 'number' && clite.init != 3)) {
|
|
try {
|
|
clite.term.writeLine(txt);
|
|
} catch(e) {}
|
|
}
|
|
var n = vfs.getNode('/var/logs');
|
|
if (n)
|
|
n.data.content += txt+'\n';
|
|
console.log(txt);
|
|
}
|
|
clite.core.execSafeAsync(function(){clite.log.init = null;});
|
|
},
|
|
write:function(txt) {
|
|
try {
|
|
clite.term.writeLine(txt);
|
|
} catch(e) {}
|
|
console.log(txt);
|
|
}
|
|
};
|
|
|
|
clite.term = {
|
|
data:{
|
|
type:'text',
|
|
form:null,
|
|
field:null,
|
|
handler:null,
|
|
prompt:'',
|
|
buff:''
|
|
},
|
|
events:{
|
|
keydown:function(e) {
|
|
if (e.key == 'Tab') {
|
|
clite.term.events.refocus();
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
keyup:function(e) {
|
|
if (clite.term.tty.data != null)
|
|
return clite.term.tty.data.data.onkeypress(e);
|
|
if (e.key.length > 1 && e.key != 'Spacebar' && clite.term.data.handler != null) {
|
|
if (e.key == 'Enter') {
|
|
clite.term.data.buff = '';
|
|
clite.term.data.handler(e.target.value);
|
|
return false;
|
|
}else{
|
|
clite.term.data.handler('\1'+e.key);
|
|
}
|
|
}
|
|
clite.term.data.buff = e.target.value;
|
|
return false;
|
|
},
|
|
refocus:function(e) {
|
|
clite.core.execSafeAsync(function() {
|
|
try{
|
|
clite.term.data.field.focus()
|
|
} catch(err) {
|
|
clite.term.genForm();
|
|
}
|
|
});
|
|
}
|
|
},
|
|
tty:{
|
|
data:null
|
|
},
|
|
clear:function() {
|
|
document.getElementById('terminal').innerHTML = '';
|
|
},
|
|
preventInput:function() {
|
|
return;
|
|
var f = document.getElementById('form');
|
|
if (!f)
|
|
return;
|
|
f.parentNode.removeChild(f);
|
|
},
|
|
writeLine:function(txt) {
|
|
var a = document.createElement('article');
|
|
a.innerHTML = clite.lib.htmlEncode(txt);
|
|
var t = document.getElementById('terminal');
|
|
if (!t)
|
|
return;
|
|
t.appendChild(a);
|
|
if (document.getElementById('form'))
|
|
clite.term.genForm();
|
|
t.scrollTop = t.scrollHeight;
|
|
},
|
|
genForm:function() {
|
|
var f = document.getElementById('form');
|
|
if (!f)
|
|
f = document.createElement('form');
|
|
f.id = 'form';
|
|
f.innerHTML = '';
|
|
var l = document.createElement('label');
|
|
l.innerHTML = clite.lib.htmlEncode(clite.term.data.prompt);
|
|
f.appendChild(l);
|
|
var i = document.createElement('input');
|
|
i.type = clite.term.getType();
|
|
i.value = clite.term.data.buff;
|
|
f.appendChild(i);
|
|
clite.term.data.form = f;
|
|
clite.term.data.field = i;
|
|
var t = document.getElementById('terminal');
|
|
if (!t)
|
|
return;
|
|
t.appendChild(f);
|
|
i.onkeydown = clite.term.events.keydown;
|
|
i.onkeyup = clite.term.events.keyup;
|
|
i.onfocusout = clite.term.events.refocus;
|
|
i.onblur = clite.term.events.refocus;
|
|
if (i.type == 'file') { // this is only needed due to dirty hacks to read in a file: path
|
|
i.onchange = function(e) {
|
|
var reader = new FileReader();
|
|
reader.onload = function() {
|
|
var cb = clite.term.data.type.callback;
|
|
clite.term.setPass(false);
|
|
clite.term.writeLine('loaded');
|
|
clite.term.preventInput();
|
|
cb(reader.result);
|
|
};
|
|
reader.onerror = function() {
|
|
var cb = clite.term.data.type.callback;
|
|
clite.term.setPass(false);
|
|
clite.term.writeLine('load failed');
|
|
clite.term.preventInput();
|
|
cb(null);
|
|
};
|
|
reader.readAsText(e.target.files[0]);
|
|
}
|
|
}
|
|
f.onsubmit = function() {return false;};
|
|
},
|
|
setPass:function(is) {
|
|
clite.term.data.type = (!!is) ? 'password' : 'text';
|
|
},
|
|
setCustom:function(type) {
|
|
clite.term.data.type = type;
|
|
},
|
|
getType:function() {
|
|
if (typeof clite.term.data.type == 'string')
|
|
return clite.term.data.type;
|
|
if (typeof clite.term.data.type == 'object' && typeof clite.term.data.type.type == 'string')
|
|
return clite.term.data.type.type;
|
|
return 'text';
|
|
},
|
|
api:{
|
|
// clears the terminal
|
|
clear:function() {
|
|
clite.term.clear();
|
|
},
|
|
ttyctrl:function(fn,v) {
|
|
switch (fn) {
|
|
case 'iget': // returns the current input string - bypassing tty read
|
|
try{
|
|
return clite.term.data.buff;
|
|
} catch(err) {
|
|
return '';
|
|
}
|
|
break;
|
|
case 'iset': // sets the current input string
|
|
try{
|
|
if (typeof v !== 'string')
|
|
v = '';
|
|
clite.term.data.field.value = v;
|
|
clite.term.data.buff = v;
|
|
return true;
|
|
} catch(err) {
|
|
return false;
|
|
}
|
|
break;
|
|
case 'prompt': // sets the prompt for input
|
|
if (typeof v !== 'string')
|
|
v = '';
|
|
clite.term.data.prompt = v;
|
|
return true;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
},
|
|
// gets an empty element with key events (allows view/less to display content in a clean element)
|
|
opentty:function() {
|
|
var tty = {
|
|
handleKeys:function(cb) {
|
|
this.data.cb = cb;
|
|
},
|
|
draw:function(data) {
|
|
if (typeof data == 'string') {
|
|
this.data.el.innerHTML += clite.lib.htmlEncode(data);
|
|
return true;
|
|
}else if (data instanceof HTMLImageElement) {
|
|
this.data.el.appendChild(data);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
clear:function() {
|
|
this.data.el.innerHTML = '';
|
|
},
|
|
setRaw:function(r) {
|
|
this.data.el.style.whiteSpace = ((!!r) ? 'normal' : 'pre-wrap');
|
|
},
|
|
data:{
|
|
cb:null,
|
|
el:null,
|
|
onkeypress:function(e) {
|
|
try{
|
|
return this.cb(e);
|
|
}catch(err) {}
|
|
}
|
|
}
|
|
};
|
|
clite.term.tty.data = tty;
|
|
tty.data.el = document.createElement('div');
|
|
tty.data.el.id = 'tty';
|
|
tty.data.el.onkeypress = tty.data.onkeypress;
|
|
var term = document.getElementById('terminal');
|
|
var p = term.parentNode;
|
|
tty.data.el.style.width = term.offsetWidth+'px';
|
|
if (p.offsetHeight > term.offsetHeight) {
|
|
tty.data.el.style.minHeight = p.offsetHeight+'px';
|
|
}else{
|
|
tty.data.el.style.minHeight = term.offsetHeight+'px';
|
|
}
|
|
tty.data.el.style.position = 'absolute';
|
|
tty.data.el.style.top = term.offsetTop+'px';
|
|
tty.data.el.style.left = term.offsetLeft+'px';
|
|
p.appendChild(tty.data.el);
|
|
// some calculations to help with putting content in
|
|
var s = document.createElement('span');
|
|
s.innerText = 'W';
|
|
tty.data.el.appendChild(s);
|
|
// 20 should not be hardcoded
|
|
tty.lines = parseInt((tty.data.el.offsetHeight/20)-1);
|
|
// TODO: shouldn't need the 0.8 thing, but urgh
|
|
tty.chars = parseInt((tty.data.el.offsetWidth/s.offsetWidth)*0.8);
|
|
tty.data.el.removeChild(s);
|
|
clite.term.events.refocus();
|
|
return tty;
|
|
},
|
|
closetty:function(tty) {
|
|
try{
|
|
tty.data.el.parentNode.removeChild(tty.data.el);
|
|
clite.term.tty.data = null;
|
|
delete tty;
|
|
}catch(err){}
|
|
}
|
|
}
|
|
};
|
|
|
|
clite.lib = {
|
|
// makes text html safe
|
|
htmlEncode:function(txt) {
|
|
var rtxt = txt.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<');
|
|
if (rtxt.length > 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 absolute path: ('foo','/etc') -> '/etc/foo': ('foo','bar') -> $PWD/bar/foo
|
|
resolvePath:function(txt,base) {
|
|
if (txt == '/')
|
|
return txt;
|
|
var env = clite.user.getEnv();
|
|
if (txt[0] == '~') {
|
|
txt = env.HOME+txt.substring(1);
|
|
}
|
|
var path = txt;
|
|
if (txt[0] != '/') {
|
|
if (typeof base != 'string')
|
|
base = env.PWD;
|
|
if (base[0] != '/')
|
|
base = env.PWD+'/'+base;
|
|
path = base+'/'+txt;
|
|
}
|
|
var parts = path.split('/');
|
|
path = '';
|
|
var i;
|
|
while ((i = parts.indexOf('.')) != -1) {
|
|
parts.splice(i,1);
|
|
}
|
|
while ((i = parts.indexOf('')) != -1) {
|
|
parts.splice(i,1);
|
|
}
|
|
while ((i = parts.indexOf('..')) != -1) {
|
|
parts.splice(i-1,2);
|
|
}
|
|
path = '/'+parts.join('/');
|
|
return path;
|
|
},
|
|
// convert a string into an argument array: 'ls /etc' -> ['ls','/etc'], supports "quoted arguments" and `command substitutions`
|
|
strToArgs:function(txt) {
|
|
var parts = [];
|
|
var sp = '';
|
|
var s = '';
|
|
var e = false;
|
|
var q = false;
|
|
var c = false;
|
|
for (var i=0; i<txt.length; i++) {
|
|
switch (txt[i]) {
|
|
case '//':
|
|
if (e) {
|
|
s+='//';
|
|
}else{
|
|
e = true;
|
|
}
|
|
break;
|
|
case '`':
|
|
if (!e) {
|
|
if (!c) {
|
|
c = true;
|
|
s += '`';
|
|
}else{
|
|
s += '`';
|
|
c = false;
|
|
}
|
|
}else{
|
|
s += txt[i];
|
|
e = false;
|
|
}
|
|
break;
|
|
case '"':
|
|
case "'":
|
|
if (!e) {
|
|
if (!q) {
|
|
sp = txt[i];
|
|
q = true;
|
|
}else if (txt[i] == sp) {
|
|
sp = '';
|
|
q = false;
|
|
}
|
|
}else{
|
|
s += txt[i];
|
|
e = false;
|
|
}
|
|
break;
|
|
case ' ':
|
|
if (!q && !c) {
|
|
parts.push(s);
|
|
s = '';
|
|
e = false;
|
|
q = false;
|
|
break;
|
|
}
|
|
default:
|
|
s+=txt[i];
|
|
e = false;
|
|
}
|
|
}
|
|
if (s.length > 0)
|
|
parts.push(s);
|
|
// TODO: resolve all paths passed as arguments? - catches ~ and * etc
|
|
return parts;
|
|
},
|
|
// 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_UNKOWN;
|
|
}
|
|
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
|
|
return clite.io.types.FT_BINARY;
|
|
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_UNKOWN;
|
|
}
|
|
return clite.io.types.FT_UNKOWN;
|
|
},
|
|
getBuffer:function() {
|
|
var buff = Object.create({
|
|
buffer:{
|
|
data:'',
|
|
pos:0
|
|
},
|
|
read:function() {
|
|
if (this.buffer.pos >= this.buffer.data.length)
|
|
return null;
|
|
var b = this.data.buffer.substring(this.buffer.pos,this.buffer.data.length-this.buffer.pos);
|
|
var i = b.indexOf('\n');
|
|
if (b < 0)
|
|
return b;
|
|
this.buffer.pos += i;
|
|
return b.substring(0,i);
|
|
},
|
|
write:function(txt) {
|
|
this.buffer.data += txt;
|
|
}
|
|
});
|
|
return buff;
|
|
},
|
|
uname:function() {
|
|
var o = Object.create({
|
|
sysname:'CLIte',
|
|
nodename:'localhost',
|
|
release:clite.state.version,
|
|
version:clite.state.version,
|
|
machine:navigator.userAgent
|
|
});
|
|
|
|
if (window.location.protocol != 'file:')
|
|
o.nodename = window.location.hostname;
|
|
|
|
return o;
|
|
},
|
|
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);
|
|
try{
|
|
io.exit(pid,v);
|
|
} catch(err) {}
|
|
}
|
|
nio.include = function(name) {
|
|
switch (name) {
|
|
case 'stdlib':
|
|
var stdlib = Object.create(clite.lib.api);
|
|
stdlib.wait = function(cb) {
|
|
return clite.lib.wait(nio.pid,cb);
|
|
}
|
|
stdlib.waitpid = function(pid,cb) {
|
|
return clite.lib.waitpid(nio.pid,pid,cb);
|
|
}
|
|
stdlib.waitall = function(cb) {
|
|
return clite.lib.waitall(nio.pid,cb);
|
|
}
|
|
return stdlib;
|
|
break;
|
|
case 'stdio':
|
|
var stdio = Object.create(clite.io);
|
|
stdio.printf = function(fmt) {
|
|
// TODO: pass arguments through
|
|
return stdio.fprintf(nio.stdout,fmt);
|
|
}
|
|
return stdio;
|
|
break;
|
|
case 'term':
|
|
return Object.create(clite.term.api);
|
|
break;
|
|
default:
|
|
return null;
|
|
break;
|
|
}
|
|
return null;
|
|
}
|
|
clite.core.execSafeAsync(function() {
|
|
call(structuredClone(env),nio);
|
|
});
|
|
return pid;
|
|
},
|
|
exec:function(path,args,env,io) {
|
|
var fd = clite.io.open(path,false);
|
|
if (!fd)
|
|
return -1;
|
|
|
|
clite.proc.update(args.join(' '));
|
|
|
|
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(fd);
|
|
if (!r) {
|
|
io.exit(-2);
|
|
return -2;
|
|
}
|
|
return 0;
|
|
},
|
|
exit:function(pid,v) {
|
|
if (pid > -1)
|
|
clite.proc.exit(pid);
|
|
if (clite.term.tty.data != null)
|
|
clite.term.api.closetty(clite.term.tty.data);
|
|
},
|
|
getuid:function() {
|
|
return clite.user.getUID();
|
|
},
|
|
getgid:function() {
|
|
return clite.user.getGID();
|
|
},
|
|
// wait for any child process to exit
|
|
wait:function(cpid,cb) {
|
|
return clite.proc.addWaitGroupAny(cpid,cb);
|
|
},
|
|
// wait for the process 'pid' to exit
|
|
waitpid:function(cpid,pid,cb) {
|
|
if (pid < 0)
|
|
return clite.lib.wait(cpid,cb);
|
|
return clite.proc.addWait(pid,cb);
|
|
},
|
|
// wait for all child processes to exit
|
|
waitall:function(cpid,cb) {
|
|
return clite.proc.addWaitGroupAll(cpid,cb);
|
|
}
|
|
}
|
|
clite.lib.api = { // this is passed to programs via include('stdlib')
|
|
basename:clite.lib.basename,
|
|
dirname:clite.lib.dirname,
|
|
resolvePath:clite.lib.resolvePath,
|
|
strToArgs:clite.lib.strToArgs,
|
|
uname:clite.lib.uname,
|
|
fork:clite.lib.fork,
|
|
exec:clite.lib.exec,
|
|
getuid:clite.lib.getuid,
|
|
getgid:clite.lib.getgid
|
|
};
|