@ai-ecoverse/slicc-kernel 1.3.0 → 1.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/README.md +44 -6
- package/dist/index.js +158 -11
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +631 -105
- package/dist/kernel-worker.js.map +4 -4
- package/dist/process-worker.js +4403 -664
- package/dist/process-worker.js.map +4 -4
- package/package.json +10 -4
package/dist/kernel-worker.js
CHANGED
|
@@ -59,34 +59,62 @@ function settle(transaction, value) {
|
|
|
59
59
|
}
|
|
60
60
|
var IndexedDbMeta = class {
|
|
61
61
|
db;
|
|
62
|
+
cache = /* @__PURE__ */ new Map();
|
|
63
|
+
channel;
|
|
64
|
+
generation = 0;
|
|
62
65
|
constructor(db) {
|
|
63
66
|
this.db = db;
|
|
64
67
|
db.onversionchange = () => db.close();
|
|
68
|
+
this.channel = new BroadcastChannel(`slicc-kernel-meta:${db.name}`);
|
|
69
|
+
this.channel.onmessage = ({ data }) => this.drop(data);
|
|
70
|
+
this.channel.unref?.();
|
|
71
|
+
}
|
|
72
|
+
drop({ paths = [], trees = [] }) {
|
|
73
|
+
this.generation++;
|
|
74
|
+
for (const path of paths) this.cache.delete(path);
|
|
75
|
+
for (const key of [...this.cache.keys()]) {
|
|
76
|
+
if (trees.some((tree) => under(key, tree))) this.cache.delete(key);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
changed(invalidation) {
|
|
80
|
+
this.drop(invalidation);
|
|
81
|
+
this.channel.postMessage(invalidation);
|
|
65
82
|
}
|
|
66
83
|
transaction(mode) {
|
|
67
84
|
const transaction = this.db.transaction(META_STORE, mode);
|
|
68
85
|
return [transaction, transaction.objectStore(META_STORE)];
|
|
69
86
|
}
|
|
70
87
|
async get(paths) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
const missing3 = paths.filter((path) => !this.cache.has(path));
|
|
89
|
+
if (missing3.length > 0) {
|
|
90
|
+
const generation = this.generation;
|
|
91
|
+
const [transaction, store] = this.transaction("readonly");
|
|
92
|
+
const found = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const path of missing3) {
|
|
94
|
+
const request2 = store.get(path);
|
|
95
|
+
request2.onsuccess = () => void found.set(path, request2.result ?? null);
|
|
96
|
+
}
|
|
97
|
+
await settle(transaction, () => void 0);
|
|
98
|
+
if (generation === this.generation)
|
|
99
|
+
for (const [path, entry] of found) this.cache.set(path, entry);
|
|
100
|
+
return paths.map(
|
|
101
|
+
(path) => (found.has(path) ? found.get(path) : this.cache.get(path)) ?? void 0
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return paths.map((path) => this.cache.get(path) ?? void 0);
|
|
80
105
|
}
|
|
81
106
|
async update(path, change) {
|
|
82
107
|
const [transaction, store] = this.transaction("readwrite");
|
|
83
108
|
const request2 = store.get(path);
|
|
109
|
+
let next;
|
|
84
110
|
request2.onsuccess = () => {
|
|
85
|
-
|
|
111
|
+
next = change(request2.result);
|
|
86
112
|
if (next) store.put(next);
|
|
87
113
|
else store.delete(path);
|
|
88
114
|
};
|
|
89
|
-
|
|
115
|
+
await settle(transaction, () => void 0);
|
|
116
|
+
this.changed({ paths: [path] });
|
|
117
|
+
this.cache.set(path, next ?? null);
|
|
90
118
|
}
|
|
91
119
|
async move(from, to, root) {
|
|
92
120
|
const [transaction, store] = this.transaction("readwrite");
|
|
@@ -102,7 +130,8 @@ var IndexedDbMeta = class {
|
|
|
102
130
|
}
|
|
103
131
|
store.put(moved(root(source.result), to));
|
|
104
132
|
};
|
|
105
|
-
|
|
133
|
+
await settle(transaction, () => void 0);
|
|
134
|
+
this.changed({ trees: [from, to] });
|
|
106
135
|
}
|
|
107
136
|
async remove(paths) {
|
|
108
137
|
const [transaction, store] = this.transaction("readwrite");
|
|
@@ -110,7 +139,8 @@ var IndexedDbMeta = class {
|
|
|
110
139
|
store.delete(path);
|
|
111
140
|
store.delete(descendants(path));
|
|
112
141
|
}
|
|
113
|
-
|
|
142
|
+
await settle(transaction, () => void 0);
|
|
143
|
+
this.changed({ trees: paths });
|
|
114
144
|
}
|
|
115
145
|
async links(dir) {
|
|
116
146
|
const [transaction, store] = this.transaction("readonly");
|
|
@@ -183,6 +213,7 @@ var S_IFLNK = 40960;
|
|
|
183
213
|
var PERM_MASK = 4095;
|
|
184
214
|
var READ_ATTEMPTS = 3;
|
|
185
215
|
var MAX_LINKS = 40;
|
|
216
|
+
var CHECK_MS = 1e3;
|
|
186
217
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
187
218
|
var ERRNO_BY_DOM_ERROR = {
|
|
188
219
|
NotFoundError: "ENOENT",
|
|
@@ -222,37 +253,100 @@ function isHandle(child) {
|
|
|
222
253
|
var OpfsFs = class {
|
|
223
254
|
root;
|
|
224
255
|
meta;
|
|
225
|
-
|
|
256
|
+
dirs = /* @__PURE__ */ new Map();
|
|
257
|
+
checked = /* @__PURE__ */ new WeakMap();
|
|
258
|
+
channel;
|
|
259
|
+
constructor(root, meta = new MemoryMeta(), channel) {
|
|
226
260
|
this.root = root;
|
|
227
261
|
this.meta = meta;
|
|
262
|
+
this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
|
|
263
|
+
if (this.channel) {
|
|
264
|
+
this.channel.onmessage = ({ data }) => {
|
|
265
|
+
for (const path of data) this.forget(path);
|
|
266
|
+
};
|
|
267
|
+
this.channel.unref?.();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
changed(...paths) {
|
|
271
|
+
for (const path of paths) this.forget(path);
|
|
272
|
+
this.channel?.postMessage(paths);
|
|
228
273
|
}
|
|
229
274
|
resolvePath(base, path) {
|
|
230
275
|
return resolvePath(base, path);
|
|
231
276
|
}
|
|
277
|
+
async walk(parts, from, start) {
|
|
278
|
+
let dir = start;
|
|
279
|
+
for (let at = from; at < parts.length; at++) {
|
|
280
|
+
dir = await dir.getDirectoryHandle(parts[at]);
|
|
281
|
+
this.dirs.set(`/${parts.slice(0, at + 1).join("/")}`, dir);
|
|
282
|
+
this.checked.set(dir, performance.now());
|
|
283
|
+
}
|
|
284
|
+
return dir;
|
|
285
|
+
}
|
|
232
286
|
async dir(path) {
|
|
233
|
-
|
|
287
|
+
const parts = split(path);
|
|
288
|
+
let from = parts.length;
|
|
289
|
+
let cached;
|
|
290
|
+
for (; from > 0 && !cached; from--)
|
|
291
|
+
cached = this.dirs.get(`/${parts.slice(0, from).join("/")}`);
|
|
292
|
+
if (cached) from++;
|
|
293
|
+
const at = `/${parts.slice(0, from).join("/")}`;
|
|
294
|
+
if (cached && !await this.current(at, cached)) {
|
|
295
|
+
this.forget(at);
|
|
296
|
+
cached = void 0;
|
|
297
|
+
from = 0;
|
|
298
|
+
}
|
|
234
299
|
try {
|
|
235
|
-
|
|
300
|
+
return await this.walk(parts, from, cached ?? this.root);
|
|
236
301
|
} catch (err) {
|
|
237
302
|
throw translate(err, path);
|
|
238
303
|
}
|
|
239
|
-
|
|
304
|
+
}
|
|
305
|
+
async current(path, dir) {
|
|
306
|
+
if (performance.now() - (this.checked.get(dir) ?? -CHECK_MS) < CHECK_MS) return true;
|
|
307
|
+
const where = await this.root.resolve(dir).catch(() => null);
|
|
308
|
+
if (where === null || `/${where.join("/")}` !== path) return false;
|
|
309
|
+
this.checked.set(dir, performance.now());
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
forget(path) {
|
|
313
|
+
for (const key of [...this.dirs.keys()]) if (within(key, path)) this.dirs.delete(key);
|
|
314
|
+
}
|
|
315
|
+
async inDir(path, op) {
|
|
316
|
+
const dir = await this.dir(path);
|
|
317
|
+
try {
|
|
318
|
+
return await op(dir);
|
|
319
|
+
} catch (err) {
|
|
320
|
+
if (err?.name !== "NotFoundError" || await this.current(path, dir))
|
|
321
|
+
throw err;
|
|
322
|
+
this.forget(path);
|
|
323
|
+
return op(await this.dir(path));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async child(parent, name2, kind) {
|
|
327
|
+
return kind === "file" ? parent.getFileHandle(name2) : parent.getDirectoryHandle(name2);
|
|
240
328
|
}
|
|
241
329
|
async parent(path) {
|
|
242
330
|
const parts = split(path);
|
|
243
331
|
const name2 = parts.pop();
|
|
244
332
|
if (name2 === void 0) throw fsError("EBUSY", path);
|
|
245
|
-
return [
|
|
333
|
+
return [`/${parts.join("/")}`, name2];
|
|
246
334
|
}
|
|
247
335
|
async handle(path) {
|
|
248
336
|
if (split(path).length === 0) return this.root;
|
|
249
337
|
const [dir, name2] = await this.parent(path);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
338
|
+
const known = this.dirs.has(path);
|
|
339
|
+
return this.inDir(dir, async (parent) => {
|
|
340
|
+
const [first, second] = known ? ["directory", "file"] : ["file", "directory"];
|
|
341
|
+
try {
|
|
342
|
+
return await this.child(parent, name2, first);
|
|
343
|
+
} catch (err) {
|
|
344
|
+
if (err?.name !== "TypeMismatchError") throw err;
|
|
345
|
+
}
|
|
346
|
+
return this.child(parent, name2, second);
|
|
347
|
+
}).catch((err) => {
|
|
348
|
+
throw translate(err, path);
|
|
349
|
+
});
|
|
256
350
|
}
|
|
257
351
|
real(path) {
|
|
258
352
|
return this.handle(path).then(
|
|
@@ -354,24 +448,25 @@ var OpfsFs = class {
|
|
|
354
448
|
}
|
|
355
449
|
async writeTarget(path) {
|
|
356
450
|
try {
|
|
357
|
-
return (await this.locate(path, true)).path;
|
|
451
|
+
return [(await this.locate(path, true)).path, true];
|
|
358
452
|
} catch (err) {
|
|
359
453
|
if (!missing(err)) throw err;
|
|
360
454
|
}
|
|
361
455
|
const [entry] = await this.meta.get([path]);
|
|
362
456
|
const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
|
|
363
|
-
return resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted));
|
|
457
|
+
return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
|
|
364
458
|
}
|
|
365
459
|
async writeFile(path, content) {
|
|
366
|
-
const target = await this.writeTarget(path);
|
|
460
|
+
const [target, existed] = await this.writeTarget(path);
|
|
367
461
|
const [dir, name2] = await this.parent(target);
|
|
368
462
|
let file;
|
|
369
463
|
try {
|
|
370
|
-
file = await dir.getFileHandle(name2, { create: true });
|
|
464
|
+
file = await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true }));
|
|
371
465
|
} catch (err) {
|
|
372
466
|
const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
|
|
373
467
|
throw code ? fsError(code, path) : translate(err, path);
|
|
374
468
|
}
|
|
469
|
+
if (!existed && content.length === 0) return;
|
|
375
470
|
const writable = await file.createWritable();
|
|
376
471
|
await writable.write(content);
|
|
377
472
|
await writable.close();
|
|
@@ -434,16 +529,21 @@ var OpfsFs = class {
|
|
|
434
529
|
if (found.handle) {
|
|
435
530
|
const [dir, name2] = await this.parent(found.path);
|
|
436
531
|
try {
|
|
437
|
-
await
|
|
532
|
+
await this.inDir(
|
|
533
|
+
dir,
|
|
534
|
+
(parent) => parent.removeEntry(name2, { recursive: options.recursive === true })
|
|
535
|
+
);
|
|
438
536
|
} catch (err) {
|
|
439
537
|
throw translate(err, path);
|
|
440
538
|
}
|
|
539
|
+
this.changed(found.path);
|
|
441
540
|
}
|
|
442
541
|
await this.meta.remove([found.path]);
|
|
443
542
|
}
|
|
444
543
|
async rename(from, to) {
|
|
445
544
|
if (from === to) return;
|
|
446
545
|
const source = await this.locate(from, false);
|
|
546
|
+
this.forget(source.path);
|
|
447
547
|
const parent = await this.locate(parentOf2(to), true);
|
|
448
548
|
const destination = resolvePath(parent.path, baseOf(to));
|
|
449
549
|
const sourceStat = await this.describe(source);
|
|
@@ -476,6 +576,7 @@ var OpfsFs = class {
|
|
|
476
576
|
const name2 = baseOf(destination);
|
|
477
577
|
const swap = targetStat?.isDirectory === true;
|
|
478
578
|
const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
|
|
579
|
+
this.forget(destination);
|
|
479
580
|
const timed = await this.explicitTimes(source.path);
|
|
480
581
|
const moved2 = await this.place(source.handle, dir, staging);
|
|
481
582
|
if (swap) await this.rm(destination, { recursive: true });
|
|
@@ -489,6 +590,7 @@ var OpfsFs = class {
|
|
|
489
590
|
copied ||= !placed;
|
|
490
591
|
}
|
|
491
592
|
if (copied) await this.restamp(destination, timed);
|
|
593
|
+
this.changed(source.path, destination);
|
|
492
594
|
}
|
|
493
595
|
async modified(path) {
|
|
494
596
|
const handle = await this.handle(path);
|
|
@@ -1206,12 +1308,12 @@ var Budget = class {
|
|
|
1206
1308
|
while (this.free < n) {
|
|
1207
1309
|
signal.throwIfAborted();
|
|
1208
1310
|
await new Promise((resolve) => {
|
|
1209
|
-
const
|
|
1210
|
-
signal.removeEventListener("abort",
|
|
1311
|
+
const done4 = () => {
|
|
1312
|
+
signal.removeEventListener("abort", done4);
|
|
1211
1313
|
resolve();
|
|
1212
1314
|
};
|
|
1213
|
-
this.waiters.push(
|
|
1214
|
-
signal.addEventListener("abort",
|
|
1315
|
+
this.waiters.push(done4);
|
|
1316
|
+
signal.addEventListener("abort", done4, { once: true });
|
|
1215
1317
|
});
|
|
1216
1318
|
}
|
|
1217
1319
|
this.free -= n;
|
|
@@ -1422,11 +1524,11 @@ var RealmProxy = class {
|
|
|
1422
1524
|
this.slots.release(1);
|
|
1423
1525
|
break;
|
|
1424
1526
|
}
|
|
1425
|
-
const
|
|
1527
|
+
const done4 = this.serve(conn).finally(() => {
|
|
1426
1528
|
this.slots.release(1);
|
|
1427
|
-
served.delete(
|
|
1529
|
+
served.delete(done4);
|
|
1428
1530
|
});
|
|
1429
|
-
served.add(
|
|
1531
|
+
served.add(done4);
|
|
1430
1532
|
}
|
|
1431
1533
|
} catch {
|
|
1432
1534
|
}
|
|
@@ -2450,44 +2552,83 @@ function envOf(pkg, raw) {
|
|
|
2450
2552
|
const env = {};
|
|
2451
2553
|
if (!raw || typeof raw !== "object") return env;
|
|
2452
2554
|
for (const [key, value] of Object.entries(raw)) {
|
|
2453
|
-
if (ENV_KEY.test(key)
|
|
2555
|
+
if (!ENV_KEY.test(key)) continue;
|
|
2556
|
+
if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
|
|
2557
|
+
else if (value === null) env[key] = null;
|
|
2454
2558
|
}
|
|
2455
2559
|
return env;
|
|
2456
2560
|
}
|
|
2561
|
+
function envFields(merged) {
|
|
2562
|
+
const env = {};
|
|
2563
|
+
const unset = [];
|
|
2564
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
2565
|
+
if (value === null) unset.push(key);
|
|
2566
|
+
else env[key] = value;
|
|
2567
|
+
}
|
|
2568
|
+
return {
|
|
2569
|
+
...Object.keys(env).length > 0 ? { env } : {},
|
|
2570
|
+
...unset.length > 0 ? { unset } : {}
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2457
2573
|
function argsOf(pkg, raw) {
|
|
2458
2574
|
if (!Array.isArray(raw) || raw.length === 0) return void 0;
|
|
2459
2575
|
if (!raw.every((arg) => typeof arg === "string")) return void 0;
|
|
2460
2576
|
return raw.map((arg) => arg.replace(PACKAGE, pkg));
|
|
2461
2577
|
}
|
|
2578
|
+
function abiOf(raw, fallback) {
|
|
2579
|
+
const abi = raw ?? fallback;
|
|
2580
|
+
return abi === "emscripten" || abi === "wasi" ? abi : void 0;
|
|
2581
|
+
}
|
|
2582
|
+
function commandOf(pkg, name2, raw, packageAbi, shared) {
|
|
2583
|
+
const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
|
|
2584
|
+
const script = inside(pkg, raw.script);
|
|
2585
|
+
if (script) {
|
|
2586
|
+
return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
|
|
2587
|
+
}
|
|
2588
|
+
const abi = abiOf(raw.abi, packageAbi);
|
|
2589
|
+
const wasm = inside(pkg, raw.wasm);
|
|
2590
|
+
const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
|
|
2591
|
+
if (!abi || !glue || !wasm) return void 0;
|
|
2592
|
+
const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
|
|
2593
|
+
const args = argsOf(pkg, raw.args);
|
|
2594
|
+
const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
|
|
2595
|
+
return {
|
|
2596
|
+
name: name2,
|
|
2597
|
+
abi,
|
|
2598
|
+
glue,
|
|
2599
|
+
wasm,
|
|
2600
|
+
argv0,
|
|
2601
|
+
...args ? { args } : {},
|
|
2602
|
+
...withEnv,
|
|
2603
|
+
...imports ? { imports } : {}
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2462
2606
|
function commandsOf(pkg, manifest) {
|
|
2463
2607
|
const slicc = manifest.slicc;
|
|
2464
|
-
if (!slicc || typeof slicc !== "object"
|
|
2465
|
-
|
|
2608
|
+
if (!slicc || typeof slicc !== "object") return [];
|
|
2609
|
+
const packageAbi = abiOf(slicc.abi, "emscripten");
|
|
2466
2610
|
const entries = slicc.commands;
|
|
2467
|
-
if (!entries || typeof entries !== "object") return [];
|
|
2611
|
+
if (!packageAbi || !entries || typeof entries !== "object") return [];
|
|
2468
2612
|
const shared = envOf(pkg, slicc.env);
|
|
2469
2613
|
const out = [];
|
|
2470
2614
|
for (const [name2, raw] of Object.entries(entries)) {
|
|
2471
2615
|
if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
|
|
2472
2616
|
continue;
|
|
2473
2617
|
}
|
|
2474
|
-
const
|
|
2475
|
-
|
|
2476
|
-
if ((raw.abi ?? "emscripten") !== "emscripten" || !glue || !wasm) continue;
|
|
2477
|
-
const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
|
|
2478
|
-
const args = argsOf(pkg, raw.args);
|
|
2479
|
-
const env = { ...shared, ...envOf(pkg, raw.env) };
|
|
2480
|
-
out.push({
|
|
2481
|
-
name: name2,
|
|
2482
|
-
glue,
|
|
2483
|
-
wasm,
|
|
2484
|
-
argv0,
|
|
2485
|
-
...args ? { args } : {},
|
|
2486
|
-
...Object.keys(env).length > 0 ? { env } : {}
|
|
2487
|
-
});
|
|
2618
|
+
const command = commandOf(pkg, name2, raw, packageAbi, shared);
|
|
2619
|
+
if (command) out.push(command);
|
|
2488
2620
|
}
|
|
2489
2621
|
return out;
|
|
2490
2622
|
}
|
|
2623
|
+
async function withPackagePaths(fs, pkg, command) {
|
|
2624
|
+
if (!command.env) return command;
|
|
2625
|
+
const env = {};
|
|
2626
|
+
for (const [key, value] of Object.entries(command.env)) {
|
|
2627
|
+
const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
|
|
2628
|
+
env[key] = path && await fs.exists(path) ? path : value;
|
|
2629
|
+
}
|
|
2630
|
+
return { ...command, env };
|
|
2631
|
+
}
|
|
2491
2632
|
async function packages(fs, modules) {
|
|
2492
2633
|
const names = await fs.readdir(modules).catch(() => []);
|
|
2493
2634
|
const dirs = [];
|
|
@@ -2513,7 +2654,8 @@ async function scanCommands(fs, modules) {
|
|
|
2513
2654
|
continue;
|
|
2514
2655
|
}
|
|
2515
2656
|
for (const command of commandsOf(pkg, manifest)) {
|
|
2516
|
-
if (!found.has(command.name))
|
|
2657
|
+
if (!found.has(command.name))
|
|
2658
|
+
found.set(command.name, await withPackagePaths(fs, pkg, command));
|
|
2517
2659
|
}
|
|
2518
2660
|
}
|
|
2519
2661
|
if (!found.has("sh") && found.has("bash")) {
|
|
@@ -2750,8 +2892,8 @@ var ChildTable = class {
|
|
|
2750
2892
|
for (; ; ) {
|
|
2751
2893
|
const candidates = this.candidates(pid, flags);
|
|
2752
2894
|
if (candidates.length === 0) throw new KernelError("ECHILD");
|
|
2753
|
-
const
|
|
2754
|
-
if (
|
|
2895
|
+
const done4 = candidates.find(([, child]) => child.code !== void 0);
|
|
2896
|
+
if (done4) return this.reap(done4[0], done4[1].code);
|
|
2755
2897
|
const changed = this.stateReport(candidates, flags);
|
|
2756
2898
|
if (changed) return changed;
|
|
2757
2899
|
if (nohang) return [0, 0];
|
|
@@ -3061,6 +3203,167 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
3061
3203
|
};
|
|
3062
3204
|
}
|
|
3063
3205
|
|
|
3206
|
+
// src/kernel/host-ops.ts
|
|
3207
|
+
var HOST_OPS = [
|
|
3208
|
+
"lock",
|
|
3209
|
+
"unlock",
|
|
3210
|
+
"async-submit",
|
|
3211
|
+
"async-wait",
|
|
3212
|
+
"async-take",
|
|
3213
|
+
"async-resolve",
|
|
3214
|
+
"async-hold",
|
|
3215
|
+
"async-cancel",
|
|
3216
|
+
"async-close"
|
|
3217
|
+
];
|
|
3218
|
+
var json2 = (value) => ({ ok: true, kind: "json", json: value });
|
|
3219
|
+
var done2 = { ok: true, kind: "void" };
|
|
3220
|
+
var LockTable = class {
|
|
3221
|
+
held = /* @__PURE__ */ new Map();
|
|
3222
|
+
lock(pid, path, exclusive, fd) {
|
|
3223
|
+
const holders = this.held.get(path) ?? /* @__PURE__ */ new Map();
|
|
3224
|
+
for (const [other, h] of holders) if (other !== pid && (exclusive || h.exclusive)) return false;
|
|
3225
|
+
const mine = holders.get(pid) ?? { exclusive, fds: /* @__PURE__ */ new Set() };
|
|
3226
|
+
mine.exclusive = exclusive;
|
|
3227
|
+
mine.fds.add(fd);
|
|
3228
|
+
holders.set(pid, mine);
|
|
3229
|
+
this.held.set(path, holders);
|
|
3230
|
+
return true;
|
|
3231
|
+
}
|
|
3232
|
+
unlock(pid, path) {
|
|
3233
|
+
const holders = this.held.get(path);
|
|
3234
|
+
holders?.delete(pid);
|
|
3235
|
+
if (holders?.size === 0) this.held.delete(path);
|
|
3236
|
+
}
|
|
3237
|
+
closed(pid, fd) {
|
|
3238
|
+
for (const [path, holders] of [...this.held]) {
|
|
3239
|
+
const mine = holders.get(pid);
|
|
3240
|
+
if (mine?.fds.delete(fd) && mine.fds.size === 0) this.unlock(pid, path);
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
release(pid) {
|
|
3244
|
+
for (const path of [...this.held.keys()]) this.unlock(pid, path);
|
|
3245
|
+
}
|
|
3246
|
+
};
|
|
3247
|
+
var AsyncOps = class {
|
|
3248
|
+
next = 1;
|
|
3249
|
+
results = /* @__PURE__ */ new Map();
|
|
3250
|
+
ready = [];
|
|
3251
|
+
waiters = /* @__PURE__ */ new Set();
|
|
3252
|
+
closed = false;
|
|
3253
|
+
run;
|
|
3254
|
+
constructor(run2) {
|
|
3255
|
+
this.run = run2;
|
|
3256
|
+
}
|
|
3257
|
+
submit(req) {
|
|
3258
|
+
const id = this.hold();
|
|
3259
|
+
this.run(req).then(
|
|
3260
|
+
(result) => this.complete(id, result),
|
|
3261
|
+
(err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
|
|
3262
|
+
);
|
|
3263
|
+
return id;
|
|
3264
|
+
}
|
|
3265
|
+
resolve(value) {
|
|
3266
|
+
const id = this.hold();
|
|
3267
|
+
this.complete(id, json2(value));
|
|
3268
|
+
return id;
|
|
3269
|
+
}
|
|
3270
|
+
hold() {
|
|
3271
|
+
const id = this.next++;
|
|
3272
|
+
this.results.set(id, void 0);
|
|
3273
|
+
return id;
|
|
3274
|
+
}
|
|
3275
|
+
take(id) {
|
|
3276
|
+
if (!this.results.has(id)) throw new KernelError("EINVAL");
|
|
3277
|
+
const result = this.results.get(id);
|
|
3278
|
+
if (!result) throw new KernelError("EAGAIN");
|
|
3279
|
+
this.cancel(id);
|
|
3280
|
+
return result;
|
|
3281
|
+
}
|
|
3282
|
+
cancel(id) {
|
|
3283
|
+
this.results.delete(id);
|
|
3284
|
+
const at = this.ready.indexOf(id);
|
|
3285
|
+
if (at >= 0) this.ready.splice(at, 1);
|
|
3286
|
+
}
|
|
3287
|
+
async wait(timeoutMs, signal) {
|
|
3288
|
+
for (; ; ) {
|
|
3289
|
+
if (this.closed) return -1;
|
|
3290
|
+
const id = this.ready.shift();
|
|
3291
|
+
if (id !== void 0) return id;
|
|
3292
|
+
if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
|
|
3293
|
+
const woke = await this.sleep(timeoutMs, signal);
|
|
3294
|
+
if (!woke) return 0;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
close() {
|
|
3298
|
+
this.closed = true;
|
|
3299
|
+
this.wake();
|
|
3300
|
+
}
|
|
3301
|
+
sleep(timeoutMs, signal) {
|
|
3302
|
+
return new Promise((resolve, reject) => {
|
|
3303
|
+
let timer;
|
|
3304
|
+
const cleanup = () => {
|
|
3305
|
+
clearTimeout(timer);
|
|
3306
|
+
this.waiters.delete(wake);
|
|
3307
|
+
signal.removeEventListener("abort", abort);
|
|
3308
|
+
};
|
|
3309
|
+
const wake = () => {
|
|
3310
|
+
cleanup();
|
|
3311
|
+
resolve(true);
|
|
3312
|
+
};
|
|
3313
|
+
const abort = () => {
|
|
3314
|
+
cleanup();
|
|
3315
|
+
reject(new KernelError("EINTR"));
|
|
3316
|
+
};
|
|
3317
|
+
this.waiters.add(wake);
|
|
3318
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
3319
|
+
if (timeoutMs !== void 0) {
|
|
3320
|
+
timer = setTimeout(() => {
|
|
3321
|
+
cleanup();
|
|
3322
|
+
resolve(false);
|
|
3323
|
+
}, timeoutMs);
|
|
3324
|
+
}
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
complete(id, result) {
|
|
3328
|
+
if (!this.results.has(id)) return;
|
|
3329
|
+
this.results.set(id, result);
|
|
3330
|
+
this.ready.push(id);
|
|
3331
|
+
this.wake();
|
|
3332
|
+
}
|
|
3333
|
+
wake() {
|
|
3334
|
+
for (const wake of [...this.waiters]) wake();
|
|
3335
|
+
}
|
|
3336
|
+
};
|
|
3337
|
+
async function hostSyscall(req, ctx) {
|
|
3338
|
+
switch (req.op) {
|
|
3339
|
+
case "lock":
|
|
3340
|
+
if (!ctx.locks) throw new KernelError("ENOSYS");
|
|
3341
|
+
if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
|
|
3342
|
+
throw new KernelError("EAGAIN");
|
|
3343
|
+
return done2;
|
|
3344
|
+
case "unlock":
|
|
3345
|
+
ctx.locks?.unlock(ctx.pid, req.path);
|
|
3346
|
+
return done2;
|
|
3347
|
+
case "async-submit":
|
|
3348
|
+
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
3349
|
+
return json2(ctx.ops.submit(req.req));
|
|
3350
|
+
case "async-wait":
|
|
3351
|
+
return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
|
|
3352
|
+
case "async-take":
|
|
3353
|
+
return ctx.ops.take(req.id);
|
|
3354
|
+
case "async-resolve":
|
|
3355
|
+
return json2(ctx.ops.resolve(req.value));
|
|
3356
|
+
case "async-hold":
|
|
3357
|
+
return json2(ctx.ops.hold());
|
|
3358
|
+
case "async-cancel":
|
|
3359
|
+
ctx.ops.cancel(req.id);
|
|
3360
|
+
return done2;
|
|
3361
|
+
case "async-close":
|
|
3362
|
+
ctx.ops.close();
|
|
3363
|
+
return done2;
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3064
3367
|
// src/kernel/net/http-syscalls.ts
|
|
3065
3368
|
var HTTP_OPS = ["net-request", "net-read", "net-close"];
|
|
3066
3369
|
var EMPTY = new Uint8Array(0);
|
|
@@ -3571,41 +3874,41 @@ function terminalOf(ctx, fd) {
|
|
|
3571
3874
|
if (!tty) throw new KernelError("ENOTTY");
|
|
3572
3875
|
return tty;
|
|
3573
3876
|
}
|
|
3574
|
-
var
|
|
3575
|
-
var
|
|
3877
|
+
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
3878
|
+
var done3 = { ok: true, kind: "void" };
|
|
3576
3879
|
function ptySyscall(req, ctx) {
|
|
3577
3880
|
switch (req.op) {
|
|
3578
3881
|
case "pty-open": {
|
|
3579
3882
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
3580
|
-
return
|
|
3883
|
+
return json3(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
3581
3884
|
}
|
|
3582
3885
|
case "pty-slave-open": {
|
|
3583
3886
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
3584
3887
|
const pair = ctx.ptys.get(req.n);
|
|
3585
3888
|
const fd = ctx.fds.install(pair.openSlave(), 3);
|
|
3586
3889
|
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
3587
|
-
return
|
|
3890
|
+
return json3(fd);
|
|
3588
3891
|
}
|
|
3589
3892
|
case "pty-number":
|
|
3590
|
-
return
|
|
3893
|
+
return json3(masterOf(ctx, req.fd).index);
|
|
3591
3894
|
case "pty-lock":
|
|
3592
3895
|
masterOf(ctx, req.fd).locked = req.lock;
|
|
3593
|
-
return
|
|
3896
|
+
return done3;
|
|
3594
3897
|
case "pty-ctty": {
|
|
3595
3898
|
const tty = terminalOf(ctx, req.fd);
|
|
3596
3899
|
const leader = ctx.jobs?.getsid(ctx.pid, 0) === ctx.pid;
|
|
3597
|
-
if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return
|
|
3900
|
+
if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done3;
|
|
3598
3901
|
if (!ctx.jobs?.acquireTerminal(ctx.pid, tty)) throw new KernelError("EPERM");
|
|
3599
|
-
return
|
|
3902
|
+
return done3;
|
|
3600
3903
|
}
|
|
3601
3904
|
case "pty-list":
|
|
3602
|
-
return
|
|
3905
|
+
return json3(ctx.ptys?.numbers() ?? []);
|
|
3603
3906
|
case "pty-packet":
|
|
3604
3907
|
masterOf(ctx, req.fd).packet = req.on;
|
|
3605
|
-
return
|
|
3908
|
+
return done3;
|
|
3606
3909
|
case "pty-winsz-set":
|
|
3607
3910
|
terminalOf(ctx, req.fd).resize(req.cols, req.rows);
|
|
3608
|
-
return
|
|
3911
|
+
return done3;
|
|
3609
3912
|
}
|
|
3610
3913
|
}
|
|
3611
3914
|
|
|
@@ -3990,7 +4293,7 @@ function socketAt(fds, fd) {
|
|
|
3990
4293
|
if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
|
|
3991
4294
|
return file;
|
|
3992
4295
|
}
|
|
3993
|
-
var ok = (
|
|
4296
|
+
var ok = (json4) => json4 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json4 };
|
|
3994
4297
|
async function socketSyscall(req, proc) {
|
|
3995
4298
|
const { fds, net } = proc;
|
|
3996
4299
|
switch (req.op) {
|
|
@@ -4316,6 +4619,9 @@ function isHttpSyscall(req) {
|
|
|
4316
4619
|
function isSocketSyscall(req) {
|
|
4317
4620
|
return req.op.startsWith("sock-");
|
|
4318
4621
|
}
|
|
4622
|
+
function isHostSyscall(req) {
|
|
4623
|
+
return HOST_OPS.includes(req.op);
|
|
4624
|
+
}
|
|
4319
4625
|
function isPtySyscall(req) {
|
|
4320
4626
|
return req.op.startsWith("pty-");
|
|
4321
4627
|
}
|
|
@@ -4353,6 +4659,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
4353
4659
|
"fd-cloexec",
|
|
4354
4660
|
"fd-list",
|
|
4355
4661
|
"proc-alarm",
|
|
4662
|
+
"dl-log",
|
|
4356
4663
|
"fd-renumber",
|
|
4357
4664
|
"fd-promote",
|
|
4358
4665
|
"fd-open-tty",
|
|
@@ -4376,7 +4683,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
4376
4683
|
"sig-pause",
|
|
4377
4684
|
...SOCKET_OPS,
|
|
4378
4685
|
...HTTP_OPS,
|
|
4379
|
-
...PTY_OPS
|
|
4686
|
+
...PTY_OPS,
|
|
4687
|
+
...HOST_OPS
|
|
4380
4688
|
]);
|
|
4381
4689
|
function isWasmSyscall(req) {
|
|
4382
4690
|
const op = req.op;
|
|
@@ -4424,6 +4732,9 @@ var WasmProcess = class {
|
|
|
4424
4732
|
const action = defaultAction(sig);
|
|
4425
4733
|
return action === "stop" ? this.stop(sig) : action;
|
|
4426
4734
|
}
|
|
4735
|
+
if (((this.options.pendingBits?.() ?? 0) & bit) !== 0 && defaultAction(sig) === "terminate") {
|
|
4736
|
+
return "terminate";
|
|
4737
|
+
}
|
|
4427
4738
|
this.options.onPending?.(sig);
|
|
4428
4739
|
const blocked = this.interrupt;
|
|
4429
4740
|
this.interrupt = new AbortController();
|
|
@@ -4468,6 +4779,15 @@ var WasmProcess = class {
|
|
|
4468
4779
|
if (isJobSyscall(req)) return this.jobSyscall(req);
|
|
4469
4780
|
if (isSocketSyscall(req)) return await this.socketSyscall(req);
|
|
4470
4781
|
if (isHttpSyscall(req)) return await this.httpSyscall(req);
|
|
4782
|
+
if (isHostSyscall(req)) {
|
|
4783
|
+
return await hostSyscall(req, {
|
|
4784
|
+
pid: this.pid,
|
|
4785
|
+
locks: this.options.locks,
|
|
4786
|
+
ops: this.asyncOps,
|
|
4787
|
+
blocking: () => this.blockingSignal(),
|
|
4788
|
+
valid: (inner) => isWasmSyscall(inner) && !isHostSyscall(inner)
|
|
4789
|
+
});
|
|
4790
|
+
}
|
|
4471
4791
|
if (isPtySyscall(req)) {
|
|
4472
4792
|
const { ptys, jobs } = this.options;
|
|
4473
4793
|
return ptySyscall(req, { pid: this.pid, fds: this.fds, ptys, jobs });
|
|
@@ -4531,6 +4851,7 @@ var WasmProcess = class {
|
|
|
4531
4851
|
case "fd-write":
|
|
4532
4852
|
return { ok: true, kind: "json", json: await this.write(req.fd, req.body, req.nonblock) };
|
|
4533
4853
|
case "fd-close":
|
|
4854
|
+
this.options.locks?.closed(this.pid, req.fd);
|
|
4534
4855
|
await Promise.resolve(this.fds.close(req.fd));
|
|
4535
4856
|
return { ok: true, kind: "void" };
|
|
4536
4857
|
case "fd-pipe": {
|
|
@@ -4802,6 +5123,9 @@ var WasmProcess = class {
|
|
|
4802
5123
|
case "proc-alarm":
|
|
4803
5124
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
4804
5125
|
return { ok: true, kind: "void" };
|
|
5126
|
+
case "dl-log":
|
|
5127
|
+
if (req.append) this.dlLog.push(req.append);
|
|
5128
|
+
return { ok: true, kind: "json", json: this.dlLog.slice(req.from) };
|
|
4805
5129
|
case "sig-mask":
|
|
4806
5130
|
this.caught = req.caught;
|
|
4807
5131
|
this.ignored = req.ignored;
|
|
@@ -4819,6 +5143,8 @@ var WasmProcess = class {
|
|
|
4819
5143
|
});
|
|
4820
5144
|
}
|
|
4821
5145
|
alarm;
|
|
5146
|
+
dlLog = [];
|
|
5147
|
+
asyncOps = new AsyncOps((req) => this.syscall(req));
|
|
4822
5148
|
alarmEvery;
|
|
4823
5149
|
setAlarm(sig, first, every, timer) {
|
|
4824
5150
|
if (!isSignal(sig)) throw new KernelError("EINVAL");
|
|
@@ -4847,6 +5173,8 @@ var WasmProcess = class {
|
|
|
4847
5173
|
if (this.exited) return;
|
|
4848
5174
|
this.exited = true;
|
|
4849
5175
|
this.clearAlarm();
|
|
5176
|
+
this.options.locks?.release(this.pid);
|
|
5177
|
+
this.asyncOps.close();
|
|
4850
5178
|
for (const pid of this.children.pids()) this.options.onReap?.(pid);
|
|
4851
5179
|
await this.fds.closeAll();
|
|
4852
5180
|
await this.options.http?.closeAll();
|
|
@@ -4857,6 +5185,10 @@ var WasmProcess = class {
|
|
|
4857
5185
|
var WASM_PROCESS_INIT = "wasm-process-init";
|
|
4858
5186
|
var WASM_PROCESS_EXIT = "wasm-process-exit";
|
|
4859
5187
|
var WASM_PROCESS_ERROR = "wasm-process-error";
|
|
5188
|
+
var WASM_THREAD_SPAWN = "wasm-thread-spawn";
|
|
5189
|
+
var WASM_THREAD_INIT = "wasm-thread-init";
|
|
5190
|
+
var WASM_THREAD_EXIT = "wasm-thread-exit";
|
|
5191
|
+
var WASM_MAX_THREADS = 64;
|
|
4860
5192
|
|
|
4861
5193
|
// src/kernel/host.ts
|
|
4862
5194
|
var CRASHED = 70;
|
|
@@ -4895,11 +5227,13 @@ function spawnWasmProcess(opts) {
|
|
|
4895
5227
|
ptys: opts.ptys,
|
|
4896
5228
|
net: opts.net,
|
|
4897
5229
|
http: opts.http,
|
|
5230
|
+
locks: opts.locks,
|
|
4898
5231
|
onReap: opts.onReap,
|
|
4899
5232
|
onPending: (sig) => void Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig)),
|
|
4900
5233
|
onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
|
|
4901
5234
|
hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
|
|
4902
|
-
raise: (sig) => signal(sig)
|
|
5235
|
+
raise: (sig) => signal(sig),
|
|
5236
|
+
...opts.program.abi === "wasi" ? { pendingBits: () => Atomics.load(header2, SAB_I_SIGNALS) } : {}
|
|
4903
5237
|
});
|
|
4904
5238
|
const token = mintSyncFsToken({ fs: opts.fs, cwd: opts.cwd });
|
|
4905
5239
|
const worker = opts.createWorker();
|
|
@@ -4910,14 +5244,15 @@ function spawnWasmProcess(opts) {
|
|
|
4910
5244
|
const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
|
|
4911
5245
|
let settle2;
|
|
4912
5246
|
const exited = new Promise((resolve) => settle2 = resolve);
|
|
4913
|
-
let
|
|
5247
|
+
let done4 = false;
|
|
4914
5248
|
let endedBy;
|
|
4915
5249
|
const finish = (code, sig) => {
|
|
4916
|
-
if (
|
|
4917
|
-
|
|
5250
|
+
if (done4) return;
|
|
5251
|
+
done4 = true;
|
|
4918
5252
|
endedBy = sig ?? process.execTermsig;
|
|
4919
5253
|
worker.removeEventListener("message", onMessage);
|
|
4920
5254
|
worker.removeEventListener("error", onError);
|
|
5255
|
+
for (const tid of [...threads.keys()]) endThread(tid);
|
|
4921
5256
|
responder.dispose();
|
|
4922
5257
|
revokeSyncFsToken(token);
|
|
4923
5258
|
worker.terminate();
|
|
@@ -4926,15 +5261,17 @@ function spawnWasmProcess(opts) {
|
|
|
4926
5261
|
() => settle2(code)
|
|
4927
5262
|
);
|
|
4928
5263
|
};
|
|
4929
|
-
const
|
|
4930
|
-
const data = event.data;
|
|
5264
|
+
const handle = (data) => {
|
|
4931
5265
|
if (data?.type === WASM_PROCESS_EXIT) {
|
|
4932
5266
|
finish(typeof data.code === "number" ? data.code : CRASHED);
|
|
4933
5267
|
} else if (data?.type === WASM_PROCESS_ERROR) {
|
|
4934
5268
|
opts.onError?.(String(data.message));
|
|
4935
5269
|
finish(CRASHED);
|
|
5270
|
+
} else if (data?.type === WASM_THREAD_SPAWN && data.thread) {
|
|
5271
|
+
spawnThread(data.thread);
|
|
4936
5272
|
}
|
|
4937
5273
|
};
|
|
5274
|
+
const onMessage = (event) => handle(event.data);
|
|
4938
5275
|
const onError = (event) => {
|
|
4939
5276
|
event.preventDefault();
|
|
4940
5277
|
opts.onError?.(String(event.message ?? "worker error"));
|
|
@@ -4942,6 +5279,49 @@ function spawnWasmProcess(opts) {
|
|
|
4942
5279
|
};
|
|
4943
5280
|
worker.addEventListener("message", onMessage);
|
|
4944
5281
|
worker.addEventListener("error", onError);
|
|
5282
|
+
const threads = /* @__PURE__ */ new Map();
|
|
5283
|
+
const endThread = (tid) => {
|
|
5284
|
+
const end = threads.get(tid);
|
|
5285
|
+
threads.delete(tid);
|
|
5286
|
+
end?.();
|
|
5287
|
+
};
|
|
5288
|
+
const spawnThread = (thread) => {
|
|
5289
|
+
if (done4) return;
|
|
5290
|
+
if (threads.size >= WASM_MAX_THREADS - 1) {
|
|
5291
|
+
opts.onError?.(`more than ${WASM_MAX_THREADS} threads`);
|
|
5292
|
+
finish(CRASHED);
|
|
5293
|
+
return;
|
|
5294
|
+
}
|
|
5295
|
+
const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
5296
|
+
const tw = opts.createWorker();
|
|
5297
|
+
const tresponder = attachSyncSabResponder(tw, tsab, token, { dispatch });
|
|
5298
|
+
const onThreadMessage = (event) => {
|
|
5299
|
+
const data = event.data;
|
|
5300
|
+
if (data?.type === WASM_THREAD_EXIT) endThread(thread.tid);
|
|
5301
|
+
else handle(data);
|
|
5302
|
+
};
|
|
5303
|
+
tw.addEventListener("message", onThreadMessage);
|
|
5304
|
+
tw.addEventListener("error", onError);
|
|
5305
|
+
threads.set(thread.tid, () => {
|
|
5306
|
+
tw.removeEventListener("message", onThreadMessage);
|
|
5307
|
+
tw.removeEventListener("error", onError);
|
|
5308
|
+
tresponder.dispose();
|
|
5309
|
+
tw.terminate();
|
|
5310
|
+
});
|
|
5311
|
+
const tinit = {
|
|
5312
|
+
type: WASM_THREAD_INIT,
|
|
5313
|
+
pid: opts.pid,
|
|
5314
|
+
program: opts.program,
|
|
5315
|
+
argv0: opts.argv0,
|
|
5316
|
+
args: opts.args,
|
|
5317
|
+
env: opts.env,
|
|
5318
|
+
cwd: opts.cwd,
|
|
5319
|
+
sab: tsab,
|
|
5320
|
+
...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
|
|
5321
|
+
thread
|
|
5322
|
+
};
|
|
5323
|
+
tw.postMessage(tinit);
|
|
5324
|
+
};
|
|
4945
5325
|
const init = {
|
|
4946
5326
|
type: WASM_PROCESS_INIT,
|
|
4947
5327
|
pid: opts.pid,
|
|
@@ -5076,8 +5456,67 @@ var JobTable = class {
|
|
|
5076
5456
|
}
|
|
5077
5457
|
};
|
|
5078
5458
|
|
|
5459
|
+
// src/process/wasi/wasi-module.ts
|
|
5460
|
+
function importedMemory(bytes) {
|
|
5461
|
+
let at = 8;
|
|
5462
|
+
const u32 = () => {
|
|
5463
|
+
let result = 0;
|
|
5464
|
+
let shift = 0;
|
|
5465
|
+
for (; ; ) {
|
|
5466
|
+
const b = bytes[at++];
|
|
5467
|
+
result |= (b & 127) << shift;
|
|
5468
|
+
if (!(b & 128)) return result >>> 0;
|
|
5469
|
+
shift += 7;
|
|
5470
|
+
}
|
|
5471
|
+
};
|
|
5472
|
+
const name2 = () => {
|
|
5473
|
+
const len = u32();
|
|
5474
|
+
const s = new TextDecoder().decode(bytes.subarray(at, at + len));
|
|
5475
|
+
at += len;
|
|
5476
|
+
return s;
|
|
5477
|
+
};
|
|
5478
|
+
const limits = () => {
|
|
5479
|
+
const flags = bytes[at++];
|
|
5480
|
+
const initial = u32();
|
|
5481
|
+
const maximum = flags & 1 ? u32() : void 0;
|
|
5482
|
+
return { initial, maximum, shared: (flags & 2) !== 0 };
|
|
5483
|
+
};
|
|
5484
|
+
while (at < bytes.length) {
|
|
5485
|
+
const id = bytes[at++];
|
|
5486
|
+
const size = u32();
|
|
5487
|
+
const end = at + size;
|
|
5488
|
+
if (id !== 2) {
|
|
5489
|
+
at = end;
|
|
5490
|
+
continue;
|
|
5491
|
+
}
|
|
5492
|
+
const count = u32();
|
|
5493
|
+
for (let i = 0; i < count; i++) {
|
|
5494
|
+
const module = name2();
|
|
5495
|
+
const field = name2();
|
|
5496
|
+
const kind = bytes[at++];
|
|
5497
|
+
if (kind === 0) u32();
|
|
5498
|
+
else if (kind === 1) {
|
|
5499
|
+
at++;
|
|
5500
|
+
limits();
|
|
5501
|
+
} else if (kind === 2) {
|
|
5502
|
+
const l = limits();
|
|
5503
|
+
return { module, name: field, ...l };
|
|
5504
|
+
} else if (kind === 3) at += 2;
|
|
5505
|
+
else if (kind === 4) {
|
|
5506
|
+
at++;
|
|
5507
|
+
u32();
|
|
5508
|
+
}
|
|
5509
|
+
}
|
|
5510
|
+
return void 0;
|
|
5511
|
+
}
|
|
5512
|
+
return void 0;
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5079
5515
|
// src/launcher.ts
|
|
5080
5516
|
var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
5517
|
+
var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
|
|
5518
|
+
var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
5519
|
+
var WASM_MAGIC = [0, 97, 115, 109];
|
|
5081
5520
|
var SHEBANG_MAX = 256;
|
|
5082
5521
|
var NOT_FOUND = 127;
|
|
5083
5522
|
var SHARED_DIRS = ["/tmp", "/home"];
|
|
@@ -5088,13 +5527,43 @@ function baseName(path) {
|
|
|
5088
5527
|
function modulePath(glue) {
|
|
5089
5528
|
return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
|
|
5090
5529
|
}
|
|
5530
|
+
function isWasm(bytes) {
|
|
5531
|
+
return WASM_MAGIC.every((b, i) => bytes[i] === b);
|
|
5532
|
+
}
|
|
5533
|
+
function shebang(head) {
|
|
5534
|
+
if (head[0] !== 35 || head[1] !== 33) return void 0;
|
|
5535
|
+
return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
|
|
5536
|
+
}
|
|
5537
|
+
function interpreter(head) {
|
|
5538
|
+
const words = shebang(head);
|
|
5539
|
+
if (!words) return "node";
|
|
5540
|
+
if (baseName(words[0]) === "env") words.shift();
|
|
5541
|
+
if (words[0] === "-S") words.shift();
|
|
5542
|
+
return baseName(words[0] ?? "");
|
|
5543
|
+
}
|
|
5544
|
+
function expandDefaults(defaults, env) {
|
|
5545
|
+
const out = {};
|
|
5546
|
+
for (const [key, value] of Object.entries(defaults ?? {})) {
|
|
5547
|
+
let missing3 = false;
|
|
5548
|
+
const expanded = value.replace(ENV_REFERENCE, (_, name2) => {
|
|
5549
|
+
const set2 = Object.hasOwn(env, name2) ? env[name2] : void 0;
|
|
5550
|
+
if (set2 === void 0) missing3 = true;
|
|
5551
|
+
return set2 ?? "";
|
|
5552
|
+
});
|
|
5553
|
+
if (!missing3) out[key] = expanded;
|
|
5554
|
+
}
|
|
5555
|
+
return out;
|
|
5556
|
+
}
|
|
5091
5557
|
function targetOf(command) {
|
|
5092
5558
|
return {
|
|
5559
|
+
abi: command.abi,
|
|
5093
5560
|
glue: command.glue,
|
|
5094
5561
|
wasm: command.wasm,
|
|
5095
5562
|
argv0: command.argv0,
|
|
5096
5563
|
...command.args ? { prefix: command.args } : {},
|
|
5097
|
-
...command.env ? { env: command.env } : {}
|
|
5564
|
+
...command.env ? { env: command.env } : {},
|
|
5565
|
+
...command.unset ? { unset: command.unset } : {},
|
|
5566
|
+
...command.imports ? { imports: command.imports } : {}
|
|
5098
5567
|
};
|
|
5099
5568
|
}
|
|
5100
5569
|
function childHandle(handle) {
|
|
@@ -5126,6 +5595,7 @@ var Launcher = class {
|
|
|
5126
5595
|
zombies = /* @__PURE__ */ new Set();
|
|
5127
5596
|
orphans = /* @__PURE__ */ new Set();
|
|
5128
5597
|
jobs = new JobTable();
|
|
5598
|
+
locks = new LockTable();
|
|
5129
5599
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
5130
5600
|
net = new LoopbackNet();
|
|
5131
5601
|
ca;
|
|
@@ -5154,35 +5624,62 @@ var Launcher = class {
|
|
|
5154
5624
|
const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
|
|
5155
5625
|
if (name2 !== void 0) {
|
|
5156
5626
|
const command = (await this.commands()).get(name2);
|
|
5157
|
-
return command && targetOf(command);
|
|
5627
|
+
return command && !command.script ? targetOf(command) : void 0;
|
|
5158
5628
|
}
|
|
5159
5629
|
const glue = await followLinks(this.base, this.fs.resolvePath(cwd, file));
|
|
5160
5630
|
const linked = COMMAND2.exec(glue)?.[1];
|
|
5161
5631
|
if (linked !== void 0) return this.resolve(`/bin/${linked}`, argv0, cwd);
|
|
5632
|
+
const head = await this.head(glue);
|
|
5633
|
+
if (!head) return void 0;
|
|
5634
|
+
if (isWasm(head)) {
|
|
5635
|
+
return {
|
|
5636
|
+
abi: "wasi",
|
|
5637
|
+
glue,
|
|
5638
|
+
wasm: glue,
|
|
5639
|
+
argv0: baseName(argv0 || file).replace(/\.wasm$/, "")
|
|
5640
|
+
};
|
|
5641
|
+
}
|
|
5642
|
+
if (interpreter(head) !== "node") return void 0;
|
|
5162
5643
|
const wasm = modulePath(glue);
|
|
5163
|
-
if (!await this.base.exists(
|
|
5164
|
-
return { glue, wasm, argv0: baseName(argv0 || file) };
|
|
5644
|
+
if (!await this.base.exists(wasm)) return void 0;
|
|
5645
|
+
return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file) };
|
|
5165
5646
|
}
|
|
5166
|
-
async
|
|
5167
|
-
let head;
|
|
5647
|
+
async head(path) {
|
|
5168
5648
|
try {
|
|
5169
|
-
|
|
5170
|
-
0,
|
|
5171
|
-
SHEBANG_MAX
|
|
5172
|
-
);
|
|
5649
|
+
return (await this.base.readFileBuffer(path)).subarray(0, SHEBANG_MAX);
|
|
5173
5650
|
} catch {
|
|
5174
5651
|
return void 0;
|
|
5175
5652
|
}
|
|
5176
|
-
|
|
5177
|
-
|
|
5653
|
+
}
|
|
5654
|
+
async scriptCommand(file) {
|
|
5655
|
+
const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
|
|
5656
|
+
if (name2 === void 0) return void 0;
|
|
5657
|
+
const command = (await this.commands()).get(name2);
|
|
5658
|
+
return command?.script ? command : void 0;
|
|
5659
|
+
}
|
|
5660
|
+
async interpreted(file, argv, cwd) {
|
|
5661
|
+
const command = await this.scriptCommand(file);
|
|
5662
|
+
const script = command?.script ?? this.fs.resolvePath(cwd, file);
|
|
5663
|
+
const words = shebang(await this.head(script) ?? new Uint8Array());
|
|
5664
|
+
if (!words) return void 0;
|
|
5178
5665
|
if (baseName(words[0]) === "env") words.shift();
|
|
5179
5666
|
const [interp, ...rest] = words;
|
|
5180
|
-
const
|
|
5181
|
-
if (!
|
|
5667
|
+
const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
|
|
5668
|
+
if (!found) return void 0;
|
|
5669
|
+
const target = command ? {
|
|
5670
|
+
...found,
|
|
5671
|
+
env: { ...found.env, ...command.env },
|
|
5672
|
+
unset: [...found.unset ?? [], ...command.unset ?? []]
|
|
5673
|
+
} : found;
|
|
5182
5674
|
const arg = rest.join(" ");
|
|
5183
5675
|
return {
|
|
5184
5676
|
target,
|
|
5185
|
-
args: [
|
|
5677
|
+
args: [
|
|
5678
|
+
...target.prefix ?? [],
|
|
5679
|
+
...arg ? [arg] : [],
|
|
5680
|
+
command ? script : file,
|
|
5681
|
+
...argv.slice(1)
|
|
5682
|
+
]
|
|
5186
5683
|
};
|
|
5187
5684
|
}
|
|
5188
5685
|
async plan(file, argv, cwd) {
|
|
@@ -5199,33 +5696,58 @@ var Launcher = class {
|
|
|
5199
5696
|
const key = `${path}:${st.size}:${st.mtime.getTime()}`;
|
|
5200
5697
|
let module = this.compiled.get(key);
|
|
5201
5698
|
if (!module) {
|
|
5202
|
-
module = this.base.readFileBuffer(path).then((bytes) =>
|
|
5699
|
+
module = this.base.readFileBuffer(path).then(async (bytes) => {
|
|
5700
|
+
const memory = importedMemory(bytes);
|
|
5701
|
+
const compiled = await WebAssembly.compile(bytes);
|
|
5702
|
+
return { module: compiled, ...memory ? { memory } : {} };
|
|
5703
|
+
});
|
|
5203
5704
|
this.compiled.set(key, module);
|
|
5204
5705
|
module.catch(() => this.compiled.delete(key));
|
|
5205
5706
|
}
|
|
5206
5707
|
return module;
|
|
5207
5708
|
});
|
|
5208
5709
|
}
|
|
5710
|
+
async names(path, module) {
|
|
5711
|
+
if (WebAssembly.Module.customSections(module, "name").length > 0) return void 0;
|
|
5712
|
+
const beside = `${path}.names`;
|
|
5713
|
+
if (await this.base.exists(beside)) return beside;
|
|
5714
|
+
const root = PACKAGE_ROOT.exec(path)?.[1];
|
|
5715
|
+
if (root === void 0) return void 0;
|
|
5716
|
+
const optional = `${root}-names${beside.slice(root.length)}`;
|
|
5717
|
+
return await this.base.exists(optional) ? optional : void 0;
|
|
5718
|
+
}
|
|
5719
|
+
async program(target, env) {
|
|
5720
|
+
if (target.abi !== "wasi") {
|
|
5721
|
+
const [glue, { module: module2 }] = await Promise.all([
|
|
5722
|
+
this.base.readFile(target.glue),
|
|
5723
|
+
this.module(target.wasm)
|
|
5724
|
+
]);
|
|
5725
|
+
return { glue, module: module2 };
|
|
5726
|
+
}
|
|
5727
|
+
const { module, memory } = await this.module(target.wasm);
|
|
5728
|
+
const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
|
|
5729
|
+
const imports = target.imports ? await this.base.readFile(target.imports) : void 0;
|
|
5730
|
+
return {
|
|
5731
|
+
abi: "wasi",
|
|
5732
|
+
glue: "",
|
|
5733
|
+
module,
|
|
5734
|
+
...memory ? { memory } : {},
|
|
5735
|
+
...names ? { names } : {},
|
|
5736
|
+
...imports !== void 0 ? { imports } : {}
|
|
5737
|
+
};
|
|
5738
|
+
}
|
|
5209
5739
|
async launch(planned, req) {
|
|
5210
5740
|
const { target, args } = planned;
|
|
5741
|
+
const env = { ...expandDefaults(target.env, { ...req.env, cwd: req.cwd }), ...req.env };
|
|
5742
|
+
for (const key of target.unset ?? []) delete env[key];
|
|
5211
5743
|
let program;
|
|
5212
5744
|
try {
|
|
5213
|
-
|
|
5214
|
-
this.base.readFile(target.glue),
|
|
5215
|
-
this.module(target.wasm)
|
|
5216
|
-
]);
|
|
5217
|
-
program = { glue, module };
|
|
5745
|
+
program = await this.program(target, env);
|
|
5218
5746
|
} catch (err) {
|
|
5219
5747
|
await req.fds.closeAll();
|
|
5220
5748
|
throw err;
|
|
5221
5749
|
}
|
|
5222
|
-
return this.start({
|
|
5223
|
-
...req,
|
|
5224
|
-
program,
|
|
5225
|
-
argv0: target.argv0,
|
|
5226
|
-
args,
|
|
5227
|
-
env: { ...target.env, ...req.env }
|
|
5228
|
-
});
|
|
5750
|
+
return this.start({ ...req, program, argv0: target.argv0, args, env });
|
|
5229
5751
|
}
|
|
5230
5752
|
start(req) {
|
|
5231
5753
|
const pid = this.nextPid++;
|
|
@@ -5248,6 +5770,7 @@ var Launcher = class {
|
|
|
5248
5770
|
ptys: this.ptys,
|
|
5249
5771
|
net: this.net,
|
|
5250
5772
|
http: new HttpHandles(this.transport),
|
|
5773
|
+
locks: this.locks,
|
|
5251
5774
|
onReap: (child) => this.reaped(child),
|
|
5252
5775
|
...req.fork ? { fork: req.fork } : {},
|
|
5253
5776
|
...req.ppid !== void 0 ? { ppid: req.ppid } : {}
|
|
@@ -5377,6 +5900,9 @@ function act(session, req) {
|
|
|
5377
5900
|
else if (req.action === "signal") session.signal(signalNumber(req.signal));
|
|
5378
5901
|
else session.close();
|
|
5379
5902
|
}
|
|
5903
|
+
function dirsChannel(name2) {
|
|
5904
|
+
return name2 === false ? void 0 : `slicc-kernel-dirs:${name2}`;
|
|
5905
|
+
}
|
|
5380
5906
|
function serveKernel(port, deps) {
|
|
5381
5907
|
let launcher;
|
|
5382
5908
|
let remote;
|
|
@@ -5403,7 +5929,7 @@ function serveKernel(port, deps) {
|
|
|
5403
5929
|
const name2 = req.metadata ?? META_DB;
|
|
5404
5930
|
const meta = name2 === false ? void 0 : await deps.metadata?.(name2);
|
|
5405
5931
|
remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
|
|
5406
|
-
const fs = new OpfsFs(root, meta);
|
|
5932
|
+
const fs = new OpfsFs(root, meta, dirsChannel(name2));
|
|
5407
5933
|
await fs.reconcile();
|
|
5408
5934
|
const started = new Launcher({
|
|
5409
5935
|
fs,
|