@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
@@ -0,0 +1,1879 @@
1
+ // -------------------------------------------------
2
+ // ----------------- FILESYSTEM---------------------
3
+ // -------------------------------------------------
4
+ // Implementation of a unix filesystem in memory.
5
+
6
+ import { LOG_9P } from '../src/const.js'
7
+ import { h } from '../src/lib.js'
8
+ import { dbg_assert, dbg_log } from '../src/log.js'
9
+ import * as marshall from '../lib/marshall.js'
10
+ import { EEXIST, ENOTEMPTY, ENOENT, EPERM } from './9p.js'
11
+ import {
12
+ P9_LOCK_SUCCESS,
13
+ P9_LOCK_BLOCKED,
14
+ P9_LOCK_TYPE_UNLCK,
15
+ P9_LOCK_TYPE_WRLCK,
16
+ P9_LOCK_TYPE_RDLCK,
17
+ } from './9p.js'
18
+
19
+ import type { QID } from '../lib/marshall.js'
20
+
21
+ // FileStorageInterface describes a backend for reading/writing file data.
22
+ export interface FileStorageInterface {
23
+ read(
24
+ sha256sum: string,
25
+ offset: number,
26
+ count: number,
27
+ file_size: number,
28
+ ): Promise<Uint8Array | null>
29
+ cache(sha256sum: string, data: Uint8Array): Promise<void>
30
+ uncache(sha256sum: string): void
31
+ }
32
+
33
+ export const S_IRWXUGO = 0x1ff
34
+ export const S_IFMT = 0xf000
35
+ export const S_IFSOCK = 0xc000
36
+ export const S_IFLNK = 0xa000
37
+ export const S_IFREG = 0x8000
38
+ export const S_IFBLK = 0x6000
39
+ export const S_IFDIR = 0x4000
40
+ export const S_IFCHR = 0x2000
41
+
42
+ //var S_IFIFO 0010000
43
+ //var S_ISUID 0004000
44
+ //var S_ISGID 0002000
45
+ //var S_ISVTX 0001000
46
+
47
+ const _O_RDONLY = 0x0000 // open for reading only
48
+ const _O_WRONLY = 0x0001 // open for writing only
49
+ const _O_RDWR = 0x0002 // open for reading and writing
50
+ const _O_ACCMODE = 0x0003 // mask for above modes
51
+
52
+ export const STATUS_INVALID = -0x1
53
+ export const STATUS_OK = 0x0
54
+ export const STATUS_ON_STORAGE = 0x2
55
+ export const STATUS_UNLINKED = 0x4
56
+ export const STATUS_FORWARDING = 0x5
57
+
58
+ const texten = new TextEncoder()
59
+
60
+ const JSONFS_VERSION = 3
61
+
62
+ const JSONFS_IDX_NAME = 0
63
+ const JSONFS_IDX_SIZE = 1
64
+ const JSONFS_IDX_MTIME = 2
65
+ const JSONFS_IDX_MODE = 3
66
+ const JSONFS_IDX_UID = 4
67
+ const JSONFS_IDX_GID = 5
68
+ const JSONFS_IDX_TARGET = 6
69
+ const JSONFS_IDX_SHA256 = 6
70
+
71
+ export interface QIDCounter {
72
+ last_qidnumber: number
73
+ }
74
+
75
+ export interface SearchPathResult {
76
+ id: number
77
+ parentid: number
78
+ name: string
79
+ forward_path: string | null
80
+ }
81
+
82
+ export class FSLockRegion {
83
+ type: number = P9_LOCK_TYPE_UNLCK
84
+ start: number = 0
85
+ length: number = Infinity
86
+ proc_id: number = -1
87
+ client_id: string = ''
88
+
89
+ get_state(): any[] {
90
+ const state: (number | string)[] = []
91
+
92
+ state[0] = this.type
93
+ state[1] = this.start
94
+ // Infinity is not JSON.stringify-able
95
+ state[2] = this.length === Infinity ? 0 : this.length
96
+ state[3] = this.proc_id
97
+ state[4] = this.client_id
98
+
99
+ return state
100
+ }
101
+
102
+ set_state(state: any[]): void {
103
+ this.type = state[0]
104
+ this.start = state[1]
105
+ this.length = state[2] === 0 ? Infinity : state[2]
106
+ this.proc_id = state[3]
107
+ this.client_id = state[4]
108
+ }
109
+
110
+ clone(): FSLockRegion {
111
+ const new_region = new FSLockRegion()
112
+ new_region.set_state(this.get_state())
113
+ return new_region
114
+ }
115
+
116
+ conflicts_with(region: FSLockRegion): boolean {
117
+ if (
118
+ this.proc_id === region.proc_id &&
119
+ this.client_id === region.client_id
120
+ )
121
+ return false
122
+ if (
123
+ this.type === P9_LOCK_TYPE_UNLCK ||
124
+ region.type === P9_LOCK_TYPE_UNLCK
125
+ )
126
+ return false
127
+ if (
128
+ this.type !== P9_LOCK_TYPE_WRLCK &&
129
+ region.type !== P9_LOCK_TYPE_WRLCK
130
+ )
131
+ return false
132
+ if (this.start + this.length <= region.start) return false
133
+ if (region.start + region.length <= this.start) return false
134
+ return true
135
+ }
136
+
137
+ is_alike(region: FSLockRegion): boolean {
138
+ return (
139
+ region.proc_id === this.proc_id &&
140
+ region.client_id === this.client_id &&
141
+ region.type === this.type
142
+ )
143
+ }
144
+
145
+ may_merge_after(region: FSLockRegion): boolean {
146
+ return (
147
+ this.is_alike(region) && region.start + region.length === this.start
148
+ )
149
+ }
150
+ }
151
+
152
+ export class Inode {
153
+ direntries: Map<string, number> = new Map()
154
+ status: number = 0
155
+ size: number = 0x0
156
+ uid: number = 0x0
157
+ gid: number = 0x0
158
+ fid: number = 0
159
+ ctime: number = 0
160
+ atime: number = 0
161
+ mtime: number = 0
162
+ major: number = 0x0
163
+ minor: number = 0x0
164
+ symlink: string = ''
165
+ mode: number = 0x01ed
166
+ qid: QID
167
+ caps: Uint8Array | undefined = undefined
168
+ nlinks: number = 0
169
+ sha256sum: string = ''
170
+ locks: FSLockRegion[] = []
171
+
172
+ // For forwarders:
173
+ mount_id: number = -1
174
+ foreign_id: number = -1
175
+
176
+ constructor(qidnumber: number) {
177
+ this.qid = {
178
+ type: 0,
179
+ version: 0,
180
+ path: qidnumber,
181
+ }
182
+ }
183
+
184
+ get_state(): any[] {
185
+ const state: any[] = []
186
+ state[0] = this.mode
187
+
188
+ if ((this.mode & S_IFMT) === S_IFDIR) {
189
+ state[1] = [...this.direntries]
190
+ } else if ((this.mode & S_IFMT) === S_IFREG) {
191
+ state[1] = this.sha256sum
192
+ } else if ((this.mode & S_IFMT) === S_IFLNK) {
193
+ state[1] = this.symlink
194
+ } else if ((this.mode & S_IFMT) === S_IFSOCK) {
195
+ state[1] = [this.minor, this.major]
196
+ } else {
197
+ state[1] = null
198
+ }
199
+
200
+ state[2] = this.locks
201
+ state[3] = this.status
202
+ state[4] = this.size
203
+ state[5] = this.uid
204
+ state[6] = this.gid
205
+ state[7] = this.fid
206
+ state[8] = this.ctime
207
+ state[9] = this.atime
208
+ state[10] = this.mtime
209
+ state[11] = this.qid.version
210
+ state[12] = this.qid.path
211
+ state[13] = this.nlinks
212
+
213
+ //state[23] = this.mount_id;
214
+ //state[24] = this.foreign_id;
215
+ //state[25] = this.caps; // currently not writable
216
+ return state
217
+ }
218
+
219
+ set_state(state: any[]): void {
220
+ this.mode = state[0]
221
+
222
+ if ((this.mode & S_IFMT) === S_IFDIR) {
223
+ this.direntries = new Map()
224
+ for (const [name, entry] of state[1]) {
225
+ this.direntries.set(name, entry)
226
+ }
227
+ } else if ((this.mode & S_IFMT) === S_IFREG) {
228
+ this.sha256sum = state[1]
229
+ } else if ((this.mode & S_IFMT) === S_IFLNK) {
230
+ this.symlink = state[1]
231
+ } else if ((this.mode & S_IFMT) === S_IFSOCK) {
232
+ ;[this.minor, this.major] = state[1]
233
+ } else {
234
+ // Nothing
235
+ }
236
+
237
+ this.locks = []
238
+ for (const lock_state of state[2]) {
239
+ const lock = new FSLockRegion()
240
+ lock.set_state(lock_state)
241
+ this.locks.push(lock)
242
+ }
243
+ this.status = state[3]
244
+ this.size = state[4]
245
+ this.uid = state[5]
246
+ this.gid = state[6]
247
+ this.fid = state[7]
248
+ this.ctime = state[8]
249
+ this.atime = state[9]
250
+ this.mtime = state[10]
251
+ this.qid.type = (this.mode & S_IFMT) >> 8
252
+ this.qid.version = state[11]
253
+ this.qid.path = state[12]
254
+ this.nlinks = state[13]
255
+
256
+ //this.mount_id = state[23];
257
+ //this.foreign_id = state[24];
258
+ //this.caps = state[20];
259
+ }
260
+ }
261
+
262
+ class FSMountInfo {
263
+ fs: FS
264
+ backtrack: Map<number, number>
265
+
266
+ constructor(filesystem: FS) {
267
+ this.fs = filesystem
268
+ this.backtrack = new Map()
269
+ }
270
+
271
+ get_state(): any[] {
272
+ const state: any[] = []
273
+
274
+ state[0] = this.fs
275
+ state[1] = [...this.backtrack]
276
+
277
+ return state
278
+ }
279
+
280
+ set_state(state: any[]): void {
281
+ this.fs = state[0]
282
+ this.backtrack = new Map(state[1])
283
+ }
284
+ }
285
+
286
+ interface JsonFS {
287
+ version: number
288
+
289
+ fsroot: any[]
290
+ size: number
291
+ }
292
+
293
+ export class FS {
294
+ inodes: Inode[]
295
+ storage: FileStorageInterface
296
+ qidcounter: QIDCounter
297
+ inodedata: Record<number, Uint8Array>
298
+ total_size: number
299
+ used_size: number
300
+ mounts: FSMountInfo[]
301
+
302
+ constructor(storage: FileStorageInterface, qidcounter?: QIDCounter) {
303
+ this.inodes = []
304
+ this.storage = storage
305
+ this.qidcounter = qidcounter || { last_qidnumber: 0 }
306
+ this.inodedata = {}
307
+ this.total_size = 256 * 1024 * 1024 * 1024
308
+ this.used_size = 0
309
+ this.mounts = []
310
+
311
+ // root entry
312
+ this.CreateDirectory('', -1)
313
+ }
314
+
315
+ get_state(): any[] {
316
+ let state: any[] = []
317
+
318
+ state[0] = this.inodes
319
+ state[1] = this.qidcounter.last_qidnumber
320
+ state[2] = []
321
+ for (const [id, data] of Object.entries(this.inodedata)) {
322
+ if ((this.inodes[Number(id)].mode & S_IFDIR) === 0) {
323
+ state[2].push([id, data])
324
+ }
325
+ }
326
+ state[3] = this.total_size
327
+ state[4] = this.used_size
328
+ state = state.concat(this.mounts)
329
+
330
+ return state
331
+ }
332
+
333
+ set_state(state: any[]): void {
334
+ this.inodes = state[0].map((s: any) => {
335
+ const inode = new Inode(0)
336
+ inode.set_state(s)
337
+ return inode
338
+ })
339
+ this.qidcounter.last_qidnumber = state[1]
340
+ this.inodedata = {}
341
+ for (const [key, rawValue] of state[2]) {
342
+ let value = rawValue
343
+ if (value.buffer.byteLength !== value.byteLength) {
344
+ // make a copy if we didn't get one
345
+ value = value.slice()
346
+ }
347
+
348
+ this.inodedata[key] = value
349
+ }
350
+ this.total_size = state[3]
351
+ this.used_size = state[4]
352
+ this.mounts = state.slice(5)
353
+ }
354
+
355
+ // -----------------------------------------------------
356
+
357
+ load_from_json(fs: JsonFS): void {
358
+ dbg_assert(!!fs, 'Invalid fs passed to load_from_json')
359
+
360
+ if (fs['version'] !== JSONFS_VERSION) {
361
+ throw 'The filesystem JSON format has changed. Please recreate the filesystem JSON.'
362
+ }
363
+
364
+ const fsroot = fs['fsroot']
365
+ this.used_size = fs['size']
366
+
367
+ for (let i = 0; i < fsroot.length; i++) {
368
+ this.LoadRecursive(fsroot[i], 0)
369
+ }
370
+ }
371
+
372
+ private LoadRecursive(data: any[], parentid: number): void {
373
+ const inode = this.CreateInode()
374
+
375
+ const name = data[JSONFS_IDX_NAME]
376
+ inode.size = data[JSONFS_IDX_SIZE]
377
+ inode.mtime = data[JSONFS_IDX_MTIME]
378
+ inode.ctime = inode.mtime
379
+ inode.atime = inode.mtime
380
+ inode.mode = data[JSONFS_IDX_MODE]
381
+ inode.uid = data[JSONFS_IDX_UID]
382
+ inode.gid = data[JSONFS_IDX_GID]
383
+
384
+ const ifmt = inode.mode & S_IFMT
385
+
386
+ if (ifmt === S_IFDIR) {
387
+ this.PushInode(inode, parentid, name)
388
+ this.LoadDir(this.inodes.length - 1, data[JSONFS_IDX_TARGET])
389
+ } else if (ifmt === S_IFREG) {
390
+ inode.status = STATUS_ON_STORAGE
391
+ inode.sha256sum = data[JSONFS_IDX_SHA256]
392
+ dbg_assert(!!inode.sha256sum)
393
+ this.PushInode(inode, parentid, name)
394
+ } else if (ifmt === S_IFLNK) {
395
+ inode.symlink = data[JSONFS_IDX_TARGET]
396
+ this.PushInode(inode, parentid, name)
397
+ } else if (ifmt === S_IFSOCK) {
398
+ // socket: ignore
399
+ } else {
400
+ dbg_log('Unexpected ifmt: ' + h(ifmt) + ' (' + name + ')', LOG_9P)
401
+ }
402
+ }
403
+
404
+ private LoadDir(parentid: number, children: any[]): void {
405
+ for (let i = 0; i < children.length; i++) {
406
+ this.LoadRecursive(children[i], parentid)
407
+ }
408
+ }
409
+
410
+ // -----------------------------------------------------
411
+
412
+ private should_be_linked(inode: Inode): boolean {
413
+ // Note: Non-root forwarder inode could still have a non-forwarder parent, so don't use
414
+ // parent inode to check.
415
+ return !this.is_forwarder(inode) || inode.foreign_id === 0
416
+ }
417
+
418
+ private link_under_dir(parentid: number, idx: number, name: string): void {
419
+ const inode = this.inodes[idx]
420
+ const parent_inode = this.inodes[parentid]
421
+
422
+ dbg_assert(
423
+ !this.is_forwarder(parent_inode),
424
+ "Filesystem: Shouldn't link under fowarder parents",
425
+ )
426
+ dbg_assert(
427
+ this.IsDirectory(parentid),
428
+ "Filesystem: Can't link under non-directories",
429
+ )
430
+ dbg_assert(
431
+ this.should_be_linked(inode),
432
+ "Filesystem: Can't link across filesystems apart from their root",
433
+ )
434
+ dbg_assert(
435
+ inode.nlinks >= 0,
436
+ 'Filesystem: Found negative nlinks value of ' + inode.nlinks,
437
+ )
438
+ dbg_assert(
439
+ !parent_inode.direntries.has(name),
440
+ "Filesystem: Name '" + name + "' is already taken",
441
+ )
442
+
443
+ parent_inode.direntries.set(name, idx)
444
+ inode.nlinks++
445
+
446
+ if (this.IsDirectory(idx)) {
447
+ dbg_assert(
448
+ !inode.direntries.has('..'),
449
+ 'Filesystem: Cannot link a directory twice',
450
+ )
451
+
452
+ if (!inode.direntries.has('.')) inode.nlinks++
453
+ inode.direntries.set('.', idx)
454
+
455
+ inode.direntries.set('..', parentid)
456
+ parent_inode.nlinks++
457
+ }
458
+ }
459
+
460
+ private unlink_from_dir(parentid: number, name: string): void {
461
+ const idx = this.Search(parentid, name)
462
+ const inode = this.inodes[idx]
463
+ const parent_inode = this.inodes[parentid]
464
+
465
+ dbg_assert(
466
+ !this.is_forwarder(parent_inode),
467
+ "Filesystem: Can't unlink from forwarders",
468
+ )
469
+ dbg_assert(
470
+ this.IsDirectory(parentid),
471
+ "Filesystem: Can't unlink from non-directories",
472
+ )
473
+
474
+ const exists = parent_inode.direntries.delete(name)
475
+ if (!exists) {
476
+ dbg_assert(
477
+ false,
478
+ "Filesystem: Can't unlink non-existent file: " + name,
479
+ )
480
+ return
481
+ }
482
+
483
+ inode.nlinks--
484
+
485
+ if (this.IsDirectory(idx)) {
486
+ dbg_assert(
487
+ inode.direntries.get('..') === parentid,
488
+ 'Filesystem: Found directory with bad parent id',
489
+ )
490
+
491
+ inode.direntries.delete('..')
492
+ parent_inode.nlinks--
493
+ }
494
+
495
+ dbg_assert(
496
+ inode.nlinks >= 0,
497
+ 'Filesystem: Found negative nlinks value of ' + inode.nlinks,
498
+ )
499
+ }
500
+
501
+ PushInode(inode: Inode, parentid: number, name: string): void {
502
+ if (parentid !== -1) {
503
+ this.inodes.push(inode)
504
+ inode.fid = this.inodes.length - 1
505
+ this.link_under_dir(parentid, inode.fid, name)
506
+ return
507
+ } else {
508
+ if (this.inodes.length === 0) {
509
+ // if root directory
510
+ this.inodes.push(inode)
511
+ inode.direntries.set('.', 0)
512
+ inode.direntries.set('..', 0)
513
+ inode.nlinks = 2
514
+ return
515
+ }
516
+ }
517
+
518
+ dbg_assert(
519
+ false,
520
+ 'Error in Filesystem: Pushed inode with name = ' +
521
+ name +
522
+ ' has no parent',
523
+ )
524
+ }
525
+
526
+ private divert(parentid: number, filename: string): number {
527
+ const old_idx = this.Search(parentid, filename)
528
+ const old_inode = this.inodes[old_idx]
529
+ const new_inode = new Inode(-1)
530
+
531
+ dbg_assert(
532
+ !!old_inode,
533
+ 'Filesystem divert: name (' + filename + ') not found',
534
+ )
535
+ dbg_assert(
536
+ this.IsDirectory(old_idx) || old_inode.nlinks <= 1,
537
+ "Filesystem: can't divert hardlinked file '" +
538
+ filename +
539
+ "' with nlinks=" +
540
+ old_inode.nlinks,
541
+ )
542
+
543
+ // Shallow copy is alright.
544
+ Object.assign(new_inode, old_inode)
545
+
546
+ const idx = this.inodes.length
547
+ this.inodes.push(new_inode)
548
+ new_inode.fid = idx
549
+
550
+ // Relink references
551
+ if (this.is_forwarder(old_inode)) {
552
+ this.mounts[old_inode.mount_id].backtrack.set(
553
+ old_inode.foreign_id,
554
+ idx,
555
+ )
556
+ }
557
+ if (this.should_be_linked(old_inode)) {
558
+ this.unlink_from_dir(parentid, filename)
559
+ this.link_under_dir(parentid, idx, filename)
560
+ }
561
+
562
+ // Update children
563
+ if (this.IsDirectory(old_idx) && !this.is_forwarder(old_inode)) {
564
+ for (const [name, child_id] of new_inode.direntries) {
565
+ if (name === '.' || name === '..') continue
566
+ if (this.IsDirectory(child_id)) {
567
+ this.inodes[child_id].direntries.set('..', idx)
568
+ }
569
+ }
570
+ }
571
+
572
+ // Relocate local data if any.
573
+ this.inodedata[idx] = this.inodedata[old_idx]
574
+ delete this.inodedata[old_idx]
575
+
576
+ // Retire old reference information.
577
+ old_inode.direntries = new Map()
578
+ old_inode.nlinks = 0
579
+
580
+ return idx
581
+ }
582
+
583
+ private copy_inode(src_inode: Inode, dest_inode: Inode): void {
584
+ Object.assign(dest_inode, src_inode, {
585
+ fid: dest_inode.fid,
586
+ direntries: dest_inode.direntries,
587
+ nlinks: dest_inode.nlinks,
588
+ })
589
+ }
590
+
591
+ CreateInode(): Inode {
592
+ const now = Math.round(Date.now() / 1000)
593
+ const inode = new Inode(++this.qidcounter.last_qidnumber)
594
+ inode.atime = inode.ctime = inode.mtime = now
595
+ return inode
596
+ }
597
+
598
+ // Note: parentid = -1 for initial root directory.
599
+ CreateDirectory(name: string, parentid: number): number {
600
+ const parent_inode = this.inodes[parentid]
601
+ if (parentid >= 0 && this.is_forwarder(parent_inode)) {
602
+ const foreign_parentid = parent_inode.foreign_id
603
+ const foreign_id = this.follow_fs(parent_inode).CreateDirectory(
604
+ name,
605
+ foreign_parentid,
606
+ )
607
+ return this.create_forwarder(parent_inode.mount_id, foreign_id)
608
+ }
609
+ const x = this.CreateInode()
610
+ x.mode = 0x01ff | S_IFDIR
611
+ if (parentid >= 0) {
612
+ x.uid = this.inodes[parentid].uid
613
+ x.gid = this.inodes[parentid].gid
614
+ x.mode = (this.inodes[parentid].mode & 0x1ff) | S_IFDIR
615
+ }
616
+ x.qid.type = S_IFDIR >> 8
617
+ this.PushInode(x, parentid, name)
618
+ this.NotifyListeners(this.inodes.length - 1, 'newdir')
619
+ return this.inodes.length - 1
620
+ }
621
+
622
+ CreateFile(filename: string, parentid: number): number {
623
+ const parent_inode = this.inodes[parentid]
624
+ if (this.is_forwarder(parent_inode)) {
625
+ const foreign_parentid = parent_inode.foreign_id
626
+ const foreign_id = this.follow_fs(parent_inode).CreateFile(
627
+ filename,
628
+ foreign_parentid,
629
+ )
630
+ return this.create_forwarder(parent_inode.mount_id, foreign_id)
631
+ }
632
+ const x = this.CreateInode()
633
+ x.uid = this.inodes[parentid].uid
634
+ x.gid = this.inodes[parentid].gid
635
+ x.qid.type = S_IFREG >> 8
636
+ x.mode = (this.inodes[parentid].mode & 0x1b6) | S_IFREG
637
+ this.PushInode(x, parentid, filename)
638
+ this.NotifyListeners(this.inodes.length - 1, 'newfile')
639
+ return this.inodes.length - 1
640
+ }
641
+
642
+ CreateNode(
643
+ filename: string,
644
+ parentid: number,
645
+ major: number,
646
+ minor: number,
647
+ ): number {
648
+ const parent_inode = this.inodes[parentid]
649
+ if (this.is_forwarder(parent_inode)) {
650
+ const foreign_parentid = parent_inode.foreign_id
651
+ const foreign_id = this.follow_fs(parent_inode).CreateNode(
652
+ filename,
653
+ foreign_parentid,
654
+ major,
655
+ minor,
656
+ )
657
+ return this.create_forwarder(parent_inode.mount_id, foreign_id)
658
+ }
659
+ const x = this.CreateInode()
660
+ x.major = major
661
+ x.minor = minor
662
+ x.uid = this.inodes[parentid].uid
663
+ x.gid = this.inodes[parentid].gid
664
+ x.qid.type = S_IFSOCK >> 8
665
+ x.mode = this.inodes[parentid].mode & 0x1b6
666
+ this.PushInode(x, parentid, filename)
667
+ return this.inodes.length - 1
668
+ }
669
+
670
+ CreateSymlink(filename: string, parentid: number, symlink: string): number {
671
+ const parent_inode = this.inodes[parentid]
672
+ if (this.is_forwarder(parent_inode)) {
673
+ const foreign_parentid = parent_inode.foreign_id
674
+ const foreign_id = this.follow_fs(parent_inode).CreateSymlink(
675
+ filename,
676
+ foreign_parentid,
677
+ symlink,
678
+ )
679
+ return this.create_forwarder(parent_inode.mount_id, foreign_id)
680
+ }
681
+ const x = this.CreateInode()
682
+ x.uid = this.inodes[parentid].uid
683
+ x.gid = this.inodes[parentid].gid
684
+ x.qid.type = S_IFLNK >> 8
685
+ x.symlink = symlink
686
+ x.mode = S_IFLNK
687
+ this.PushInode(x, parentid, filename)
688
+ return this.inodes.length - 1
689
+ }
690
+
691
+ async CreateTextFile(
692
+ filename: string,
693
+ parentid: number,
694
+ str: string,
695
+ ): Promise<number> {
696
+ const parent_inode = this.inodes[parentid]
697
+ if (this.is_forwarder(parent_inode)) {
698
+ const foreign_parentid = parent_inode.foreign_id
699
+ const foreign_id = await this.follow_fs(
700
+ parent_inode,
701
+ ).CreateTextFile(filename, foreign_parentid, str)
702
+ return this.create_forwarder(parent_inode.mount_id, foreign_id)
703
+ }
704
+ const id = this.CreateFile(filename, parentid)
705
+ const x = this.inodes[id]
706
+ const data = new Uint8Array(str.length)
707
+ x.size = str.length
708
+ for (let j = 0; j < str.length; j++) {
709
+ data[j] = str.charCodeAt(j)
710
+ }
711
+ await this.set_data(id, data)
712
+ return id
713
+ }
714
+
715
+ async CreateBinaryFile(
716
+ filename: string,
717
+ parentid: number,
718
+ buffer: Uint8Array,
719
+ ): Promise<number> {
720
+ const parent_inode = this.inodes[parentid]
721
+ if (this.is_forwarder(parent_inode)) {
722
+ const foreign_parentid = parent_inode.foreign_id
723
+ const foreign_id = await this.follow_fs(
724
+ parent_inode,
725
+ ).CreateBinaryFile(filename, foreign_parentid, buffer)
726
+ return this.create_forwarder(parent_inode.mount_id, foreign_id)
727
+ }
728
+ const id = this.CreateFile(filename, parentid)
729
+ const x = this.inodes[id]
730
+ const data = new Uint8Array(buffer.length)
731
+ data.set(buffer)
732
+ await this.set_data(id, data)
733
+ x.size = buffer.length
734
+ return id
735
+ }
736
+
737
+ async OpenInode(id: number, mode: number | undefined): Promise<void> {
738
+ const inode = this.inodes[id]
739
+ if (this.is_forwarder(inode)) {
740
+ return await this.follow_fs(inode).OpenInode(inode.foreign_id, mode)
741
+ }
742
+ if ((inode.mode & S_IFMT) === S_IFDIR) {
743
+ this.FillDirectory(id)
744
+ }
745
+ }
746
+
747
+ async CloseInode(id: number): Promise<void> {
748
+ const inode = this.inodes[id]
749
+ if (this.is_forwarder(inode)) {
750
+ return await this.follow_fs(inode).CloseInode(inode.foreign_id)
751
+ }
752
+ if (inode.status === STATUS_ON_STORAGE) {
753
+ this.storage.uncache(inode.sha256sum)
754
+ }
755
+ if (inode.status === STATUS_UNLINKED) {
756
+ inode.status = STATUS_INVALID
757
+ await this.DeleteData(id)
758
+ }
759
+ }
760
+
761
+ async Rename(
762
+ olddirid: number,
763
+ oldname: string,
764
+ newdirid: number,
765
+ newname: string,
766
+ ): Promise<number> {
767
+ if (olddirid === newdirid && oldname === newname) {
768
+ return 0
769
+ }
770
+ const oldid = this.Search(olddirid, oldname)
771
+ if (oldid === -1) {
772
+ return -ENOENT
773
+ }
774
+
775
+ // For event notification near end of method.
776
+ const oldpath = this.GetFullPath(olddirid) + '/' + oldname
777
+
778
+ const newid = this.Search(newdirid, newname)
779
+ if (newid !== -1) {
780
+ const ret = this.Unlink(newdirid, newname)
781
+ if (ret < 0) return ret
782
+ }
783
+
784
+ const idx = oldid // idx contains the id which we want to rename
785
+ const inode = this.inodes[idx]
786
+ const olddir = this.inodes[olddirid]
787
+ const newdir = this.inodes[newdirid]
788
+
789
+ if (!this.is_forwarder(olddir) && !this.is_forwarder(newdir)) {
790
+ // Move inode within current filesystem.
791
+
792
+ this.unlink_from_dir(olddirid, oldname)
793
+ this.link_under_dir(newdirid, idx, newname)
794
+
795
+ inode.qid.version++
796
+ } else if (
797
+ this.is_forwarder(olddir) &&
798
+ olddir.mount_id === newdir.mount_id
799
+ ) {
800
+ // Move inode within the same child filesystem.
801
+
802
+ const ret = await this.follow_fs(olddir).Rename(
803
+ olddir.foreign_id,
804
+ oldname,
805
+ newdir.foreign_id,
806
+ newname,
807
+ )
808
+
809
+ if (ret < 0) return ret
810
+ } else if (this.is_a_root(idx)) {
811
+ // The actual inode is a root of some descendant filesystem.
812
+ // Moving mountpoint across fs not supported - needs to update all corresponding forwarders.
813
+ dbg_log(
814
+ 'XXX: Attempted to move mountpoint (' + oldname + ') - skipped',
815
+ LOG_9P,
816
+ )
817
+ return -EPERM
818
+ } else if (!this.IsDirectory(idx) && this.GetInode(idx).nlinks > 1) {
819
+ // Move hardlinked inode vertically in mount tree.
820
+ dbg_log(
821
+ 'XXX: Attempted to move hardlinked file (' +
822
+ oldname +
823
+ ') ' +
824
+ 'across filesystems - skipped',
825
+ LOG_9P,
826
+ )
827
+ return -EPERM
828
+ } else {
829
+ // Jump between filesystems.
830
+
831
+ // Can't work with both old and new inode information without first diverting the old
832
+ // information into a new idx value.
833
+ const diverted_old_idx = this.divert(olddirid, oldname)
834
+ const old_real_inode = this.GetInode(idx)
835
+
836
+ const data = await this.Read(
837
+ diverted_old_idx,
838
+ 0,
839
+ old_real_inode.size,
840
+ )
841
+
842
+ if (this.is_forwarder(newdir)) {
843
+ // Create new inode.
844
+ const foreign_fs = this.follow_fs(newdir)
845
+ const foreign_id = this.IsDirectory(diverted_old_idx)
846
+ ? foreign_fs.CreateDirectory(newname, newdir.foreign_id)
847
+ : foreign_fs.CreateFile(newname, newdir.foreign_id)
848
+
849
+ const new_real_inode = foreign_fs.GetInode(foreign_id)
850
+ this.copy_inode(old_real_inode, new_real_inode)
851
+
852
+ // Point to this new location.
853
+ this.set_forwarder(idx, newdir.mount_id, foreign_id)
854
+ } else {
855
+ // Replace current forwarder with real inode.
856
+ this.delete_forwarder(inode)
857
+ this.copy_inode(old_real_inode, inode)
858
+
859
+ // Link into new location in this filesystem.
860
+ this.link_under_dir(newdirid, idx, newname)
861
+ }
862
+
863
+ // Rewrite data to newly created destination.
864
+ await this.ChangeSize(idx, old_real_inode.size)
865
+ if (data && data.length) {
866
+ await this.Write(idx, 0, data.length, data)
867
+ }
868
+
869
+ // Move children to newly created destination.
870
+ if (this.IsDirectory(idx)) {
871
+ for (const child_filename of this.GetChildren(
872
+ diverted_old_idx,
873
+ )) {
874
+ const ret = await this.Rename(
875
+ diverted_old_idx,
876
+ child_filename,
877
+ idx,
878
+ child_filename,
879
+ )
880
+ if (ret < 0) return ret
881
+ }
882
+ }
883
+
884
+ // Perform destructive changes only after migration succeeded.
885
+ await this.DeleteData(diverted_old_idx)
886
+ const ret = this.Unlink(olddirid, oldname)
887
+ if (ret < 0) return ret
888
+ }
889
+
890
+ this.NotifyListeners(idx, 'rename', { oldpath: oldpath })
891
+
892
+ return 0
893
+ }
894
+
895
+ async Write(
896
+ id: number,
897
+ offset: number,
898
+ count: number,
899
+ buffer: Uint8Array | null,
900
+ ): Promise<void> {
901
+ this.NotifyListeners(id, 'write')
902
+ const inode = this.inodes[id]
903
+
904
+ if (this.is_forwarder(inode)) {
905
+ const foreign_id = inode.foreign_id
906
+ await this.follow_fs(inode).Write(foreign_id, offset, count, buffer)
907
+ return
908
+ }
909
+
910
+ let data = await this.get_buffer(id)
911
+
912
+ if (!data || data.length < offset + count) {
913
+ await this.ChangeSize(id, Math.floor(((offset + count) * 3) / 2))
914
+ inode.size = offset + count
915
+ data = await this.get_buffer(id)
916
+ } else if (inode.size < offset + count) {
917
+ inode.size = offset + count
918
+ }
919
+ if (buffer && data) {
920
+ data.set(buffer.subarray(0, count), offset)
921
+ }
922
+ if (data) {
923
+ await this.set_data(id, data)
924
+ }
925
+ }
926
+
927
+ async Read(
928
+ inodeid: number,
929
+ offset: number,
930
+ count: number,
931
+ ): Promise<Uint8Array | null> {
932
+ const inode = this.inodes[inodeid]
933
+ if (this.is_forwarder(inode)) {
934
+ const foreign_id = inode.foreign_id
935
+ return await this.follow_fs(inode).Read(foreign_id, offset, count)
936
+ }
937
+
938
+ return await this.get_data(inodeid, offset, count)
939
+ }
940
+
941
+ Search(parentid: number, name: string): number {
942
+ const parent_inode = this.inodes[parentid]
943
+
944
+ if (this.is_forwarder(parent_inode)) {
945
+ const foreign_parentid = parent_inode.foreign_id
946
+ const foreign_id = this.follow_fs(parent_inode).Search(
947
+ foreign_parentid,
948
+ name,
949
+ )
950
+ if (foreign_id === -1) return -1
951
+ return this.get_forwarder(parent_inode.mount_id, foreign_id)
952
+ }
953
+
954
+ const childid = parent_inode.direntries.get(name)
955
+ return childid === undefined ? -1 : childid
956
+ }
957
+
958
+ CountUsedInodes(): number {
959
+ let count = this.inodes.length
960
+ for (const { fs, backtrack } of this.mounts) {
961
+ count += fs.CountUsedInodes()
962
+
963
+ // Forwarder inodes don't count.
964
+ count -= backtrack.size
965
+ }
966
+ return count
967
+ }
968
+
969
+ CountFreeInodes(): number {
970
+ let count = 1024 * 1024
971
+ for (const { fs } of this.mounts) {
972
+ count += fs.CountFreeInodes()
973
+ }
974
+ return count
975
+ }
976
+
977
+ GetTotalSize(): number {
978
+ let size = this.used_size
979
+ for (const { fs } of this.mounts) {
980
+ size += fs.GetTotalSize()
981
+ }
982
+ return size
983
+ }
984
+
985
+ GetSpace(): number {
986
+ let size = this.total_size
987
+ for (const { fs } of this.mounts) {
988
+ size += fs.GetSpace()
989
+ }
990
+ return size
991
+ }
992
+
993
+ GetDirectoryName(idx: number): string {
994
+ const parent_inode = this.inodes[this.GetParent(idx)]
995
+
996
+ if (this.is_forwarder(parent_inode)) {
997
+ return this.follow_fs(parent_inode).GetDirectoryName(
998
+ this.inodes[idx].foreign_id,
999
+ )
1000
+ }
1001
+
1002
+ // Root directory.
1003
+ if (!parent_inode) return ''
1004
+
1005
+ for (const [name, childid] of parent_inode.direntries) {
1006
+ if (childid === idx) return name
1007
+ }
1008
+
1009
+ dbg_assert(
1010
+ false,
1011
+ "Filesystem: Found directory inode whose parent doesn't link to it",
1012
+ )
1013
+ return ''
1014
+ }
1015
+
1016
+ GetFullPath(idx: number): string {
1017
+ dbg_assert(
1018
+ this.IsDirectory(idx),
1019
+ 'Filesystem: Cannot get full path of non-directory inode',
1020
+ )
1021
+
1022
+ let path = ''
1023
+
1024
+ while (idx !== 0) {
1025
+ path = '/' + this.GetDirectoryName(idx) + path
1026
+ idx = this.GetParent(idx)
1027
+ }
1028
+ return path.substring(1)
1029
+ }
1030
+
1031
+ Link(parentid: number, targetid: number, name: string): number {
1032
+ if (this.IsDirectory(targetid)) {
1033
+ return -EPERM
1034
+ }
1035
+
1036
+ const parent_inode = this.inodes[parentid]
1037
+ const inode = this.inodes[targetid]
1038
+
1039
+ if (this.is_forwarder(parent_inode)) {
1040
+ if (
1041
+ !this.is_forwarder(inode) ||
1042
+ inode.mount_id !== parent_inode.mount_id
1043
+ ) {
1044
+ dbg_log(
1045
+ 'XXX: Attempted to hardlink a file into a child filesystem - skipped',
1046
+ LOG_9P,
1047
+ )
1048
+ return -EPERM
1049
+ }
1050
+ return this.follow_fs(parent_inode).Link(
1051
+ parent_inode.foreign_id,
1052
+ inode.foreign_id,
1053
+ name,
1054
+ )
1055
+ }
1056
+
1057
+ if (this.is_forwarder(inode)) {
1058
+ dbg_log(
1059
+ 'XXX: Attempted to hardlink file across filesystems - skipped',
1060
+ LOG_9P,
1061
+ )
1062
+ return -EPERM
1063
+ }
1064
+
1065
+ this.link_under_dir(parentid, targetid, name)
1066
+ return 0
1067
+ }
1068
+
1069
+ Unlink(parentid: number, name: string): number {
1070
+ if (name === '.' || name === '..') {
1071
+ // Also guarantees that root cannot be deleted.
1072
+ return -EPERM
1073
+ }
1074
+ const idx = this.Search(parentid, name)
1075
+ const inode = this.inodes[idx]
1076
+ const parent_inode = this.inodes[parentid]
1077
+
1078
+ // forward if necessary
1079
+ if (this.is_forwarder(parent_inode)) {
1080
+ dbg_assert(
1081
+ this.is_forwarder(inode),
1082
+ 'Children of forwarders should be forwarders',
1083
+ )
1084
+
1085
+ const foreign_parentid = parent_inode.foreign_id
1086
+ return this.follow_fs(parent_inode).Unlink(foreign_parentid, name)
1087
+
1088
+ // Keep the forwarder dangling - file is still accessible.
1089
+ }
1090
+
1091
+ if (this.IsDirectory(idx) && !this.IsEmpty(idx)) {
1092
+ return -ENOTEMPTY
1093
+ }
1094
+
1095
+ this.unlink_from_dir(parentid, name)
1096
+
1097
+ if (inode.nlinks === 0) {
1098
+ // don't delete the content. The file is still accessible
1099
+ inode.status = STATUS_UNLINKED
1100
+ this.NotifyListeners(idx, 'delete')
1101
+ }
1102
+ return 0
1103
+ }
1104
+
1105
+ async DeleteData(idx: number): Promise<void> {
1106
+ const inode = this.inodes[idx]
1107
+ if (this.is_forwarder(inode)) {
1108
+ await this.follow_fs(inode).DeleteData(inode.foreign_id)
1109
+ return
1110
+ }
1111
+ inode.size = 0
1112
+ delete this.inodedata[idx]
1113
+ }
1114
+
1115
+ private async get_buffer(idx: number): Promise<Uint8Array | null> {
1116
+ const inode = this.inodes[idx]
1117
+ dbg_assert(
1118
+ !!inode,
1119
+ `Filesystem get_buffer: idx ${idx} does not point to an inode`,
1120
+ )
1121
+
1122
+ if (this.inodedata[idx]) {
1123
+ return this.inodedata[idx]
1124
+ } else if (inode.status === STATUS_ON_STORAGE) {
1125
+ dbg_assert(
1126
+ !!inode.sha256sum,
1127
+ 'Filesystem get_data: found inode on server without sha256sum',
1128
+ )
1129
+ return await this.storage.read(
1130
+ inode.sha256sum,
1131
+ 0,
1132
+ inode.size,
1133
+ inode.size,
1134
+ )
1135
+ } else {
1136
+ return null
1137
+ }
1138
+ }
1139
+
1140
+ private async get_data(
1141
+ idx: number,
1142
+ offset: number,
1143
+ count: number,
1144
+ ): Promise<Uint8Array | null> {
1145
+ const inode = this.inodes[idx]
1146
+ dbg_assert(
1147
+ !!inode,
1148
+ `Filesystem get_data: idx ${idx} does not point to an inode`,
1149
+ )
1150
+
1151
+ if (this.inodedata[idx]) {
1152
+ return this.inodedata[idx].subarray(offset, offset + count)
1153
+ } else if (inode.status === STATUS_ON_STORAGE) {
1154
+ dbg_assert(
1155
+ !!inode.sha256sum,
1156
+ 'Filesystem get_data: found inode on server without sha256sum',
1157
+ )
1158
+ return await this.storage.read(
1159
+ inode.sha256sum,
1160
+ offset,
1161
+ count,
1162
+ inode.size,
1163
+ )
1164
+ } else {
1165
+ return null
1166
+ }
1167
+ }
1168
+
1169
+ private async set_data(idx: number, buffer: Uint8Array): Promise<void> {
1170
+ // Current scheme: Save all modified buffers into local inodedata.
1171
+ this.inodedata[idx] = buffer
1172
+ if (this.inodes[idx].status === STATUS_ON_STORAGE) {
1173
+ this.inodes[idx].status = STATUS_OK
1174
+ this.storage.uncache(this.inodes[idx].sha256sum)
1175
+ }
1176
+ }
1177
+
1178
+ GetInode(idx: number): Inode {
1179
+ dbg_assert(!isNaN(idx), 'Filesystem GetInode: NaN idx')
1180
+ dbg_assert(
1181
+ idx >= 0 && idx < this.inodes.length,
1182
+ 'Filesystem GetInode: out of range idx:' + idx,
1183
+ )
1184
+
1185
+ const inode = this.inodes[idx]
1186
+ if (this.is_forwarder(inode)) {
1187
+ return this.follow_fs(inode).GetInode(inode.foreign_id)
1188
+ }
1189
+
1190
+ return inode
1191
+ }
1192
+
1193
+ async ChangeSize(idx: number, newsize: number): Promise<void> {
1194
+ const inode = this.GetInode(idx)
1195
+ const temp = await this.get_data(idx, 0, inode.size)
1196
+ if (newsize === inode.size) return
1197
+ const data = new Uint8Array(newsize)
1198
+ inode.size = newsize
1199
+ if (temp) {
1200
+ const size = Math.min(temp.length, inode.size)
1201
+ data.set(temp.subarray(0, size), 0)
1202
+ }
1203
+ await this.set_data(idx, data)
1204
+ }
1205
+
1206
+ SearchPath(path: string): SearchPathResult {
1207
+ path = path.replace('//', '/')
1208
+ const walk = path.split('/')
1209
+ if (walk.length > 0 && walk[walk.length - 1].length === 0) walk.pop()
1210
+ if (walk.length > 0 && walk[0].length === 0) walk.shift()
1211
+ const n = walk.length
1212
+
1213
+ let parentid = -1
1214
+ let id = 0
1215
+ let forward_path: string | null = null
1216
+ let i = 0
1217
+ for (i = 0; i < n; i++) {
1218
+ parentid = id
1219
+ id = this.Search(parentid, walk[i])
1220
+ if (!forward_path && this.is_forwarder(this.inodes[parentid])) {
1221
+ forward_path = '/' + walk.slice(i).join('/')
1222
+ }
1223
+ if (id === -1) {
1224
+ if (i < n - 1)
1225
+ return {
1226
+ id: -1,
1227
+ parentid: -1,
1228
+ name: walk[i],
1229
+ forward_path,
1230
+ }
1231
+ return {
1232
+ id: -1,
1233
+ parentid: parentid,
1234
+ name: walk[i],
1235
+ forward_path,
1236
+ }
1237
+ }
1238
+ }
1239
+ return { id: id, parentid: parentid, name: walk[i], forward_path }
1240
+ }
1241
+
1242
+ // -----------------------------------------------------
1243
+
1244
+ GetRecursiveList(
1245
+ dirid: number,
1246
+ list: { parentid: number; name: string }[],
1247
+ ): void {
1248
+ if (this.is_forwarder(this.inodes[dirid])) {
1249
+ const foreign_fs = this.follow_fs(this.inodes[dirid])
1250
+ const foreign_dirid = this.inodes[dirid].foreign_id
1251
+ const mount_id = this.inodes[dirid].mount_id
1252
+
1253
+ const foreign_start = list.length
1254
+ foreign_fs.GetRecursiveList(foreign_dirid, list)
1255
+ for (let i = foreign_start; i < list.length; i++) {
1256
+ list[i].parentid = this.get_forwarder(
1257
+ mount_id,
1258
+ list[i].parentid,
1259
+ )
1260
+ }
1261
+ return
1262
+ }
1263
+ for (const [name, id] of this.inodes[dirid].direntries) {
1264
+ if (name !== '.' && name !== '..') {
1265
+ list.push({ parentid: dirid, name })
1266
+ if (this.IsDirectory(id)) {
1267
+ this.GetRecursiveList(id, list)
1268
+ }
1269
+ }
1270
+ }
1271
+ }
1272
+
1273
+ RecursiveDelete(path: string): void {
1274
+ const toDelete: { parentid: number; name: string }[] = []
1275
+ const ids = this.SearchPath(path)
1276
+ if (ids.id === -1) return
1277
+
1278
+ this.GetRecursiveList(ids.id, toDelete)
1279
+
1280
+ for (let i = toDelete.length - 1; i >= 0; i--) {
1281
+ const ret = this.Unlink(toDelete[i].parentid, toDelete[i].name)
1282
+ dbg_assert(
1283
+ ret === 0,
1284
+ 'Filesystem RecursiveDelete failed at parent=' +
1285
+ toDelete[i].parentid +
1286
+ ", name='" +
1287
+ toDelete[i].name +
1288
+ "' with error code: " +
1289
+ -ret,
1290
+ )
1291
+ }
1292
+ }
1293
+
1294
+ DeleteNode(path: string): void {
1295
+ const ids = this.SearchPath(path)
1296
+ if (ids.id === -1) return
1297
+
1298
+ if ((this.inodes[ids.id].mode & S_IFMT) === S_IFREG) {
1299
+ const ret = this.Unlink(ids.parentid, ids.name)
1300
+ dbg_assert(
1301
+ ret === 0,
1302
+ 'Filesystem DeleteNode failed with error code: ' + -ret,
1303
+ )
1304
+ } else if ((this.inodes[ids.id].mode & S_IFMT) === S_IFDIR) {
1305
+ this.RecursiveDelete(path)
1306
+ const ret = this.Unlink(ids.parentid, ids.name)
1307
+ dbg_assert(
1308
+ ret === 0,
1309
+ 'Filesystem DeleteNode failed with error code: ' + -ret,
1310
+ )
1311
+ }
1312
+ }
1313
+
1314
+ NotifyListeners(_id: number, _action: string, _info?: any): void {
1315
+ //if(info==undefined)
1316
+ // info = {};
1317
+ //var path = this.GetFullPath(id);
1318
+ //if (this.watchFiles[path] === true && action=='write') {
1319
+ // message.Send("WatchFileEvent", path);
1320
+ //}
1321
+ //for (var directory of this.watchDirectories) {
1322
+ // if (this.watchDirectories.hasOwnProperty(directory)) {
1323
+ // var indexOf = path.indexOf(directory)
1324
+ // if(indexOf === 0 || indexOf === 1)
1325
+ // message.Send("WatchDirectoryEvent", {path: path, event: action, info: info});
1326
+ // }
1327
+ //}
1328
+ }
1329
+
1330
+ Check(): void {
1331
+ for (let i = 1; i < this.inodes.length; i++) {
1332
+ if (this.inodes[i].status === STATUS_INVALID) continue
1333
+
1334
+ const inode = this.GetInode(i)
1335
+ if (inode.nlinks < 0) {
1336
+ dbg_log(
1337
+ 'Error in filesystem: negative nlinks=' +
1338
+ inode.nlinks +
1339
+ ' at id =' +
1340
+ i,
1341
+ LOG_9P,
1342
+ )
1343
+ }
1344
+
1345
+ if (this.IsDirectory(i)) {
1346
+ const inode = this.GetInode(i)
1347
+ if (this.IsDirectory(i) && this.GetParent(i) < 0) {
1348
+ dbg_log(
1349
+ 'Error in filesystem: negative parent id ' + i,
1350
+ LOG_9P,
1351
+ )
1352
+ }
1353
+ for (const [name, id] of inode.direntries) {
1354
+ if (name.length === 0) {
1355
+ dbg_log(
1356
+ 'Error in filesystem: inode with no name and id ' +
1357
+ id,
1358
+ LOG_9P,
1359
+ )
1360
+ }
1361
+
1362
+ for (const c of name) {
1363
+ if (c < ' ') {
1364
+ dbg_log(
1365
+ 'Error in filesystem: Unallowed char in filename',
1366
+ LOG_9P,
1367
+ )
1368
+ }
1369
+ }
1370
+ }
1371
+ }
1372
+ }
1373
+ }
1374
+
1375
+ FillDirectory(dirid: number): void {
1376
+ const inode = this.inodes[dirid]
1377
+ if (this.is_forwarder(inode)) {
1378
+ // XXX: The ".." of a mountpoint should point back to an inode in this fs.
1379
+ // Otherwise, ".." gets the wrong qid and mode.
1380
+ this.follow_fs(inode).FillDirectory(inode.foreign_id)
1381
+ return
1382
+ }
1383
+
1384
+ let size = 0
1385
+ for (const name of inode.direntries.keys()) {
1386
+ size += 13 + 8 + 1 + 2 + texten.encode(name).length
1387
+ }
1388
+ const data = (this.inodedata[dirid] = new Uint8Array(size))
1389
+ inode.size = size
1390
+
1391
+ let offset = 0x0
1392
+ for (const [name, id] of inode.direntries) {
1393
+ const child = this.GetInode(id)
1394
+ offset += marshall.Marshall(
1395
+ ['Q', 'd', 'b', 's'],
1396
+ [
1397
+ child.qid,
1398
+ offset + 13 + 8 + 1 + 2 + texten.encode(name).length,
1399
+ child.mode >> 12,
1400
+ name,
1401
+ ],
1402
+ data,
1403
+ offset,
1404
+ )
1405
+ }
1406
+ }
1407
+
1408
+ RoundToDirentry(dirid: number, offset_target: number): number {
1409
+ const data = this.inodedata[dirid]
1410
+ dbg_assert(
1411
+ !!data,
1412
+ `FS directory data for dirid=${dirid} should be generated`,
1413
+ )
1414
+ dbg_assert(
1415
+ data.length > 0,
1416
+ 'FS directory should have at least an entry',
1417
+ )
1418
+
1419
+ if (offset_target >= data.length) {
1420
+ return data.length
1421
+ }
1422
+
1423
+ let offset = 0
1424
+ while (true) {
1425
+ const next_offset = marshall.Unmarshall(['Q', 'd'], data, {
1426
+ offset,
1427
+ })[1]
1428
+ if (next_offset > offset_target) break
1429
+ offset = next_offset
1430
+ }
1431
+
1432
+ return offset
1433
+ }
1434
+
1435
+ IsDirectory(idx: number): boolean {
1436
+ const inode = this.inodes[idx]
1437
+ if (this.is_forwarder(inode)) {
1438
+ return this.follow_fs(inode).IsDirectory(inode.foreign_id)
1439
+ }
1440
+ return (inode.mode & S_IFMT) === S_IFDIR
1441
+ }
1442
+
1443
+ IsEmpty(idx: number): boolean {
1444
+ const inode = this.inodes[idx]
1445
+ if (this.is_forwarder(inode)) {
1446
+ return this.follow_fs(inode).IsDirectory(inode.foreign_id)
1447
+ }
1448
+ for (const name of inode.direntries.keys()) {
1449
+ if (name !== '.' && name !== '..') return false
1450
+ }
1451
+ return true
1452
+ }
1453
+
1454
+ GetChildren(idx: number): string[] {
1455
+ dbg_assert(
1456
+ this.IsDirectory(idx),
1457
+ 'Filesystem: cannot get children of non-directory inode',
1458
+ )
1459
+ const inode = this.inodes[idx]
1460
+ if (this.is_forwarder(inode)) {
1461
+ return this.follow_fs(inode).GetChildren(inode.foreign_id)
1462
+ }
1463
+ const children: string[] = []
1464
+ for (const name of inode.direntries.keys()) {
1465
+ if (name !== '.' && name !== '..') {
1466
+ children.push(name)
1467
+ }
1468
+ }
1469
+ return children
1470
+ }
1471
+
1472
+ GetParent(idx: number): number {
1473
+ dbg_assert(
1474
+ this.IsDirectory(idx),
1475
+ 'Filesystem: cannot get parent of non-directory inode',
1476
+ )
1477
+
1478
+ const inode = this.inodes[idx]
1479
+
1480
+ if (this.should_be_linked(inode)) {
1481
+ return inode.direntries.get('..') ?? -1
1482
+ } else {
1483
+ const foreign_dirid = this.follow_fs(inode).GetParent(
1484
+ inode.foreign_id,
1485
+ )
1486
+ dbg_assert(
1487
+ foreign_dirid !== -1,
1488
+ 'Filesystem: should not have invalid parent ids',
1489
+ )
1490
+ return this.get_forwarder(inode.mount_id, foreign_dirid)
1491
+ }
1492
+ }
1493
+
1494
+ // -----------------------------------------------------
1495
+
1496
+ // only support for security.capabilities
1497
+ PrepareCAPs(id: number): number {
1498
+ const inode = this.GetInode(id)
1499
+ if (inode.caps) return inode.caps.length
1500
+ inode.caps = new Uint8Array(20)
1501
+ // format is little endian
1502
+ // note: getxattr returns -EINVAL if using revision 1 format.
1503
+ // note: getxattr presents revision 3 as revision 2 when revision 3 is not needed.
1504
+ // magic_etc (revision=0x02: 20 bytes)
1505
+ inode.caps[0] = 0x00
1506
+ inode.caps[1] = 0x00
1507
+ inode.caps[2] = 0x00
1508
+ inode.caps[3] = 0x02
1509
+
1510
+ // lower
1511
+ // permitted (first 32 capabilities)
1512
+ inode.caps[4] = 0xff
1513
+ inode.caps[5] = 0xff
1514
+ inode.caps[6] = 0xff
1515
+ inode.caps[7] = 0xff
1516
+ // inheritable (first 32 capabilities)
1517
+ inode.caps[8] = 0xff
1518
+ inode.caps[9] = 0xff
1519
+ inode.caps[10] = 0xff
1520
+ inode.caps[11] = 0xff
1521
+
1522
+ // higher
1523
+ // permitted (last 6 capabilities)
1524
+ inode.caps[12] = 0x3f
1525
+ inode.caps[13] = 0x00
1526
+ inode.caps[14] = 0x00
1527
+ inode.caps[15] = 0x00
1528
+ // inheritable (last 6 capabilities)
1529
+ inode.caps[16] = 0x3f
1530
+ inode.caps[17] = 0x00
1531
+ inode.caps[18] = 0x00
1532
+ inode.caps[19] = 0x00
1533
+
1534
+ return inode.caps.length
1535
+ }
1536
+
1537
+ // -----------------------------------------------------
1538
+
1539
+ private set_forwarder(
1540
+ idx: number,
1541
+ mount_id: number,
1542
+ foreign_id: number,
1543
+ ): void {
1544
+ const inode = this.inodes[idx]
1545
+
1546
+ dbg_assert(
1547
+ inode.nlinks === 0,
1548
+ 'Filesystem: attempted to convert an inode into forwarder before unlinking the inode',
1549
+ )
1550
+
1551
+ if (this.is_forwarder(inode)) {
1552
+ this.mounts[inode.mount_id].backtrack.delete(inode.foreign_id)
1553
+ }
1554
+
1555
+ inode.status = STATUS_FORWARDING
1556
+ inode.mount_id = mount_id
1557
+ inode.foreign_id = foreign_id
1558
+
1559
+ this.mounts[mount_id].backtrack.set(foreign_id, idx)
1560
+ }
1561
+
1562
+ private create_forwarder(mount_id: number, foreign_id: number): number {
1563
+ const inode = this.CreateInode()
1564
+
1565
+ const idx = this.inodes.length
1566
+ this.inodes.push(inode)
1567
+ inode.fid = idx
1568
+
1569
+ this.set_forwarder(idx, mount_id, foreign_id)
1570
+ return idx
1571
+ }
1572
+
1573
+ private is_forwarder(inode: Inode): boolean {
1574
+ return inode.status === STATUS_FORWARDING
1575
+ }
1576
+
1577
+ private is_a_root(idx: number): boolean {
1578
+ return this.GetInode(idx).fid === 0
1579
+ }
1580
+
1581
+ private get_forwarder(mount_id: number, foreign_id: number): number {
1582
+ const mount = this.mounts[mount_id]
1583
+
1584
+ dbg_assert(
1585
+ foreign_id >= 0,
1586
+ 'Filesystem get_forwarder: invalid foreign_id: ' + foreign_id,
1587
+ )
1588
+ dbg_assert(
1589
+ !!mount,
1590
+ 'Filesystem get_forwarder: invalid mount number: ' + mount_id,
1591
+ )
1592
+
1593
+ const result = mount.backtrack.get(foreign_id)
1594
+
1595
+ if (result === undefined) {
1596
+ // Create if not already exists.
1597
+ return this.create_forwarder(mount_id, foreign_id)
1598
+ }
1599
+
1600
+ return result
1601
+ }
1602
+
1603
+ private delete_forwarder(inode: Inode): void {
1604
+ dbg_assert(
1605
+ this.is_forwarder(inode),
1606
+ 'Filesystem delete_forwarder: expected forwarder',
1607
+ )
1608
+
1609
+ inode.status = STATUS_INVALID
1610
+ this.mounts[inode.mount_id].backtrack.delete(inode.foreign_id)
1611
+ }
1612
+
1613
+ private follow_fs(inode: Inode): FS {
1614
+ const mount = this.mounts[inode.mount_id]
1615
+
1616
+ dbg_assert(
1617
+ this.is_forwarder(inode),
1618
+ 'Filesystem follow_fs: inode should be a forwarding inode',
1619
+ )
1620
+ dbg_assert(
1621
+ !!mount,
1622
+ 'Filesystem follow_fs: inode<id=' +
1623
+ inode.fid +
1624
+ '> should point to valid mounted FS',
1625
+ )
1626
+
1627
+ return mount.fs
1628
+ }
1629
+
1630
+ Mount(path: string, fs: FS): number {
1631
+ dbg_assert(
1632
+ fs.qidcounter === this.qidcounter,
1633
+ "Cannot mount filesystem whose qid numbers aren't synchronised with current filesystem.",
1634
+ )
1635
+
1636
+ const path_infos = this.SearchPath(path)
1637
+
1638
+ if (path_infos.parentid === -1) {
1639
+ dbg_log('Mount failed: parent for path not found: ' + path, LOG_9P)
1640
+ return -ENOENT
1641
+ }
1642
+ if (path_infos.id !== -1) {
1643
+ dbg_log(
1644
+ 'Mount failed: file already exists at path: ' + path,
1645
+ LOG_9P,
1646
+ )
1647
+ return -EEXIST
1648
+ }
1649
+ if (path_infos.forward_path) {
1650
+ const parent = this.inodes[path_infos.parentid]
1651
+ const ret = this.follow_fs(parent).Mount(
1652
+ path_infos.forward_path,
1653
+ fs,
1654
+ )
1655
+ if (ret < 0) return ret
1656
+ return this.get_forwarder(parent.mount_id, ret)
1657
+ }
1658
+
1659
+ const mount_id = this.mounts.length
1660
+ this.mounts.push(new FSMountInfo(fs))
1661
+
1662
+ const idx = this.create_forwarder(mount_id, 0)
1663
+ this.link_under_dir(path_infos.parentid, idx, path_infos.name)
1664
+
1665
+ return idx
1666
+ }
1667
+
1668
+ DescribeLock(
1669
+ type: number,
1670
+ start: number,
1671
+ length: number,
1672
+ proc_id: number,
1673
+ client_id: string,
1674
+ ): FSLockRegion {
1675
+ dbg_assert(
1676
+ type === P9_LOCK_TYPE_RDLCK ||
1677
+ type === P9_LOCK_TYPE_WRLCK ||
1678
+ type === P9_LOCK_TYPE_UNLCK,
1679
+ 'Filesystem: Invalid lock type: ' + type,
1680
+ )
1681
+ dbg_assert(
1682
+ start >= 0,
1683
+ 'Filesystem: Invalid negative lock starting offset: ' + start,
1684
+ )
1685
+ dbg_assert(
1686
+ length > 0,
1687
+ 'Filesystem: Invalid non-positive lock length: ' + length,
1688
+ )
1689
+
1690
+ const lock = new FSLockRegion()
1691
+ lock.type = type
1692
+ lock.start = start
1693
+ lock.length = length
1694
+ lock.proc_id = proc_id
1695
+ lock.client_id = client_id
1696
+
1697
+ return lock
1698
+ }
1699
+
1700
+ GetLock(id: number, request: FSLockRegion): FSLockRegion | null {
1701
+ const inode = this.inodes[id]
1702
+
1703
+ if (this.is_forwarder(inode)) {
1704
+ const foreign_id = inode.foreign_id
1705
+ return this.follow_fs(inode).GetLock(foreign_id, request)
1706
+ }
1707
+
1708
+ for (const region of inode.locks) {
1709
+ if (request.conflicts_with(region)) {
1710
+ return region.clone()
1711
+ }
1712
+ }
1713
+ return null
1714
+ }
1715
+
1716
+ Lock(id: number, request: FSLockRegion, flags: number): number {
1717
+ const inode = this.inodes[id]
1718
+
1719
+ if (this.is_forwarder(inode)) {
1720
+ const foreign_id = inode.foreign_id
1721
+ return this.follow_fs(inode).Lock(foreign_id, request, flags)
1722
+ }
1723
+
1724
+ request = request.clone()
1725
+
1726
+ // (1) Check whether lock is possible before any modification.
1727
+ if (request.type !== P9_LOCK_TYPE_UNLCK && this.GetLock(id, request)) {
1728
+ return P9_LOCK_BLOCKED
1729
+ }
1730
+
1731
+ // (2) Subtract requested region from locks of the same owner.
1732
+ for (let i = 0; i < inode.locks.length; i++) {
1733
+ const region = inode.locks[i]
1734
+
1735
+ dbg_assert(
1736
+ region.length > 0,
1737
+ 'Filesystem: Found non-positive lock region length: ' +
1738
+ region.length,
1739
+ )
1740
+ dbg_assert(
1741
+ region.type === P9_LOCK_TYPE_RDLCK ||
1742
+ region.type === P9_LOCK_TYPE_WRLCK,
1743
+ 'Filesystem: Found invalid lock type: ' + region.type,
1744
+ )
1745
+ dbg_assert(
1746
+ !inode.locks[i - 1] || inode.locks[i - 1].start <= region.start,
1747
+ 'Filesystem: Locks should be sorted by starting offset',
1748
+ )
1749
+
1750
+ // Skip to requested region.
1751
+ if (region.start + region.length <= request.start) continue
1752
+
1753
+ // Check whether we've skipped past the requested region.
1754
+ if (request.start + request.length <= region.start) break
1755
+
1756
+ // Skip over locks of different owners.
1757
+ if (
1758
+ region.proc_id !== request.proc_id ||
1759
+ region.client_id !== request.client_id
1760
+ ) {
1761
+ dbg_assert(
1762
+ !region.conflicts_with(request),
1763
+ 'Filesytem: Found conflicting lock region, despite already checked for conflicts',
1764
+ )
1765
+ continue
1766
+ }
1767
+
1768
+ // Pretend region would be split into parts 1 and 2.
1769
+ const start1 = region.start
1770
+ const start2 = request.start + request.length
1771
+ const length1 = request.start - start1
1772
+ const length2 = region.start + region.length - start2
1773
+
1774
+ if (length1 > 0 && length2 > 0 && region.type === request.type) {
1775
+ // Requested region is already locked with the required type.
1776
+ // Return early - no need to modify anything.
1777
+ return P9_LOCK_SUCCESS
1778
+ }
1779
+
1780
+ if (length1 > 0) {
1781
+ // Shrink from right / first half of the split.
1782
+ region.length = length1
1783
+ }
1784
+
1785
+ if (length1 <= 0 && length2 > 0) {
1786
+ // Shrink from left.
1787
+ region.start = start2
1788
+ region.length = length2
1789
+ } else if (length2 > 0) {
1790
+ // Add second half of the split.
1791
+
1792
+ // Fast-forward to correct location.
1793
+ while (i < inode.locks.length && inode.locks[i].start < start2)
1794
+ i++
1795
+
1796
+ inode.locks.splice(
1797
+ i,
1798
+ 0,
1799
+ this.DescribeLock(
1800
+ region.type,
1801
+ start2,
1802
+ length2,
1803
+ region.proc_id,
1804
+ region.client_id,
1805
+ ),
1806
+ )
1807
+ } else if (length1 <= 0) {
1808
+ // Requested region completely covers this region. Delete.
1809
+ inode.locks.splice(i, 1)
1810
+ i--
1811
+ }
1812
+ }
1813
+
1814
+ // (3) Insert requested lock region as a whole.
1815
+ // No point in adding the requested lock region as fragmented bits in the above loop
1816
+ // and having to merge them all back into one.
1817
+ if (request.type !== P9_LOCK_TYPE_UNLCK) {
1818
+ let new_region = request
1819
+ let has_merged = false
1820
+ let i = 0
1821
+
1822
+ // Fast-forward to requested position, and try merging with previous region.
1823
+ for (; i < inode.locks.length; i++) {
1824
+ if (new_region.may_merge_after(inode.locks[i])) {
1825
+ inode.locks[i].length += request.length
1826
+ new_region = inode.locks[i]
1827
+ has_merged = true
1828
+ }
1829
+ if (request.start <= inode.locks[i].start) break
1830
+ }
1831
+
1832
+ if (!has_merged) {
1833
+ inode.locks.splice(i, 0, new_region)
1834
+ i++
1835
+ }
1836
+
1837
+ // Try merging with the subsequent alike region.
1838
+ for (; i < inode.locks.length; i++) {
1839
+ if (!inode.locks[i].is_alike(new_region)) continue
1840
+
1841
+ if (inode.locks[i].may_merge_after(new_region)) {
1842
+ new_region.length += inode.locks[i].length
1843
+ inode.locks.splice(i, 1)
1844
+ }
1845
+
1846
+ // No more mergable regions after this.
1847
+ break
1848
+ }
1849
+ }
1850
+
1851
+ return P9_LOCK_SUCCESS
1852
+ }
1853
+
1854
+ read_dir(path: string): string[] | undefined {
1855
+ const p = this.SearchPath(path)
1856
+
1857
+ if (p.id === -1) {
1858
+ return undefined
1859
+ }
1860
+
1861
+ const dir = this.GetInode(p.id)
1862
+
1863
+ return Array.from(dir.direntries.keys()).filter(
1864
+ (path) => path !== '.' && path !== '..',
1865
+ )
1866
+ }
1867
+
1868
+ read_file(file: string): Promise<Uint8Array | null> {
1869
+ const p = this.SearchPath(file)
1870
+
1871
+ if (p.id === -1) {
1872
+ return Promise.resolve(null)
1873
+ }
1874
+
1875
+ const inode = this.GetInode(p.id)
1876
+
1877
+ return this.Read(p.id, 0, inode.size)
1878
+ }
1879
+ }