@aptre/v86 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSE.MIT +22 -0
  3. package/Readme.md +237 -0
  4. package/dist/v86.browser.js +26666 -0
  5. package/dist/v86.browser.js.map +7 -0
  6. package/dist/v86.js +26632 -0
  7. package/dist/v86.js.map +7 -0
  8. package/gen/generate_analyzer.ts +512 -0
  9. package/gen/generate_interpreter.ts +522 -0
  10. package/gen/generate_jit.ts +624 -0
  11. package/gen/rust_ast.ts +107 -0
  12. package/gen/util.ts +35 -0
  13. package/gen/x86_table.ts +1836 -0
  14. package/lib/9p.ts +1547 -0
  15. package/lib/filesystem.ts +1879 -0
  16. package/lib/marshall.ts +168 -0
  17. package/lib/softfloat/softfloat.c +32501 -0
  18. package/lib/zstd/zstddeclib.c +13520 -0
  19. package/package.json +75 -0
  20. package/src/acpi.ts +267 -0
  21. package/src/browser/dummy_screen.ts +106 -0
  22. package/src/browser/fake_network.ts +1771 -0
  23. package/src/browser/fetch_network.ts +361 -0
  24. package/src/browser/filestorage.ts +124 -0
  25. package/src/browser/inbrowser_network.ts +57 -0
  26. package/src/browser/keyboard.ts +564 -0
  27. package/src/browser/main.ts +3415 -0
  28. package/src/browser/mouse.ts +255 -0
  29. package/src/browser/network.ts +142 -0
  30. package/src/browser/print_stats.ts +336 -0
  31. package/src/browser/screen.ts +978 -0
  32. package/src/browser/serial.ts +316 -0
  33. package/src/browser/speaker.ts +1223 -0
  34. package/src/browser/starter.ts +1688 -0
  35. package/src/browser/wisp_network.ts +332 -0
  36. package/src/browser/worker_bus.ts +64 -0
  37. package/src/buffer.ts +652 -0
  38. package/src/bus.ts +78 -0
  39. package/src/const.ts +128 -0
  40. package/src/cpu.ts +2891 -0
  41. package/src/dma.ts +474 -0
  42. package/src/elf.ts +251 -0
  43. package/src/floppy.ts +1778 -0
  44. package/src/ide.ts +3455 -0
  45. package/src/io.ts +504 -0
  46. package/src/iso9660.ts +317 -0
  47. package/src/kernel.ts +250 -0
  48. package/src/lib.ts +645 -0
  49. package/src/log.ts +149 -0
  50. package/src/main.ts +199 -0
  51. package/src/ne2k.ts +1589 -0
  52. package/src/pci.ts +815 -0
  53. package/src/pit.ts +406 -0
  54. package/src/ps2.ts +820 -0
  55. package/src/rtc.ts +537 -0
  56. package/src/rust/analysis.rs +101 -0
  57. package/src/rust/codegen.rs +2660 -0
  58. package/src/rust/config.rs +3 -0
  59. package/src/rust/control_flow.rs +425 -0
  60. package/src/rust/cpu/apic.rs +658 -0
  61. package/src/rust/cpu/arith.rs +1207 -0
  62. package/src/rust/cpu/call_indirect.rs +2 -0
  63. package/src/rust/cpu/cpu.rs +4501 -0
  64. package/src/rust/cpu/fpu.rs +923 -0
  65. package/src/rust/cpu/global_pointers.rs +112 -0
  66. package/src/rust/cpu/instructions.rs +2486 -0
  67. package/src/rust/cpu/instructions_0f.rs +5261 -0
  68. package/src/rust/cpu/ioapic.rs +316 -0
  69. package/src/rust/cpu/memory.rs +351 -0
  70. package/src/rust/cpu/misc_instr.rs +613 -0
  71. package/src/rust/cpu/mod.rs +16 -0
  72. package/src/rust/cpu/modrm.rs +133 -0
  73. package/src/rust/cpu/pic.rs +402 -0
  74. package/src/rust/cpu/sse_instr.rs +361 -0
  75. package/src/rust/cpu/string.rs +701 -0
  76. package/src/rust/cpu/vga.rs +175 -0
  77. package/src/rust/cpu_context.rs +69 -0
  78. package/src/rust/dbg.rs +98 -0
  79. package/src/rust/gen/analyzer.rs +3807 -0
  80. package/src/rust/gen/analyzer0f.rs +3992 -0
  81. package/src/rust/gen/interpreter.rs +4447 -0
  82. package/src/rust/gen/interpreter0f.rs +5404 -0
  83. package/src/rust/gen/jit.rs +5080 -0
  84. package/src/rust/gen/jit0f.rs +5547 -0
  85. package/src/rust/gen/mod.rs +14 -0
  86. package/src/rust/jit.rs +2443 -0
  87. package/src/rust/jit_instructions.rs +7881 -0
  88. package/src/rust/js_api.rs +6 -0
  89. package/src/rust/leb.rs +46 -0
  90. package/src/rust/lib.rs +29 -0
  91. package/src/rust/modrm.rs +330 -0
  92. package/src/rust/opstats.rs +249 -0
  93. package/src/rust/page.rs +15 -0
  94. package/src/rust/paging.rs +25 -0
  95. package/src/rust/prefix.rs +15 -0
  96. package/src/rust/profiler.rs +155 -0
  97. package/src/rust/regs.rs +38 -0
  98. package/src/rust/softfloat.rs +286 -0
  99. package/src/rust/state_flags.rs +27 -0
  100. package/src/rust/wasmgen/mod.rs +2 -0
  101. package/src/rust/wasmgen/wasm_builder.rs +1047 -0
  102. package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
  103. package/src/rust/zstd.rs +105 -0
  104. package/src/sb16.ts +1928 -0
  105. package/src/state.ts +359 -0
  106. package/src/uart.ts +472 -0
  107. package/src/vga.ts +2791 -0
  108. package/src/virtio.ts +1756 -0
  109. package/src/virtio_balloon.ts +273 -0
  110. package/src/virtio_console.ts +372 -0
  111. package/src/virtio_net.ts +326 -0
package/lib/9p.ts ADDED
@@ -0,0 +1,1547 @@
1
+ // -------------------------------------------------
2
+ // --------------------- 9P ------------------------
3
+ // -------------------------------------------------
4
+ // Implementation of the 9p filesystem device following the
5
+ // 9P2000.L protocol ( https://code.google.com/p/diod/wiki/protocol )
6
+
7
+ import { LOG_9P } from './../src/const.js'
8
+ import {
9
+ VirtIO,
10
+ VirtQueue,
11
+ VirtQueueBufferChain,
12
+ VIRTIO_F_VERSION_1,
13
+ VIRTIO_F_RING_EVENT_IDX,
14
+ VIRTIO_F_RING_INDIRECT_DESC,
15
+ } from '../src/virtio.js'
16
+ import { S_IFREG, S_IFDIR, STATUS_UNLINKED } from './filesystem.js'
17
+ import * as marshall from '../lib/marshall.js'
18
+ import { dbg_log, dbg_assert } from '../src/log.js'
19
+ import { h } from '../src/lib.js'
20
+
21
+ import type { CPU } from '../src/cpu.js'
22
+ import type { BusConnector } from '../src/bus.js'
23
+ import type { FS } from './filesystem.js'
24
+
25
+ // More accurate filenames in 9p debug messages at the cost of performance.
26
+ const TRACK_FILENAMES = false
27
+
28
+ // Feature bit (bit position) for mount tag.
29
+ const VIRTIO_9P_F_MOUNT_TAG = 0
30
+ // Assumed max tag length in bytes.
31
+ const VIRTIO_9P_MAX_TAGLEN = 254
32
+
33
+ const MAX_REPLYBUFFER_SIZE = 16 * 1024 * 1024
34
+
35
+ export const EPERM = 1 /* Operation not permitted */
36
+ export const ENOENT = 2 /* No such file or directory */
37
+ export const EEXIST = 17 /* File exists */
38
+ export const EINVAL = 22 /* Invalid argument */
39
+ export const EOPNOTSUPP = 95 /* Operation is not supported */
40
+ export const ENOTEMPTY = 39 /* Directory not empty */
41
+ export const EPROTO = 71 /* Protocol error */
42
+
43
+ const P9_SETATTR_MODE = 0x00000001
44
+ const P9_SETATTR_UID = 0x00000002
45
+ const P9_SETATTR_GID = 0x00000004
46
+ const P9_SETATTR_SIZE = 0x00000008
47
+ const P9_SETATTR_ATIME = 0x00000010
48
+ const P9_SETATTR_MTIME = 0x00000020
49
+ const P9_SETATTR_CTIME = 0x00000040
50
+ const P9_SETATTR_ATIME_SET = 0x00000080
51
+ const P9_SETATTR_MTIME_SET = 0x00000100
52
+
53
+ const _P9_STAT_MODE_DIR = 0x80000000
54
+ const _P9_STAT_MODE_APPEND = 0x40000000
55
+ const _P9_STAT_MODE_EXCL = 0x20000000
56
+ const _P9_STAT_MODE_MOUNT = 0x10000000
57
+ const _P9_STAT_MODE_AUTH = 0x08000000
58
+ const _P9_STAT_MODE_TMP = 0x04000000
59
+ const _P9_STAT_MODE_SYMLINK = 0x02000000
60
+ const _P9_STAT_MODE_LINK = 0x01000000
61
+ const _P9_STAT_MODE_DEVICE = 0x00800000
62
+ const _P9_STAT_MODE_NAMED_PIPE = 0x00200000
63
+ const _P9_STAT_MODE_SOCKET = 0x00100000
64
+ const _P9_STAT_MODE_SETUID = 0x00080000
65
+ const _P9_STAT_MODE_SETGID = 0x00040000
66
+ const _P9_STAT_MODE_SETVTX = 0x00010000
67
+
68
+ export const P9_LOCK_TYPE_RDLCK = 0
69
+ export const P9_LOCK_TYPE_WRLCK = 1
70
+ export const P9_LOCK_TYPE_UNLCK = 2
71
+ const P9_LOCK_TYPES = ['shared', 'exclusive', 'unlock']
72
+
73
+ const _P9_LOCK_FLAGS_BLOCK = 1
74
+ const _P9_LOCK_FLAGS_RECLAIM = 2
75
+
76
+ export const P9_LOCK_SUCCESS = 0
77
+ export const P9_LOCK_BLOCKED = 1
78
+ export const P9_LOCK_ERROR = 2
79
+ export const P9_LOCK_GRACE = 3
80
+
81
+ const FID_NONE = -1
82
+ const FID_INODE = 1
83
+ const FID_XATTR = 2
84
+
85
+ interface Fid {
86
+ inodeid: number
87
+ type: number
88
+ uid: number
89
+ dbg_name: string
90
+ }
91
+
92
+ type P9Handler = (
93
+ reqbuf: Uint8Array,
94
+ reply: (replybuf: Uint8Array) => void,
95
+ ) => void
96
+
97
+ function range(size: number): number[] {
98
+ return Array.from(Array(size).keys())
99
+ }
100
+
101
+ function init_virtio(
102
+ cpu: CPU,
103
+ configspace_taglen: number,
104
+ configspace_tagname: number[],
105
+ receive: (bufchain: VirtQueueBufferChain) => void,
106
+ ): VirtIO {
107
+ const virtio = new VirtIO(cpu, {
108
+ name: 'virtio-9p',
109
+ pci_id: 0x06 << 3,
110
+ device_id: 0x1049,
111
+ subsystem_device_id: 9,
112
+ common: {
113
+ initial_port: 0xa800,
114
+ queues: [
115
+ {
116
+ size_supported: 32,
117
+ notify_offset: 0,
118
+ },
119
+ ],
120
+ features: [
121
+ VIRTIO_9P_F_MOUNT_TAG,
122
+ VIRTIO_F_VERSION_1,
123
+ VIRTIO_F_RING_EVENT_IDX,
124
+ VIRTIO_F_RING_INDIRECT_DESC,
125
+ ],
126
+ on_driver_ok: () => {},
127
+ },
128
+ notification: {
129
+ initial_port: 0xa900,
130
+ single_handler: false,
131
+ handlers: [
132
+ (queue_id: number) => {
133
+ if (queue_id !== 0) {
134
+ dbg_assert(
135
+ false,
136
+ 'Virtio9P Notified for non-existent queue: ' +
137
+ queue_id +
138
+ ' (expected queue_id of 0)',
139
+ )
140
+ return
141
+ }
142
+ const virtqueue = virtio.queues[0]
143
+ while (virtqueue.has_request()) {
144
+ const bufchain = virtqueue.pop_request()
145
+ receive(bufchain)
146
+ }
147
+ virtqueue.notify_me_after(0)
148
+ // Don't flush replies here: async replies are not completed yet.
149
+ },
150
+ ],
151
+ },
152
+ isr_status: {
153
+ initial_port: 0xa700,
154
+ },
155
+ device_specific: {
156
+ initial_port: 0xa600,
157
+ struct: [
158
+ {
159
+ bytes: 2,
160
+ name: 'mount tag length',
161
+ read: () => configspace_taglen,
162
+ write: (_data: number) => {
163
+ /* read only */
164
+ },
165
+ },
166
+ ].concat(
167
+ range(VIRTIO_9P_MAX_TAGLEN).map((index) => ({
168
+ bytes: 1,
169
+ name: 'mount tag name ' + index,
170
+ // Note: configspace_tagname may have changed after set_state
171
+ read: () => configspace_tagname[index] || 0,
172
+ write: (_data: number) => {
173
+ /* read only */
174
+ },
175
+ })),
176
+ ),
177
+ },
178
+ })
179
+ return virtio
180
+ }
181
+
182
+ export class Virtio9p {
183
+ fs: FS
184
+ bus: BusConnector
185
+ configspace_tagname: number[]
186
+ configspace_taglen: number
187
+ virtio: VirtIO
188
+ virtqueue: VirtQueue
189
+ VERSION: string
190
+ BLOCKSIZE: number
191
+ msize: number
192
+ replybuffer: Uint8Array
193
+ replybuffersize: number
194
+ fids: Fid[]
195
+
196
+ constructor(filesystem: FS, cpu: CPU, bus: BusConnector) {
197
+ this.fs = filesystem
198
+ this.bus = bus
199
+
200
+ this.configspace_tagname = [0x68, 0x6f, 0x73, 0x74, 0x39, 0x70] // "host9p" string
201
+ this.configspace_taglen = this.configspace_tagname.length // num bytes
202
+
203
+ this.virtio = init_virtio(
204
+ cpu,
205
+ this.configspace_taglen,
206
+ this.configspace_tagname,
207
+ this.ReceiveRequest.bind(this),
208
+ )
209
+ this.virtqueue = this.virtio.queues[0]
210
+
211
+ this.VERSION = '9P2000.L'
212
+ this.BLOCKSIZE = 8192 // Let's define one page.
213
+ this.msize = 8192 // maximum message size
214
+ this.replybuffer = new Uint8Array(this.msize * 2) // Twice the msize to stay on the safe site
215
+ this.replybuffersize = 0
216
+ this.fids = []
217
+ }
218
+
219
+ get_state(): any[] {
220
+ const state: any[] = []
221
+
222
+ state[0] = this.configspace_tagname
223
+ state[1] = this.configspace_taglen
224
+ state[2] = this.virtio
225
+ state[3] = this.VERSION
226
+ state[4] = this.BLOCKSIZE
227
+ state[5] = this.msize
228
+ state[6] = this.replybuffer
229
+ state[7] = this.replybuffersize
230
+ state[8] = this.fids.map(function (f) {
231
+ return [f.inodeid, f.type, f.uid, f.dbg_name]
232
+ })
233
+ state[9] = this.fs
234
+
235
+ return state
236
+ }
237
+
238
+ set_state(state: any[]): void {
239
+ this.configspace_tagname = state[0]
240
+ this.configspace_taglen = state[1]
241
+ this.virtio.set_state(state[2])
242
+ this.virtqueue = this.virtio.queues[0]
243
+ this.VERSION = state[3]
244
+ this.BLOCKSIZE = state[4]
245
+ this.msize = state[5]
246
+ this.replybuffer = state[6]
247
+ this.replybuffersize = state[7]
248
+
249
+ this.fids = state[8].map(function (f: any[]) {
250
+ return { inodeid: f[0], type: f[1], uid: f[2], dbg_name: f[3] }
251
+ })
252
+ this.fs.set_state(state[9])
253
+ }
254
+
255
+ // Note: dbg_name is only used for debugging messages and may not be the same as the filename,
256
+ // since it is not synchronised with renames done outside of 9p. Hard-links, linking and unlinking
257
+ // operations also mean that having a single filename no longer makes sense.
258
+ // Set TRACK_FILENAMES = true to sync dbg_name during 9p renames.
259
+ Createfid(
260
+ inodeid: number,
261
+ type: number,
262
+ uid: number,
263
+ dbg_name: string,
264
+ ): Fid {
265
+ return { inodeid, type, uid, dbg_name }
266
+ }
267
+
268
+ update_dbg_name(idx: number, newname: string): void {
269
+ for (const fid of this.fids) {
270
+ if (fid.inodeid === idx) fid.dbg_name = newname
271
+ }
272
+ }
273
+
274
+ reset(): void {
275
+ this.fids = []
276
+ this.virtio.reset()
277
+ }
278
+
279
+ BuildReply(id: number, tag: number, payloadsize: number): void {
280
+ dbg_assert(payloadsize >= 0, '9P: Negative payload size')
281
+ marshall.Marshall(
282
+ ['w', 'b', 'h'],
283
+ [payloadsize + 7, id + 1, tag],
284
+ this.replybuffer,
285
+ 0,
286
+ )
287
+ if (payloadsize + 7 >= this.replybuffer.length) {
288
+ dbg_log('Error in 9p: payloadsize exceeds maximum length', LOG_9P)
289
+ }
290
+ this.replybuffersize = payloadsize + 7
291
+ }
292
+
293
+ SendError(tag: number, errormsg: string, errorcode: number): void {
294
+ const size = marshall.Marshall(['w'], [errorcode], this.replybuffer, 7)
295
+ this.BuildReply(6, tag, size)
296
+ }
297
+
298
+ SendReply(bufchain: VirtQueueBufferChain): void {
299
+ dbg_assert(this.replybuffersize >= 0, '9P: Negative replybuffersize')
300
+ bufchain.set_next_blob(
301
+ this.replybuffer.subarray(0, this.replybuffersize),
302
+ )
303
+ this.virtqueue.push_reply(bufchain)
304
+ this.virtqueue.flush_replies()
305
+ }
306
+
307
+ async ReceiveRequest(bufchain: VirtQueueBufferChain): Promise<void> {
308
+ // TODO: split into header + data blobs to avoid unnecessary copying.
309
+ const buffer = new Uint8Array(bufchain.length_readable)
310
+ bufchain.get_next_blob(buffer)
311
+
312
+ const state = { offset: 0 }
313
+ const header = marshall.Unmarshall(['w', 'b', 'h'], buffer, state)
314
+ let size = header[0]
315
+ const id = header[1]
316
+ const tag = header[2]
317
+
318
+ switch (id) {
319
+ case 8: {
320
+ // statfs
321
+ size = this.fs.GetTotalSize() // size used by all files
322
+ const space = this.fs.GetSpace()
323
+
324
+ const req: any[] = []
325
+ req[0] = 0x01021997
326
+ req[1] = this.BLOCKSIZE // optimal transfer block size
327
+ req[2] = Math.floor(space / req[1]) // free blocks
328
+ req[3] = req[2] - Math.floor(size / req[1]) // free blocks in fs
329
+ req[4] = req[2] - Math.floor(size / req[1]) // free blocks avail to non-superuser
330
+ req[5] = this.fs.CountUsedInodes() // total number of inodes
331
+ req[6] = this.fs.CountFreeInodes()
332
+ req[7] = 0 // file system id?
333
+ req[8] = 256 // maximum length of filenames
334
+
335
+ size = marshall.Marshall(
336
+ ['w', 'w', 'd', 'd', 'd', 'd', 'd', 'd', 'w'],
337
+ req,
338
+ this.replybuffer,
339
+ 7,
340
+ )
341
+ this.BuildReply(id, tag, size)
342
+ this.SendReply(bufchain)
343
+ break
344
+ }
345
+
346
+ case 112: // topen
347
+ case 12: {
348
+ // tlopen
349
+ let req = marshall.Unmarshall(['w', 'w'], buffer, state)
350
+ const fid = req[0]
351
+ const mode = req[1]
352
+ dbg_log('[open] fid=' + fid + ', mode=' + mode, LOG_9P)
353
+ const idx = this.fids[fid].inodeid
354
+ const inode = this.fs.GetInode(idx)
355
+ dbg_log(
356
+ 'file open ' + this.fids[fid].dbg_name + ' tag:' + tag,
357
+ LOG_9P,
358
+ )
359
+ await this.fs.OpenInode(idx, mode)
360
+
361
+ req = []
362
+ req[0] = inode.qid
363
+ req[1] = this.msize - 24
364
+ marshall.Marshall(['Q', 'w'], req, this.replybuffer, 7)
365
+ this.BuildReply(id, tag, 13 + 4)
366
+ this.SendReply(bufchain)
367
+ break
368
+ }
369
+
370
+ case 70: {
371
+ // link
372
+ const req = marshall.Unmarshall(['w', 'w', 's'], buffer, state)
373
+ const dfid = req[0]
374
+ const fid = req[1]
375
+ const name = req[2]
376
+ dbg_log('[link] dfid=' + dfid + ', name=' + name, LOG_9P)
377
+
378
+ const ret = this.fs.Link(
379
+ this.fids[dfid].inodeid,
380
+ this.fids[fid].inodeid,
381
+ name,
382
+ )
383
+
384
+ if (ret < 0) {
385
+ let error_message = ''
386
+ if (ret === -EPERM)
387
+ error_message = 'Operation not permitted'
388
+ else {
389
+ error_message = 'Unknown error: ' + -ret
390
+ dbg_assert(
391
+ false,
392
+ '[link]: Unexpected error code: ' + -ret,
393
+ )
394
+ }
395
+ this.SendError(tag, error_message, -ret)
396
+ this.SendReply(bufchain)
397
+ break
398
+ }
399
+
400
+ this.BuildReply(id, tag, 0)
401
+ this.SendReply(bufchain)
402
+ break
403
+ }
404
+
405
+ case 16: {
406
+ // symlink
407
+ const req = marshall.Unmarshall(
408
+ ['w', 's', 's', 'w'],
409
+ buffer,
410
+ state,
411
+ )
412
+ const fid = req[0]
413
+ const name = req[1]
414
+ const symgt = req[2]
415
+ const gid = req[3]
416
+ dbg_log(
417
+ '[symlink] fid=' +
418
+ fid +
419
+ ', name=' +
420
+ name +
421
+ ', symgt=' +
422
+ symgt +
423
+ ', gid=' +
424
+ gid,
425
+ LOG_9P,
426
+ )
427
+ const idx = this.fs.CreateSymlink(
428
+ name,
429
+ this.fids[fid].inodeid,
430
+ symgt,
431
+ )
432
+ const inode = this.fs.GetInode(idx)
433
+ inode.uid = this.fids[fid].uid
434
+ inode.gid = gid
435
+ marshall.Marshall(['Q'], [inode.qid], this.replybuffer, 7)
436
+ this.BuildReply(id, tag, 13)
437
+ this.SendReply(bufchain)
438
+ break
439
+ }
440
+
441
+ case 18: {
442
+ // mknod
443
+ const req = marshall.Unmarshall(
444
+ ['w', 's', 'w', 'w', 'w', 'w'],
445
+ buffer,
446
+ state,
447
+ )
448
+ const fid = req[0]
449
+ const name = req[1]
450
+ const mode = req[2]
451
+ const major = req[3]
452
+ const minor = req[4]
453
+ const gid = req[5]
454
+ dbg_log(
455
+ '[mknod] fid=' +
456
+ fid +
457
+ ', name=' +
458
+ name +
459
+ ', major=' +
460
+ major +
461
+ ', minor=' +
462
+ minor +
463
+ '',
464
+ LOG_9P,
465
+ )
466
+ const idx = this.fs.CreateNode(
467
+ name,
468
+ this.fids[fid].inodeid,
469
+ major,
470
+ minor,
471
+ )
472
+ const inode = this.fs.GetInode(idx)
473
+ inode.mode = mode
474
+ //inode.mode = mode | S_IFCHR; // XXX: fails "Mknod - fifo" test
475
+ inode.uid = this.fids[fid].uid
476
+ inode.gid = gid
477
+ marshall.Marshall(['Q'], [inode.qid], this.replybuffer, 7)
478
+ this.BuildReply(id, tag, 13)
479
+ this.SendReply(bufchain)
480
+ break
481
+ }
482
+
483
+ case 22: {
484
+ // TREADLINK
485
+ const req = marshall.Unmarshall(['w'], buffer, state)
486
+ const fid = req[0]
487
+ const inode = this.fs.GetInode(this.fids[fid].inodeid)
488
+ dbg_log(
489
+ '[readlink] fid=' +
490
+ fid +
491
+ ' name=' +
492
+ this.fids[fid].dbg_name +
493
+ ' target=' +
494
+ inode.symlink,
495
+ LOG_9P,
496
+ )
497
+ size = marshall.Marshall(
498
+ ['s'],
499
+ [inode.symlink],
500
+ this.replybuffer,
501
+ 7,
502
+ )
503
+ this.BuildReply(id, tag, size)
504
+ this.SendReply(bufchain)
505
+ break
506
+ }
507
+
508
+ case 72: {
509
+ // tmkdir
510
+ const req = marshall.Unmarshall(
511
+ ['w', 's', 'w', 'w'],
512
+ buffer,
513
+ state,
514
+ )
515
+ const fid = req[0]
516
+ const name = req[1]
517
+ const mode = req[2]
518
+ const gid = req[3]
519
+ dbg_log(
520
+ '[mkdir] fid=' +
521
+ fid +
522
+ ', name=' +
523
+ name +
524
+ ', mode=' +
525
+ mode +
526
+ ', gid=' +
527
+ gid,
528
+ LOG_9P,
529
+ )
530
+ const idx = this.fs.CreateDirectory(
531
+ name,
532
+ this.fids[fid].inodeid,
533
+ )
534
+ const inode = this.fs.GetInode(idx)
535
+ inode.mode = mode | S_IFDIR
536
+ inode.uid = this.fids[fid].uid
537
+ inode.gid = gid
538
+ marshall.Marshall(['Q'], [inode.qid], this.replybuffer, 7)
539
+ this.BuildReply(id, tag, 13)
540
+ this.SendReply(bufchain)
541
+ break
542
+ }
543
+
544
+ case 14: {
545
+ // tlcreate
546
+ const req = marshall.Unmarshall(
547
+ ['w', 's', 'w', 'w', 'w'],
548
+ buffer,
549
+ state,
550
+ )
551
+ const fid = req[0]
552
+ const name = req[1]
553
+ const flags = req[2]
554
+ const mode = req[3]
555
+ const gid = req[4]
556
+ this.bus.send('9p-create', [name, this.fids[fid].inodeid])
557
+ dbg_log(
558
+ '[create] fid=' +
559
+ fid +
560
+ ', name=' +
561
+ name +
562
+ ', flags=' +
563
+ flags +
564
+ ', mode=' +
565
+ mode +
566
+ ', gid=' +
567
+ gid,
568
+ LOG_9P,
569
+ )
570
+ const idx = this.fs.CreateFile(name, this.fids[fid].inodeid)
571
+ this.fids[fid].inodeid = idx
572
+ this.fids[fid].type = FID_INODE
573
+ this.fids[fid].dbg_name = name
574
+ const inode = this.fs.GetInode(idx)
575
+ inode.uid = this.fids[fid].uid
576
+ inode.gid = gid
577
+ inode.mode = mode | S_IFREG
578
+ marshall.Marshall(
579
+ ['Q', 'w'],
580
+ [inode.qid, this.msize - 24],
581
+ this.replybuffer,
582
+ 7,
583
+ )
584
+ this.BuildReply(id, tag, 13 + 4)
585
+ this.SendReply(bufchain)
586
+ break
587
+ }
588
+
589
+ case 52: {
590
+ // lock
591
+ const req = marshall.Unmarshall(
592
+ ['w', 'b', 'w', 'd', 'd', 'w', 's'],
593
+ buffer,
594
+ state,
595
+ )
596
+ const fid = req[0]
597
+ const flags = req[2]
598
+ const lock_length = req[4] === 0 ? Infinity : req[4]
599
+ const lock_request = this.fs.DescribeLock(
600
+ req[1],
601
+ req[3],
602
+ lock_length,
603
+ req[5],
604
+ req[6],
605
+ )
606
+ dbg_log(
607
+ '[lock] fid=' +
608
+ fid +
609
+ ', type=' +
610
+ P9_LOCK_TYPES[lock_request.type] +
611
+ ', start=' +
612
+ lock_request.start +
613
+ ', length=' +
614
+ lock_request.length +
615
+ ', proc_id=' +
616
+ lock_request.proc_id,
617
+ )
618
+
619
+ const ret = this.fs.Lock(
620
+ this.fids[fid].inodeid,
621
+ lock_request,
622
+ flags,
623
+ )
624
+
625
+ marshall.Marshall(['b'], [ret], this.replybuffer, 7)
626
+ this.BuildReply(id, tag, 1)
627
+ this.SendReply(bufchain)
628
+ break
629
+ }
630
+
631
+ case 54: {
632
+ // getlock
633
+ const req = marshall.Unmarshall(
634
+ ['w', 'b', 'd', 'd', 'w', 's'],
635
+ buffer,
636
+ state,
637
+ )
638
+ const fid = req[0]
639
+ const lock_length = req[3] === 0 ? Infinity : req[3]
640
+ const lock_request = this.fs.DescribeLock(
641
+ req[1],
642
+ req[2],
643
+ lock_length,
644
+ req[4],
645
+ req[5],
646
+ )
647
+ dbg_log(
648
+ '[getlock] fid=' +
649
+ fid +
650
+ ', type=' +
651
+ P9_LOCK_TYPES[lock_request.type] +
652
+ ', start=' +
653
+ lock_request.start +
654
+ ', length=' +
655
+ lock_request.length +
656
+ ', proc_id=' +
657
+ lock_request.proc_id,
658
+ )
659
+
660
+ let ret_lock = this.fs.GetLock(
661
+ this.fids[fid].inodeid,
662
+ lock_request,
663
+ )
664
+
665
+ if (!ret_lock) {
666
+ ret_lock = lock_request
667
+ ret_lock.type = P9_LOCK_TYPE_UNLCK
668
+ }
669
+
670
+ const ret_length =
671
+ ret_lock.length === Infinity ? 0 : ret_lock.length
672
+
673
+ size = marshall.Marshall(
674
+ ['b', 'd', 'd', 'w', 's'],
675
+ [
676
+ ret_lock.type,
677
+ ret_lock.start,
678
+ ret_length,
679
+ ret_lock.proc_id,
680
+ ret_lock.client_id,
681
+ ],
682
+ this.replybuffer,
683
+ 7,
684
+ )
685
+
686
+ this.BuildReply(id, tag, size)
687
+ this.SendReply(bufchain)
688
+ break
689
+ }
690
+
691
+ case 24: {
692
+ // getattr
693
+ const req = marshall.Unmarshall(['w', 'd'], buffer, state)
694
+ const fid = req[0]
695
+ const inode = this.fs.GetInode(this.fids[fid].inodeid)
696
+ dbg_log(
697
+ '[getattr]: fid=' +
698
+ fid +
699
+ ' name=' +
700
+ this.fids[fid].dbg_name +
701
+ ' request mask=' +
702
+ req[1],
703
+ LOG_9P,
704
+ )
705
+ if (!inode || inode.status === STATUS_UNLINKED) {
706
+ dbg_log('getattr: unlinked', LOG_9P)
707
+ this.SendError(tag, 'No such file or directory', ENOENT)
708
+ this.SendReply(bufchain)
709
+ break
710
+ }
711
+ req[0] = req[1] // request mask
712
+ req[1] = inode.qid
713
+
714
+ req[2] = inode.mode
715
+ req[3] = inode.uid // user id
716
+ req[4] = inode.gid // group id
717
+
718
+ req[5] = inode.nlinks // number of hard links
719
+ req[6] = (inode.major << 8) | inode.minor // device id low
720
+ req[7] = inode.size // size low
721
+ req[8] = this.BLOCKSIZE
722
+ req[9] = Math.floor(inode.size / 512 + 1) // blk size low
723
+ req[10] = inode.atime // atime
724
+ req[11] = 0x0
725
+ req[12] = inode.mtime // mtime
726
+ req[13] = 0x0
727
+ req[14] = inode.ctime // ctime
728
+ req[15] = 0x0
729
+ req[16] = 0x0 // btime
730
+ req[17] = 0x0
731
+ req[18] = 0x0 // st_gen
732
+ req[19] = 0x0 // data_version
733
+ marshall.Marshall(
734
+ [
735
+ 'd',
736
+ 'Q',
737
+ 'w',
738
+ 'w',
739
+ 'w',
740
+ 'd',
741
+ 'd',
742
+ 'd',
743
+ 'd',
744
+ 'd',
745
+ 'd',
746
+ 'd', // atime
747
+ 'd',
748
+ 'd', // mtime
749
+ 'd',
750
+ 'd', // ctime
751
+ 'd',
752
+ 'd', // btime
753
+ 'd',
754
+ 'd',
755
+ ],
756
+ req,
757
+ this.replybuffer,
758
+ 7,
759
+ )
760
+ this.BuildReply(id, tag, 8 + 13 + 4 + 4 + 4 + 8 * 15)
761
+ this.SendReply(bufchain)
762
+ break
763
+ }
764
+
765
+ case 26: {
766
+ // setattr
767
+ const req = marshall.Unmarshall(
768
+ [
769
+ 'w',
770
+ 'w',
771
+ 'w', // mode
772
+ 'w',
773
+ 'w', // uid, gid
774
+ 'd', // size
775
+ 'd',
776
+ 'd', // atime
777
+ 'd',
778
+ 'd', // mtime
779
+ ],
780
+ buffer,
781
+ state,
782
+ )
783
+ const fid = req[0]
784
+ const inode = this.fs.GetInode(this.fids[fid].inodeid)
785
+ dbg_log(
786
+ '[setattr]: fid=' +
787
+ fid +
788
+ ' request mask=' +
789
+ req[1] +
790
+ ' name=' +
791
+ this.fids[fid].dbg_name,
792
+ LOG_9P,
793
+ )
794
+ if (req[1] & P9_SETATTR_MODE) {
795
+ // XXX: check mode (S_IFREG or S_IFDIR or similar should be set)
796
+ inode.mode = req[2]
797
+ }
798
+ if (req[1] & P9_SETATTR_UID) {
799
+ inode.uid = req[3]
800
+ }
801
+ if (req[1] & P9_SETATTR_GID) {
802
+ inode.gid = req[4]
803
+ }
804
+ if (req[1] & P9_SETATTR_ATIME) {
805
+ inode.atime = Math.floor(new Date().getTime() / 1000)
806
+ }
807
+ if (req[1] & P9_SETATTR_MTIME) {
808
+ inode.mtime = Math.floor(new Date().getTime() / 1000)
809
+ }
810
+ if (req[1] & P9_SETATTR_CTIME) {
811
+ inode.ctime = Math.floor(new Date().getTime() / 1000)
812
+ }
813
+ if (req[1] & P9_SETATTR_ATIME_SET) {
814
+ inode.atime = req[6]
815
+ }
816
+ if (req[1] & P9_SETATTR_MTIME_SET) {
817
+ inode.mtime = req[8]
818
+ }
819
+ if (req[1] & P9_SETATTR_SIZE) {
820
+ await this.fs.ChangeSize(this.fids[fid].inodeid, req[5])
821
+ }
822
+ this.BuildReply(id, tag, 0)
823
+ this.SendReply(bufchain)
824
+ break
825
+ }
826
+
827
+ case 50: {
828
+ // fsync
829
+ const req = marshall.Unmarshall(['w', 'd'], buffer, state)
830
+ const _fid = req[0]
831
+ this.BuildReply(id, tag, 0)
832
+ this.SendReply(bufchain)
833
+ break
834
+ }
835
+
836
+ case 40: // TREADDIR
837
+ case 116: {
838
+ // read
839
+ const req = marshall.Unmarshall(['w', 'd', 'w'], buffer, state)
840
+ const fid = req[0]
841
+ const offset = req[1]
842
+ let count = req[2]
843
+ const inode = this.fs.GetInode(this.fids[fid].inodeid)
844
+ if (id === 40)
845
+ dbg_log(
846
+ '[treaddir]: fid=' +
847
+ fid +
848
+ ' offset=' +
849
+ offset +
850
+ ' count=' +
851
+ count,
852
+ LOG_9P,
853
+ )
854
+ if (id === 116)
855
+ dbg_log(
856
+ '[read]: fid=' +
857
+ fid +
858
+ ' (' +
859
+ this.fids[fid].dbg_name +
860
+ ') offset=' +
861
+ offset +
862
+ ' count=' +
863
+ count +
864
+ ' fidtype=' +
865
+ this.fids[fid].type,
866
+ LOG_9P,
867
+ )
868
+ if (!inode || inode.status === STATUS_UNLINKED) {
869
+ dbg_log('read/treaddir: unlinked', LOG_9P)
870
+ this.SendError(tag, 'No such file or directory', ENOENT)
871
+ this.SendReply(bufchain)
872
+ break
873
+ }
874
+ if (this.fids[fid].type === FID_XATTR) {
875
+ if (inode.caps!.length < offset + count)
876
+ count = inode.caps!.length - offset
877
+ for (let i = 0; i < count; i++)
878
+ this.replybuffer[7 + 4 + i] = inode.caps![offset + i]
879
+ marshall.Marshall(['w'], [count], this.replybuffer, 7)
880
+ this.BuildReply(id, tag, 4 + count)
881
+ this.SendReply(bufchain)
882
+ } else {
883
+ await this.fs.OpenInode(this.fids[fid].inodeid, undefined)
884
+ const inodeid = this.fids[fid].inodeid
885
+
886
+ count = Math.min(count, this.replybuffer.length - (7 + 4))
887
+
888
+ if (inode.size < offset + count) count = inode.size - offset
889
+ else if (id === 40) {
890
+ // for directories, return whole number of dir-entries.
891
+ count =
892
+ this.fs.RoundToDirentry(inodeid, offset + count) -
893
+ offset
894
+ }
895
+ if (offset > inode.size) {
896
+ // offset can be greater than available - should return count of zero.
897
+ // See http://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor30
898
+ count = 0
899
+ }
900
+
901
+ this.bus.send('9p-read-start', [this.fids[fid].dbg_name])
902
+
903
+ const data = await this.fs.Read(inodeid, offset, count)
904
+
905
+ this.bus.send('9p-read-end', [
906
+ this.fids[fid].dbg_name,
907
+ count,
908
+ ])
909
+
910
+ if (data) {
911
+ this.replybuffer.set(data, 7 + 4)
912
+ }
913
+ marshall.Marshall(['w'], [count], this.replybuffer, 7)
914
+ this.BuildReply(id, tag, 4 + count)
915
+ this.SendReply(bufchain)
916
+ }
917
+ break
918
+ }
919
+
920
+ case 118: {
921
+ // write
922
+ const req = marshall.Unmarshall(['w', 'd', 'w'], buffer, state)
923
+ const fid = req[0]
924
+ const offset = req[1]
925
+ const count = req[2]
926
+
927
+ const filename = this.fids[fid].dbg_name
928
+
929
+ dbg_log(
930
+ '[write]: fid=' +
931
+ fid +
932
+ ' (' +
933
+ filename +
934
+ ') offset=' +
935
+ offset +
936
+ ' count=' +
937
+ count +
938
+ ' fidtype=' +
939
+ this.fids[fid].type,
940
+ LOG_9P,
941
+ )
942
+ if (this.fids[fid].type === FID_XATTR) {
943
+ // XXX: xattr not supported yet. Ignore write.
944
+ this.SendError(tag, 'Setxattr not supported', EOPNOTSUPP)
945
+ this.SendReply(bufchain)
946
+ break
947
+ } else {
948
+ // XXX: Size of the subarray is unchecked
949
+ await this.fs.Write(
950
+ this.fids[fid].inodeid,
951
+ offset,
952
+ count,
953
+ buffer.subarray(state.offset),
954
+ )
955
+ }
956
+
957
+ this.bus.send('9p-write-end', [filename, count])
958
+
959
+ marshall.Marshall(['w'], [count], this.replybuffer, 7)
960
+ this.BuildReply(id, tag, 4)
961
+ this.SendReply(bufchain)
962
+ break
963
+ }
964
+
965
+ case 74: {
966
+ // RENAMEAT
967
+ const req = marshall.Unmarshall(
968
+ ['w', 's', 'w', 's'],
969
+ buffer,
970
+ state,
971
+ )
972
+ const olddirfid = req[0]
973
+ const oldname = req[1]
974
+ const newdirfid = req[2]
975
+ const newname = req[3]
976
+ dbg_log(
977
+ '[renameat]: oldname=' + oldname + ' newname=' + newname,
978
+ LOG_9P,
979
+ )
980
+ const ret = await this.fs.Rename(
981
+ this.fids[olddirfid].inodeid,
982
+ oldname,
983
+ this.fids[newdirfid].inodeid,
984
+ newname,
985
+ )
986
+ if (ret < 0) {
987
+ let error_message = ''
988
+ if (ret === -ENOENT)
989
+ error_message = 'No such file or directory'
990
+ else if (ret === -EPERM)
991
+ error_message = 'Operation not permitted'
992
+ else if (ret === -ENOTEMPTY)
993
+ error_message = 'Directory not empty'
994
+ else {
995
+ error_message = 'Unknown error: ' + -ret
996
+ dbg_assert(
997
+ false,
998
+ '[renameat]: Unexpected error code: ' + -ret,
999
+ )
1000
+ }
1001
+ this.SendError(tag, error_message, -ret)
1002
+ this.SendReply(bufchain)
1003
+ break
1004
+ }
1005
+ if (TRACK_FILENAMES) {
1006
+ const newidx = this.fs.Search(
1007
+ this.fids[newdirfid].inodeid,
1008
+ newname,
1009
+ )
1010
+ this.update_dbg_name(newidx, newname)
1011
+ }
1012
+ this.BuildReply(id, tag, 0)
1013
+ this.SendReply(bufchain)
1014
+ break
1015
+ }
1016
+
1017
+ case 76: {
1018
+ // TUNLINKAT
1019
+ const req = marshall.Unmarshall(['w', 's', 'w'], buffer, state)
1020
+ const dirfd = req[0]
1021
+ const name = req[1]
1022
+ const flags = req[2]
1023
+ dbg_log(
1024
+ '[unlink]: dirfd=' +
1025
+ dirfd +
1026
+ ' name=' +
1027
+ name +
1028
+ ' flags=' +
1029
+ flags,
1030
+ LOG_9P,
1031
+ )
1032
+ const fid_search = this.fs.Search(
1033
+ this.fids[dirfd].inodeid,
1034
+ name,
1035
+ )
1036
+ if (fid_search === -1) {
1037
+ this.SendError(tag, 'No such file or directory', ENOENT)
1038
+ this.SendReply(bufchain)
1039
+ break
1040
+ }
1041
+ const ret = this.fs.Unlink(this.fids[dirfd].inodeid, name)
1042
+ if (ret < 0) {
1043
+ let error_message = ''
1044
+ if (ret === -ENOTEMPTY)
1045
+ error_message = 'Directory not empty'
1046
+ else if (ret === -EPERM)
1047
+ error_message = 'Operation not permitted'
1048
+ else {
1049
+ error_message = 'Unknown error: ' + -ret
1050
+ dbg_assert(
1051
+ false,
1052
+ '[unlink]: Unexpected error code: ' + -ret,
1053
+ )
1054
+ }
1055
+ this.SendError(tag, error_message, -ret)
1056
+ this.SendReply(bufchain)
1057
+ break
1058
+ }
1059
+ this.BuildReply(id, tag, 0)
1060
+ this.SendReply(bufchain)
1061
+ break
1062
+ }
1063
+
1064
+ case 100: {
1065
+ // version
1066
+ const version = marshall.Unmarshall(['w', 's'], buffer, state)
1067
+ dbg_log(
1068
+ '[version]: msize=' + version[0] + ' version=' + version[1],
1069
+ LOG_9P,
1070
+ )
1071
+ if (this.msize !== version[0]) {
1072
+ this.msize = version[0]
1073
+ this.replybuffer = new Uint8Array(
1074
+ Math.min(MAX_REPLYBUFFER_SIZE, this.msize * 2),
1075
+ )
1076
+ }
1077
+ size = marshall.Marshall(
1078
+ ['w', 's'],
1079
+ [this.msize, this.VERSION],
1080
+ this.replybuffer,
1081
+ 7,
1082
+ )
1083
+ this.BuildReply(id, tag, size)
1084
+ this.SendReply(bufchain)
1085
+ break
1086
+ }
1087
+
1088
+ case 104: {
1089
+ // attach
1090
+ // return root directorie's QID
1091
+ const req = marshall.Unmarshall(
1092
+ ['w', 'w', 's', 's', 'w'],
1093
+ buffer,
1094
+ state,
1095
+ )
1096
+ const fid = req[0]
1097
+ const uid = req[4]
1098
+ dbg_log(
1099
+ '[attach]: fid=' +
1100
+ fid +
1101
+ ' afid=' +
1102
+ h(req[1]) +
1103
+ ' uname=' +
1104
+ req[2] +
1105
+ ' aname=' +
1106
+ req[3],
1107
+ LOG_9P,
1108
+ )
1109
+ this.fids[fid] = this.Createfid(0, FID_INODE, uid, '')
1110
+ const inode = this.fs.GetInode(this.fids[fid].inodeid)
1111
+ marshall.Marshall(['Q'], [inode.qid], this.replybuffer, 7)
1112
+ this.BuildReply(id, tag, 13)
1113
+ this.SendReply(bufchain)
1114
+ this.bus.send('9p-attach')
1115
+ break
1116
+ }
1117
+
1118
+ case 108: {
1119
+ // tflush
1120
+ const req = marshall.Unmarshall(['h'], buffer, state)
1121
+ const _oldtag = req[0]
1122
+ dbg_log('[flush] ' + tag, LOG_9P)
1123
+ this.BuildReply(id, tag, 0)
1124
+ this.SendReply(bufchain)
1125
+ break
1126
+ }
1127
+
1128
+ case 110: {
1129
+ // walk
1130
+ const req = marshall.Unmarshall(['w', 'w', 'h'], buffer, state)
1131
+ const fid = req[0]
1132
+ const nwfid = req[1]
1133
+ const nwname = req[2]
1134
+ dbg_log(
1135
+ '[walk]: fid=' +
1136
+ req[0] +
1137
+ ' nwfid=' +
1138
+ req[1] +
1139
+ ' nwname=' +
1140
+ nwname,
1141
+ LOG_9P,
1142
+ )
1143
+ if (nwname === 0) {
1144
+ this.fids[nwfid] = this.Createfid(
1145
+ this.fids[fid].inodeid,
1146
+ FID_INODE,
1147
+ this.fids[fid].uid,
1148
+ this.fids[fid].dbg_name,
1149
+ )
1150
+ marshall.Marshall(['h'], [0], this.replybuffer, 7)
1151
+ this.BuildReply(id, tag, 2)
1152
+ this.SendReply(bufchain)
1153
+ break
1154
+ }
1155
+ const wnames: string[] = []
1156
+ for (let i = 0; i < nwname; i++) {
1157
+ wnames.push('s')
1158
+ }
1159
+ const walk = marshall.Unmarshall(
1160
+ wnames as marshall.MarshallTypeCode[],
1161
+ buffer,
1162
+ state,
1163
+ )
1164
+ let idx = this.fids[fid].inodeid
1165
+ let offset = 7 + 2
1166
+ let nwidx = 0
1167
+ dbg_log(
1168
+ 'walk in dir ' +
1169
+ this.fids[fid].dbg_name +
1170
+ ' to: ' +
1171
+ walk.toString(),
1172
+ LOG_9P,
1173
+ )
1174
+ for (let i = 0; i < nwname; i++) {
1175
+ idx = this.fs.Search(idx, walk[i])
1176
+
1177
+ if (idx === -1) {
1178
+ dbg_log('Could not find: ' + walk[i], LOG_9P)
1179
+ break
1180
+ }
1181
+ offset += marshall.Marshall(
1182
+ ['Q'],
1183
+ [this.fs.GetInode(idx).qid],
1184
+ this.replybuffer,
1185
+ offset,
1186
+ )
1187
+ nwidx++
1188
+ this.fids[nwfid] = this.Createfid(
1189
+ idx,
1190
+ FID_INODE,
1191
+ this.fids[fid].uid,
1192
+ walk[i],
1193
+ )
1194
+ }
1195
+ marshall.Marshall(['h'], [nwidx], this.replybuffer, 7)
1196
+ this.BuildReply(id, tag, offset - 7)
1197
+ this.SendReply(bufchain)
1198
+ break
1199
+ }
1200
+
1201
+ case 120: {
1202
+ // clunk
1203
+ const req = marshall.Unmarshall(['w'], buffer, state)
1204
+ dbg_log('[clunk]: fid=' + req[0], LOG_9P)
1205
+ if (this.fids[req[0]] && this.fids[req[0]].inodeid >= 0) {
1206
+ await this.fs.CloseInode(this.fids[req[0]].inodeid)
1207
+ this.fids[req[0]].inodeid = -1
1208
+ this.fids[req[0]].type = FID_NONE
1209
+ }
1210
+ this.BuildReply(id, tag, 0)
1211
+ this.SendReply(bufchain)
1212
+ break
1213
+ }
1214
+
1215
+ case 32: {
1216
+ // txattrcreate
1217
+ const req = marshall.Unmarshall(
1218
+ ['w', 's', 'd', 'w'],
1219
+ buffer,
1220
+ state,
1221
+ )
1222
+ const fid = req[0]
1223
+ const name = req[1]
1224
+ const attr_size = req[2]
1225
+ const flags = req[3]
1226
+ dbg_log(
1227
+ '[txattrcreate]: fid=' +
1228
+ fid +
1229
+ ' name=' +
1230
+ name +
1231
+ ' attr_size=' +
1232
+ attr_size +
1233
+ ' flags=' +
1234
+ flags,
1235
+ LOG_9P,
1236
+ )
1237
+
1238
+ // XXX: xattr not supported yet. E.g. checks corresponding to the flags needed.
1239
+ this.fids[fid].type = FID_XATTR
1240
+
1241
+ this.BuildReply(id, tag, 0)
1242
+ this.SendReply(bufchain)
1243
+ break
1244
+ }
1245
+
1246
+ case 30: {
1247
+ // xattrwalk
1248
+ const req = marshall.Unmarshall(['w', 'w', 's'], buffer, state)
1249
+ const _fid = req[0]
1250
+ const _newfid = req[1]
1251
+ const _name = req[2]
1252
+ dbg_log(
1253
+ '[xattrwalk]: fid=' +
1254
+ req[0] +
1255
+ ' newfid=' +
1256
+ req[1] +
1257
+ ' name=' +
1258
+ req[2],
1259
+ LOG_9P,
1260
+ )
1261
+
1262
+ // Workaround for Linux restarts writes until full blocksize
1263
+ this.SendError(tag, 'Setxattr not supported', EOPNOTSUPP)
1264
+ this.SendReply(bufchain)
1265
+ break
1266
+ }
1267
+
1268
+ default:
1269
+ dbg_log(
1270
+ 'Error in Virtio9p: Unknown id ' + id + ' received',
1271
+ LOG_9P,
1272
+ )
1273
+ dbg_assert(false)
1274
+ break
1275
+ }
1276
+ }
1277
+ }
1278
+
1279
+ export class Virtio9pHandler {
1280
+ handle_fn: P9Handler
1281
+ tag_bufchain: Map<number, VirtQueueBufferChain>
1282
+ configspace_tagname: number[]
1283
+ configspace_taglen: number
1284
+ virtio: VirtIO
1285
+ virtqueue: VirtQueue
1286
+
1287
+ constructor(handle_fn: P9Handler, cpu: CPU) {
1288
+ this.handle_fn = handle_fn
1289
+ this.tag_bufchain = new Map()
1290
+
1291
+ this.configspace_tagname = [0x68, 0x6f, 0x73, 0x74, 0x39, 0x70] // "host9p" string
1292
+ this.configspace_taglen = this.configspace_tagname.length // num bytes
1293
+
1294
+ this.virtio = init_virtio(
1295
+ cpu,
1296
+ this.configspace_taglen,
1297
+ this.configspace_tagname,
1298
+ async (bufchain: VirtQueueBufferChain) => {
1299
+ // TODO: split into header + data blobs to avoid unnecessary copying.
1300
+ const reqbuf = new Uint8Array(bufchain.length_readable)
1301
+ bufchain.get_next_blob(reqbuf)
1302
+
1303
+ const reqheader = marshall.Unmarshall(['w', 'b', 'h'], reqbuf, {
1304
+ offset: 0,
1305
+ })
1306
+ const reqtag = reqheader[2]
1307
+
1308
+ this.tag_bufchain.set(reqtag, bufchain)
1309
+ this.handle_fn(reqbuf, (replybuf: Uint8Array) => {
1310
+ const replyheader = marshall.Unmarshall(
1311
+ ['w', 'b', 'h'],
1312
+ replybuf,
1313
+ { offset: 0 },
1314
+ )
1315
+ const replytag = replyheader[2]
1316
+
1317
+ const bufchain = this.tag_bufchain.get(replytag)
1318
+ if (!bufchain) {
1319
+ console.error('No bufchain found for tag: ' + replytag)
1320
+ return
1321
+ }
1322
+
1323
+ bufchain.set_next_blob(replybuf)
1324
+ this.virtqueue.push_reply(bufchain)
1325
+ this.virtqueue.flush_replies()
1326
+
1327
+ this.tag_bufchain.delete(replytag)
1328
+ })
1329
+ },
1330
+ )
1331
+ this.virtqueue = this.virtio.queues[0]
1332
+ }
1333
+
1334
+ get_state(): any[] {
1335
+ const state: any[] = []
1336
+
1337
+ state[0] = this.configspace_tagname
1338
+ state[1] = this.configspace_taglen
1339
+ state[2] = this.virtio
1340
+ state[3] = this.tag_bufchain
1341
+
1342
+ return state
1343
+ }
1344
+
1345
+ set_state(state: any[]): void {
1346
+ this.configspace_tagname = state[0]
1347
+ this.configspace_taglen = state[1]
1348
+ this.virtio.set_state(state[2])
1349
+ this.virtqueue = this.virtio.queues[0]
1350
+ this.tag_bufchain = state[3]
1351
+ }
1352
+
1353
+ reset(): void {
1354
+ this.virtio.reset()
1355
+ }
1356
+ }
1357
+
1358
+ export class Virtio9pProxy {
1359
+ socket: WebSocket | undefined
1360
+ cpu: CPU
1361
+ send_queue: Uint8Array[]
1362
+ url: string
1363
+ reconnect_interval: number
1364
+ last_connect_attempt: number
1365
+ send_queue_limit: number
1366
+ destroyed: boolean
1367
+ tag_bufchain: Map<number, VirtQueueBufferChain>
1368
+ configspace_tagname: number[]
1369
+ configspace_taglen: number
1370
+ virtio: VirtIO
1371
+ virtqueue: VirtQueue
1372
+
1373
+ constructor(url: string, cpu: CPU) {
1374
+ this.socket = undefined
1375
+ this.cpu = cpu
1376
+
1377
+ // TODO: circular buffer?
1378
+ this.send_queue = []
1379
+ this.url = url
1380
+
1381
+ this.reconnect_interval = 10000
1382
+ this.last_connect_attempt = Date.now() - this.reconnect_interval
1383
+ this.send_queue_limit = 64
1384
+ this.destroyed = false
1385
+
1386
+ this.tag_bufchain = new Map()
1387
+
1388
+ this.configspace_tagname = [0x68, 0x6f, 0x73, 0x74, 0x39, 0x70] // "host9p" string
1389
+ this.configspace_taglen = this.configspace_tagname.length // num bytes
1390
+
1391
+ this.virtio = init_virtio(
1392
+ cpu,
1393
+ this.configspace_taglen,
1394
+ this.configspace_tagname,
1395
+ async (bufchain: VirtQueueBufferChain) => {
1396
+ // TODO: split into header + data blobs to avoid unnecessary copying.
1397
+ const reqbuf = new Uint8Array(bufchain.length_readable)
1398
+ bufchain.get_next_blob(reqbuf)
1399
+
1400
+ const reqheader = marshall.Unmarshall(['w', 'b', 'h'], reqbuf, {
1401
+ offset: 0,
1402
+ })
1403
+ const reqtag = reqheader[2]
1404
+
1405
+ this.tag_bufchain.set(reqtag, bufchain)
1406
+ this.send(reqbuf)
1407
+ },
1408
+ )
1409
+ this.virtqueue = this.virtio.queues[0]
1410
+ }
1411
+
1412
+ get_state(): any[] {
1413
+ const state: any[] = []
1414
+
1415
+ state[0] = this.configspace_tagname
1416
+ state[1] = this.configspace_taglen
1417
+ state[2] = this.virtio
1418
+ state[3] = this.tag_bufchain
1419
+
1420
+ return state
1421
+ }
1422
+
1423
+ set_state(state: any[]): void {
1424
+ this.configspace_tagname = state[0]
1425
+ this.configspace_taglen = state[1]
1426
+ this.virtio.set_state(state[2])
1427
+ this.virtqueue = this.virtio.queues[0]
1428
+ this.tag_bufchain = state[3]
1429
+ }
1430
+
1431
+ reset(): void {
1432
+ this.virtio.reset()
1433
+ }
1434
+
1435
+ handle_message(e: MessageEvent): void {
1436
+ const replybuf = new Uint8Array(e.data)
1437
+ const replyheader = marshall.Unmarshall(['w', 'b', 'h'], replybuf, {
1438
+ offset: 0,
1439
+ })
1440
+ const replytag = replyheader[2]
1441
+
1442
+ const bufchain = this.tag_bufchain.get(replytag)
1443
+ if (!bufchain) {
1444
+ console.error(
1445
+ 'Virtio9pProxy: No bufchain found for tag: ' + replytag,
1446
+ )
1447
+ return
1448
+ }
1449
+
1450
+ bufchain.set_next_blob(replybuf)
1451
+ this.virtqueue.push_reply(bufchain)
1452
+ this.virtqueue.flush_replies()
1453
+
1454
+ this.tag_bufchain.delete(replytag)
1455
+ }
1456
+
1457
+ handle_close(_e: CloseEvent): void {
1458
+ if (!this.destroyed) {
1459
+ this.connect()
1460
+ setTimeout(this.connect.bind(this), this.reconnect_interval)
1461
+ }
1462
+ }
1463
+
1464
+ handle_open(_e: Event): void {
1465
+ for (let i = 0; i < this.send_queue.length; i++) {
1466
+ this.send(this.send_queue[i])
1467
+ }
1468
+
1469
+ this.send_queue = []
1470
+ }
1471
+
1472
+ handle_error(_e: Event): void {
1473
+ //console.log("onerror", e);
1474
+ }
1475
+
1476
+ destroy(): void {
1477
+ this.destroyed = true
1478
+ if (this.socket) {
1479
+ this.socket.close()
1480
+ }
1481
+ }
1482
+
1483
+ connect(): void {
1484
+ if (typeof WebSocket === 'undefined') {
1485
+ return
1486
+ }
1487
+
1488
+ if (this.socket) {
1489
+ const state = this.socket.readyState
1490
+
1491
+ if (state === 0 || state === 1) {
1492
+ // already or almost there
1493
+ return
1494
+ }
1495
+ }
1496
+
1497
+ const now = Date.now()
1498
+
1499
+ if (this.last_connect_attempt + this.reconnect_interval > now) {
1500
+ return
1501
+ }
1502
+
1503
+ this.last_connect_attempt = Date.now()
1504
+
1505
+ try {
1506
+ this.socket = new WebSocket(this.url)
1507
+ } catch (e) {
1508
+ console.error(e)
1509
+ return
1510
+ }
1511
+
1512
+ this.socket.binaryType = 'arraybuffer'
1513
+
1514
+ this.socket.onopen = this.handle_open.bind(this)
1515
+ this.socket.onmessage = this.handle_message.bind(this)
1516
+ this.socket.onclose = this.handle_close.bind(this)
1517
+ this.socket.onerror = this.handle_error.bind(this)
1518
+ }
1519
+
1520
+ send(data: Uint8Array): void {
1521
+ if (!this.socket || this.socket.readyState !== 1) {
1522
+ this.send_queue.push(data)
1523
+
1524
+ if (this.send_queue.length > 2 * this.send_queue_limit) {
1525
+ this.send_queue = this.send_queue.slice(-this.send_queue_limit)
1526
+ }
1527
+
1528
+ this.connect()
1529
+ } else {
1530
+ // Copy into a plain ArrayBuffer for WebSocket.send() compatibility
1531
+ const buf = new ArrayBuffer(data.byteLength)
1532
+ new Uint8Array(buf).set(data)
1533
+ this.socket.send(buf)
1534
+ }
1535
+ }
1536
+
1537
+ change_proxy(url: string): void {
1538
+ this.url = url
1539
+
1540
+ if (this.socket) {
1541
+ this.socket.onclose = function () {}
1542
+ this.socket.onerror = function () {}
1543
+ this.socket.close()
1544
+ this.socket = undefined
1545
+ }
1546
+ }
1547
+ }