CLIte/clite/core.js
2023-11-16 06:29:37 +10:00

1814 lines
43 KiB
JavaScript

var clite = {
state:{
version:'0.1-13',
isinit:false,
terminal:null,
input:{
type:'text',
form:null,
field:null
}
},
events:{
keydown:function(e) {
return clite.events.keypress(e);
},
keypress:function(e) {
if (e.key == 'Tab') {
clite.shell.tabfill();
clite.events.refocus();
return false;
}
return true;
},
keyup:function(e) {
try {
if (e.key == 'Down' || e.key == 'ArrowDown') {
clite.shell.history.down();
return false;
}
if (e.key == 'Up' || e.key == 'ArrowUp') {
clite.shell.history.up();
return false;
}
if (e.key == 'Tab') {
return false;
}
var t = clite.state.input.field.value;
if (e.key != 'Enter') {
clite.shell.history.setCurrent(t);
return true;
}
clite.shell.run(t);
return false;
} catch(err) {return false;}
},
refocus:function(e) {
try {
clite.term.genForm();
clite.state.input.field.focus();
} catch(err) {}
}
},
core:{
execSafe:function(f) {
try{
f();
} catch(e) {
clite.log.write(e.message);
return false;
}
return true;
},
execSafeAsync:function(f) {
setTimeout(f,10);
},
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);
}
},
init:function() {
if (this.state.isinit)
return;
this.state.isinit = true;
this.core.execSafeAsync(function() {
clite.term.clear(false);
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;
return true;
}
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-----
clite/core.css:/usr/clite/web/core.css:0:0:-rw-r-----
data/intro.txt:/usr/share/introduction:0:0:-rw-rw-r--
data/about.txt:/usr/share/site/about:0:0:-rw-rw-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-----';
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 /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.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');
// read in /usr/share/introduction and write to shell
if (window.location.protocol == 'file:') {
clite.user.genGuest();
clite.shell.writeLine(`
_____ _ _____ _
/ ____| | |_ _| |
| | | | | | | |_ ___
| | | | | | | __/ _ \\
| |____| |____ _| |_| || __/
\\_____|______|_____|\\__\\___|`);
}else{
var fd = clite.io.open('/usr/share/introduction',function(fd) {
clite.user.genGuest();
clite.shell.writeLine(`
_____ _ _____ _
/ ____| | |_ _| |
| | | | | | | |_ ___
| | | | | | | __/ _ \\
| |____| |____ _| |_| || __/
\\_____|______|_____|\\__\\___|`);
if (!fd)
return;
var d = clite.io.readAll(fd);
if (d != null)
clite.shell.writeLine(d);
clite.io.close(fd);
});
}
}
}
init1();
});
}
};
clite.proc = {
init:function(vfs) {
var vfsapi = vfs;
var data = {
nextid:1,
procs:[]
};
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;
data.procs.forEach(function(p,i) {
if (index > -1)
return;
if (p.pid == pid)
index = i;
});
return index;
}
clite.proc.add = function(cl,fn) {
var proc = Object.create({
pid:data.nextid++,
command:cl,
func:fn
});
data.procs.push(proc);
vfsapi.mkFile('/proc/'+proc.pid);
var n = vfsapi.getNode('/proc/'+proc.pid);
n.data.content = 'command: '+proc.command+'\npid: '+proc.pid;
return proc.pid;
}
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);
return true;
}
clite.proc.count = function() {
return data.procs.length;
}
clite.proc.init = null;
return true;
},
add:function(cl,fn) {return 0;},
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:[]
};
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(user,uid,gid,groups) {
udata.name = user;
udata.uid = uid;
udata.gid = gid;
udata.groups = groups;
clite.shell.env.HOME = '/usr/home/'+user;
clite.shell.env.PWD = '/usr/home/'+user;
clite.shell.env.USER = user;
clite.shell.prompt.generate();
clite.user.hasLogin = function() {
return true;
}
}
clite.user.genGuest = function() {
clite.term.clear();
vfs.mkDir('/usr/home/guest');
var n = vfs.getNode('/usr/home/guest');
if (n) {
n.perms = '-rwx------';
n.uid = 1;
n.gid = 1;
}
vfs.mkLink('/usr/home/guest/web','/usr/share/site');
setLogin('guest',1,1,[]);
clite.events.refocus();
}
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;
},
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) {
if (fd.node.data.content == null)
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;
}
}
}
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) {
if (fd.node.data.content == null)
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;
}
}
}
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.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);
}
},
// 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
};
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,
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 = {
tty:{
data:null,
keydown:function(e) {
if (e.key == 'Tab')
return false;
},
keypress:function(e) {
if (e.key == 'Tab') {
clite.events.refocus();
return false;
}
// urgh, no. handle press in keyup because fuck js
//return clite.term.tty.data.data.onkeypress(e);
},
keyup:function(e) {
return clite.term.tty.data.data.onkeypress(e);
}
},
clear:function(form) {
document.getElementById('terminal').innerHTML = '';
if (form)
clite.term.genForm();
},
preventInput:function() {
var f = document.getElementById('form');
if (!f)
return;
f.parentNode.removeChild(f);
},
hasInput:function() {
var f = document.getElementById('form');
if (!f)
return false;
return true;
},
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);
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.shell.prompt.getHTML();
f.appendChild(l);
var i = document.createElement('input');
i.type = clite.term.getType();
i.value = clite.shell.history.getCurrent();
f.appendChild(i);
clite.state.input.form = f;
clite.state.input.field = i;
var t = document.getElementById('terminal');
if (!t)
return;
t.appendChild(f);
if (clite.term.tty.data != null) {
i.onkeydown = clite.term.tty.keydown;
i.onkeypress = clite.term.tty.keypress;
i.onkeyup = clite.term.tty.keyup;
}else if (clite.shell.reading.is) {
i.onkeydown = clite.shell.reading.keydown;
i.onkeypress = clite.shell.reading.keypress;
i.onkeyup = clite.shell.reading.keyup;
}else{
i.onkeydown = clite.events.keydown;
i.onkeypress = clite.events.keypress;
i.onkeyup = clite.events.keyup;
}
i.onfocusout = clite.events.refocus;
i.onblur = clite.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.state.input.type.callback;
clite.term.setPass(false);
clite.term.writeLine('loaded');
clite.term.preventInput();
cb(reader.result);
};
reader.onerror = function() {
var cb = clite.state.input.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.state.input.type = (!!is) ? 'password' : 'text';
},
setCustom:function(type) {
clite.state.input.type = type;
},
getType:function() {
if (typeof clite.state.input.type == 'string')
return clite.state.input.type;
if (typeof clite.state.input.type == 'object' && typeof clite.state.input.type.type == 'string')
return clite.state.input.type.type;
return 'text';
},
api:{
clear:function() { // clears the terminal
clite.term.clear(false);
},
createTTY:function() { // gets an empty element with key events (allows view/less to display content in a clean element)
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.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.shell = {
reading:{
is:false,
buff:'',
callback:null,
keydown:function(e) {
if (e.key == 'Tab')
return false;
},
keypress:function(e) {
if (e.key == 'Tab') {
clite.events.refocus();
return false;
}
// urgh, no. handle press in keyup because fuck js
//return clite.term.tty.data.data.onkeypress(e);
},
keyup:function(e) {
if (e.key.length > 1 && e.key != 'Spacebar') {
clite.shell.reading.is = false;
clite.term.preventInput();
if (e.key == 'Enter') {
clite.shell.reading.callback(clite.shell.reading.buff);
}else{
clite.shell.reading.callback(e);
}
return false;
}
clite.shell.reading.buff = e.target.value;
}
},
exit:function(pid,v) {
clite.proc.exit(pid);
if (clite.term.tty.data != null)
clite.term.api.closeTTY(clite.term.tty.data);
clite.shell.prompt.pop();
clite.events.refocus();
},
readLine:function(cb) {
clite.shell.reading.is = true;
clite.shell.reading.buff = '';
clite.shell.reading.callback = cb;
clite.events.refocus();
},
writeLine:function(txt) {
clite.term.writeLine(txt);
clite.events.refocus();
},
resolvePath:function(txt) {
var paths = clite.shell.env.PATH.split(':');
var path = null;
paths.forEach(function(p) {
if (path)
return;
var rp = clite.lib.resolvePath(txt,p);
var fd = clite.io.open(rp,false);
if (fd) {
clite.io.close(fd);
path = rp;
}
});
return path;
},
exec:function(args,io) {
var path = clite.shell.resolvePath(args[0]);
if (!path) {
clite.term.writeLine('Shell: unknown command: '+args[0]);
io.exit(-1);
return;
}
var fd = clite.io.open(path);
if (!fd) {
clite.term.writeLine('Shell: unknown command: '+args[0]);
io.exit(-1);
return;
}
var pid = clite.proc.add(args.join(' '),fd.node.data.content);
io.exit = function(v) {
clite.shell.exit(pid,v);
}
var r = clite.core.execSafe(function() {
var rr = fd.node.data.content(args,Object.create(clite.shell.env),io);
if (typeof rr == 'number')
io.exit(rr);
});
if (!r) {
clite.term.writeLine('Shell: error in command:'+args[0]);
io.exit(-1);
}
clite.io.close(fd);
},
include:function(name) {
switch (name) {
case 'stdlib':
return clite.lib.api;
break;
case 'stdio':
return clite.io;
break;
case 'term':
return clite.term.api;
break;
default:
return null;
break;
}
return null;
},
preprocess:function(txt) {
var parts = txt.split('$');
var pptxt = '';
if (parts.length == 1)
return txt;
parts.forEach(function(p,i) {
if (i > 0) {
var b = p;
var e = '';
var pi = b.indexOf(' ');
if (pi > 0) {
e = b.substring(pi);
b = b.substring(0,pi);
}
if (b[0] == '{') {
var pi = b.indexOf('}');
b = b.substring(1,pi);
}
if (typeof clite.shell.env[b] === 'string') {
b = clite.shell.env[b];
}else{
b = '';
}
pptxt = b+e;
return;
}
pptxt += p;
});
txt = pptxt;
parts = txt.split('`');
pptxt = '';
if (parts.length == 1)
return txt;
parts.forEach(function(p,i) {
if (i > 0) {
var b = p;
var e = '';
var pi = b.indexOf('`');
if (pi > 0) {
e = b.substring(pi);
b = b.substring(0,pi);
}
// TODO: replace b with the output of the command in b (command substitution)
pptxt = b+e;
return;
}
pptxt += p;
});
return pptxt;
},
run:function(txt) {
if (txt.length < 1) {
clite.shell.writeLine(clite.shell.prompt.get());
return;
}
clite.shell.history.add(txt);
clite.shell.history.resetCurrent();
clite.shell.writeLine(clite.shell.prompt.get()+txt);
clite.shell.prompt.push('');
clite.term.preventInput();
// split command line into arguments
var args = clite.lib.strToArgs(txt);
// TODO: check for io redirects and piping, then setup stdio accordingly
args.forEach(function(val,i) {
args[i] = clite.shell.preprocess(val);
});
// prepare stdio (stdin,stdout,stderr)
var stdio = {
read:clite.shell.readLine,
write:clite.term.writeLine,
error:clite.term.writeLine,
exit:clite.shell.exit, // equivalent to C's exit(), needed by asynchronous programs
include:clite.shell.include, // include libraries, allows them to be used in a program
istty:{
stdin:true, // false if stdin (read) is not clite.shell.readLine
stdout:true // false if stdout (write) is not clite.term.writeLine
}
};
// check for a macro
// then either run the macro or expand the program to be executed via PATH
if (typeof clite.shell.macro[args[0]] != 'undefined') {
var r = clite.shell.macro[args[0]](args,stdio);
clite.shell.exit(r);
}else{
clite.shell.exec(args,stdio);
}
},
tabfill:function() {
var c = clite.shell.history.getCurrent();
var parts = clite.lib.strToArgs(c);
var ai = parts.length-1;
if (ai < 0)
return;
var a = parts[ai];
var add = '';
if (ai == 0) {
// test for shell macros/builtins
Object.keys(clite.shell.macro).forEach(function(key) {
if (add == '' && key.substring(0,a.length) == a) {
add = key.substring(a.length);
}
});
if (add == '') {
// test for commands
var pparts = clite.shell.env.PATH.split(':');
pparts.forEach(function(pp) {
if (add != '')
return;
var fd = clite.io.open('/bin');
var f;
while (add == '' && (f = clite.io.read(fd)) != null) {
if (f.name.substring(0,a.length) == a) {
add = f.name.substring(a.length)+' ';
}
}
clite.io.close(fd);
});
}
}else{
// TODO: path fill
}
if (add == '')
return;
clite.shell.history.setCurrent(c+add);
},
env:{
USER:'root',
PWD:'/',
HOME:'/',
PATH:'/bin'
},
macro:{
clear:function(args,io) {
clite.term.clear();
},
cd:function(args,io) {
var dir = clite.shell.env.HOME;
if (args.length > 1) {
dir = clite.lib.resolvePath(args[1]);
}
var fd = clite.io.open(dir,false);
if (!fd) {
io.error('invalid directory: '+dir);
return;
}
if (!fd.node.data.isdir) {
io.error('invalid directory: '+dir);
return;
}
clite.io.close(fd);
clite.shell.env.PWD = dir;
clite.shell.prompt.generate();
},
pwd:function(args,io) {
io.write(clite.shell.env.PWD);
},
echo:function(args,io) {
args.shift();
var txt = args.join(' ');
io.write(txt);
},
which:function(args,io) {
if (args.length <2)
return;
if (typeof clite.shell.macro[args[1]] != 'undefined') {
return;
}else{
var path = clite.shell.resolvePath(args[1]);
if (!path)
return;
io.write(args[1]+' is '+path);
}
},
type:function(args,io) {
if (args.length <2)
return;
if (typeof clite.shell.macro[args[1]] != 'undefined') {
io.write(args[1]+' is a shell builtin');
}else{
var path = clite.shell.resolvePath(args[1]);
if (!path)
return;
io.write(args[1]+' is '+path);
}
},
whoami:function(args,io) {
io.write(clite.shell.env.USER);
},
alias:function(args,io) {
io.write('unimplemented');
},
export:function(args,io) {
if (args.length == 1) {
Object.keys(clite.shell.env).forEach(function(key) {
io.write(key+'='+clite.shell.env[key]);
});
return;
}
var parts = args[1].split('=');
if (parts.length != 2)
return;
clite.shell.env[parts[0]] = parts[1];
}
},
prompt:{
list:[],
data:'# ',
set:function(txt) {
clite.shell.prompt.list = [];
clite.shell.prompt.data = txt;
},
push:function(txt) {
clite.shell.prompt.list.push(clite.shell.prompt.data);
clite.shell.prompt.data = txt;
},
pop:function() {
if (clite.shell.prompt.list.length > 0)
clite.shell.prompt.data = clite.shell.prompt.list.pop();
},
generate:function() {
var p = clite.shell.env.USER+':'+clite.shell.env.PWD;
if (clite.user.getUID() == 0) {
p += '# ';
}else{
p += '$ ';
}
clite.shell.prompt.set(p);
},
get:function() {
return clite.shell.prompt.data;
},
getHTML:function() {
return clite.lib.htmlEncode(clite.shell.prompt.data);
}
},
history:{
data:[],
current:'',
index:0,
add:function(txt) {
if (txt.length < 1)
return;
clite.shell.history.data.push(txt);
clite.shell.history.index = clite.shell.history.data.length;
},
up:function() {
if (clite.shell.history.index == 0)
return;
clite.shell.history.index--;
clite.events.refocus();
},
down:function() {
if (clite.shell.history.index == clite.shell.history.data.length)
return;
clite.shell.history.index++;
clite.events.refocus();
},
setCurrent:function(txt) {
clite.shell.history.current = txt;
},
getCurrent:function() {
if (clite.shell.history.index == clite.shell.history.data.length)
return clite.shell.history.current;
return clite.shell.history.data[clite.shell.history.index];
},
resetCurrent:function() {
clite.shell.history.current = '';
clite.shell.history.index = clite.shell.history.data.length;
}
}
};
clite.lib = {
// makes text html safe
htmlEncode:function(txt) {
var rtxt = txt.replace(/&/g, '&amp').replace(/>/g, '&gt').replace(/</g, '&lt');
if (rtxt.length > 0 && rtxt[rtxt.length-1] == ' ')
return rtxt.substring(0,rtxt.length-1)+'&nbsp;';
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;
if (txt[0] == '~')
txt = clite.shell.env.HOME+txt.substring(1);
var path = txt;
if (txt[0] != '/') {
if (typeof base != 'string')
base = clite.shell.env.PWD;
if (base[0] != '/')
base = clite.shell.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);
return parts;
},
getFileType:function(fd) {
// returns an int identifier for the file type:
// 0: unknown
// 1: text file
// 2: executable/binary (function)
// 3: directory
// 4: link
// 5: device
// 6: unloaded remote file
// 7: shell script
// 8: image
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() {
return 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;
return b.substring(0,i);
},
write:function(txt) {
this.buffer.data += txt;
}
});
},
exec:function(args,io) {
}
}
clite.lib.api = { // this is passed to programs via include('stdlib')
basename:clite.lib.basename,
dirname:clite.lib.dirname,
resolvePath:clite.lib.resolvePath,
getFileType:clite.lib.getFileType,
strToArgs:clite.lib.strToArgs,
exec:clite.lib.exec
};