@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.
- package/LICENSE +22 -0
- package/LICENSE.MIT +22 -0
- package/Readme.md +237 -0
- package/dist/v86.browser.js +26666 -0
- package/dist/v86.browser.js.map +7 -0
- package/dist/v86.js +26632 -0
- package/dist/v86.js.map +7 -0
- package/gen/generate_analyzer.ts +512 -0
- package/gen/generate_interpreter.ts +522 -0
- package/gen/generate_jit.ts +624 -0
- package/gen/rust_ast.ts +107 -0
- package/gen/util.ts +35 -0
- package/gen/x86_table.ts +1836 -0
- package/lib/9p.ts +1547 -0
- package/lib/filesystem.ts +1879 -0
- package/lib/marshall.ts +168 -0
- package/lib/softfloat/softfloat.c +32501 -0
- package/lib/zstd/zstddeclib.c +13520 -0
- package/package.json +75 -0
- package/src/acpi.ts +267 -0
- package/src/browser/dummy_screen.ts +106 -0
- package/src/browser/fake_network.ts +1771 -0
- package/src/browser/fetch_network.ts +361 -0
- package/src/browser/filestorage.ts +124 -0
- package/src/browser/inbrowser_network.ts +57 -0
- package/src/browser/keyboard.ts +564 -0
- package/src/browser/main.ts +3415 -0
- package/src/browser/mouse.ts +255 -0
- package/src/browser/network.ts +142 -0
- package/src/browser/print_stats.ts +336 -0
- package/src/browser/screen.ts +978 -0
- package/src/browser/serial.ts +316 -0
- package/src/browser/speaker.ts +1223 -0
- package/src/browser/starter.ts +1688 -0
- package/src/browser/wisp_network.ts +332 -0
- package/src/browser/worker_bus.ts +64 -0
- package/src/buffer.ts +652 -0
- package/src/bus.ts +78 -0
- package/src/const.ts +128 -0
- package/src/cpu.ts +2891 -0
- package/src/dma.ts +474 -0
- package/src/elf.ts +251 -0
- package/src/floppy.ts +1778 -0
- package/src/ide.ts +3455 -0
- package/src/io.ts +504 -0
- package/src/iso9660.ts +317 -0
- package/src/kernel.ts +250 -0
- package/src/lib.ts +645 -0
- package/src/log.ts +149 -0
- package/src/main.ts +199 -0
- package/src/ne2k.ts +1589 -0
- package/src/pci.ts +815 -0
- package/src/pit.ts +406 -0
- package/src/ps2.ts +820 -0
- package/src/rtc.ts +537 -0
- package/src/rust/analysis.rs +101 -0
- package/src/rust/codegen.rs +2660 -0
- package/src/rust/config.rs +3 -0
- package/src/rust/control_flow.rs +425 -0
- package/src/rust/cpu/apic.rs +658 -0
- package/src/rust/cpu/arith.rs +1207 -0
- package/src/rust/cpu/call_indirect.rs +2 -0
- package/src/rust/cpu/cpu.rs +4501 -0
- package/src/rust/cpu/fpu.rs +923 -0
- package/src/rust/cpu/global_pointers.rs +112 -0
- package/src/rust/cpu/instructions.rs +2486 -0
- package/src/rust/cpu/instructions_0f.rs +5261 -0
- package/src/rust/cpu/ioapic.rs +316 -0
- package/src/rust/cpu/memory.rs +351 -0
- package/src/rust/cpu/misc_instr.rs +613 -0
- package/src/rust/cpu/mod.rs +16 -0
- package/src/rust/cpu/modrm.rs +133 -0
- package/src/rust/cpu/pic.rs +402 -0
- package/src/rust/cpu/sse_instr.rs +361 -0
- package/src/rust/cpu/string.rs +701 -0
- package/src/rust/cpu/vga.rs +175 -0
- package/src/rust/cpu_context.rs +69 -0
- package/src/rust/dbg.rs +98 -0
- package/src/rust/gen/analyzer.rs +3807 -0
- package/src/rust/gen/analyzer0f.rs +3992 -0
- package/src/rust/gen/interpreter.rs +4447 -0
- package/src/rust/gen/interpreter0f.rs +5404 -0
- package/src/rust/gen/jit.rs +5080 -0
- package/src/rust/gen/jit0f.rs +5547 -0
- package/src/rust/gen/mod.rs +14 -0
- package/src/rust/jit.rs +2443 -0
- package/src/rust/jit_instructions.rs +7881 -0
- package/src/rust/js_api.rs +6 -0
- package/src/rust/leb.rs +46 -0
- package/src/rust/lib.rs +29 -0
- package/src/rust/modrm.rs +330 -0
- package/src/rust/opstats.rs +249 -0
- package/src/rust/page.rs +15 -0
- package/src/rust/paging.rs +25 -0
- package/src/rust/prefix.rs +15 -0
- package/src/rust/profiler.rs +155 -0
- package/src/rust/regs.rs +38 -0
- package/src/rust/softfloat.rs +286 -0
- package/src/rust/state_flags.rs +27 -0
- package/src/rust/wasmgen/mod.rs +2 -0
- package/src/rust/wasmgen/wasm_builder.rs +1047 -0
- package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
- package/src/rust/zstd.rs +105 -0
- package/src/sb16.ts +1928 -0
- package/src/state.ts +359 -0
- package/src/uart.ts +472 -0
- package/src/vga.ts +2791 -0
- package/src/virtio.ts +1756 -0
- package/src/virtio_balloon.ts +273 -0
- package/src/virtio_console.ts +372 -0
- 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
|
+
}
|