get a working vfs and unix io

This commit is contained in:
Lisa Milne 2023-11-07 09:56:22 +10:00
parent 1381e55db6
commit 8df6253e2b
2 changed files with 437 additions and 19 deletions

View file

@ -7,7 +7,7 @@ header h1 {line-height:40px; font-size:30px;}
section {}
section.content {}
section.content div#terminal {border: 1px solid #FFFFFF; width:100%; min-width: 500px; max-width:1000px; margin:10px auto; overflow:hidden; min-height:100px; padding:10px;}
section.content article {}
section.content article {unicode-bidi: embed; white-space: pre;}
section.content form {}
section.content form label, section.content form input, section.content form input:focus {display:block; float:left; border:none; margin:0; padding:0; font-family:monospace; font-size:14px; line-height:20px; background-color:#000000; color:#FFFFFF; outline:none;}

View file

@ -62,10 +62,9 @@ var clite = {
head.appendChild(script);
},
file:function(name,callback) {
if (window.location.protocol == 'file:') {
clite.shell.writeLine('open file: '+name);
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.events.refocus();
clite.shell.writeLine('open file: '+name);
return;
}
@ -84,26 +83,412 @@ var clite = {
this.core.execSafeAsync(function() {
clite.term.clear(false);
// setup vfs
clite.log.write('Setting up VFS');
clite.vfs.init();
var vfsapi = clite.vfs.getApi();
clite.log.init(vfsapi);
clite.log.write('Mounting wfs on /');
// mount core (root) filesystem using data/filesys.txt
// populate /dev
clite.core.load.file('data/filesys.txt',function(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 = '-r--r-----';
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);
// TODO: make the full path if needed
if (!fn) {
if (perms[0] == 'd') {
vfsapi.mkDir(path);
}else{
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.log.write('added "'+path+'" ("'+fn.name+'")');
}
clite.io.close(fd);
// populate /dev (data/filesys.txt is /dev/wfs
clite.log.write('Populating /dev');
// check cookies for login
clite.log.write('Checking for user session');
// if new user:
// read in /usr/share/introduction and write to shell
// if logged in:
// create user session
clite.log.write('Creating Shell');
clite.events.refocus();
});
clite.core.load.file('data/filesys.txt',clite.shell.writeLine);
});
}
};
clite.io = {
init:function() {
var vfsapi = clite.vfs.getApi();
function getFileDes(path,link) {
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,
canwrite:false,
remote:{
ispending:p,
callback:null
}
});
if (p)
clite.core.load.file(n.data.remote,function(d) {
n.data.content = d;
try{
fd.remote.callback(fd);
} catch(err) {}
});
// TODO check permissions to see if this is writable
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) {
return getFileDes(path,false);
}
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.pos >= fd.node.data.content.length)
return null;
return fd.node.data.content[fd.pos++];
}
clite.io.readLine = function(fd) {
if (fd.node.data.content == null)
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.write = function(fd,data) {
if (!fd.canwrite)
return false;
if (typeof fd.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);
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);
if (!fd || !fd.canwrite)
return false;
return vfsapi.remove(path);
}
clite.io.link = function(path,target) {
if (!getFileDes(target,false))
return false;
var fd = getFileDes(path,true);
if (fd) {
if (!fd.canwrite)
return false;
fd.node.data.content = target;
return true;
}
return vfsapi.mkLink(path,target);
}
},
creat:null,
open:null,
close:null,
read:null,
readLine:null,
write:null,
ftruncate:null,
truncate:null,
seek:null,
remove:null,
link: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) {
return true;
}
function mkNode() {
var fsnode = {
name: '',
uid: 0,
gid: 0,
perms:'-rw-r-----',
data:{
remote:null,
isdir:false,
islink: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[0] = 'd';
clite.vfs.getApi = function() {
// TODO: only allow for the root user
return vfsdata.api;
}
vfsdata.api.getNode = function(path) {
var n = vfsdata.fs;
var parts = path.split('/');
while (parts.length > 0) {
if (!n.data.isdir || !checkCanOpen(n))
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[0] = 'd';
// TODO: set uid/gid/permissions
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 = '';
// TODO: set uid/gid/permissions
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.perms[0] = 'l';
n.data.content = target;
// TODO: set uid/gid/permissions
parent.data.content.push(n);
return true;
}
vfsdata.api.remove = function(path) {
return false;
}
// make some directories
vfsdata.api.mkDir('/bin');
vfsdata.api.mkDir('/dev');
vfsdata.api.mkDir('/etc');
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('/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) {
// TODO: after login don't write to term or shell
try {
if (clite.term.hasInput()) {
clite.shell.writeLine(txt);
}else{
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) {
// TODO: after login log to /var/log/messages instead of to shell
try {
clite.shell.writeLine(txt);
clite.term.writeLine(txt);
} catch(e) {}
console.log(txt);
}
@ -115,6 +500,26 @@ clite.term = {
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 = txt;
var t = document.getElementById('terminal');
if (!t)
return;
t.appendChild(a);
},
genForm:function() {
var f = document.getElementById('form');
if (!f)
@ -137,18 +542,22 @@ clite.term = {
i.onkeyup = clite.events.keyup;
i.onfocusout = clite.events.refocus;
i.onblur = clite.events.refocus;
if (i.type == 'file') {
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() {
clite.state.input.type.callback(reader.result);
var cb = clite.state.input.type.callback;
clite.term.setPass(false);
clite.shell.writeLine('loaded');
clite.term.writeLine('loaded');
clite.term.preventInput();
cb(reader.result);
};
reader.onerror = function() {
clite.state.input.type.callback(null);
var cb = clite.state.input.type.callback;
clite.term.setPass(false);
clite.shell.writeLine('load failed');
clite.term.writeLine('load failed');
clite.term.preventInput();
cb(null);
};
reader.readAsText(e.target.files[0]);
}
@ -174,12 +583,7 @@ clite.shell = {
readLine:function(cb) {
},
writeLine:function(txt) {
var a = document.createElement('article');
a.innerHTML = txt;
var t = document.getElementById('terminal');
if (!t)
return;
t.appendChild(a);
clite.term.writeLine(txt);
clite.events.refocus();
},
exec:function(txt) {
@ -193,6 +597,8 @@ clite.shell = {
clite.shell.history.add(txt);
clite.shell.history.resetCurrent();
clite.shell.writeLine(clite.shell.prompt.getHTML()+txt);
// check for a macro
// then either run the macro or expand the program to be executed via PATH
clite.shell.exec(txt);
},
prompt:{
@ -254,6 +660,7 @@ clite.shell = {
};
clite.lib = {
// makes text html safe
htmlEncode:function(txt) {
return txt
.replace(/&/g, '&amp')
@ -262,5 +669,16 @@ clite.lib = {
.replace(/>/g, '&gt')
.replace(/</g, '&lt')
.replace(/ /g, '&nbsp;');
},
// 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('/');
}
}