initial version of virtio and 9p filesystem support; props to the jor1k project for donating some code
This commit is contained in:
parent
1f3d42499f
commit
4a63b1f70c
5
Makefile
5
Makefile
|
@ -38,14 +38,15 @@ CLOSURE_FLAGS=\
|
|||
|
||||
CORE_FILES=const.js io.js main.js fpu.js ide.js pci.js floppy.js memory.js\
|
||||
dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js hpet.js acpi.js\
|
||||
cpu_state.js ne2k.js
|
||||
cpu_state.js ne2k.js virtio.js
|
||||
LIB_FILES=../lib/9p.js ../lib/filesystem.js ../lib/jor1k.js ../lib/marshall.js ../lib/utf8.js
|
||||
BROWSER_FILES=browser/main.js browser/screen.js\
|
||||
browser/keyboard.js browser/mouse.js browser/serial.js\
|
||||
browser/network.js browser/lib.js
|
||||
NODE_FILES=node/main.js node/keyboard_sdl.js\
|
||||
node/screen_sdl.js node/keyboard_tty.js node/screen_tty.js
|
||||
|
||||
build/v86_all.js: src/*.js src/browser/*.js build/cpu.js
|
||||
build/v86_all.js: src/*.js src/browser/*.js build/cpu.js lib/*.js
|
||||
-ls -lh build/v86_all.js
|
||||
cd src &&\
|
||||
java -jar $(CLOSURE) \
|
||||
|
|
|
@ -27,6 +27,7 @@ v86 emulates an x86-compatible CPU and hardware. Here's a list of emulated hardw
|
|||
- A PCI bus. This one is partly incomplete and not used by every device.
|
||||
- An IDE disk controller.
|
||||
- An NE2000 (8390) PCI network card.
|
||||
- A virtio filesystem.
|
||||
|
||||
|
||||
How to build, run and embed?
|
||||
|
@ -100,6 +101,7 @@ Credits
|
|||
- https://github.com/creationix/node-sdl
|
||||
- ascii.ttf (used in node) from http://www.apollosoft.de/ASCII/indexen.htm
|
||||
- [Disk Images](https://github.com/copy/images)
|
||||
- [The jor1k project](https://github.com/s-macke/jor1k) for 9p and filesystem drivers
|
||||
|
||||
|
||||
More questions?
|
||||
|
|
569
lib/9p.js
Normal file
569
lib/9p.js
Normal file
|
@ -0,0 +1,569 @@
|
|||
// -------------------------------------------------
|
||||
// --------------------- 9P ------------------------
|
||||
// -------------------------------------------------
|
||||
// Implementation of the 9p filesystem device following the
|
||||
// 9P2000.L protocol ( https://code.google.com/p/diod/wiki/protocol )
|
||||
|
||||
"use strict";
|
||||
|
||||
// TODO
|
||||
// flush
|
||||
// lock?
|
||||
// correct hard links
|
||||
|
||||
var EPERM = 1; /* Operation not permitted */
|
||||
var ENOENT = 2; /* No such file or directory */
|
||||
var EINVAL = 22; /* Invalid argument */
|
||||
var ENOTSUPP = 524; /* Operation is not supported */
|
||||
var ENOTEMPTY = 39; /* Directory not empty */
|
||||
var EPROTO = 71 /* Protocol error */
|
||||
|
||||
var P9_SETATTR_MODE = 0x00000001;
|
||||
var P9_SETATTR_UID = 0x00000002;
|
||||
var P9_SETATTR_GID = 0x00000004;
|
||||
var P9_SETATTR_SIZE = 0x00000008;
|
||||
var P9_SETATTR_ATIME = 0x00000010;
|
||||
var P9_SETATTR_MTIME = 0x00000020;
|
||||
var P9_SETATTR_CTIME = 0x00000040;
|
||||
var P9_SETATTR_ATIME_SET = 0x00000080;
|
||||
var P9_SETATTR_MTIME_SET = 0x00000100;
|
||||
|
||||
var P9_STAT_MODE_DIR = 0x80000000;
|
||||
var P9_STAT_MODE_APPEND = 0x40000000;
|
||||
var P9_STAT_MODE_EXCL = 0x20000000;
|
||||
var P9_STAT_MODE_MOUNT = 0x10000000;
|
||||
var P9_STAT_MODE_AUTH = 0x08000000;
|
||||
var P9_STAT_MODE_TMP = 0x04000000;
|
||||
var P9_STAT_MODE_SYMLINK = 0x02000000;
|
||||
var P9_STAT_MODE_LINK = 0x01000000;
|
||||
var P9_STAT_MODE_DEVICE = 0x00800000;
|
||||
var P9_STAT_MODE_NAMED_PIPE = 0x00200000;
|
||||
var P9_STAT_MODE_SOCKET = 0x00100000;
|
||||
var P9_STAT_MODE_SETUID = 0x00080000;
|
||||
var P9_STAT_MODE_SETGID = 0x00040000;
|
||||
var P9_STAT_MODE_SETVTX = 0x00010000;
|
||||
|
||||
var FID_NONE = -1;
|
||||
var FID_INODE = 1;
|
||||
var FID_XATTR = 2;
|
||||
|
||||
/** @constructor */
|
||||
function Virtio9p(filesystem) {
|
||||
this.fs = filesystem;
|
||||
this.SendReply = function(x) {};
|
||||
this.deviceid = 0x9; // 9p filesystem
|
||||
this.hostfeature = 0x1; // mountpoint
|
||||
//this.configspace = [0x0, 0x4, 0x68, 0x6F, 0x73, 0x74]; // length of string and "host" string
|
||||
//this.configspace = [0x0, 0x9, 0x2F, 0x64, 0x65, 0x76, 0x2F, 0x72, 0x6F, 0x6F, 0x74 ]; // length of string and "/dev/root" string
|
||||
|
||||
this.configspace = [0x6, 0x0, 0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // length of string and "host9p" string
|
||||
this.VERSION = "9P2000.L";
|
||||
this.BLOCKSIZE = 8192; // Let's define one page.
|
||||
this.msize = 8192; // maximum message size
|
||||
this.replybuffer = new Uint8Array(this.msize*2); // Twice the msize to stay on the safe site
|
||||
this.replybuffersize = 0;
|
||||
this.fid2inode = [];
|
||||
this.fidtype = [];
|
||||
|
||||
this._state_skip = ["fs", "SendReply"];
|
||||
}
|
||||
|
||||
Virtio9p.prototype.Reset = function() {
|
||||
this.fid2inode = [];
|
||||
this.fidtype = [];
|
||||
}
|
||||
|
||||
Virtio9p.prototype.BuildReply = function(id, tag, payloadsize) {
|
||||
Marshall(["w", "b", "h"], [payloadsize+7, id+1, tag], this.replybuffer, 0);
|
||||
if ((payloadsize+7) >= this.replybuffer.length) {
|
||||
DebugMessage("Error in 9p: payloadsize exceeds maximum length");
|
||||
}
|
||||
//for(var i=0; i<payload.length; i++)
|
||||
// this.replybuffer[7+i] = payload[i];
|
||||
this.replybuffersize = payloadsize+7;
|
||||
return;
|
||||
}
|
||||
|
||||
Virtio9p.prototype.SendError = function (tag, errormsg, errorcode) {
|
||||
//var size = Marshall(["s", "w"], [errormsg, errorcode], this.replybuffer, 7);
|
||||
var size = Marshall(["w"], [errorcode], this.replybuffer, 7);
|
||||
this.BuildReply(6, tag, size);
|
||||
}
|
||||
|
||||
Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
|
||||
var header = Unmarshall2(["w", "b", "h"], GetByte);
|
||||
//var size = header[0];
|
||||
var id = header[1];
|
||||
var tag = header[2];
|
||||
//DebugMessage("size:" + size + " id:" + id + " tag:" + tag);
|
||||
|
||||
switch(id)
|
||||
{
|
||||
case 8: // statfs
|
||||
var size = this.fs.GetTotalSize();
|
||||
var req = [];
|
||||
req[0] = 0x01021997;
|
||||
req[1] = this.BLOCKSIZE; // optimal transfer block size
|
||||
req[2] = Math.floor(1024*1024*1024/req[1]); // free blocks, let's say 1GB
|
||||
req[3] = req[2] - Math.floor(size/req[1]); // free blocks in fs
|
||||
req[4] = req[2] - Math.floor(size/req[1]); // free blocks avail to non-superuser
|
||||
req[5] = this.fs.inodes.length; // total number of inodes
|
||||
req[6] = 1024*1024;
|
||||
req[7] = 0; // file system id?
|
||||
req[8] = 256; // maximum length of filenames
|
||||
|
||||
size = Marshall(["w", "w", "d", "d", "d", "d", "d", "d", "w"], req, this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, size);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 112: // topen
|
||||
case 12: // tlopen
|
||||
var req = Unmarshall2(["w", "w"], GetByte);
|
||||
var fid = req[0];
|
||||
var mode = req[1];
|
||||
DebugMessage("[open] fid=" + fid + ", mode=" + mode);
|
||||
var inode = this.fs.GetInode(this.fid2inode[fid]);
|
||||
req[0] = inode.qid;
|
||||
req[1] = this.msize - 24;
|
||||
Marshall(["Q", "w"], req, this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 13+4);
|
||||
DebugMessage("file open " + inode.name);
|
||||
//if (inode.status == STATUS_LOADING) return;
|
||||
var ret = this.fs.OpenInode(this.fid2inode[fid], mode);
|
||||
this.fs.AddEvent(this.fid2inode[fid],
|
||||
function() {
|
||||
DebugMessage("file opened " + inode.name + " tag:"+tag);
|
||||
req[0] = inode.qid;
|
||||
req[1] = this.msize - 24;
|
||||
Marshall(["Q", "w"], req, this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 13+4);
|
||||
this.SendReply(index);
|
||||
}.bind(this)
|
||||
);
|
||||
break;
|
||||
|
||||
case 70: // link (just copying)
|
||||
var req = Unmarshall2(["w", "w", "s"], GetByte);
|
||||
var dfid = req[0];
|
||||
var fid = req[1];
|
||||
var name = req[2];
|
||||
DebugMessage("[link] dfid=" + dfid + ", name=" + name);
|
||||
var inode = this.fs.CreateInode();
|
||||
var inodetarget = this.fs.GetInode(this.fid2inode[fid]);
|
||||
var targetdata = this.fs.inodedata[this.fid2inode[fid]];
|
||||
//inode = inodetarget;
|
||||
inode.mode = inodetarget.mode;
|
||||
inode.size = inodetarget.size;
|
||||
inode.symlink = inodetarget.symlink;
|
||||
var data = this.fs.inodedata[this.fs.inodes.length] = new Uint8Array(inode.size);
|
||||
inode.waswritten = true;
|
||||
for(var i=0; i<inode.size; i++) {
|
||||
data[i] = targetdata[i];
|
||||
}
|
||||
inode.name = name;
|
||||
inode.parentid = this.fid2inode[dfid];
|
||||
this.fs.PushInode(inode);
|
||||
|
||||
//inode.uid = inodetarget.uid;
|
||||
//inode.gid = inodetarget.gid;
|
||||
//inode.mode = inodetarget.mode | S_IFLNK;
|
||||
this.BuildReply(id, tag, 0);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 16: // symlink
|
||||
var req = Unmarshall2(["w", "s", "s", "w"], GetByte);
|
||||
var fid = req[0];
|
||||
var name = req[1];
|
||||
var symgt = req[2];
|
||||
var gid = req[3];
|
||||
DebugMessage("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid);
|
||||
var idx = this.fs.CreateSymlink(name, this.fid2inode[fid], symgt);
|
||||
var inode = this.fs.GetInode(idx);
|
||||
inode.uid = gid;
|
||||
inode.gid = gid;
|
||||
Marshall(["Q"], [inode.qid], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 13);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 18: // mknod
|
||||
var req = Unmarshall2(["w", "s", "w", "w", "w", "w"], GetByte);
|
||||
var fid = req[0];
|
||||
var name = req[1];
|
||||
var mode = req[2];
|
||||
var major = req[3];
|
||||
var minor = req[4];
|
||||
//var gid = req[5];
|
||||
DebugMessage("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "");
|
||||
var idx = this.fs.CreateNode(name, this.fid2inode[fid], major, minor);
|
||||
var inode = this.fs.GetInode(idx);
|
||||
inode.mode = mode;
|
||||
inode.uid = gid;
|
||||
inode.gid = gid;
|
||||
Marshall(["Q"], [inode.qid], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 13);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
|
||||
case 22: // TREADLINK
|
||||
var req = Unmarshall2(["w"], GetByte);
|
||||
var fid = req[0];
|
||||
DebugMessage("[readlink] fid=" + fid);
|
||||
var inode = this.fs.GetInode(this.fid2inode[fid]);
|
||||
var size = Marshall(["s"], [inode.symlink], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, size);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
|
||||
case 72: // tmkdir
|
||||
var req = Unmarshall2(["w", "s", "w", "w"], GetByte);
|
||||
var fid = req[0];
|
||||
var name = req[1];
|
||||
var mode = req[2];
|
||||
var gid = req[3];
|
||||
DebugMessage("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid);
|
||||
var idx = this.fs.CreateDirectory(name, this.fid2inode[fid]);
|
||||
var inode = this.fs.GetInode(idx);
|
||||
inode.mode = mode | S_IFDIR;
|
||||
inode.uid = gid;
|
||||
inode.gid = gid;
|
||||
Marshall(["Q"], [inode.qid], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 13);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 14: // tlcreate
|
||||
var req = Unmarshall2(["w", "s", "w", "w", "w"], GetByte);
|
||||
var fid = req[0];
|
||||
var name = req[1];
|
||||
var flags = req[2];
|
||||
var mode = req[3];
|
||||
var gid = req[4];
|
||||
DebugMessage("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid);
|
||||
var idx = this.fs.CreateFile(name, this.fid2inode[fid]);
|
||||
this.fid2inode[fid] = idx;
|
||||
this.fidtype[fid] = FID_INODE;
|
||||
var inode = this.fs.GetInode(idx);
|
||||
inode.uid = gid;
|
||||
inode.gid = gid;
|
||||
inode.mode = mode;
|
||||
Marshall(["Q", "w"], [inode.qid, this.msize - 24], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 13+4);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 52: // lock always suceed
|
||||
DebugMessage("lock file\n");
|
||||
Marshall(["w"], [0], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 1);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
/*
|
||||
case 54: // getlock
|
||||
break;
|
||||
*/
|
||||
|
||||
case 24: // getattr
|
||||
var req = Unmarshall2(["w", "d"], GetByte);
|
||||
var fid = req[0];
|
||||
var inode = this.fs.GetInode(this.fid2inode[fid]);
|
||||
DebugMessage("[getattr]: fid=" + fid + " name=" + inode.name + " request mask=" + req[1]);
|
||||
req[0] |= 0x1000; // P9_STATS_GEN
|
||||
|
||||
req[0] = req[1]; // request mask
|
||||
req[1] = inode.qid;
|
||||
|
||||
req[2] = inode.mode;
|
||||
req[3] = inode.uid; // user id
|
||||
req[4] = inode.gid; // group id
|
||||
|
||||
req[5] = 0x1; // number of hard links
|
||||
req[6] = (inode.major<<8) | (inode.minor); // device id low
|
||||
req[7] = inode.size; // size low
|
||||
req[8] = inode.size; // blk size low
|
||||
req[9] = Math.floor(inode.size/this.BLOCKSIZE+1); // number of file system blocks
|
||||
req[10] = inode.atime; // atime
|
||||
req[11] = 0x0;
|
||||
req[12] = inode.mtime; // mtime
|
||||
req[13] = 0x0;
|
||||
req[14] = inode.ctime; // ctime
|
||||
req[15] = 0x0;
|
||||
req[16] = 0x0; // btime
|
||||
req[17] = 0x0;
|
||||
req[18] = 0x0; // st_gen
|
||||
req[19] = 0x0; // data_version
|
||||
Marshall([
|
||||
"d", "Q",
|
||||
"w",
|
||||
"w", "w",
|
||||
"d", "d",
|
||||
"d", "d", "d",
|
||||
"d", "d", // atime
|
||||
"d", "d", // mtime
|
||||
"d", "d", // ctime
|
||||
"d", "d", // btime
|
||||
"d", "d",
|
||||
], req, this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 8 + 13 + 4 + 4+ 4 + 8*15);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 26: // setattr
|
||||
var req = Unmarshall2(["w", "w",
|
||||
"w", // mode
|
||||
"w", "w", // uid, gid
|
||||
"d", // size
|
||||
"d", "d", // atime
|
||||
"d", "d"] // mtime
|
||||
, GetByte);
|
||||
var fid = req[0];
|
||||
var inode = this.fs.GetInode(this.fid2inode[fid]);
|
||||
DebugMessage("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" +inode.name);
|
||||
if (req[1] & P9_SETATTR_MODE) {
|
||||
inode.mode = req[2];
|
||||
}
|
||||
if (req[1] & P9_SETATTR_UID) {
|
||||
inode.uid = req[3];
|
||||
}
|
||||
if (req[1] & P9_SETATTR_GID) {
|
||||
inode.gid = req[4];
|
||||
}
|
||||
if (req[1] & P9_SETATTR_ATIME_SET) {
|
||||
inode.atime = req[6];
|
||||
}
|
||||
if (req[1] & P9_SETATTR_MTIME_SET) {
|
||||
inode.atime = req[8];
|
||||
}
|
||||
if (req[1] & P9_SETATTR_ATIME) {
|
||||
inode.atime = Math.floor((new Date()).getTime()/1000);
|
||||
}
|
||||
if (req[1] & P9_SETATTR_MTIME) {
|
||||
inode.mtime = Math.floor((new Date()).getTime()/1000);
|
||||
}
|
||||
if (req[1] & P9_SETATTR_CTIME) {
|
||||
inode.ctime = Math.floor((new Date()).getTime()/1000);
|
||||
}
|
||||
if (req[1] & P9_SETATTR_SIZE) {
|
||||
this.fs.ChangeSize(this.fid2inode[fid], req[5]);
|
||||
}
|
||||
this.BuildReply(id, tag, 0);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 50: // fsync
|
||||
var req = Unmarshall2(["w", "d"], GetByte);
|
||||
var fid = req[0];
|
||||
this.BuildReply(id, tag, 0);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 40: // TREADDIR
|
||||
case 116: // read
|
||||
var req = Unmarshall2(["w", "d", "w"], GetByte);
|
||||
var fid = req[0];
|
||||
var offset = req[1];
|
||||
var count = req[2];
|
||||
var inode = this.fs.GetInode(this.fid2inode[fid]);
|
||||
if (id == 40) DebugMessage("[treaddir]: fid=" + fid + " offset=" + offset + " count=" + count);
|
||||
if (id == 116) DebugMessage("[read]: fid=" + fid + " (" + inode.name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fidtype[fid]);
|
||||
if (this.fidtype[fid] == FID_XATTR) {
|
||||
if (inode.caps.length < offset+count) count = inode.caps.length - offset;
|
||||
for(var i=0; i<count; i++)
|
||||
this.replybuffer[7+4+i] = inode.caps[offset+i];
|
||||
Marshall(["w"], [count], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 4 + count);
|
||||
this.SendReply(index);
|
||||
} else {
|
||||
this.fs.OpenInode(this.fid2inode[fid]);
|
||||
this.fs.AddEvent(this.fid2inode[fid],
|
||||
function() {
|
||||
if (inode.size < offset+count) count = inode.size - offset;
|
||||
var data = this.fs.inodedata[this.fid2inode[fid]];
|
||||
if(data) {
|
||||
for(var i=0; i<count; i++)
|
||||
this.replybuffer[7+4+i] = data[offset+i];
|
||||
}
|
||||
Marshall(["w"], [count], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 4 + count);
|
||||
this.SendReply(index);
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 118: // write
|
||||
var req = Unmarshall2(["w", "d", "w"], GetByte);
|
||||
var fid = req[0];
|
||||
var offset = req[1];
|
||||
var count = req[2];
|
||||
DebugMessage("[write]: fid=" + fid + " (" + this.fs.inodes[this.fid2inode[fid]].name + ") offset=" + offset + " count=" + count);
|
||||
this.fs.Write(this.fid2inode[fid], offset, count, GetByte);
|
||||
Marshall(["w"], [count], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 4);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 74: // RENAMEAT
|
||||
var req = Unmarshall2(["w", "s", "w", "s"], GetByte);
|
||||
var olddirfid = req[0];
|
||||
var oldname = req[1];
|
||||
var newdirfid = req[2];
|
||||
var newname = req[3];
|
||||
DebugMessage("[renameat]: oldname=" + oldname + " newname=" + newname);
|
||||
var ret = this.fs.Rename(this.fid2inode[olddirfid], oldname, this.fid2inode[newdirfid], newname);
|
||||
if (ret == false) {
|
||||
this.SendError(tag, "No such file or directory", ENOENT);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
}
|
||||
this.BuildReply(id, tag, 0);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 76: // TUNLINKAT
|
||||
var req = Unmarshall2(["w", "s", "w"], GetByte);
|
||||
var dirfd = req[0];
|
||||
var name = req[1];
|
||||
var flags = req[2];
|
||||
DebugMessage("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags);
|
||||
var fid = this.fs.Search(this.fid2inode[dirfd], name);
|
||||
if (fid == -1) {
|
||||
this.SendError(tag, "No such file or directory", ENOENT);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
}
|
||||
var ret = this.fs.Unlink(fid);
|
||||
if (!ret) {
|
||||
this.SendError(tag, "Directory not empty", ENOTEMPTY);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
}
|
||||
this.BuildReply(fid, tag, 0);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 100: // version
|
||||
var version = Unmarshall2(["w", "s"], GetByte);
|
||||
DebugMessage("[version]: msize=" + version[0] + " version=" + version[1]);
|
||||
this.msize = version[0];
|
||||
var size = Marshall(["w", "s"], [this.msize, this.VERSION], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, size);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 104: // attach
|
||||
// return root directorie's QID
|
||||
var req = Unmarshall2(["w", "w", "s", "s"], GetByte);
|
||||
var fid = req[0];
|
||||
DebugMessage("[attach]: fid=" + fid + " afid=" + hex8(req[1]) + " uname=" + req[2] + " aname=" + req[3]);
|
||||
this.fid2inode[fid] = 0;
|
||||
this.fidtype[fid] = FID_INODE;
|
||||
var inode = this.fs.GetInode(this.fid2inode[fid]);
|
||||
Marshall(["Q"], [inode.qid], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 13);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 108: // tflush
|
||||
var req = Unmarshall2(["h"], GetByte);
|
||||
var oldtag = req[0];
|
||||
DebugMessage("[flush] " + tag);
|
||||
//Marshall(["Q"], [inode.qid], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 0);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
|
||||
case 110: // walk
|
||||
var req = Unmarshall2(["w", "w", "h"], GetByte);
|
||||
var fid = req[0];
|
||||
var nwfid = req[1];
|
||||
var nwname = req[2];
|
||||
DebugMessage("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname);
|
||||
if (nwname == 0) {
|
||||
this.fid2inode[nwfid] = this.fid2inode[fid];
|
||||
Marshall(["h"], [0], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 2);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
}
|
||||
var wnames = [];
|
||||
for(var i=0; i<nwname; i++) {
|
||||
wnames.push("s");
|
||||
}
|
||||
var walk = Unmarshall2(wnames, GetByte);
|
||||
var idx = this.fid2inode[fid];
|
||||
var offset = 7+2;
|
||||
var nwidx = 0;
|
||||
//console.log(idx, this.fs.inodes[idx]);
|
||||
DebugMessage("walk in dir " + this.fs.inodes[idx].name + " to: " + walk.toString());
|
||||
for(var i=0; i<nwname; i++) {
|
||||
idx = this.fs.Search(idx, walk[i]);
|
||||
|
||||
if (idx == -1) {
|
||||
DebugMessage("Could not find: " + walk[i]);
|
||||
break;
|
||||
}
|
||||
offset += Marshall(["Q"], [this.fs.inodes[idx].qid], this.replybuffer, offset);
|
||||
nwidx++;
|
||||
DebugMessage(this.fid2inode[nwfid]);
|
||||
this.fid2inode[nwfid] = idx;
|
||||
this.fidtype[nwfid] = FID_INODE;
|
||||
}
|
||||
Marshall(["h"], [nwidx], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, offset-7);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 120: // clunk
|
||||
var req = Unmarshall2(["w"], GetByte);
|
||||
DebugMessage("[clunk]: fid=" + req[0]);
|
||||
if (this.fid2inode[req[0]] >= 0) {
|
||||
this.fs.CloseInode(this.fid2inode[req[0]]);
|
||||
this.fid2inode[req[0]] = -1;
|
||||
this.fidtype[req[0]] = FID_NONE;
|
||||
}
|
||||
this.BuildReply(id, tag, 0);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 32:
|
||||
this.SendError(tag, "Operation i not supported", ENOTSUPP);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
case 30: // xattrwalk
|
||||
var req = Unmarshall2(["w", "w", "s"], GetByte);
|
||||
var fid = req[0];
|
||||
var newfid = req[1];
|
||||
var name = req[2];
|
||||
DebugMessage("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2]);
|
||||
this.fid2inode[newfid] = this.fid2inode[fid];
|
||||
this.fidtype[newfid] = FID_NONE;
|
||||
var length = 0;
|
||||
if (name == "security.capability") {
|
||||
length = this.fs.PrepareCAPs(this.fid2inode[fid]);
|
||||
this.fidtype[newfid] = FID_XATTR;
|
||||
}
|
||||
Marshall(["d"], [length], this.replybuffer, 7);
|
||||
this.BuildReply(id, tag, 8);
|
||||
this.SendReply(index);
|
||||
break;
|
||||
|
||||
default:
|
||||
DebugMessage("Error in Virtio9p: Unknown id " + id + " received");
|
||||
abort();
|
||||
//this.SendError(tag, "Operation i not supported", ENOTSUPP);
|
||||
//this.SendReply(index);
|
||||
break;
|
||||
}
|
||||
|
||||
//consistency checks if there are problems with the filesystem
|
||||
//this.fs.Check();
|
||||
}
|
||||
|
847
lib/filesystem.js
Executable file
847
lib/filesystem.js
Executable file
|
@ -0,0 +1,847 @@
|
|||
// -------------------------------------------------
|
||||
// ----------------- FILESYSTEM---------------------
|
||||
// -------------------------------------------------
|
||||
// Implementation of a unix filesystem in memory.
|
||||
|
||||
"use strict";
|
||||
|
||||
var S_IRWXUGO = 0x1FF;
|
||||
var S_IFMT = 0xF000;
|
||||
var S_IFSOCK = 0xC000;
|
||||
var S_IFLNK = 0xA000;
|
||||
var S_IFREG = 0x8000;
|
||||
var S_IFBLK = 0x6000;
|
||||
var S_IFDIR = 0x4000;
|
||||
var S_IFCHR = 0x2000;
|
||||
|
||||
//var S_IFIFO 0010000
|
||||
//var S_ISUID 0004000
|
||||
//var S_ISGID 0002000
|
||||
//var S_ISVTX 0001000
|
||||
|
||||
var O_RDONLY = 0x0000; // open for reading only
|
||||
var O_WRONLY = 0x0001; // open for writing only
|
||||
var O_RDWR = 0x0002; // open for reading and writing
|
||||
var O_ACCMODE = 0x0003; // mask for above modes
|
||||
|
||||
var STATUS_INVALID = -0x1;
|
||||
var STATUS_OK = 0x0;
|
||||
var STATUS_OPEN = 0x1;
|
||||
var STATUS_ON_SERVER = 0x2;
|
||||
var STATUS_LOADING = 0x3;
|
||||
var STATUS_UNLINKED = 0x4;
|
||||
|
||||
/** @constructor */
|
||||
function FS(baseurl) {
|
||||
this.inodes = [];
|
||||
this.events = [];
|
||||
|
||||
this.baseurl = baseurl;
|
||||
|
||||
this.qidnumber = 0x0;
|
||||
this.filesinloadingqueue = 0;
|
||||
this.OnLoaded = function() {};
|
||||
|
||||
//this.tar = new TAR(this);
|
||||
this.userinfo = [];
|
||||
|
||||
this.inodedata = {};
|
||||
|
||||
|
||||
//RegisterMessage("LoadFilesystem", this.LoadFilesystem.bind(this) );
|
||||
//RegisterMessage("MergeFile", this.MergeFile.bind(this) );
|
||||
//RegisterMessage("tar",
|
||||
// function(data) {
|
||||
// SendToMaster("tar", this.tar.Pack(data));
|
||||
// }.bind(this)
|
||||
//);
|
||||
//RegisterMessage("sync",
|
||||
// function(data) {
|
||||
// SendToMaster("sync", this.tar.Pack(data));
|
||||
// }.bind(this)
|
||||
//);
|
||||
|
||||
// root entry
|
||||
this.CreateDirectory("", -1);
|
||||
|
||||
this._state_skip = ["OnLoaded"];
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------
|
||||
FS.prototype.LoadFilesystem = function(userinfo)
|
||||
{
|
||||
this.userinfo = userinfo;
|
||||
this.LoadFSXML(this.userinfo.basefsURL);
|
||||
this.OnLoaded = function() {
|
||||
}.bind(this);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------
|
||||
|
||||
FS.prototype.AddEvent = function(id, OnEvent) {
|
||||
var inode = this.inodes[id];
|
||||
if (inode.status == STATUS_OK) {
|
||||
OnEvent();
|
||||
return;
|
||||
}
|
||||
this.events.push({id: id, OnEvent: OnEvent});
|
||||
}
|
||||
|
||||
FS.prototype.HandleEvent = function(id) {
|
||||
if (this.filesinloadingqueue == 0) {
|
||||
this.OnLoaded();
|
||||
this.OnLoaded = function() {}
|
||||
}
|
||||
//DebugMessage("number of events: " + this.events.length);
|
||||
for(var i = this.events.length - 1; i >= 0; i--) {
|
||||
if (this.events[i].id != id) continue;
|
||||
this.events[i].OnEvent();
|
||||
this.events.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------
|
||||
//FS.prototype.LoadImage = function(url)
|
||||
//{
|
||||
// dbg_assert(false);
|
||||
// if (!url) return;
|
||||
// //DebugMessage("Load Image " + url);
|
||||
///*
|
||||
// if (typeof Worker !== 'undefined') {
|
||||
// LoadBZIP2Resource(url,
|
||||
// function(m){ for(var i=0; i<m.size; i++) this.tar.Unpack(m.data[i]); }.bind(this),
|
||||
// function(e){DebugMessage("Error: Could not load " + url + ". Skipping.");});
|
||||
// return;
|
||||
// }
|
||||
//*/
|
||||
// LoadBinaryResource(url,
|
||||
// function(buffer){
|
||||
// var buffer8 = new Uint8Array(buffer);
|
||||
// bzip2.simple(buffer8, this.tar.Unpack.bind(this.tar));
|
||||
// }.bind(this),
|
||||
// function(error){DebugMessage("Error: Could not load " + url + ". Skipping.");});
|
||||
//}
|
||||
// -----------------------------------------------------
|
||||
|
||||
|
||||
function ReadVariable(buffer, offset) {
|
||||
var variable = [];
|
||||
variable.name = "";
|
||||
variable.value = "";
|
||||
|
||||
// read blanks
|
||||
for(var i=offset; i<buffer.length; i++) {
|
||||
if (buffer[i] == '>') return variable;
|
||||
if (buffer[i] == '/') return variable;
|
||||
if (buffer[i] != ' ') break;
|
||||
}
|
||||
offset = i;
|
||||
if (buffer[i] == '>') return variable;
|
||||
|
||||
// read variable name
|
||||
for(var i=offset; i<buffer.length; i++) {
|
||||
if (buffer[i] == '>') break;
|
||||
if (buffer[i] == '=') break;
|
||||
variable.name = variable.name + buffer[i];
|
||||
}
|
||||
offset = i+1;
|
||||
if (variable.name.length == 0) return variable;
|
||||
// read variable value
|
||||
for(var i=offset+1; i<buffer.length; i++) {
|
||||
if (buffer[i] == '>') break;
|
||||
if (buffer[i] == '\'') break;
|
||||
variable.value = variable.value + buffer[i];
|
||||
}
|
||||
offset = i+1;
|
||||
variable.offset = offset;
|
||||
DebugMessage("read " + variable.name + "=" + variable.value);
|
||||
return variable;
|
||||
}
|
||||
|
||||
function ReadTag(buffer, offset) {
|
||||
var tag = [];
|
||||
tag.type = "";
|
||||
tag.name = "";
|
||||
tag.mode = 0x0;
|
||||
tag.uid = 0x0;
|
||||
tag.gid = 0x0;
|
||||
tag.path = "";
|
||||
tag.src = "";
|
||||
tag.compressed = false;
|
||||
tag.load = false;
|
||||
|
||||
if (buffer[offset] != '<') return tag;
|
||||
for(var i=offset+1; i<buffer.length; i++) {
|
||||
if (buffer[i] == ' ') break;
|
||||
if (buffer[i] == '\n') break;
|
||||
if (buffer[i] == '>') break;
|
||||
tag.type = tag.type + buffer[i];
|
||||
}
|
||||
offset = i;
|
||||
// read variables
|
||||
do {
|
||||
var variable = ReadVariable(buffer, offset);
|
||||
if (variable.name == "name") tag.name = variable.value;
|
||||
if (variable.name == "mode") tag.mode = parseInt(variable.value, 8);
|
||||
if (variable.name == "uid") tag.uid = parseInt(variable.value, 10);
|
||||
if (variable.name == "gid") tag.gid = parseInt(variable.value, 10);
|
||||
if (variable.name == "path") tag.path = variable.value;
|
||||
if (variable.name == "size") tag.size = parseInt(variable.value, 10);
|
||||
if (variable.name == "src") tag.src = variable.value;
|
||||
if (variable.name == "compressed") tag.compressed = true;
|
||||
if (variable.name == "load") tag.load = true;
|
||||
offset = variable.offset;
|
||||
} while(variable.name.length != 0);
|
||||
return tag;
|
||||
};
|
||||
|
||||
FS.prototype.CheckEarlyload = function(path)
|
||||
{
|
||||
for(var i=0; i<this.userinfo.earlyload.length; i++) {
|
||||
if (this.userinfo.earlyload[i] == path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FS.prototype.LoadFSXML = function(urls)
|
||||
{
|
||||
DebugMessage("Load filesystem information from " + urls);
|
||||
LoadXMLResource(urls, this.OnJSONLoaded.bind(this), function(error){throw error;});
|
||||
}
|
||||
|
||||
FS.prototype.OnJSONLoaded = function(fs)
|
||||
{
|
||||
//console.time("JSON");
|
||||
var data = JSON.parse(fs);
|
||||
//console.timeEnd("JSON");
|
||||
|
||||
var fsroot = data["fsroot"];
|
||||
var me = this;
|
||||
|
||||
setTimeout(function()
|
||||
{
|
||||
//console.time("Load");
|
||||
for(var i = 0; i < fsroot.length; i++) {
|
||||
LoadRecursive(fsroot[i], 0);
|
||||
}
|
||||
//console.timeEnd("Load");
|
||||
|
||||
if(DEBUG)
|
||||
{
|
||||
//console.time("Check");
|
||||
//me.Check();
|
||||
//console.timeEnd("Check");
|
||||
}
|
||||
}, 100);
|
||||
|
||||
function LoadRecursive(data, parentid)
|
||||
{
|
||||
var inode = me.CreateInode();
|
||||
|
||||
inode.name = data.name;
|
||||
inode.uid = data.uid || 0;
|
||||
inode.gid = data.gid || 0;
|
||||
inode.atime = Math.floor(data.atime) || inode.atime;
|
||||
inode.ctime = Math.floor(data.ctime) || inode.ctime;
|
||||
inode.mtime = Math.floor(data.mtime) || inode.mtime;
|
||||
inode.parentid = parentid;
|
||||
inode.mode = data.mode & 511;
|
||||
inode.size = data.size || 0;
|
||||
|
||||
switch(data.type)
|
||||
{
|
||||
case "dir":
|
||||
inode.updatedir = true;
|
||||
inode.mode |= S_IFDIR;
|
||||
var p = me.inodes.length;
|
||||
me.PushInode(inode);
|
||||
var children = data.children;
|
||||
for(var i = 0; i < children.length; i++) {
|
||||
LoadRecursive(children[i], p);
|
||||
}
|
||||
break;
|
||||
|
||||
case "file":
|
||||
inode.mode |= S_IFREG;
|
||||
var idx = me.inodes.length;
|
||||
inode.status = STATUS_ON_SERVER;
|
||||
me.PushInode(inode);
|
||||
var url = me.baseurl + me.GetFullPath(idx);
|
||||
inode.url = url;
|
||||
break;
|
||||
|
||||
case "link":
|
||||
inode.mode |= S_IFLNK;
|
||||
inode.symlink = data.target;
|
||||
me.PushInode(inode);
|
||||
break;
|
||||
|
||||
default:
|
||||
DebugMessage("Invalid message type: ", data.type);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The filesystem is responsible to add the correct time. This is a hack
|
||||
// Have to find a better solution.
|
||||
FS.prototype.AppendDateHack = function(idx) {
|
||||
if (this.GetFullPath(idx) != "etc/init.d/rcS") return;
|
||||
var inode = this.inodes[idx];
|
||||
var date = new Date();
|
||||
var datestring =
|
||||
"\ndate -s \"" +
|
||||
date.getFullYear() +
|
||||
"-" +
|
||||
(date.getMonth()+1) +
|
||||
"-" +
|
||||
date.getDate() +
|
||||
" " +
|
||||
date.getHours() +
|
||||
":" +
|
||||
date.getMinutes() +
|
||||
":" +
|
||||
date.getSeconds() +
|
||||
"\"\n";
|
||||
var size = inode.size;
|
||||
this.ChangeSize(idx, size+datestring.length);
|
||||
var data = this.inodedata[idx];
|
||||
for(var i=0; i<datestring.length; i++) {
|
||||
data[i+size] = datestring.charCodeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Loads the data from a url for a specific inode
|
||||
FS.prototype.LoadFile = function(idx) {
|
||||
var inode = this.inodes[idx];
|
||||
if (inode.status != STATUS_ON_SERVER) {
|
||||
return;
|
||||
}
|
||||
inode.status = STATUS_LOADING;
|
||||
this.filesinloadingqueue++;
|
||||
|
||||
//if (inode.compressed) {
|
||||
// inode.data = new Uint8Array(inode.size);
|
||||
// LoadBinaryResource(inode.url + ".bz2",
|
||||
// function(buffer){
|
||||
// var buffer8 = new Uint8Array(buffer);
|
||||
// var ofs = 0;
|
||||
// bzip2.simple(buffer8, function(x){inode.data[ofs++] = x;}.bind(this) );
|
||||
// inode.status = STATUS_OK;
|
||||
// this.filesinloadingqueue--;
|
||||
// this.HandleEvent(idx);
|
||||
// }.bind(this),
|
||||
// function(error){throw error;});
|
||||
// return;
|
||||
//}
|
||||
|
||||
LoadBinaryResource(inode.url,
|
||||
function(buffer){
|
||||
var data = this.inodedata[idx] = new Uint8Array(buffer);
|
||||
inode.size = data.length; // correct size if the previous was wrong.
|
||||
inode.status = STATUS_OK;
|
||||
if (inode.name == "rcS") {
|
||||
this.AppendDateHack(idx);
|
||||
}
|
||||
this.filesinloadingqueue--;
|
||||
this.HandleEvent(idx);
|
||||
}.bind(this),
|
||||
function(error){throw error;});
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
|
||||
FS.prototype.PushInode = function(inode) {
|
||||
if (inode.parentid != -1) {
|
||||
this.inodes.push(inode);
|
||||
this.inodes[inode.parentid].updatedir = true;
|
||||
inode.nextid = this.inodes[inode.parentid].firstid;
|
||||
this.inodes[inode.parentid].firstid = this.inodes.length-1;
|
||||
return;
|
||||
} else {
|
||||
if (this.inodes.length == 0) { // if root directory
|
||||
this.inodes.push(inode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DebugMessage("Error in Filesystem: Pushed inode with name = "+ inode.name + " has no parent");
|
||||
abort();
|
||||
|
||||
}
|
||||
|
||||
|
||||
FS.prototype.CreateInode = function() {
|
||||
//console.log("CreateInode", Error().stack);
|
||||
this.qidnumber++;
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
return {
|
||||
updatedir : false, // did the directory listing changed?
|
||||
parentid: -1,
|
||||
firstid : -1, // first file id in directory
|
||||
nextid : -1, // next id in directory
|
||||
status : 0,
|
||||
name : "",
|
||||
size : 0x0,
|
||||
uid : 0x0,
|
||||
gid : 0x0,
|
||||
ctime : now,
|
||||
atime : now,
|
||||
mtime : now,
|
||||
major : 0x0,
|
||||
minor : 0x0,
|
||||
//data : new Uint8Array(0),
|
||||
symlink : "",
|
||||
mode : 0x01ED,
|
||||
qid: {type: 0, version: 0, path: this.qidnumber},
|
||||
url: "", // url to download the file
|
||||
waswritten: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
FS.prototype.CreateDirectory = function(name, parentid) {
|
||||
var x = this.CreateInode();
|
||||
x.name = name;
|
||||
x.parentid = parentid;
|
||||
x.mode = 0x01FF | S_IFDIR;
|
||||
if (parentid >= 0) {
|
||||
x.uid = this.inodes[parentid].uid;
|
||||
x.gid = this.inodes[parentid].gid;
|
||||
x.mode = (this.inodes[parentid].mode & 0x1FF) | S_IFDIR;
|
||||
}
|
||||
x.qid.type = S_IFDIR >> 8;
|
||||
this.PushInode(x);
|
||||
return this.inodes.length-1;
|
||||
}
|
||||
|
||||
FS.prototype.CreateFile = function(filename, parentid) {
|
||||
var x = this.CreateInode();
|
||||
x.name = filename;
|
||||
x.parentid = parentid;
|
||||
x.uid = this.inodes[parentid].uid;
|
||||
x.gid = this.inodes[parentid].gid;
|
||||
x.qid.type = S_IFREG >> 8;
|
||||
x.mode = (this.inodes[parentid].mode & 0x1B6) | S_IFREG;
|
||||
this.PushInode(x);
|
||||
return this.inodes.length-1;
|
||||
}
|
||||
|
||||
|
||||
FS.prototype.CreateNode = function(filename, parentid, major, minor) {
|
||||
var x = this.CreateInode();
|
||||
x.name = filename;
|
||||
x.parentid = parentid;
|
||||
x.major = major;
|
||||
x.minor = minor;
|
||||
x.uid = this.inodes[parentid].uid;
|
||||
x.gid = this.inodes[parentid].gid;
|
||||
x.qid.type = S_IFSOCK >> 8;
|
||||
x.mode = (this.inodes[parentid].mode & 0x1B6);
|
||||
this.PushInode(x);
|
||||
return this.inodes.length-1;
|
||||
}
|
||||
|
||||
FS.prototype.CreateSymlink = function(filename, parentid, symlink) {
|
||||
var x = this.CreateInode();
|
||||
x.name = filename;
|
||||
x.parentid = parentid;
|
||||
x.uid = this.inodes[parentid].uid;
|
||||
x.gid = this.inodes[parentid].gid;
|
||||
x.qid.type = S_IFLNK >> 8;
|
||||
x.symlink = symlink;
|
||||
x.mode = S_IFLNK;
|
||||
this.PushInode(x);
|
||||
return this.inodes.length-1;
|
||||
}
|
||||
|
||||
FS.prototype.CreateTextFile = function(filename, parentid, str) {
|
||||
var id = this.CreateFile(filename, parentid);
|
||||
var x = this.inodes[id];
|
||||
var data = this.inodedata[id] = new Uint8Array(str.length);
|
||||
x.waswritten = true;
|
||||
x.size = str.length;
|
||||
for (var j in str) {
|
||||
data[j] = str.charCodeAt(j);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
FS.prototype.OpenInode = function(id, mode) {
|
||||
var inode = this.GetInode(id);
|
||||
if ((inode.mode&S_IFMT) == S_IFDIR) {
|
||||
this.FillDirectory(id);
|
||||
}
|
||||
/*
|
||||
var type = "";
|
||||
switch(inode.mode&S_IFMT) {
|
||||
case S_IFREG: type = "File"; break;
|
||||
case S_IFBLK: type = "Block Device"; break;
|
||||
case S_IFDIR: type = "Directory"; break;
|
||||
case S_IFCHR: type = "Character Device"; break;
|
||||
}
|
||||
*/
|
||||
//DebugMessage("open:" + this.GetFullPath(id) + " status:" + inode.status);
|
||||
if (inode.status == STATUS_ON_SERVER) {
|
||||
this.LoadFile(id);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FS.prototype.CloseInode = function(id) {
|
||||
//DebugMessage("close: " + this.GetFullPath(id));
|
||||
var inode = this.GetInode(id);
|
||||
if (inode.status == STATUS_UNLINKED) {
|
||||
//DebugMessage("Filesystem: Delete unlinked file");
|
||||
inode.status == STATUS_INVALID;
|
||||
delete this.inodedata[id];
|
||||
inode.waswritten = true;
|
||||
inode.size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
FS.prototype.Rename = function(olddirid, oldname, newdirid, newname) {
|
||||
//DebugMessage("Rename " + oldname + " to " + newname);
|
||||
if ((olddirid == newdirid) && (oldname == newname)) {
|
||||
return true;
|
||||
}
|
||||
var oldid = this.Search(olddirid, oldname);
|
||||
if (oldid == -1) {
|
||||
return false;
|
||||
}
|
||||
var newid = this.Search(newdirid, newname);
|
||||
if (newid != -1) {
|
||||
this.Unlink(newid);
|
||||
}
|
||||
|
||||
var idx = oldid; // idx contains the id which we want to rename
|
||||
var inode = this.inodes[idx];
|
||||
|
||||
// remove inode ids
|
||||
if (this.inodes[inode.parentid].firstid == idx) {
|
||||
this.inodes[inode.parentid].firstid = inode.nextid;
|
||||
} else {
|
||||
var id = this.FindPreviousID(idx);
|
||||
if (id == -1) {
|
||||
//DebugMessage("Error in Filesystem: Cannot find previous id of inode");
|
||||
abort();
|
||||
}
|
||||
this.inodes[id].nextid = inode.nextid;
|
||||
}
|
||||
|
||||
inode.parentid = newdirid;
|
||||
inode.name = newname;
|
||||
inode.qid.version++;
|
||||
|
||||
inode.nextid = this.inodes[inode.parentid].firstid;
|
||||
this.inodes[inode.parentid].firstid = idx;
|
||||
|
||||
this.inodes[olddirid].updatedir = true;
|
||||
this.inodes[newdirid].updatedir = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
FS.prototype.Write = function(id, offset, count, GetByte) {
|
||||
var inode = this.inodes[id];
|
||||
var data = this.inodedata[id];
|
||||
inode.waswritten = true;
|
||||
|
||||
if (!data || data.length < (offset+count)) {
|
||||
this.ChangeSize(id, Math.floor(((offset+count)*3)/2) );
|
||||
inode.size = offset + count;
|
||||
data = this.inodedata[id];
|
||||
} else
|
||||
if (inode.size < (offset+count)) {
|
||||
inode.size = offset + count;
|
||||
}
|
||||
for(var i=0; i<count; i++)
|
||||
data[offset+i] = GetByte();
|
||||
}
|
||||
|
||||
FS.prototype.Search = function(parentid, name) {
|
||||
var id = this.inodes[parentid].firstid;
|
||||
while(id != -1) {
|
||||
if (this.inodes[id].parentid != parentid) { // consistency check
|
||||
DebugMessage("Error in Filesystem: Found inode with wrong parent id");
|
||||
}
|
||||
if (this.inodes[id].name == name) return id;
|
||||
id = this.inodes[id].nextid;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
FS.prototype.GetTotalSize = function() {
|
||||
return 1234567;
|
||||
//var size = 0;
|
||||
//for(var i=0; i<this.inodes.length; i++) {
|
||||
// var d = this.inodes[i].data;
|
||||
// size += d ? d.length : 0;
|
||||
//}
|
||||
//return size;
|
||||
}
|
||||
|
||||
FS.prototype.GetFullPath = function(idx) {
|
||||
var path = "";
|
||||
|
||||
while(idx != 0) {
|
||||
path = "/" + this.inodes[idx].name + path;
|
||||
idx = this.inodes[idx].parentid;
|
||||
}
|
||||
return path.substring(1);
|
||||
}
|
||||
|
||||
// no double linked list. So, we need this
|
||||
FS.prototype.FindPreviousID = function(idx) {
|
||||
var inode = this.GetInode(idx);
|
||||
var id = this.inodes[inode.parentid].firstid;
|
||||
while(id != -1) {
|
||||
if (this.inodes[id].nextid == idx) return id;
|
||||
id = this.inodes[id].nextid;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
FS.prototype.Unlink = function(idx) {
|
||||
if (idx == 0) return false; // root node cannot be deleted
|
||||
var inode = this.GetInode(idx);
|
||||
//DebugMessage("Unlink " + inode.name);
|
||||
|
||||
// check if directory is not empty
|
||||
if ((inode.mode&S_IFMT) == S_IFDIR) {
|
||||
if (inode.firstid != -1) return false;
|
||||
}
|
||||
|
||||
// update ids
|
||||
if (this.inodes[inode.parentid].firstid == idx) {
|
||||
this.inodes[inode.parentid].firstid = inode.nextid;
|
||||
} else {
|
||||
var id = this.FindPreviousID(idx);
|
||||
if (id == -1) {
|
||||
DebugMessage("Error in Filesystem: Cannot find previous id of inode");
|
||||
abort();
|
||||
}
|
||||
this.inodes[id].nextid = inode.nextid;
|
||||
}
|
||||
// don't delete the content. The file is still accessible
|
||||
this.inodes[inode.parentid].updatedir = true;
|
||||
inode.status = STATUS_UNLINKED;
|
||||
inode.nextid = -1;
|
||||
inode.firstid = -1;
|
||||
inode.parentid = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
FS.prototype.GetInode = function(idx)
|
||||
{
|
||||
if (isNaN(idx)) {
|
||||
DebugMessage("Error in filesystem: id is not a number ");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((idx < 0) || (idx > this.inodes.length)) {
|
||||
DebugMessage("Error in filesystem: Attempt to get inode with id " + idx);
|
||||
return 0;
|
||||
}
|
||||
return this.inodes[idx];
|
||||
}
|
||||
|
||||
FS.prototype.ChangeSize = function(idx, newsize)
|
||||
{
|
||||
var inode = this.GetInode(idx);
|
||||
var temp = this.inodedata[idx];
|
||||
//DebugMessage("change size to: " + newsize);
|
||||
if (newsize == inode.size) return;
|
||||
var data = this.inodedata[idx] = new Uint8Array(newsize);
|
||||
inode.size = newsize;
|
||||
inode.waswritten = true;
|
||||
if(!temp) return;
|
||||
var size = Math.min(temp.length, inode.size);
|
||||
for(var i=0; i<size; i++) {
|
||||
data[i] = temp[i];
|
||||
}
|
||||
}
|
||||
|
||||
FS.prototype.SearchPath = function(path) {
|
||||
//path = path.replace(/\/\//g, "/");
|
||||
path = path.replace("//", "/");
|
||||
var walk = path.split("/");
|
||||
var n = walk.length;
|
||||
if (walk[n-1].length == 0) walk.pop();
|
||||
if (walk[0].length == 0) walk.shift();
|
||||
n = walk.length;
|
||||
|
||||
var parentid = 0;
|
||||
var id = -1;
|
||||
for(var i=0; i<n; i++) {
|
||||
id = this.Search(parentid, walk[i]);
|
||||
if (id == -1) {
|
||||
if (i < n-1) return {id: -1, parentid: -1, name: walk[i]}; // one name of the path cannot be found
|
||||
return {id: -1, parentid: parentid, name: walk[i]}; // the last element in the path does not exist, but the parent
|
||||
}
|
||||
parentid = id;
|
||||
}
|
||||
return {id: id, parentid: parentid, name: walk[i]};
|
||||
}
|
||||
// -----------------------------------------------------
|
||||
|
||||
FS.prototype.GetRecursiveList = function(dirid, list) {
|
||||
var id = this.inodes[dirid].firstid;
|
||||
while(id != -1) {
|
||||
list.push(id);
|
||||
if ((this.inodes[id].mode&S_IFMT) == S_IFDIR) {
|
||||
this.GetRecursiveList(id, list);
|
||||
}
|
||||
id = this.inodes[id].nextid;
|
||||
}
|
||||
}
|
||||
|
||||
FS.prototype.MergeFile = function(file) {
|
||||
throw "unimplemented";
|
||||
//DebugMessage("Merge path:" + file.name);
|
||||
//var ids = this.SearchPath(file.name);
|
||||
//if (ids.parentid == -1) return; // not even the path seems to exist
|
||||
//if (ids.id == -1) {
|
||||
// ids.id = this.CreateFile(ids.name, ids.parentid);
|
||||
//}
|
||||
//this.inodes[ids.id].data = file.data;
|
||||
//this.inodes[ids.id].size = file.data.length;
|
||||
}
|
||||
|
||||
|
||||
FS.prototype.Check = function() {
|
||||
for(var i=1; i<this.inodes.length; i++)
|
||||
{
|
||||
if (this.inodes[i].status == STATUS_INVALID) continue;
|
||||
if (this.inodes[i].nextid == i) {
|
||||
DebugMessage("Error in filesystem: file points to itself");
|
||||
abort();
|
||||
}
|
||||
|
||||
var inode = this.GetInode(i);
|
||||
if (inode.parentid < 0) {
|
||||
DebugMessage("Error in filesystem: negative parent id " + i);
|
||||
}
|
||||
var n = inode.name.length;
|
||||
if (n == 0) {
|
||||
DebugMessage("Error in filesystem: inode with no name and id " + i);
|
||||
}
|
||||
|
||||
for (var j in inode.name) {
|
||||
var c = inode.name.charCodeAt(j);
|
||||
if (c < 32) {
|
||||
DebugMessage("Error in filesystem: Unallowed char in filename");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
FS.prototype.FillDirectory = function(dirid) {
|
||||
var inode = this.GetInode(dirid);
|
||||
if (!inode.updatedir) return;
|
||||
var parentid = inode.parentid;
|
||||
if (parentid == -1) parentid = 0; // if root directory point to the root directory
|
||||
|
||||
// first get size
|
||||
var size = 0;
|
||||
var id = this.inodes[dirid].firstid;
|
||||
while(id != -1) {
|
||||
size += 13 + 8 + 1 + 2 + UTF8Length(this.inodes[id].name);
|
||||
id = this.inodes[id].nextid;
|
||||
}
|
||||
|
||||
size += 13 + 8 + 1 + 2 + 1; // "." entry
|
||||
size += 13 + 8 + 1 + 2 + 2; // ".." entry
|
||||
//DebugMessage("size of dir entry: " + size);
|
||||
var data = this.inodedata[dirid] = new Uint8Array(size);
|
||||
inode.waswritten = true;
|
||||
inode.size = size;
|
||||
|
||||
var offset = 0x0;
|
||||
offset += Marshall(
|
||||
["Q", "d", "b", "s"],
|
||||
[this.inodes[dirid].qid,
|
||||
offset+13+8+1+2+1,
|
||||
this.inodes[dirid].mode >> 12,
|
||||
"."],
|
||||
data, offset);
|
||||
|
||||
offset += Marshall(
|
||||
["Q", "d", "b", "s"],
|
||||
[this.inodes[parentid].qid,
|
||||
offset+13+8+1+2+2,
|
||||
this.inodes[parentid].mode >> 12,
|
||||
".."],
|
||||
data, offset);
|
||||
|
||||
id = this.inodes[dirid].firstid;
|
||||
while(id != -1) {
|
||||
offset += Marshall(
|
||||
["Q", "d", "b", "s"],
|
||||
[this.inodes[id].qid,
|
||||
offset+13+8+1+2+UTF8Length(this.inodes[id].name),
|
||||
this.inodes[id].mode >> 12,
|
||||
this.inodes[id].name],
|
||||
data, offset);
|
||||
id = this.inodes[id].nextid;
|
||||
}
|
||||
inode.updatedir = false;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------
|
||||
|
||||
// only support for security.capabilities
|
||||
// should return a "struct vfs_cap_data" defined in
|
||||
// linux/capability for format
|
||||
// check also:
|
||||
// sys/capability.h
|
||||
// http://lxr.free-electrons.com/source/security/commoncap.c#L376
|
||||
// http://man7.org/linux/man-pages/man7/capabilities.7.html
|
||||
// http://man7.org/linux/man-pages/man8/getcap.8.html
|
||||
// http://man7.org/linux/man-pages/man3/libcap.3.html
|
||||
FS.prototype.PrepareCAPs = function(id) {
|
||||
var inode = this.GetInode(id);
|
||||
if (inode.caps) return inode.caps.length;
|
||||
inode.caps = new Uint8Array(12);
|
||||
// format is little endian
|
||||
// magic_etc (revision=0x01: 12 bytes)
|
||||
inode.caps[0] = 0x00;
|
||||
inode.caps[1] = 0x00;
|
||||
inode.caps[2] = 0x00;
|
||||
inode.caps[3] = 0x01;
|
||||
// permitted (full capabilities)
|
||||
inode.caps[4] = 0xFF;
|
||||
inode.caps[5] = 0xFF;
|
||||
inode.caps[6] = 0xFF;
|
||||
inode.caps[7] = 0xFF;
|
||||
// inheritable (full capabilities
|
||||
inode.caps[8] = 0xFF;
|
||||
inode.caps[9] = 0xFF;
|
||||
inode.caps[10] = 0xFF;
|
||||
inode.caps[11] = 0xFF;
|
||||
|
||||
return inode.caps.length;
|
||||
}
|
||||
|
||||
FS.prototype.ClearCache = function()
|
||||
{
|
||||
for(var id in this.inodedata)
|
||||
{
|
||||
if(!this.inodes[id].waswritten)
|
||||
{
|
||||
delete this.inodedata[id];
|
||||
}
|
||||
}
|
||||
};
|
108
lib/jor1k.js
Normal file
108
lib/jor1k.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
"use strict";
|
||||
|
||||
// jor1k compatibility
|
||||
|
||||
var VIRTIO_MAGIC_REG = 0x0;
|
||||
var VIRTIO_VERSION_REG = 0x4;
|
||||
var VIRTIO_DEVICE_REG = 0x8;
|
||||
var VIRTIO_VENDOR_REG = 0xc;
|
||||
var VIRTIO_HOSTFEATURES_REG = 0x10;
|
||||
var VIRTIO_HOSTFEATURESSEL_REG = 0x14;
|
||||
var VIRTIO_GUESTFEATURES_REG = 0x20;
|
||||
var VIRTIO_GUESTFEATURESSEL_REG = 0x24;
|
||||
var VIRTIO_GUEST_PAGE_SIZE_REG = 0x28;
|
||||
var VIRTIO_QUEUESEL_REG = 0x30;
|
||||
var VIRTIO_QUEUENUMMAX_REG = 0x34;
|
||||
var VIRTIO_QUEUENUM_REG = 0x38;
|
||||
var VIRTIO_QUEUEALIGN_REG = 0x3C;
|
||||
var VIRTIO_QUEUEPFN_REG = 0x40;
|
||||
var VIRTIO_QUEUENOTIFY_REG = 0x50;
|
||||
var VIRTIO_INTERRUPTSTATUS_REG = 0x60;
|
||||
var VIRTIO_INTERRUPTACK_REG = 0x64;
|
||||
var VIRTIO_STATUS_REG = 0x70;
|
||||
|
||||
var VRING_DESC_F_NEXT = 1; /* This marks a buffer as continuing via the next field. */
|
||||
var VRING_DESC_F_WRITE = 2; /* This marks a buffer as write-only (otherwise read-only). */
|
||||
var VRING_DESC_F_INDIRECT = 4; /* This means the buffer contains a list of buffer descriptors. */
|
||||
|
||||
|
||||
function Swap16(x)
|
||||
{
|
||||
// OR1K is big endian, therefore no conversion needed for us
|
||||
return x;
|
||||
}
|
||||
function Swap32(x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
function abort()
|
||||
{
|
||||
if(DEBUG)
|
||||
{
|
||||
throw "abort";
|
||||
}
|
||||
}
|
||||
var hex8 = h;
|
||||
|
||||
var DebugMessage;
|
||||
|
||||
if(DEBUG)
|
||||
{
|
||||
DebugMessage = console.log.bind(console);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugMessage = function() {};
|
||||
}
|
||||
|
||||
|
||||
function LoadXMLResource(url, OnSuccess, OnError) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', url, true);
|
||||
//req.overrideMimeType('text/xml');
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState != 4) {
|
||||
return;
|
||||
}
|
||||
if ((req.status != 200) && (req.status != 0)) {
|
||||
OnError("Error: Could not load XML file " + url);
|
||||
return;
|
||||
}
|
||||
OnSuccess(req.responseText);
|
||||
};
|
||||
req.send(null);
|
||||
}
|
||||
|
||||
|
||||
function LoadBinaryResource(url, OnSuccess, OnError) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', url, true);
|
||||
req.responseType = "arraybuffer";
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState != 4) {
|
||||
return;
|
||||
}
|
||||
if ((req.status != 200) && (req.status != 0)) {
|
||||
OnError("Error: Could not load file " + url);
|
||||
return;
|
||||
}
|
||||
var arrayBuffer = req.response;
|
||||
if (arrayBuffer) {
|
||||
OnSuccess(arrayBuffer);
|
||||
} else {
|
||||
OnError("Error: No data received from: " + url);
|
||||
}
|
||||
};
|
||||
/*
|
||||
req.onload = function(e)
|
||||
{
|
||||
var arrayBuffer = req.response;
|
||||
if (arrayBuffer) {
|
||||
OnLoadFunction(arrayBuffer);
|
||||
}
|
||||
};
|
||||
*/
|
||||
req.send(null);
|
||||
}
|
||||
|
166
lib/marshall.js
Normal file
166
lib/marshall.js
Normal file
|
@ -0,0 +1,166 @@
|
|||
// -------------------------------------------------
|
||||
// ------------------ Marshall ---------------------
|
||||
// -------------------------------------------------
|
||||
// helper functions for virtio and 9p.
|
||||
|
||||
|
||||
// Inserts data from an array to a byte aligned struct in memory
|
||||
function Marshall(typelist, input, struct, offset) {
|
||||
var item;
|
||||
var size = 0;
|
||||
for (var i=0; i < typelist.length; i++) {
|
||||
item = input[i];
|
||||
switch (typelist[i]) {
|
||||
case "w":
|
||||
struct[offset++] = item & 0xFF;
|
||||
struct[offset++] = (item >> 8) & 0xFF;
|
||||
struct[offset++] = (item >> 16) & 0xFF;
|
||||
struct[offset++] = (item >> 24) & 0xFF;
|
||||
size += 4;
|
||||
break;
|
||||
case "d": // double word
|
||||
struct[offset++] = item & 0xFF;
|
||||
struct[offset++] = (item >> 8) & 0xFF;
|
||||
struct[offset++] = (item >> 16) & 0xFF;
|
||||
struct[offset++] = (item >> 24) & 0xFF;
|
||||
struct[offset++] = 0x0;
|
||||
struct[offset++] = 0x0;
|
||||
struct[offset++] = 0x0;
|
||||
struct[offset++] = 0x0;
|
||||
size += 8;
|
||||
break;
|
||||
case "h":
|
||||
struct[offset++] = item & 0xFF;
|
||||
struct[offset++] = item >> 8;
|
||||
size += 2;
|
||||
break;
|
||||
case "b":
|
||||
struct[offset++] = item;
|
||||
size += 1;
|
||||
break;
|
||||
case "s":
|
||||
var lengthoffset = offset;
|
||||
var length = 0;
|
||||
struct[offset++] = 0; // set the length later
|
||||
struct[offset++] = 0;
|
||||
size += 2;
|
||||
for (var j in item) {
|
||||
var utf8 = UnicodeToUTF8Stream(item.charCodeAt(j));
|
||||
utf8.forEach( function(c) {
|
||||
struct[offset++] = c;
|
||||
size += 1;
|
||||
length++;
|
||||
});
|
||||
}
|
||||
struct[lengthoffset+0] = length & 0xFF;
|
||||
struct[lengthoffset+1] = (length >> 8) & 0xFF;
|
||||
break;
|
||||
case "Q":
|
||||
Marshall(["b", "w", "d"], [item.type, item.version, item.path], struct, offset)
|
||||
offset += 13;
|
||||
size += 13;
|
||||
break;
|
||||
default:
|
||||
DebugMessage("Marshall: Unknown type=" + typelist[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
};
|
||||
|
||||
|
||||
// Extracts data from a byte aligned struct in memory to an array
|
||||
function Unmarshall(typelist, struct, offset) {
|
||||
var output = [];
|
||||
for (var i=0; i < typelist.length; i++) {
|
||||
switch (typelist[i]) {
|
||||
case "w":
|
||||
var val = struct[offset++];
|
||||
val += struct[offset++] << 8;
|
||||
val += struct[offset++] << 16;
|
||||
val += (struct[offset++] << 24) >>> 0;
|
||||
output.push(val);
|
||||
break;
|
||||
case "d":
|
||||
var val = struct[offset++];
|
||||
val += struct[offset++] << 8;
|
||||
val += struct[offset++] << 16;
|
||||
val += (struct[offset++] << 24) >>> 0;
|
||||
offset += 4;
|
||||
output.push(val);
|
||||
break;
|
||||
case "h":
|
||||
var val = struct[offset++];
|
||||
output.push(val + (struct[offset++] << 8));
|
||||
break;
|
||||
case "b":
|
||||
output.push(struct[offset++]);
|
||||
break;
|
||||
case "s":
|
||||
var len = struct[offset++];
|
||||
len += struct[offset++] << 8;
|
||||
var str = '';
|
||||
var utf8converter = new UTF8StreamToUnicode();
|
||||
for (var j=0; j < len; j++) {
|
||||
var c = utf8converter.Put(struct[offset++])
|
||||
if (c == -1) continue;
|
||||
str += String.fromCharCode(c);
|
||||
}
|
||||
output.push(str);
|
||||
break;
|
||||
default:
|
||||
DebugMessage("Error in Unmarshall: Unknown type=" + typelist[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
// Extracts data from a byte aligned struct in memory to an array
|
||||
function Unmarshall2(typelist, GetByte) {
|
||||
var output = [];
|
||||
for (var i=0; i < typelist.length; i++) {
|
||||
switch (typelist[i]) {
|
||||
case "w":
|
||||
var val = GetByte();
|
||||
val += GetByte() << 8;
|
||||
val += GetByte() << 16;
|
||||
val += (GetByte() << 24) >>> 0;
|
||||
output.push(val);
|
||||
break;
|
||||
case "d":
|
||||
var val = GetByte();
|
||||
val += GetByte() << 8;
|
||||
val += GetByte() << 16;
|
||||
val += (GetByte() << 24) >>> 0;
|
||||
GetByte();GetByte();GetByte();GetByte();
|
||||
output.push(val);
|
||||
break;
|
||||
case "h":
|
||||
var val = GetByte();
|
||||
output.push(val + (GetByte() << 8));
|
||||
break;
|
||||
case "b":
|
||||
output.push(GetByte());
|
||||
break;
|
||||
case "s":
|
||||
var len = GetByte();
|
||||
len += GetByte() << 8;
|
||||
var str = '';
|
||||
var utf8converter = new UTF8StreamToUnicode();
|
||||
for (var j=0; j < len; j++) {
|
||||
var c = utf8converter.Put(GetByte())
|
||||
if (c == -1) continue;
|
||||
str += String.fromCharCode(c);
|
||||
}
|
||||
output.push(str);
|
||||
break;
|
||||
default:
|
||||
DebugMessage("Error in Unmarshall2: Unknown type=" + typelist[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
61
lib/utf8.js
Normal file
61
lib/utf8.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
// -------------------------------------------------
|
||||
// ------------------ UTF8 Helpers -----------------
|
||||
// -------------------------------------------------
|
||||
|
||||
"use strict";
|
||||
|
||||
/** @constructor */
|
||||
function UTF8StreamToUnicode() {
|
||||
|
||||
this.stream = new Uint8Array(5);
|
||||
this.ofs = 0;
|
||||
|
||||
this.Put = function(key) {
|
||||
this.stream[this.ofs] = key;
|
||||
this.ofs++;
|
||||
switch(this.ofs) {
|
||||
case 1:
|
||||
if (this.stream[0] < 128) {
|
||||
this.ofs = 0;
|
||||
return this.stream[0];
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if ((this.stream[0]&0xE0) == 0xC0)
|
||||
if ((this.stream[1]&0xC0) == 0x80) {
|
||||
this.ofs = 0;
|
||||
return ((this.stream[0]&0x1F)<<6) | (this.stream[1]&0x3F);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
break;
|
||||
|
||||
case 4:
|
||||
break;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
this.ofs = 0;
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
function UnicodeToUTF8Stream(key)
|
||||
{
|
||||
if (key < 0x80) return [key];
|
||||
if (key < 0x800) return [0xC0|((key>>6)&0x1F), 0x80|(key&0x3F)];
|
||||
}
|
||||
|
||||
function UTF8Length(s)
|
||||
{
|
||||
var length = 0;
|
||||
for(var i=0; i<s.length; i++) {
|
||||
var c = s.charCodeAt(i);
|
||||
length += c<128?1:2;
|
||||
}
|
||||
return length;
|
||||
}
|
11
loader.js
11
loader.js
|
@ -2,13 +2,14 @@
|
|||
|
||||
(function()
|
||||
{
|
||||
var
|
||||
CORE_FILES = "const.js io.js main.js ide.js fpu.js pci.js floppy.js " +
|
||||
var CORE_FILES = "const.js io.js main.js ide.js fpu.js pci.js floppy.js " +
|
||||
"memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js acpi.js hpet.js " +
|
||||
"ne2k.js cpu_state.js",
|
||||
BROWSER_FILES = "main.js screen.js keyboard.js mouse.js serial.js lib.js network.js",
|
||||
LIB_FILES = "esprima.js walk.js";
|
||||
"ne2k.js cpu_state.js virtio.js";
|
||||
var BROWSER_FILES = "main.js screen.js keyboard.js mouse.js serial.js lib.js network.js";
|
||||
var LIB_FILES = "esprima.js walk.js";
|
||||
|
||||
// jor1k stuff
|
||||
LIB_FILES += " jor1k.js 9p.js filesystem.js marshall.js utf8.js";
|
||||
|
||||
load_scripts("cpu.js", "build/");
|
||||
load_scripts(CORE_FILES, "src/");
|
||||
|
|
|
@ -743,6 +743,11 @@ v86.prototype.init = function(settings)
|
|||
{
|
||||
this.devices.net = new Ne2k(this, settings.network_adapter);
|
||||
}
|
||||
|
||||
if(settings.fs9p)
|
||||
{
|
||||
this.devices.virtio = new VirtIO(this, settings.fs9p);
|
||||
}
|
||||
}
|
||||
|
||||
if(DEBUG)
|
||||
|
|
344
src/virtio.js
Normal file
344
src/virtio.js
Normal file
|
@ -0,0 +1,344 @@
|
|||
"use strict";
|
||||
|
||||
|
||||
/** @constructor */
|
||||
function VirtIO(cpu, filesystem)
|
||||
{
|
||||
// http://ozlabs.org/~rusty/virtio-spec/virtio-0.9.5.pdf
|
||||
|
||||
this.pci_space = [
|
||||
0xf4, 0x1a, 0x09, 0x10, 0x07, 0x05, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0xa8, 0x00, 0x00, 0x00, 0x10, 0xbf, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x1a, 0x09, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00,
|
||||
];
|
||||
this.pci_id = 0x06 << 3;
|
||||
this.pci_bars = [
|
||||
{
|
||||
size: 128 * 1024,
|
||||
},
|
||||
];
|
||||
|
||||
cpu.devices.pci.register_device(this);
|
||||
|
||||
var io = cpu.io;
|
||||
|
||||
io.register_read(0xA800, this, undefined, undefined, function()
|
||||
{
|
||||
// device features
|
||||
return 1;
|
||||
});
|
||||
|
||||
io.register_write(0xA804, this, undefined, undefined, function(data)
|
||||
{
|
||||
// write guest features
|
||||
dbg_log("Guest feature selection: " + h(data, 8), LOG_VIRTIO);
|
||||
});
|
||||
|
||||
io.register_write(0xA80E, this, undefined, function(data)
|
||||
{
|
||||
// rw queue select
|
||||
dbg_log("Queue select: " + h(data, 4), LOG_VIRTIO);
|
||||
this.queue_select = data;
|
||||
}, undefined);
|
||||
|
||||
io.register_read(0xA80C, this, undefined, function()
|
||||
{
|
||||
// read queue size
|
||||
dbg_log("Read queue size", LOG_VIRTIO);
|
||||
return this.queue_size;
|
||||
}, undefined);
|
||||
|
||||
io.register_read(0xA808, this, undefined, undefined, function()
|
||||
{
|
||||
// rw queue address
|
||||
dbg_log("Read queue address", LOG_VIRTIO);
|
||||
|
||||
if(this.queue_select === 0)
|
||||
{
|
||||
return this.queue_address;
|
||||
}
|
||||
else
|
||||
{
|
||||
// queue does not exist
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
io.register_write(0xA808, this, undefined, undefined, function(data)
|
||||
{
|
||||
// rw queue address
|
||||
dbg_log("Write queue address: " + h(data, 8), LOG_VIRTIO);
|
||||
this.queue_address = data;
|
||||
});
|
||||
|
||||
io.register_write(0xA812, this, function(data)
|
||||
{
|
||||
dbg_log("Write device status: " + h(data, 2), LOG_VIRTIO);
|
||||
this.device_status = data;
|
||||
});
|
||||
|
||||
io.register_read(0xA812, this, function()
|
||||
{
|
||||
dbg_log("Read device status", LOG_VIRTIO);
|
||||
return this.device_status;
|
||||
});
|
||||
|
||||
io.register_read(0xA813, this, function()
|
||||
{
|
||||
dbg_log("Read isr", LOG_VIRTIO);
|
||||
|
||||
// reading resets the isr
|
||||
var isr = this.isr;
|
||||
this.isr = 0;
|
||||
return isr;
|
||||
});
|
||||
|
||||
io.register_write(0xA810, this, undefined, function(data)
|
||||
{
|
||||
dbg_log("Write queue notify: " + h(data, 4), LOG_VIRTIO);
|
||||
|
||||
// only queue 0 supported
|
||||
dbg_assert(data === 0);
|
||||
|
||||
var queue_start = this.queue_address << 12;
|
||||
var ring_start = queue_start + 16 * this.queue_size;
|
||||
var ring_desc_start = ring_start + 4;
|
||||
|
||||
var flags = this.memory.read16(ring_start),
|
||||
// index of the next free ring
|
||||
idx = this.memory.read16(ring_start + 2);
|
||||
|
||||
dbg_log("idx=" + h(idx, 4), LOG_VIRTIO);
|
||||
//dbg_assert(idx < this.queue_size);
|
||||
|
||||
var mask = this.queue_size - 1;
|
||||
idx &= mask;
|
||||
|
||||
while(this.last_idx !== idx)
|
||||
{
|
||||
var desc_idx = this.memory.read16(ring_desc_start + this.last_idx * 2);
|
||||
this.handle_descriptor(desc_idx);
|
||||
|
||||
this.last_idx = this.last_idx + 1 & mask;
|
||||
}
|
||||
});
|
||||
|
||||
this.irq = 0xC;
|
||||
this.pic = cpu.devices.pic;
|
||||
|
||||
this.queue_select = 0;
|
||||
this.device_status = 0;
|
||||
this.isr = 0;
|
||||
|
||||
// these should be stored per queue if there is more than one queue
|
||||
this.last_idx = 0;
|
||||
this.queue_size = 32;
|
||||
this.queue_address = 0;
|
||||
|
||||
|
||||
this.memory = cpu.memory;
|
||||
|
||||
for(var i = 0; i < 128; i++)
|
||||
{
|
||||
io.register_read(0xA814 + i, this, function(port)
|
||||
{
|
||||
dbg_log("Read device " + h(port), LOG_VIRTIO);
|
||||
//dbg_assert(typeof this.device.configspace[port] === "number");
|
||||
|
||||
return this.device.configspace[port];
|
||||
}.bind(this, i), undefined, undefined);
|
||||
|
||||
io.register_write(0xA814 + i, this, function(port, data)
|
||||
{
|
||||
dbg_log("Write device " + h(port) + ": " + h(data, 2), LOG_VIRTIO);
|
||||
}.bind(this, i), undefined, undefined);
|
||||
}
|
||||
|
||||
// should be generalized to support more devices than just the filesystem
|
||||
this.device = new Virtio9p(filesystem);
|
||||
this.device.SendReply = this.device_reply.bind(this);
|
||||
|
||||
this._state_skip = ["memory", "pic"];
|
||||
this._state_restore = function()
|
||||
{
|
||||
this.device.SendReply = this.device_reply.bind(this);
|
||||
};
|
||||
}
|
||||
|
||||
VirtIO.prototype.handle_descriptor = function(idx)
|
||||
{
|
||||
var next = idx;
|
||||
var desc_start = this.queue_address << 12;
|
||||
|
||||
var buffer_idx = 0;
|
||||
var buffers = [];
|
||||
|
||||
do
|
||||
{
|
||||
var addr = desc_start + next * 16;
|
||||
var flags = this.memory.read16(addr + 12);
|
||||
|
||||
if(flags & VRING_DESC_F_WRITE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(flags & VRING_DESC_F_INDIRECT) {
|
||||
dbg_assert(false, "unsupported");
|
||||
}
|
||||
|
||||
var addr_low = this.memory.read32s(addr);
|
||||
var addr_high = this.memory.read32s(addr + 4);
|
||||
var len = this.memory.read32s(addr + 8) >>> 0;
|
||||
|
||||
buffers.push({
|
||||
addr_low: addr_low,
|
||||
addr_high: addr_high,
|
||||
len: len,
|
||||
});
|
||||
|
||||
dbg_log("descriptor: addr=" + h(addr_high, 8) + ":" + h(addr_low, 8) +
|
||||
" len=" + h(len, 8) + " flags=" + h(flags, 4) + " next=" + h(next, 4), LOG_VIRTIO);
|
||||
|
||||
if(flags & VRING_DESC_F_NEXT)
|
||||
{
|
||||
next = this.memory.read16(addr + 14);
|
||||
dbg_assert(next < this.queue_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
next = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while(true);
|
||||
|
||||
var buffer_len = -1;
|
||||
var pointer = 0;
|
||||
|
||||
var infos = {
|
||||
start: idx,
|
||||
next: next,
|
||||
};
|
||||
|
||||
this.device.ReceiveRequest(infos, function()
|
||||
{
|
||||
// return one byte
|
||||
|
||||
if(pointer >= buffer_len)
|
||||
{
|
||||
if(buffer_idx === buffers.length)
|
||||
{
|
||||
dbg_log("Read more data than descriptor has", LOG_VIRTIO);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var buf = buffers[buffer_idx++];
|
||||
|
||||
addr_low = buf.addr_low;
|
||||
buffer_len = buf.len;
|
||||
pointer = 0;
|
||||
}
|
||||
|
||||
return this.memory.read8(addr_low + pointer++);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
VirtIO.prototype.device_reply = function(infos)
|
||||
{
|
||||
if(infos.next === -1)
|
||||
{
|
||||
dbg_log("Reply to invalid index", LOG_VIRTIO);
|
||||
return;
|
||||
}
|
||||
|
||||
var mask = this.queue_size - 1;
|
||||
var result_length = this.device.replybuffersize;
|
||||
|
||||
var next = infos.next;
|
||||
var desc_start = this.queue_address << 12;
|
||||
|
||||
var buffer_idx = 0;
|
||||
var buffers = [];
|
||||
|
||||
do
|
||||
{
|
||||
var addr = desc_start + next * 16;
|
||||
var flags = this.memory.read16(addr + 12);
|
||||
|
||||
if((flags & VRING_DESC_F_WRITE) === 0)
|
||||
{
|
||||
dbg_log("Bug: Readonly ring after writeonly ring", LOG_VIRTIO);
|
||||
break;
|
||||
}
|
||||
|
||||
var addr_low = this.memory.read32s(addr);
|
||||
var addr_high = this.memory.read32s(addr + 4);
|
||||
var len = this.memory.read32s(addr + 8) >>> 0;
|
||||
|
||||
buffers.push({
|
||||
addr_low: addr_low,
|
||||
addr_high: addr_high,
|
||||
len: len,
|
||||
});
|
||||
|
||||
dbg_log("descriptor: addr=" + h(addr_high, 8) + ":" + h(addr_low, 8) +
|
||||
" len=" + h(len, 8) + " flags=" + h(flags, 4) + " next=" + h(next, 4), LOG_VIRTIO);
|
||||
|
||||
if(flags & VRING_DESC_F_NEXT)
|
||||
{
|
||||
next = this.memory.read16(addr + 14);
|
||||
dbg_assert(next < this.queue_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while(true);
|
||||
|
||||
var buffer_len = -1;
|
||||
var pointer = 0;
|
||||
|
||||
for(var i = 0; i < result_length; i++)
|
||||
{
|
||||
var data = this.device.replybuffer[i];
|
||||
|
||||
if(pointer >= buffer_len)
|
||||
{
|
||||
if(buffer_idx === buffers.length)
|
||||
{
|
||||
dbg_log("Write more data than descriptor has", LOG_VIRTIO);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var buf = buffers[buffer_idx++];
|
||||
|
||||
addr_low = buf.addr_low;
|
||||
buffer_len = buf.len;
|
||||
pointer = 0;
|
||||
}
|
||||
|
||||
this.memory.write8(addr_low + pointer++, data);
|
||||
}
|
||||
|
||||
var used_desc_start = (this.queue_address << 12) + 16 * this.queue_size + 4 + 2 * this.queue_size;
|
||||
used_desc_start = used_desc_start + 4095 & ~4095;
|
||||
|
||||
var flags = this.memory.read16(used_desc_start);
|
||||
var used_idx = this.memory.read16(used_desc_start + 2);
|
||||
this.memory.write16(used_desc_start + 2, used_idx + 1);
|
||||
|
||||
dbg_log("used descriptor: addr=" + h(used_desc_start, 8) + " flags=" + h(flags, 4) + " idx=" + h(used_idx, 4), LOG_VIRTIO);
|
||||
|
||||
used_idx &= mask;
|
||||
var used_desc_offset = used_desc_start + 4 + used_idx * 8;
|
||||
this.memory.write32(used_desc_offset, infos.start);
|
||||
this.memory.write32(used_desc_offset + 4, result_length);
|
||||
|
||||
this.isr |= 1;
|
||||
this.pic.push_irq(this.irq);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in a new issue