mirror of
https://github.com/copy/v86.git
synced 2025-12-31 04:23:15 +00:00
update implementation based on feedback and added a pre-made websocket proxy
This commit is contained in:
parent
f020b016f5
commit
1acdbbe785
4 changed files with 326 additions and 71 deletions
367
lib/9p.js
367
lib/9p.js
|
|
@ -87,39 +87,17 @@ function range(size)
|
|||
return Array.from(Array(size).keys());
|
||||
}
|
||||
|
||||
/** @typedef {function(!Uint8Array, function(!Uint8Array):void):void} */
|
||||
export let P9RequestHandler;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {(!FS|!P9RequestHandler)} fs_or_handler
|
||||
* @param {CPU} cpu
|
||||
* @param {Function} receive
|
||||
*/
|
||||
export function Virtio9p(fs_or_handler, cpu, bus) {
|
||||
if (typeof fs_or_handler === "function") {
|
||||
/** @type {(Function|undefined)} */
|
||||
this.handle_fn = fs_or_handler;
|
||||
this.tag_bufchain = new Map();
|
||||
} else {
|
||||
/** @type {(FS|undefined)} */
|
||||
this.fs = fs_or_handler;
|
||||
}
|
||||
|
||||
/** @const @type {BusConnector} */
|
||||
this.bus = bus;
|
||||
|
||||
function Virtio9pDevice(cpu, receive) {
|
||||
//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_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
|
||||
this.configspace_taglen = this.configspace_tagname.length; // num bytes
|
||||
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.fids = [];
|
||||
|
||||
/** @type {VirtIO} */
|
||||
this.virtio = new VirtIO(cpu,
|
||||
|
|
@ -164,7 +142,7 @@ export function Virtio9p(fs_or_handler, cpu, bus) {
|
|||
while(this.virtqueue.has_request())
|
||||
{
|
||||
const bufchain = this.virtqueue.pop_request();
|
||||
this.ReceiveRequest(bufchain);
|
||||
receive(bufchain);
|
||||
}
|
||||
this.virtqueue.notify_me_after(0);
|
||||
// Don't flush replies here: async replies are not completed yet.
|
||||
|
|
@ -200,13 +178,37 @@ export function Virtio9p(fs_or_handler, cpu, bus) {
|
|||
this.virtqueue = this.virtio.queues[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {FS} filesystem
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
export function Virtio9p(filesystem, cpu, bus) {
|
||||
/** @type {FS} */
|
||||
this.fs = filesystem;
|
||||
|
||||
/** @const @type {BusConnector} */
|
||||
this.bus = bus;
|
||||
|
||||
|
||||
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.fids = [];
|
||||
|
||||
this.device = new Virtio9pDevice(cpu, this.ReceiveRequest.bind(this));
|
||||
}
|
||||
|
||||
Virtio9p.prototype.get_state = function()
|
||||
{
|
||||
var state = [];
|
||||
|
||||
state[0] = this.configspace_tagname;
|
||||
state[1] = this.configspace_taglen;
|
||||
state[2] = this.virtio;
|
||||
state[0] = this.device.configspace_tagname;
|
||||
state[1] = this.device.configspace_taglen;
|
||||
state[2] = this.device.virtio;
|
||||
state[3] = this.VERSION;
|
||||
state[4] = this.BLOCKSIZE;
|
||||
state[5] = this.msize;
|
||||
|
|
@ -220,10 +222,10 @@ Virtio9p.prototype.get_state = function()
|
|||
|
||||
Virtio9p.prototype.set_state = function(state)
|
||||
{
|
||||
this.configspace_tagname = state[0];
|
||||
this.configspace_taglen = state[1];
|
||||
this.virtio.set_state(state[2]);
|
||||
this.virtqueue = this.virtio.queues[0];
|
||||
this.device.configspace_tagname = state[0];
|
||||
this.device.configspace_taglen = state[1];
|
||||
this.device.virtio.set_state(state[2]);
|
||||
this.device.virtqueue = this.device.virtio.queues[0];
|
||||
this.VERSION = state[3];
|
||||
this.BLOCKSIZE = state[4];
|
||||
this.msize = state[5];
|
||||
|
|
@ -233,9 +235,7 @@ Virtio9p.prototype.set_state = function(state)
|
|||
{
|
||||
return { inodeid: f[0], type: f[1], uid: f[2], dbg_name: f[3] };
|
||||
});
|
||||
if (this.fs) {
|
||||
this.fs.set_state(state[9]);
|
||||
}
|
||||
this.fs.set_state(state[9]);
|
||||
};
|
||||
|
||||
// Note: dbg_name is only used for debugging messages and may not be the same as the filename,
|
||||
|
|
@ -256,7 +256,7 @@ Virtio9p.prototype.update_dbg_name = function(idx, newname)
|
|||
|
||||
Virtio9p.prototype.reset = function() {
|
||||
this.fids = [];
|
||||
this.virtio.reset();
|
||||
this.device.virtio.reset();
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -280,8 +280,8 @@ Virtio9p.prototype.SendError = function (tag, errormsg, errorcode) {
|
|||
Virtio9p.prototype.SendReply = function (bufchain) {
|
||||
dbg_assert(this.replybuffersize >= 0, "9P: Negative replybuffersize");
|
||||
bufchain.set_next_blob(this.replybuffer.subarray(0, this.replybuffersize));
|
||||
this.virtqueue.push_reply(bufchain);
|
||||
this.virtqueue.flush_replies();
|
||||
this.device.virtqueue.push_reply(bufchain);
|
||||
this.device.virtqueue.flush_replies();
|
||||
};
|
||||
|
||||
Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
||||
|
|
@ -296,34 +296,6 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
var tag = header[2];
|
||||
//dbg_log("size:" + size + " id:" + id + " tag:" + tag, LOG_9P);
|
||||
|
||||
// if a 9p request handler was given
|
||||
if (this.handle_fn) {
|
||||
this.tag_bufchain.set(tag, bufchain);
|
||||
this.handle_fn(buffer, (replybuf) => {
|
||||
var reply_header = marshall.Unmarshall(["w", "b", "h"], replybuf, { offset: 0 });
|
||||
var reply_tag = reply_header[2];
|
||||
|
||||
// Create a new buffer for each response instead of reusing the same one
|
||||
this.replybuffer = new Uint8Array(replybuf.byteLength);
|
||||
this.replybuffer.set(replybuf);
|
||||
this.replybuffersize = replybuf.byteLength;
|
||||
|
||||
const bufchain = this.tag_bufchain.get(reply_tag);
|
||||
if (!bufchain) {
|
||||
console.error("No bufchain found for tag: " + reply_tag);
|
||||
return;
|
||||
}
|
||||
|
||||
bufchain.set_next_blob(replybuf);
|
||||
this.virtqueue.push_reply(bufchain);
|
||||
this.virtqueue.flush_replies();
|
||||
|
||||
this.tag_bufchain.delete(reply_tag);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, if a filesystem was given
|
||||
switch(id)
|
||||
{
|
||||
case 8: // statfs
|
||||
|
|
@ -920,3 +892,266 @@ Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
|
|||
//consistency checks if there are problems with the filesystem
|
||||
//this.fs.Check();
|
||||
};
|
||||
|
||||
/** @typedef {function(Uint8Array, function(Uint8Array):void):void} */
|
||||
export let P9Handler;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {P9Handler} handle_fn
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
export function Virtio9pHandler(handle_fn, cpu) {
|
||||
/** @type {P9Handler} */
|
||||
this.handle_fn = handle_fn;
|
||||
this.tag_bufchain = new Map();
|
||||
this.device = new Virtio9pDevice(cpu, async (bufchain) => {
|
||||
// TODO: split into header + data blobs to avoid unnecessary copying.
|
||||
const reqbuf = new Uint8Array(bufchain.length_readable);
|
||||
bufchain.get_next_blob(reqbuf);
|
||||
|
||||
var reqheader = marshall.Unmarshall(["w", "b", "h"], reqbuf, { offset : 0 });
|
||||
var reqtag = reqheader[2];
|
||||
|
||||
this.tag_bufchain.set(reqtag, bufchain);
|
||||
this.handle_fn(reqbuf, (replybuf) => {
|
||||
var replyheader = marshall.Unmarshall(["w", "b", "h"], replybuf, { offset: 0 });
|
||||
var replytag = replyheader[2];
|
||||
|
||||
const bufchain = this.tag_bufchain.get(replytag);
|
||||
if (!bufchain) {
|
||||
console.error("No bufchain found for tag: " + replytag);
|
||||
return;
|
||||
}
|
||||
|
||||
bufchain.set_next_blob(replybuf);
|
||||
this.device.virtqueue.push_reply(bufchain);
|
||||
this.device.virtqueue.flush_replies();
|
||||
|
||||
this.tag_bufchain.delete(replytag);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Virtio9pHandler.prototype.get_state = function()
|
||||
{
|
||||
var state = [];
|
||||
|
||||
state[0] = this.device.configspace_tagname;
|
||||
state[1] = this.device.configspace_taglen;
|
||||
state[2] = this.device.virtio;
|
||||
state[3] = this.tag_bufchain;
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
Virtio9pHandler.prototype.set_state = function(state)
|
||||
{
|
||||
this.device.configspace_tagname = state[0];
|
||||
this.device.configspace_taglen = state[1];
|
||||
this.device.virtio.set_state(state[2]);
|
||||
this.device.virtqueue = this.device.virtio.queues[0];
|
||||
this.tag_bufchain = state[3];
|
||||
};
|
||||
|
||||
|
||||
Virtio9pHandler.prototype.reset = function() {
|
||||
this.device.virtio.reset();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {CPU} cpu
|
||||
*/
|
||||
export function Virtio9pProxy(url, cpu)
|
||||
{
|
||||
this.socket = undefined;
|
||||
this.cpu = cpu;
|
||||
|
||||
// TODO: circular buffer?
|
||||
this.send_queue = [];
|
||||
this.url = url;
|
||||
|
||||
this.reconnect_interval = 10000;
|
||||
this.last_connect_attempt = Date.now() - this.reconnect_interval;
|
||||
this.send_queue_limit = 64;
|
||||
this.destroyed = false;
|
||||
|
||||
this.tag_bufchain = new Map();
|
||||
this.device = new Virtio9pDevice(cpu, async (bufchain) => {
|
||||
// TODO: split into header + data blobs to avoid unnecessary copying.
|
||||
const reqbuf = new Uint8Array(bufchain.length_readable);
|
||||
bufchain.get_next_blob(reqbuf);
|
||||
|
||||
const reqheader = marshall.Unmarshall(["w", "b", "h"], reqbuf, { offset : 0 });
|
||||
const reqtag = reqheader[2];
|
||||
|
||||
this.tag_bufchain.set(reqtag, bufchain);
|
||||
this.send(reqbuf);
|
||||
});
|
||||
}
|
||||
|
||||
Virtio9pProxy.prototype.get_state = function()
|
||||
{
|
||||
var state = [];
|
||||
|
||||
state[0] = this.device.configspace_tagname;
|
||||
state[1] = this.device.configspace_taglen;
|
||||
state[2] = this.device.virtio;
|
||||
state[3] = this.tag_bufchain;
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.set_state = function(state)
|
||||
{
|
||||
this.device.configspace_tagname = state[0];
|
||||
this.device.configspace_taglen = state[1];
|
||||
this.device.virtio.set_state(state[2]);
|
||||
this.device.virtqueue = this.device.virtio.queues[0];
|
||||
this.tag_bufchain = state[3];
|
||||
};
|
||||
|
||||
|
||||
Virtio9pProxy.prototype.reset = function() {
|
||||
this.device.virtio.reset();
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.handle_message = function(e)
|
||||
{
|
||||
const replybuf = new Uint8Array(e.data);
|
||||
const replyheader = marshall.Unmarshall(["w", "b", "h"], replybuf, { offset: 0 });
|
||||
const replytag = replyheader[2];
|
||||
|
||||
const bufchain = this.tag_bufchain.get(replytag);
|
||||
if (!bufchain) {
|
||||
console.error("Virtio9pProxy: No bufchain found for tag: " + replytag);
|
||||
return;
|
||||
}
|
||||
|
||||
bufchain.set_next_blob(replybuf);
|
||||
this.device.virtqueue.push_reply(bufchain);
|
||||
this.device.virtqueue.flush_replies();
|
||||
|
||||
this.tag_bufchain.delete(replytag);
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.handle_close = function(e)
|
||||
{
|
||||
//console.log("onclose", e);
|
||||
|
||||
if(!this.destroyed)
|
||||
{
|
||||
this.connect();
|
||||
setTimeout(this.connect.bind(this), this.reconnect_interval);
|
||||
}
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.handle_open = function(e)
|
||||
{
|
||||
//console.log("open", e);
|
||||
|
||||
for(var i = 0; i < this.send_queue.length; i++)
|
||||
{
|
||||
this.send(this.send_queue[i]);
|
||||
}
|
||||
|
||||
this.send_queue = [];
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.handle_error = function(e)
|
||||
{
|
||||
//console.log("onerror", e);
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.destroy = function()
|
||||
{
|
||||
this.destroyed = true;
|
||||
if(this.socket)
|
||||
{
|
||||
this.socket.close();
|
||||
}
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.connect = function()
|
||||
{
|
||||
if(typeof WebSocket === "undefined")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.socket)
|
||||
{
|
||||
var state = this.socket.readyState;
|
||||
|
||||
if(state === 0 || state === 1)
|
||||
{
|
||||
// already or almost there
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
|
||||
if(this.last_connect_attempt + this.reconnect_interval > now)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.last_connect_attempt = Date.now();
|
||||
|
||||
try
|
||||
{
|
||||
this.socket = new WebSocket(this.url);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.binaryType = "arraybuffer";
|
||||
|
||||
this.socket.onopen = this.handle_open.bind(this);
|
||||
this.socket.onmessage = this.handle_message.bind(this);
|
||||
this.socket.onclose = this.handle_close.bind(this);
|
||||
this.socket.onerror = this.handle_error.bind(this);
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.send = function(data)
|
||||
{
|
||||
//console.log("send", data);
|
||||
|
||||
if(!this.socket || this.socket.readyState !== 1)
|
||||
{
|
||||
this.send_queue.push(data);
|
||||
|
||||
if(this.send_queue.length > 2 * this.send_queue_limit)
|
||||
{
|
||||
this.send_queue = this.send_queue.slice(-this.send_queue_limit);
|
||||
}
|
||||
|
||||
this.connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.socket.send(data);
|
||||
}
|
||||
};
|
||||
|
||||
Virtio9pProxy.prototype.change_proxy = function(url)
|
||||
{
|
||||
this.url = url;
|
||||
|
||||
if(this.socket)
|
||||
{
|
||||
this.socket.onclose = function() {};
|
||||
this.socket.onerror = function() {};
|
||||
this.socket.close();
|
||||
this.socket = undefined;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -427,7 +427,12 @@ V86.prototype.continue_init = async function(emulator, options)
|
|||
settings.handle9p = options.filesystem.handle9p;
|
||||
}
|
||||
|
||||
if(options.filesystem && !options.filesystem.handle9p)
|
||||
if(options.filesystem && options.filesystem.proxy_url)
|
||||
{
|
||||
settings.proxy9p = options.filesystem.proxy_url;
|
||||
}
|
||||
|
||||
if(options.filesystem && !options.filesystem.handle9p && !options.filesystem.proxy_url)
|
||||
{
|
||||
var fs_url = options.filesystem.basefs;
|
||||
var base_url = options.filesystem.baseurl;
|
||||
|
|
|
|||
14
src/cpu.js
14
src/cpu.js
|
|
@ -33,7 +33,7 @@ import { IDEController } from "./ide.js";
|
|||
import { VirtioNet } from "./virtio_net.js";
|
||||
import { VGAScreen } from "./vga.js";
|
||||
import { VirtioBalloon } from "./virtio_balloon.js";
|
||||
import { Virtio9p } from "../lib/9p.js";
|
||||
import { Virtio9p, Virtio9pHandler, Virtio9pProxy } from "../lib/9p.js";
|
||||
|
||||
import { load_kernel } from "./kernel.js";
|
||||
|
||||
|
|
@ -1162,9 +1162,17 @@ CPU.prototype.init = function(settings, device_bus)
|
|||
this.devices.virtio_net = new VirtioNet(this, device_bus, settings.preserve_mac_from_state_image);
|
||||
}
|
||||
|
||||
if(settings.fs9p || settings.handle9p)
|
||||
if(settings.fs9p)
|
||||
{
|
||||
this.devices.virtio_9p = new Virtio9p(settings.fs9p || settings.handle9p, this, device_bus);
|
||||
this.devices.virtio_9p = new Virtio9p(settings.fs9p, this, device_bus);
|
||||
}
|
||||
else if(settings.handle9p)
|
||||
{
|
||||
this.devices.virtio_9p = new Virtio9pHandler(settings.handle9p, this);
|
||||
}
|
||||
else if(settings.proxy9p)
|
||||
{
|
||||
this.devices.virtio_9p = new Virtio9pProxy(settings.proxy9p, this);
|
||||
}
|
||||
if(settings.virtio_console)
|
||||
{
|
||||
|
|
|
|||
9
v86.d.ts
vendored
9
v86.d.ts
vendored
|
|
@ -238,9 +238,16 @@ export interface V86Options {
|
|||
/**
|
||||
* A function that will be called for each 9p request.
|
||||
* If specified, this will back Virtio9p instead of a filesystem.
|
||||
* Use this to connect Virtio9p to a custom 9p server.
|
||||
* Use this to build or connect to a custom 9p server.
|
||||
*/
|
||||
handle9p?: (reqbuf: Uint8Array, reply: (replybuf: Uint8Array) => void) => void;
|
||||
|
||||
/**
|
||||
* A URL to a websocket proxy for 9p.
|
||||
* If specified, this will back Virtio9p instead of a filesystem.
|
||||
* Use this to connect to a custom 9p server over websocket.
|
||||
*/
|
||||
proxy_url?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue