10
const VIRTIO_9P_F_MOUNT_TAG = 0;
12
const VIRTIO_9P_MAX_TAGLEN = 254;
25
var P9_SETATTR_MODE = 0x00000001;
26
var P9_SETATTR_UID = 0x00000002;
27
var P9_SETATTR_GID = 0x00000004;
28
var P9_SETATTR_SIZE = 0x00000008;
29
var P9_SETATTR_ATIME = 0x00000010;
30
var P9_SETATTR_MTIME = 0x00000020;
31
var P9_SETATTR_CTIME = 0x00000040;
32
var P9_SETATTR_ATIME_SET = 0x00000080;
33
var P9_SETATTR_MTIME_SET = 0x00000100;
35
var P9_STAT_MODE_DIR = 0x80000000;
36
var P9_STAT_MODE_APPEND = 0x40000000;
37
var P9_STAT_MODE_EXCL = 0x20000000;
38
var P9_STAT_MODE_MOUNT = 0x10000000;
39
var P9_STAT_MODE_AUTH = 0x08000000;
40
var P9_STAT_MODE_TMP = 0x04000000;
41
var P9_STAT_MODE_SYMLINK = 0x02000000;
42
var P9_STAT_MODE_LINK = 0x01000000;
43
var P9_STAT_MODE_DEVICE = 0x00800000;
44
var P9_STAT_MODE_NAMED_PIPE = 0x00200000;
45
var P9_STAT_MODE_SOCKET = 0x00100000;
46
var P9_STAT_MODE_SETUID = 0x00080000;
47
var P9_STAT_MODE_SETGID = 0x00040000;
48
var P9_STAT_MODE_SETVTX = 0x00010000;
50
const P9_LOCK_TYPE_RDLCK = 0;
51
const P9_LOCK_TYPE_WRLCK = 1;
52
const P9_LOCK_TYPE_UNLCK = 2;
53
const P9_LOCK_TYPES = Object.freeze(["shared", "exclusive", "unlock"]);
55
const P9_LOCK_FLAGS_BLOCK = 1;
56
const P9_LOCK_FLAGS_RECLAIM = 2;
58
const P9_LOCK_SUCCESS = 0;
59
const P9_LOCK_BLOCKED = 1;
60
const P9_LOCK_ERROR = 2;
61
const P9_LOCK_GRACE = 3;
73
function Virtio9p(filesystem, cpu, bus) {
82
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70];
83
this.configspace_taglen = this.configspace_tagname.length;
84
this.VERSION = "9P2000.L";
85
this.BLOCKSIZE = 8192;
87
this.replybuffer = new Uint8Array(this.msize*2);
88
this.replybuffersize = 0;
93
this.virtio = new VirtIO(cpu,
98
subsystem_device_id: 9,
101
initial_port: 0xA800,
111
VIRTIO_9P_F_MOUNT_TAG,
113
VIRTIO_F_RING_EVENT_IDX,
114
VIRTIO_F_RING_INDIRECT_DESC,
116
on_driver_ok: () => {},
120
initial_port: 0xA900,
121
single_handler: false,
128
dbg_assert(false, "Virtio9P Notified for non-existent queue: " + queue_id +
129
" (expected queue_id of 0)");
132
while(this.virtqueue.has_request())
134
const bufchain = this.virtqueue.pop_request();
135
this.ReceiveRequest(bufchain);
137
this.virtqueue.notify_me_after(0);
144
initial_port: 0xA700,
148
initial_port: 0xA600,
153
name: "mount tag length",
154
read: () => this.configspace_taglen,
157
].concat(v86util.range(VIRTIO_9P_MAX_TAGLEN).map(index =>
160
name: "mount tag name " + index,
162
read: () => this.configspace_tagname[index] || 0,
168
this.virtqueue = this.virtio.queues[0];
171
Virtio9p.prototype.get_state = function()
175
state[0] = this.configspace_tagname;
176
state[1] = this.configspace_taglen;
177
state[2] = this.virtio;
178
state[3] = this.VERSION;
179
state[4] = this.BLOCKSIZE;
180
state[5] = this.msize;
181
state[6] = this.replybuffer;
182
state[7] = this.replybuffersize;
183
state[8] = this.fids.map(function(f) { return [f.inodeid, f.type, f.uid, f.dbg_name]; });
189
Virtio9p.prototype.set_state = function(state)
191
this.configspace_tagname = state[0];
192
this.configspace_taglen = state[1];
193
this.virtio.set_state(state[2]);
194
this.virtqueue = this.virtio.queues[0];
195
this.VERSION = state[3];
196
this.BLOCKSIZE = state[4];
197
this.msize = state[5];
198
this.replybuffer = state[6];
199
this.replybuffersize = state[7];
200
this.fids = state[8].map(function(f)
202
return { inodeid: f[0], type: f[1], uid: f[2], dbg_name: f[3] };
204
this.fs.set_state(state[9]);
211
Virtio9p.prototype.Createfid = function(inodeid, type, uid, dbg_name) {
212
return {inodeid, type, uid, dbg_name};
215
Virtio9p.prototype.update_dbg_name = function(idx, newname)
217
for(const fid of this.fids)
219
if(fid.inodeid === idx) fid.dbg_name = newname;
223
Virtio9p.prototype.Reset = function() {
228
Virtio9p.prototype.BuildReply = function(id, tag, payloadsize) {
229
dbg_assert(payloadsize >= 0, "9P: Negative payload size");
230
marshall.Marshall(["w", "b", "h"], [payloadsize+7, id+1, tag], this.replybuffer, 0);
231
if ((payloadsize+7) >= this.replybuffer.length) {
232
message.Debug("Error in 9p: payloadsize exceeds maximum length");
236
this.replybuffersize = payloadsize+7;
240
Virtio9p.prototype.SendError = function (tag, errormsg, errorcode) {
242
var size = marshall.Marshall(["w"], [errorcode], this.replybuffer, 7);
243
this.BuildReply(6, tag, size);
246
Virtio9p.prototype.SendReply = function (bufchain) {
247
dbg_assert(this.replybuffersize >= 0, "9P: Negative replybuffersize");
248
bufchain.set_next_blob(this.replybuffer.subarray(0, this.replybuffersize));
249
this.virtqueue.push_reply(bufchain);
250
this.virtqueue.flush_replies();
253
Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
255
const buffer = new Uint8Array(bufchain.length_readable);
256
bufchain.get_next_blob(buffer);
258
const state = { offset : 0 };
259
var header = marshall.Unmarshall(["w", "b", "h"], buffer, state);
260
var size = header[0];
268
size = this.fs.GetTotalSize();
269
var space = this.fs.GetSpace();
272
req[1] = this.BLOCKSIZE;
273
req[2] = Math.floor(space/req[1]);
274
req[3] = req[2] - Math.floor(size/req[1]);
275
req[4] = req[2] - Math.floor(size/req[1]);
276
req[5] = this.fs.CountUsedInodes();
277
req[6] = this.fs.CountFreeInodes();
281
size = marshall.Marshall(["w", "w", "d", "d", "d", "d", "d", "d", "w"], req, this.replybuffer, 7);
282
this.BuildReply(id, tag, size);
283
this.SendReply(bufchain);
288
var req = marshall.Unmarshall(["w", "w"], buffer, state);
291
message.Debug("[open] fid=" + fid + ", mode=" + mode);
292
var idx = this.fids[fid].inodeid;
293
var inode = this.fs.GetInode(idx);
294
message.Debug("file open " + this.fids[fid].dbg_name);
296
var ret = this.fs.OpenInode(idx, mode);
298
this.fs.AddEvent(this.fids[fid].inodeid,
300
message.Debug("file opened " + this.fids[fid].dbg_name + " tag:"+tag);
303
req[1] = this.msize - 24;
304
marshall.Marshall(["Q", "w"], req, this.replybuffer, 7);
305
this.BuildReply(id, tag, 13+4);
306
this.SendReply(bufchain);
312
var req = marshall.Unmarshall(["w", "w", "s"], buffer, state);
316
message.Debug("[link] dfid=" + dfid + ", name=" + name);
318
var ret = this.fs.Link(this.fids[dfid].inodeid, this.fids[fid].inodeid, name);
322
let error_message = "";
323
if(ret === -EPERM) error_message = "Operation not permitted";
326
error_message = "Unknown error: " + (-ret);
327
dbg_assert(false, "[link]: Unexpected error code: " + (-ret));
329
this.SendError(tag, error_message, -ret);
330
this.SendReply(bufchain);
334
this.BuildReply(id, tag, 0);
335
this.SendReply(bufchain);
339
var req = marshall.Unmarshall(["w", "s", "s", "w"], buffer, state);
344
message.Debug("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid);
345
var idx = this.fs.CreateSymlink(name, this.fids[fid].inodeid, symgt);
346
var inode = this.fs.GetInode(idx);
347
inode.uid = this.fids[fid].uid;
349
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
350
this.BuildReply(id, tag, 13);
351
this.SendReply(bufchain);
355
var req = marshall.Unmarshall(["w", "s", "w", "w", "w", "w"], buffer, state);
362
message.Debug("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "");
363
var idx = this.fs.CreateNode(name, this.fids[fid].inodeid, major, minor);
364
var inode = this.fs.GetInode(idx);
366
inode.uid = this.fids[fid].uid;
368
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
369
this.BuildReply(id, tag, 13);
370
this.SendReply(bufchain);
375
var req = marshall.Unmarshall(["w"], buffer, state);
377
var inode = this.fs.GetInode(this.fids[fid].inodeid);
378
message.Debug("[readlink] fid=" + fid + " name=" + this.fids[fid].dbg_name + " target=" + inode.symlink);
379
size = marshall.Marshall(["s"], [inode.symlink], this.replybuffer, 7);
380
this.BuildReply(id, tag, size);
381
this.SendReply(bufchain);
386
var req = marshall.Unmarshall(["w", "s", "w", "w"], buffer, state);
391
message.Debug("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid);
392
var idx = this.fs.CreateDirectory(name, this.fids[fid].inodeid);
393
var inode = this.fs.GetInode(idx);
394
inode.mode = mode | S_IFDIR;
395
inode.uid = this.fids[fid].uid;
397
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
398
this.BuildReply(id, tag, 13);
399
this.SendReply(bufchain);
403
var req = marshall.Unmarshall(["w", "s", "w", "w", "w"], buffer, state);
409
this.bus.send("9p-create", [name, this.fids[fid].inodeid]);
410
message.Debug("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid);
411
var idx = this.fs.CreateFile(name, this.fids[fid].inodeid);
412
this.fids[fid].inodeid = idx;
413
this.fids[fid].type = FID_INODE;
414
this.fids[fid].dbg_name = name;
415
var inode = this.fs.GetInode(idx);
416
inode.uid = this.fids[fid].uid;
419
marshall.Marshall(["Q", "w"], [inode.qid, this.msize - 24], this.replybuffer, 7);
420
this.BuildReply(id, tag, 13+4);
421
this.SendReply(bufchain);
425
var req = marshall.Unmarshall(["w", "b", "w", "d", "d", "w", "s"], buffer, state);
428
var lock_length = req[4] === 0 ? Infinity : req[4];
429
var lock_request = this.fs.DescribeLock(req[1], req[3], lock_length, req[5], req[6]);
430
message.Debug("[lock] fid=" + fid +
431
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
432
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
434
var ret = this.fs.Lock(this.fids[fid].inodeid, lock_request, flags);
436
marshall.Marshall(["b"], [ret], this.replybuffer, 7);
437
this.BuildReply(id, tag, 1);
438
this.SendReply(bufchain);
442
var req = marshall.Unmarshall(["w", "b", "d", "d", "w", "s"], buffer, state);
444
var lock_length = req[3] === 0 ? Infinity : req[3];
445
var lock_request = this.fs.DescribeLock(req[1], req[2], lock_length, req[4], req[5]);
446
message.Debug("[getlock] fid=" + fid +
447
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
448
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
450
var ret = this.fs.GetLock(this.fids[fid].inodeid, lock_request);
455
ret.type = P9_LOCK_TYPE_UNLCK;
458
var ret_length = ret.length === Infinity ? 0 : ret.length;
460
size = marshall.Marshall(["b", "d", "d", "w", "s"],
461
[ret.type, ret.start, ret_length, ret.proc_id, ret.client_id],
462
this.replybuffer, 7);
464
this.BuildReply(id, tag, size);
465
this.SendReply(bufchain);
469
var req = marshall.Unmarshall(["w", "d"], buffer, state);
471
var inode = this.fs.GetInode(this.fids[fid].inodeid);
472
message.Debug("[getattr]: fid=" + fid + " name=" + this.fids[fid].dbg_name + " request mask=" + req[1]);
473
if(!inode || inode.status === STATUS_UNLINKED)
475
message.Debug("getattr: unlinked");
476
this.SendError(tag, "No such file or directory", ENOENT);
477
this.SendReply(bufchain);
489
req[5] = inode.nlinks;
490
req[6] = (inode.major<<8) | (inode.minor);
492
req[8] = this.BLOCKSIZE;
493
req[9] = Math.floor(inode.size/512+1);
494
req[10] = inode.atime;
496
req[12] = inode.mtime;
498
req[14] = inode.ctime;
515
], req, this.replybuffer, 7);
516
this.BuildReply(id, tag, 8 + 13 + 4 + 4+ 4 + 8*15);
517
this.SendReply(bufchain);
521
var req = marshall.Unmarshall(["w", "w",
529
var inode = this.fs.GetInode(this.fids[fid].inodeid);
530
message.Debug("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" + this.fids[fid].dbg_name);
531
if (req[1] & P9_SETATTR_MODE) {
534
if (req[1] & P9_SETATTR_UID) {
537
if (req[1] & P9_SETATTR_GID) {
540
if (req[1] & P9_SETATTR_ATIME) {
541
inode.atime = Math.floor((new Date()).getTime()/1000);
543
if (req[1] & P9_SETATTR_MTIME) {
544
inode.mtime = Math.floor((new Date()).getTime()/1000);
546
if (req[1] & P9_SETATTR_CTIME) {
547
inode.ctime = Math.floor((new Date()).getTime()/1000);
549
if (req[1] & P9_SETATTR_ATIME_SET) {
550
inode.atime = req[6];
552
if (req[1] & P9_SETATTR_MTIME_SET) {
553
inode.mtime = req[8];
555
if (req[1] & P9_SETATTR_SIZE) {
556
await this.fs.ChangeSize(this.fids[fid].inodeid, req[5]);
558
this.BuildReply(id, tag, 0);
559
this.SendReply(bufchain);
563
var req = marshall.Unmarshall(["w", "d"], buffer, state);
565
this.BuildReply(id, tag, 0);
566
this.SendReply(bufchain);
571
var req = marshall.Unmarshall(["w", "d", "w"], buffer, state);
575
var inode = this.fs.GetInode(this.fids[fid].inodeid);
576
if (id == 40) message.Debug("[treaddir]: fid=" + fid + " offset=" + offset + " count=" + count);
577
if (id == 116) message.Debug("[read]: fid=" + fid + " (" + this.fids[fid].dbg_name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
578
if(!inode || inode.status === STATUS_UNLINKED)
580
message.Debug("read/treaddir: unlinked");
581
this.SendError(tag, "No such file or directory", ENOENT);
582
this.SendReply(bufchain);
585
if (this.fids[fid].type == FID_XATTR) {
586
if (inode.caps.length < offset+count) count = inode.caps.length - offset;
587
for(var i=0; i<count; i++)
588
this.replybuffer[7+4+i] = inode.caps[offset+i];
589
marshall.Marshall(["w"], [count], this.replybuffer, 7);
590
this.BuildReply(id, tag, 4 + count);
591
this.SendReply(bufchain);
593
this.fs.OpenInode(this.fids[fid].inodeid, undefined);
594
const inodeid = this.fids[fid].inodeid;
596
count = Math.min(count, this.replybuffer.length - (7 + 4));
598
if (inode.size < offset+count) count = inode.size - offset;
602
count = this.fs.RoundToDirentry(inodeid, offset + count) - offset;
604
if(offset > inode.size)
611
this.bus.send("9p-read-start", [this.fids[fid].dbg_name]);
613
const data = await this.fs.Read(inodeid, offset, count);
615
this.bus.send("9p-read-end", [this.fids[fid].dbg_name, count]);
618
this.replybuffer.set(data, 7 + 4);
620
marshall.Marshall(["w"], [count], this.replybuffer, 7);
621
this.BuildReply(id, tag, 4 + count);
622
this.SendReply(bufchain);
627
var req = marshall.Unmarshall(["w", "d", "w"], buffer, state);
632
const filename = this.fids[fid].dbg_name;
634
message.Debug("[write]: fid=" + fid + " (" + filename + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
635
if(this.fids[fid].type === FID_XATTR)
638
this.SendError(tag, "Setxattr not supported", EOPNOTSUPP);
639
this.SendReply(bufchain);
645
await this.fs.Write(this.fids[fid].inodeid, offset, count, buffer.subarray(state.offset));
648
this.bus.send("9p-write-end", [filename, count]);
650
marshall.Marshall(["w"], [count], this.replybuffer, 7);
651
this.BuildReply(id, tag, 4);
652
this.SendReply(bufchain);
656
var req = marshall.Unmarshall(["w", "s", "w", "s"], buffer, state);
657
var olddirfid = req[0];
658
var oldname = req[1];
659
var newdirfid = req[2];
660
var newname = req[3];
661
message.Debug("[renameat]: oldname=" + oldname + " newname=" + newname);
662
var ret = await this.fs.Rename(this.fids[olddirfid].inodeid, oldname, this.fids[newdirfid].inodeid, newname);
664
let error_message = "";
665
if(ret === -ENOENT) error_message = "No such file or directory";
666
else if(ret === -EPERM) error_message = "Operation not permitted";
667
else if(ret === -ENOTEMPTY) error_message = "Directory not empty";
670
error_message = "Unknown error: " + (-ret);
671
dbg_assert(false, "[renameat]: Unexpected error code: " + (-ret));
673
this.SendError(tag, error_message, -ret);
674
this.SendReply(bufchain);
679
const newidx = this.fs.Search(this.fids[newdirfid].inodeid, newname);
680
this.update_dbg_name(newidx, newname);
682
this.BuildReply(id, tag, 0);
683
this.SendReply(bufchain);
687
var req = marshall.Unmarshall(["w", "s", "w"], buffer, state);
691
message.Debug("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags);
692
var fid = this.fs.Search(this.fids[dirfd].inodeid, name);
694
this.SendError(tag, "No such file or directory", ENOENT);
695
this.SendReply(bufchain);
698
var ret = this.fs.Unlink(this.fids[dirfd].inodeid, name);
700
let error_message = "";
701
if(ret === -ENOTEMPTY) error_message = "Directory not empty";
702
else if(ret === -EPERM) error_message = "Operation not permitted";
705
error_message = "Unknown error: " + (-ret);
706
dbg_assert(false, "[unlink]: Unexpected error code: " + (-ret));
708
this.SendError(tag, error_message, -ret);
709
this.SendReply(bufchain);
712
this.BuildReply(id, tag, 0);
713
this.SendReply(bufchain);
717
var version = marshall.Unmarshall(["w", "s"], buffer, state);
718
message.Debug("[version]: msize=" + version[0] + " version=" + version[1]);
719
this.msize = version[0];
720
size = marshall.Marshall(["w", "s"], [this.msize, this.VERSION], this.replybuffer, 7);
721
this.BuildReply(id, tag, size);
722
this.SendReply(bufchain);
727
var req = marshall.Unmarshall(["w", "w", "s", "s", "w"], buffer, state);
730
message.Debug("[attach]: fid=" + fid + " afid=" + hex8(req[1]) + " uname=" + req[2] + " aname=" + req[3]);
731
this.fids[fid] = this.Createfid(0, FID_INODE, uid, "");
732
var inode = this.fs.GetInode(this.fids[fid].inodeid);
733
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
734
this.BuildReply(id, tag, 13);
735
this.SendReply(bufchain);
739
var req = marshall.Unmarshall(["h"], buffer, state);
741
message.Debug("[flush] " + tag);
743
this.BuildReply(id, tag, 0);
744
this.SendReply(bufchain);
749
var req = marshall.Unmarshall(["w", "w", "h"], buffer, state);
753
message.Debug("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname);
755
this.fids[nwfid] = this.Createfid(this.fids[fid].inodeid, FID_INODE, this.fids[fid].uid, this.fids[fid].dbg_name);
757
marshall.Marshall(["h"], [0], this.replybuffer, 7);
758
this.BuildReply(id, tag, 2);
759
this.SendReply(bufchain);
763
for(var i=0; i<nwname; i++) {
766
var walk = marshall.Unmarshall(wnames, buffer, state);
767
var idx = this.fids[fid].inodeid;
771
message.Debug("walk in dir " + this.fids[fid].dbg_name + " to: " + walk.toString());
772
for(var i=0; i<nwname; i++) {
773
idx = this.fs.Search(idx, walk[i]);
776
message.Debug("Could not find: " + walk[i]);
779
offset += marshall.Marshall(["Q"], [this.fs.GetInode(idx).qid], this.replybuffer, offset);
784
this.fids[nwfid] = this.Createfid(idx, FID_INODE, this.fids[fid].uid, walk[i]);
786
marshall.Marshall(["h"], [nwidx], this.replybuffer, 7);
787
this.BuildReply(id, tag, offset-7);
788
this.SendReply(bufchain);
792
var req = marshall.Unmarshall(["w"], buffer, state);
793
message.Debug("[clunk]: fid=" + req[0]);
794
if (this.fids[req[0]] && this.fids[req[0]].inodeid >= 0) {
795
await this.fs.CloseInode(this.fids[req[0]].inodeid);
796
this.fids[req[0]].inodeid = -1;
797
this.fids[req[0]].type = FID_NONE;
799
this.BuildReply(id, tag, 0);
800
this.SendReply(bufchain);
804
var req = marshall.Unmarshall(["w", "s", "d", "w"], buffer, state);
807
var attr_size = req[2];
809
message.Debug("[txattrcreate]: fid=" + fid + " name=" + name + " attr_size=" + attr_size + " flags=" + flags);
812
this.fids[fid].type = FID_XATTR;
814
this.BuildReply(id, tag, 0);
815
this.SendReply(bufchain);
821
var req = marshall.Unmarshall(["w", "w", "s"], buffer, state);
825
message.Debug("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2]);
828
this.SendError(tag, "Setxattr not supported", EOPNOTSUPP);
829
this.SendReply(bufchain);
846
message.Debug("Error in Virtio9p: Unknown id " + id + " received");