CLIte/clite/core.js

2729 lines
65 KiB
JavaScript

var clite = {
state:{
version:'0.4.0',
isinit:false,
runlevel:1,
bios:null,
},
includes:{
// a list of program/command files to load
prog:['commands.js','shell.js','vi.js'],
// a list of library files to load
libs:['libclite.js','libcurses.js','libstd.js','libstdio.js','libterm.js','libtime.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) {
try{
setTimeout(f,10);
} 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');
// TODO: this all needs redoing with bios calls
setTimeout(function() {
clite.log.write('Bringing down the system');
var s = Array.from(document.getElementsByTagName('src'));
s.forEach(function(el,i) {
if (i>0)
el.parentNode.removeChild(el);
});
clite.console.clear();
clite.log.write('Resetting system core');
var b = clite.state.bios;
delete clite;
clite = null;
setTimeout(b.boot.preBoot,100);
},10);
},
shutdown:function() {
clite.log.write('The system is shutting down');
// TODO: this all needs redoing with bios calls
setTimeout(function() {
var s = Array.from(document.getElementsByTagName('src'));
s.forEach(function(el) {
el.parentNode.removeChild(el);
});
clite.console.clear();
delete clite;
clite = null;
},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:null,
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.perms = '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.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(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.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(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.perms = '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.perms = '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.perms = '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.perms = 'crw-rw-rw-';
n.data.isdev = true;
}
return true;
}
function defaultConfig() {
// /etc/env contains the default environment variables
vfsapi.mkFile(0,'/etc/env');
var n = vfsapi.getNode(0,'/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(0,'/etc/passwd');
var n = vfsapi.getNode(0,'/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/passwd contains user account details
vfsapi.mkFile(0,'/etc/group');
var n = vfsapi.getNode(0,'/etc/group');
if (!n) {
clite.log.write('group config failure');
return false;
}
n.data.content = `
root:x:0:
guest:x:1:
`;
// TODO: check cookies for a local user
n.perms = '-rw-r--r--';
// /etc/greeting is displayed by the shell after login
vfsapi.mkFile(0,'/etc/greeting');
var n = vfsapi.getNode(0,'/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(0,'/etc/shrc');
var 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.perms = '-rwxr-xr-x';
}
function loadCommands(nextfn) {
clite.commands = {
data:null,
load:function(name,fn) {
vfsapi.mkFile(0,'/bin/'+name);
var f = vfsapi.getNode(0,'/bin/'+name);
if (!f)
return;
f.perms = '-rwxr-xr-x';
f.data.content = fn;
vfsapi.mkFile(0,'/usr/src/'+name+'.js');
f = vfsapi.getNode(0,'/usr/src/'+name+'.js');
if (!f)
return;
f.perms = '-rw-rw-r--';
f.data.content = 'function main'+fn.toString().substring(8);
}
}
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.perms = '-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.perms = '-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/'+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.perms = '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.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(0,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');
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');
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(1);
var io = {
pid:0,
stdin:clite.io.open(1,'/dev/tty0',clite.io.flags.O_RDONLY),
stdout:clite.io.open(1,'/dev/tty0',clite.io.flags.O_WRONLY),
stderr:clite.io.open(1,'/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);
clite.proc.setLogin(io.pid,1);
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);
});
group.waitAny = [];
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);
});
group.waitAll = [];
return true;
}
clite.proc.add = function(cpid,fn) {
if (typeof fn !== 'function')
return 0;
var proc = Object.create({
uid:0, // the user id this process is running as
gid:0, // the 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
func: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.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(cl) {
var i = getProcIndex(-1);
if (i<0)
return false;
var proc = data.procs[i];
var n = vfsapi.getNode(0,'/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(0,'/proc/'+pid);
proc.waits.forEach(function(w) {
clite.core.execSafeAsync(function() {w(pid)});
});
exitGroup(proc.gpid,pid);
// 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.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.uid = uid;
proc.gid = clite.user.getGID(uid);
return true;
}
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',
uid:0,
gid:0,
groups:[],
env:{
HOME:'/',
USER:this.name,
}
}],
groups:[{
name:'root',
gid:0,
users:[]
}]
};
var env = {};
function getUser(uid) {
for (var i=0; i<data.users.length; i++) {
if (data.users[i].uid == uid)
return data.users[i];
}
return null;
}
function getGroup(gid) {
for (var i=0; i<data.groups.length; i++) {
if (data.groups[i].gid == gid)
return data.groups[i];
}
return null;
}
function loadUsers() {
// load in user data from /etc/passwd
var n = vfsapi.getFile(0,'/etc/passwd');
if (!n)
return false;
var lines = n.split('\n');
lines.forEach(function(line) {
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;
var uid = parseInt(sects[2]);
var udata = getUser(uid);
if (!udata) {
udata = {};
data.users.push(udata);
}
udata.name = sects[0];
udata.uid = parseInt(sects[2]);
udata.gid = parseInt(sects[3]);
udata.groups = [];
udata.env = {};
udata.env.HOME = sects[5];
udata.env.SHELL = sects[6];
udata.env.USER = udata.name;
udata.env.PWD = udata.env.HOME;
});
// load in data from /etc/group
n = vfsapi.getFile(0,'/etc/group');
if (!n)
return false;
var lines = n.split('\n');
lines.forEach(function(line) {
if (line[0] == '#')
return;
var sects = line.split(':');
// 0 groupname
// 1 null
// 2 gid
// 3 csv users
if (sects.length != 4)
return;
var gid = parseInt(sects[2]);
var gdata = getGroup(gid);
if (!gdata) {
gdata = {};
data.groups.push(gdata);
}
gdata.name = sects[0];
gdata.gid = parseInt(sects[2]);
gdata.users = sects[3].split(',');
});
// load the environment
n = vfsapi.getFile(0,'/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;
var name = sects[0].trim();
var value = sects[1].trim();
env[name] = value;
data.users.forEach(function(u) {
u.env[name] = value;
});
});
return true;
}
clite.user.checkGID = function(uid,gid) {
var udata = getUser(uid);
if (!udata)
return false;
if (gid == udata.gid)
return true;
if (udata.groups.indexOf(gid) > -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.perms = '-rwx------';
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.perms = '-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.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.mapUserName = function(name) {
for (var i=0; i<data.users.length; i++) {
if (data.users[i].name == name)
return data.users[i].uid;
}
return -1;
}
clite.user.mapGroupName = function(name) {
for (var i=0; i<data.groups.length; i++) {
if (data.groups[i].name == name)
return data.groups[i].gid;
}
return -1;
}
loadUsers();
return false;
},
getGID:function(uid) { // get the primary group id of a user
return 0;
},
checkGID:function(uid,gid) { // check if the user is in a group
return true;
},
getEnv:function(uid) { // 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,nuid,ngid,pid) {
var uid = clite.proc.getUID(pid);
if (p[7] == 'r')
return true;
if (clite.user.checkGID(uid,ngid) && p[4] == 'r')
return true;
if (nuid == uid && p[1] == 'r')
return true;
return false;
},
checkWritable:function(p,nuid,ngid,pid) {
var uid = clite.proc.getUID(pid);
if (p[8] == 'w')
return true;
if (clite.user.checkGID(uid,ngid) && p[5] == 'w')
return true;
if (nuid == uid && p[2] == 'w')
return true;
return false;
},
checkExecutable:function(p,nuid,ngid,pid) {
var uid = clite.proc.getUID(pid);
if (p[9] == 'x')
return true;
if (clite.user.checkGID(uid,ngid) && p[6] == 'x')
return true;
if (nuid == uid && p[3] == 'x')
return true;
return false;
}
};
function getFileDesPre(pid,path,link) {
var uid = clite.proc.getUID(pid);
var n = vfsapi.getNode(uid,path);
if (!n)
return null;
if (n.data.islink && !link) // if link is false, follow links
n = vfsapi.getNode(uid,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:null
}
});
fd.canread = perms.checkReadable(fd.node.perms,fd.node.uid,fd.node.gid,pid);
fd.canwrite = perms.checkWritable(fd.node.perms,fd.node.uid,fd.node.gid,pid);
fd.canexec = perms.checkExecutable(fd.node.perms,fd.node.uid,fd.node.gid,pid);
return fd;
}
function getFileDesPost(fd,cb) {
if (typeof cb === 'function') {
fd.remote.callback = cb;
if (fd.remote.ispending) {
clite.state.bios.core.load.file(fd.node.data.remote,function(d) {
fd.node.data.content = d;
fd.remote.ispending = false;
try{
fd.remote.callback(fd);
} catch(err) {}
});
}else{
clite.core.execSafeAsync(function() {fd.remote.callback(fd);});
}
}
return fd;
}
function getFileDes(pid,path,link,cb) {
var fd = getFileDesPre(pid,path,link);
if (!fd)
return null;
return getFileDesPost(fd,cb);
}
clite.io.creat = function(pid,path,type) {
var uid = clite.proc.getUID(pid);
switch (type) {
case 'd':
return vfsapi.mkDir(uid,path);
break;
case 'l':
return vfsapi.mkLink(uid,path);
break;
default:
return vfsapi.mkFile(uid,path);
}
}
clite.io.open = function(pid,path,flags,cb) {
var read = false;
var write = false;
var exec = false;
var search = false;
var append = false;
var create = false;
var directory = false;
var excl = false;
var nofollow = false;
var nonblock = false;
var trunc = false;
if ((flags&clite.io.flags.O_RDONLY) == clite.io.flags.O_RDONLY)
read = true;
if ((flags&clite.io.flags.O_WRONLY) == clite.io.flags.O_WRONLY)
write = true;
if ((flags&clite.io.flags.O_EXEC) == clite.io.flags.O_EXEC)
exec = true;
if ((flags&clite.io.flags.O_SEARCH) == clite.io.flags.O_SEARCH)
search = true;
// must have one of these set at least
if (!read && !write && !exec && !search)
return null;
if ((flags&clite.io.flags.O_APPEND) == clite.io.flags.O_APPEND) {
if (!read)
return null;
append = true;
}
if ((flags&clite.io.flags.O_CREAT) == clite.io.flags.O_CREAT)
create = true;
if ((flags&clite.io.flags.O_DIRECTORY) == clite.io.flags.O_DIRECTORY)
directory = true;
if ((flags&clite.io.flags.O_EXCL) == clite.io.flags.O_EXCL)
excl = true;
if ((flags&clite.io.flags.O_NOFOLLOW) == clite.io.flags.O_NOFOLLOW)
nofollow = true;
if ((flags&clite.io.flags.O_SYNC) == clite.io.flags.O_SYNC)
cb = false;
if ((flags&clite.io.flags.O_TRUNC) == clite.io.flags.O_TRUNC)
trunc = true;
var fd = getFileDesPre(pid,path,nofollow);
if (!fd) {
if (create) {
if (directory) {
if (!clite.io.mkdir(pid,path))
return null;
}else{
if (!clite.io.creat(pid,path,'-'))
return null;
}
fd = getFileDesPre(pid,path,nofollow);
}
if (!fd)
return null;
}else if (create && excl) {
return null;
}
var type = clite.lib.getFileType(fd);
if (directory) {
if (type != clite.io.types.FT_DIR)
return null;
if (read || search) {
if (!fd.canread || !fd.canexec)
return null
}else{
fd.canread = false;
}
if (write)
return null;
return getFileDesPost(fd,cb);
}
// check again (search is only for directories, and is basically read)
if (!read && !write && !exec)
return null;
if (trunc && write) {
if (type == clite.io.types.FT_SCRIPT || type == clite.io.types.FT_TEXT) {
fd.node.data.content = '';
}else if (type == clite.io.types.FT_REMOTE) {
return null; // cannot truncate unloaded remote data
}
}
return getFileDesPost(fd,cb);
}
clite.io.close = function(pid,fd) {
try{
fd.node = null;
delete fd;
} catch(err) {}
}
clite.io.read = 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 (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 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();
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();
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);
return vfsapi.remove(uid,path);
}
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;
return true;
}
var uid = clite.proc.getUID(pid);
return vfsapi.mkLink(uid,path,target);
},
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:0,
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
},
perms:fd.node.perms
});
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) {
var uid = clite.proc.getUID(pid);
if (fd.node.uid != uid)
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);
}
fd.node.time.change = clite.time.sec();
return true;
}
clite.io.chmod = function(pid,path,mode) {
var fd = getFileDes(pid,path,true,false);
return clite.io.fchmod(pid,fd,mode);
}
clite.io.isatty = function(pid,fd) {
return fd.node.data.istty;
}
clite.io.mkdir = function(pid,path,mode) {
if (typeof mode !== 'string' || mode.length < 9 || mode.length > 10)
mode = 'drwxr-xr-x';
if (mode.length == 9)
mode = 'd'+mode;
if (mode[0] != 'd')
mode = 'd'+mode.substring(0,9);
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.perms = mode;
return true;
}
clite.io.vfprintf = function(pid,fd,fmt,args) {
var str = '';
var parts = fmt.replace(/%%/g,'&percnt;').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<parts.length-1) {
str += '%';
d = true;
return;
}
// %[parameter][flags][width][.precision][length]type
//clite.log.write(fs);
var param = i;
var nparam = true;
var ind = fs.indexOf('$');
if (ind > -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<fs.length; j++) {
var v = parseInt(fs[j],10);
if (v > -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<fs.length; j++) {
var v = parseInt(fs[j],10);
if (v > -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(/&percnt;/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_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,
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
},
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<node.data.content.length; i++) {
var nn = node.data.content[i];
if (nn.name == name)
return nn;
}
}
function checkCanOpen(uid,node) {
if (!uid)
return true;
if (node.perms[7] == 'r')
return true;
if (clite.user.checkGID(uid,node.gid) && node.perms[4] == 'r')
return true;
if (node.uid == uid && node.perms[1] == 'r')
return true;
return false;
}
function checkCanWrite(uid,node) {
if (!uid)
return true;
if (node.perms[8] == 'w')
return true;
if (clite.user.checkGID(uid,node.gid) && node.perms[5] == 'w')
return true;
if (node.uid == uid && node.perms[2] == 'w')
return true;
return false;
}
function mkNode() {
var fsnode = {
name: '',
uid: 0,
gid: 0,
perms:'-rw-r--r--',
time:{
access:clite.time.sec(),
modify:clite.time.sec(),
change:clite.time.sec()
},
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.state.runlevel == 1)
return vfsdata.api;
return null;
}
vfsdata.api.getNode = function(uid,path) {
var n = vfsdata.fs;
var parts = path.split('/');
while (parts.length > 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.perms = '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.perms = '-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.perms = '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;
}
// 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,'/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,'/tmp');
if (n)
n.perms = 'drwxrwxrwx';
clite.io.init();
vfsdata.api.isinit = true;
clite.core.execSafeAsync(function() {clite.vfs.init = null;});
},
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]);
}
},
popLine:function(t) {
var off = -1;
var l = '';
for (var i=0; i<t.buff.length; i++) {
if (t.buff[i].code == 10) {
off = i+1;
break;
}
// special case, returns any non-character key found
if (t.buff[i].code <32 || t.buff[i].code > 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.perms = '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<str.length; i++) {
let k = {
down:false,
code:str.charCodeAt(i),
char:str[i],
lchar:str[i].toLowerCase()
};
clite.tty.input(k);
}
},
read:function(id,cb) {
if (id<0 || id>=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<str.length; i++) {
var ch = str[i];
var cc = ch.charCodeAt(0);
// TODO: escape/control sequences
if (cc < 32 || cc > 126) {
switch (cc) {
case 8:
clite.tty.internal.write(id,String.fromCharCode(8));
break;
case 10: // NL/LF to CR+LF
clite.tty.internal.write(id,String.fromCharCode(13));
clite.tty.internal.write(id,String.fromCharCode(10));
break;
default:
clite.tty.internal.write(id,'^');
}
}else{
clite.tty.internal.write(id,ch);
}
}
},
clear:function(id) {
if (id<0 || id>=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;
default:
return false;
}
}
};
clite.console = {
data:{
vgaio:null,
state:0,
size:[80,25],
cursor:[0,0,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() {
if (
clite.console.data.cursor[0] == clite.console.data.cursor[2]
&& clite.console.data.cursor[1] == clite.console.data.cursor[3]
)
return;
clite.console.data.vgaio.writeSet(2,clite.console.data.cursor[0],clite.console.data.cursor[1],false);
clite.console.data.cursor[0] = clite.console.data.cursor[2];
clite.console.data.cursor[1] = clite.console.data.cursor[3];
clite.console.data.vgaio.writeSet(2,clite.console.data.cursor[0],clite.console.data.cursor[1],true);
},
setCursor:function(x,y) {
clite.console.data.cursor[2] = x;
clite.console.data.cursor[3] = 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<str.length; i++) {
if (str[i] == '\n') {
x = 0;
y++;
continue;
}
if (x >= 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, '&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 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
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_UNKOWN;
}
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;
},
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) {}
}
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;
}
if (clite.lib.getFileType(fd) != clite.io.types.FT_BINARY) {
clite.io.close(io.pid,fd);
return -4;
}
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(io.pid,fd);
if (!r) {
io.exit(-2);
return -2;
}
return 0;
},
exit:function(pid,v) {
if (pid > -1)
clite.proc.exit(pid);
}
}
window.BIOSentry(clite.init);