@ai-ecoverse/slicc-kernel 1.15.0 → 1.16.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 +12 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +463 -65
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +5 -1
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +441 -62
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +5 -1
- package/dist/process-worker.js.map +2 -2
- package/package.json +2 -1
package/dist/kernel-worker.js
CHANGED
|
@@ -76,9 +76,9 @@ var IndexedDbMeta = class {
|
|
|
76
76
|
if (trees.some((tree) => under(key, tree))) this.cache.delete(key);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
changed(
|
|
80
|
-
this.drop(
|
|
81
|
-
this.channel.postMessage(
|
|
79
|
+
changed(invalidation2) {
|
|
80
|
+
this.drop(invalidation2);
|
|
81
|
+
this.channel.postMessage(invalidation2);
|
|
82
82
|
}
|
|
83
83
|
transaction(mode) {
|
|
84
84
|
const transaction = this.db.transaction(META_STORE, mode);
|
|
@@ -2952,9 +2952,9 @@ var RemoteTransport = class {
|
|
|
2952
2952
|
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
2953
2953
|
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2954
2954
|
var PACKAGE = /\$\{package\}/g;
|
|
2955
|
-
function inside(pkg,
|
|
2956
|
-
if (typeof
|
|
2957
|
-
const clean =
|
|
2955
|
+
function inside(pkg, rel2) {
|
|
2956
|
+
if (typeof rel2 !== "string" || rel2 === "") return void 0;
|
|
2957
|
+
const clean = rel2.replace(/^\.\//, "");
|
|
2958
2958
|
if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
|
|
2959
2959
|
return `${pkg}/${clean}`;
|
|
2960
2960
|
}
|
|
@@ -6159,6 +6159,57 @@ function serveFilesystem(port, handlers, capabilities = {}) {
|
|
|
6159
6159
|
return { invalidate: (paths) => reply({ invalidate: paths }) };
|
|
6160
6160
|
}
|
|
6161
6161
|
|
|
6162
|
+
// src/mount/medium.ts
|
|
6163
|
+
var EMPTY_ROOT = { kind: "directory", size: 0, mtime: 0, mode: 16877 };
|
|
6164
|
+
var absent = () => fsError2("ENOMEDIUM", "No medium found");
|
|
6165
|
+
function mediumSlot(lost, onLost) {
|
|
6166
|
+
let current;
|
|
6167
|
+
function wrap(pick) {
|
|
6168
|
+
return async (...args) => {
|
|
6169
|
+
const medium = current;
|
|
6170
|
+
if (!medium) throw absent();
|
|
6171
|
+
try {
|
|
6172
|
+
return await pick(medium.handlers)(...args);
|
|
6173
|
+
} catch (err) {
|
|
6174
|
+
if (!await lost(err, medium.key)) throw err;
|
|
6175
|
+
if (current === medium) {
|
|
6176
|
+
current = void 0;
|
|
6177
|
+
onLost(medium.key);
|
|
6178
|
+
}
|
|
6179
|
+
throw absent();
|
|
6180
|
+
}
|
|
6181
|
+
};
|
|
6182
|
+
}
|
|
6183
|
+
const getattr = wrap((h) => h.getattr);
|
|
6184
|
+
const readdir = wrap((h) => h.readdir);
|
|
6185
|
+
const statfs = wrap((h) => async () => await h.statfs?.() ?? null);
|
|
6186
|
+
return {
|
|
6187
|
+
handlers: {
|
|
6188
|
+
getattr: (path) => current || path !== "/" ? getattr(path) : Promise.resolve(EMPTY_ROOT),
|
|
6189
|
+
readdir: (path) => current || path !== "/" ? readdir(path) : Promise.resolve([]),
|
|
6190
|
+
open: wrap((h) => h.open),
|
|
6191
|
+
read: wrap((h) => h.read),
|
|
6192
|
+
write: wrap((h) => h.write),
|
|
6193
|
+
release: wrap((h) => h.release),
|
|
6194
|
+
mkdir: wrap((h) => h.mkdir),
|
|
6195
|
+
rmdir: wrap((h) => h.rmdir),
|
|
6196
|
+
unlink: wrap((h) => h.unlink),
|
|
6197
|
+
rename: wrap((h) => h.rename),
|
|
6198
|
+
symlink: wrap((h) => h.symlink),
|
|
6199
|
+
readlink: wrap((h) => h.readlink),
|
|
6200
|
+
setattr: wrap((h) => h.setattr),
|
|
6201
|
+
statfs: () => current ? statfs() : Promise.resolve(null)
|
|
6202
|
+
},
|
|
6203
|
+
present: () => current !== void 0,
|
|
6204
|
+
insert(key, handlers) {
|
|
6205
|
+
current = { key, handlers };
|
|
6206
|
+
},
|
|
6207
|
+
eject() {
|
|
6208
|
+
current = void 0;
|
|
6209
|
+
}
|
|
6210
|
+
};
|
|
6211
|
+
}
|
|
6212
|
+
|
|
6162
6213
|
// src/mount/fsa.ts
|
|
6163
6214
|
var FSA_CAPABILITIES = { listingStats: true };
|
|
6164
6215
|
var codeOf = (err) => err?.code;
|
|
@@ -6246,7 +6297,6 @@ function kernelFsHandlers(fs) {
|
|
|
6246
6297
|
}
|
|
6247
6298
|
};
|
|
6248
6299
|
}
|
|
6249
|
-
var EMPTY_ROOT = { kind: "directory", size: 0, mtime: 0, mode: 16749 };
|
|
6250
6300
|
function granted(handle) {
|
|
6251
6301
|
if (!handle.queryPermission) return Promise.resolve(true);
|
|
6252
6302
|
return handle.queryPermission({ mode: "readwrite" }).then(
|
|
@@ -6255,48 +6305,345 @@ function granted(handle) {
|
|
|
6255
6305
|
);
|
|
6256
6306
|
}
|
|
6257
6307
|
function removableMedium(onLost, open = (handle) => new OpfsFs(handle)) {
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6308
|
+
const slot = mediumSlot(
|
|
6309
|
+
async (err, handle) => denied(err) && !await granted(handle),
|
|
6310
|
+
onLost
|
|
6311
|
+
);
|
|
6312
|
+
return {
|
|
6313
|
+
handlers: slot.handlers,
|
|
6314
|
+
present: slot.present,
|
|
6315
|
+
insert: (handle) => slot.insert(handle, kernelFsHandlers(open(handle))),
|
|
6316
|
+
eject: slot.eject
|
|
6317
|
+
};
|
|
6318
|
+
}
|
|
6319
|
+
|
|
6320
|
+
// src/mount/hostfs.ts
|
|
6321
|
+
var HOSTFS_IO = 16 * 1024 * 1024;
|
|
6322
|
+
var codeOf2 = (err) => err?.code;
|
|
6323
|
+
var unreachable = (err) => codeOf2(err) === "ENOTCONN";
|
|
6324
|
+
function rel(path) {
|
|
6325
|
+
return path.replace(/^\/+/, "");
|
|
6326
|
+
}
|
|
6327
|
+
var HostfsClient = class {
|
|
6328
|
+
grant;
|
|
6329
|
+
regrant;
|
|
6330
|
+
fetch;
|
|
6331
|
+
constructor(grant, regrant, fetch2) {
|
|
6332
|
+
this.grant = grant;
|
|
6333
|
+
this.regrant = regrant;
|
|
6334
|
+
this.fetch = fetch2;
|
|
6335
|
+
}
|
|
6336
|
+
get capabilities() {
|
|
6337
|
+
return this.grant.capabilities ?? {};
|
|
6338
|
+
}
|
|
6339
|
+
get token() {
|
|
6340
|
+
return this.grant.token;
|
|
6341
|
+
}
|
|
6342
|
+
get maxIo() {
|
|
6343
|
+
return this.capabilities.maxIo ?? HOSTFS_IO;
|
|
6344
|
+
}
|
|
6345
|
+
async send(path, init, handle = false, retried = false) {
|
|
6346
|
+
let response;
|
|
6347
|
+
try {
|
|
6348
|
+
response = await this.fetch(`${this.grant.url}${path}`, {
|
|
6349
|
+
...init,
|
|
6350
|
+
credentials: "omit",
|
|
6351
|
+
headers: {
|
|
6352
|
+
...init.headers,
|
|
6353
|
+
"X-Hostfs-Token": this.grant.token
|
|
6271
6354
|
}
|
|
6272
|
-
|
|
6355
|
+
});
|
|
6356
|
+
} catch (err) {
|
|
6357
|
+
throw fsError2("ENOTCONN", `the host proxy is unreachable: ${err.message}`);
|
|
6358
|
+
}
|
|
6359
|
+
if (response.ok) return response;
|
|
6360
|
+
if (response.headers.get("X-Proxy-Error")) {
|
|
6361
|
+
await response.body?.cancel();
|
|
6362
|
+
if (retried || response.status !== 403) {
|
|
6363
|
+
throw fsError2("EACCES", `the host proxy refused this folder (${response.status})`);
|
|
6273
6364
|
}
|
|
6274
|
-
|
|
6365
|
+
this.grant = await this.regrant();
|
|
6366
|
+
if (handle)
|
|
6367
|
+
throw fsError2("ESTALE", "the folder was granted again, so its open files are gone");
|
|
6368
|
+
return this.send(path, init, false, true);
|
|
6369
|
+
}
|
|
6370
|
+
const errno = response.headers.get("X-Hostfs-Errno") ?? "EIO";
|
|
6371
|
+
const body = await response.json().catch(() => ({}));
|
|
6372
|
+
throw fsError2(errno, typeof body.message === "string" ? body.message : errno);
|
|
6373
|
+
}
|
|
6374
|
+
async call(op, body = {}, handle = false) {
|
|
6375
|
+
const response = await this.send(
|
|
6376
|
+
"/api/hostfs",
|
|
6377
|
+
{
|
|
6378
|
+
method: "POST",
|
|
6379
|
+
headers: { "Content-Type": "application/json" },
|
|
6380
|
+
body: JSON.stringify({ op, ...body })
|
|
6381
|
+
},
|
|
6382
|
+
handle
|
|
6383
|
+
);
|
|
6384
|
+
return await response.json();
|
|
6385
|
+
}
|
|
6386
|
+
async read(fh, offset, size, ifMatch) {
|
|
6387
|
+
const response = await this.send(
|
|
6388
|
+
"/api/hostfs",
|
|
6389
|
+
{
|
|
6390
|
+
method: "POST",
|
|
6391
|
+
headers: { "Content-Type": "application/json" },
|
|
6392
|
+
body: JSON.stringify({ op: "read", fh, offset, size, ...ifMatch ? { ifMatch } : {} })
|
|
6393
|
+
},
|
|
6394
|
+
true
|
|
6395
|
+
);
|
|
6396
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
6397
|
+
}
|
|
6398
|
+
async write(fh, offset, bytes) {
|
|
6399
|
+
const response = await this.send(
|
|
6400
|
+
"/api/hostfs/write",
|
|
6401
|
+
{
|
|
6402
|
+
method: "PUT",
|
|
6403
|
+
headers: {
|
|
6404
|
+
"Content-Type": "application/octet-stream",
|
|
6405
|
+
"X-Hostfs-Request": JSON.stringify({ fh, offset })
|
|
6406
|
+
},
|
|
6407
|
+
body: bytes
|
|
6408
|
+
},
|
|
6409
|
+
true
|
|
6410
|
+
);
|
|
6411
|
+
await response.body?.cancel();
|
|
6412
|
+
}
|
|
6413
|
+
async watch(signal, onEvent, onOpen = () => void 0) {
|
|
6414
|
+
const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
|
|
6415
|
+
onOpen();
|
|
6416
|
+
const reader = response.body.getReader();
|
|
6417
|
+
const decoder2 = new TextDecoder();
|
|
6418
|
+
let buffered = "";
|
|
6419
|
+
try {
|
|
6420
|
+
for (; ; ) {
|
|
6421
|
+
const { done: done4, value } = await reader.read();
|
|
6422
|
+
if (done4) return;
|
|
6423
|
+
buffered += decoder2.decode(value, { stream: true });
|
|
6424
|
+
let end = buffered.indexOf("\n");
|
|
6425
|
+
while (end >= 0) {
|
|
6426
|
+
const line = buffered.slice(0, end).trim();
|
|
6427
|
+
buffered = buffered.slice(end + 1);
|
|
6428
|
+
if (line) onEvent(JSON.parse(line));
|
|
6429
|
+
end = buffered.indexOf("\n");
|
|
6430
|
+
}
|
|
6431
|
+
}
|
|
6432
|
+
} catch (err) {
|
|
6433
|
+
if (signal.aborted) return;
|
|
6434
|
+
throw fsError2("ENOTCONN", `the host proxy's watch broke: ${err.message}`);
|
|
6435
|
+
} finally {
|
|
6436
|
+
reader.releaseLock();
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
};
|
|
6440
|
+
function hostfsHandlers(current) {
|
|
6441
|
+
const handles = /* @__PURE__ */ new Map();
|
|
6442
|
+
let nextFh = 0;
|
|
6443
|
+
const fresh = (remote) => {
|
|
6444
|
+
if (remote.client.token === remote.token) return remote;
|
|
6445
|
+
throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
|
|
6446
|
+
};
|
|
6447
|
+
const live = (fh) => {
|
|
6448
|
+
const remote = handles.get(fh);
|
|
6449
|
+
if (!remote) throw fsError2("EBADF", `handle ${fh}`);
|
|
6450
|
+
return fresh(remote);
|
|
6451
|
+
};
|
|
6452
|
+
const call = (op, body = {}) => current().call(op, body);
|
|
6453
|
+
async function writable(client, path, flags) {
|
|
6454
|
+
const opening = () => client.call("open", { path: rel(path), ...flags });
|
|
6455
|
+
try {
|
|
6456
|
+
return { opened: await opening(), restore: void 0 };
|
|
6457
|
+
} catch (err) {
|
|
6458
|
+
if (!flags.write || codeOf2(err) !== "EACCES") throw err;
|
|
6459
|
+
const mode = (await client.call("stat", { path: rel(path) })).mode ?? 0;
|
|
6460
|
+
if (mode & 128) throw err;
|
|
6461
|
+
const restore = { path: rel(path), mode };
|
|
6462
|
+
await client.call("setattr", { path: rel(path), mode: mode | 128 });
|
|
6463
|
+
const opened2 = await opening().catch(async (again) => {
|
|
6464
|
+
await client.call("setattr", restore);
|
|
6465
|
+
throw again;
|
|
6466
|
+
});
|
|
6467
|
+
return { opened: opened2, restore };
|
|
6468
|
+
}
|
|
6275
6469
|
}
|
|
6276
|
-
const getattr = wrap((h) => h.getattr);
|
|
6277
|
-
const readdir = wrap((h) => h.readdir);
|
|
6278
6470
|
return {
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
mkdir: wrap((h) => h.mkdir),
|
|
6287
|
-
rmdir: wrap((h) => h.rmdir),
|
|
6288
|
-
unlink: wrap((h) => h.unlink),
|
|
6289
|
-
rename: wrap((h) => h.rename),
|
|
6290
|
-
symlink: wrap((h) => h.symlink),
|
|
6291
|
-
readlink: wrap((h) => h.readlink),
|
|
6292
|
-
setattr: wrap((h) => h.setattr)
|
|
6471
|
+
getattr: async (path) => await call("stat", { path: rel(path) }),
|
|
6472
|
+
async readdir(path) {
|
|
6473
|
+
const { entries } = await call(
|
|
6474
|
+
"list",
|
|
6475
|
+
{ path: rel(path) }
|
|
6476
|
+
);
|
|
6477
|
+
return entries.map(({ name: name2, attr }) => ({ name: name2, kind: attr.kind, attr }));
|
|
6293
6478
|
},
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6479
|
+
async open(path, flags) {
|
|
6480
|
+
const client = current();
|
|
6481
|
+
const token = client.token;
|
|
6482
|
+
const { opened: opened2, restore } = await writable(client, path, flags);
|
|
6483
|
+
handles.set(++nextFh, {
|
|
6484
|
+
client,
|
|
6485
|
+
token,
|
|
6486
|
+
fh: opened2.fh,
|
|
6487
|
+
etag: flags.write ? void 0 : opened2.attr?.etag,
|
|
6488
|
+
restore
|
|
6489
|
+
});
|
|
6490
|
+
return nextFh;
|
|
6297
6491
|
},
|
|
6298
|
-
|
|
6299
|
-
|
|
6492
|
+
async read(fh, offset, size) {
|
|
6493
|
+
const remote = live(fh);
|
|
6494
|
+
return remote.client.read(
|
|
6495
|
+
remote.fh,
|
|
6496
|
+
offset,
|
|
6497
|
+
Math.min(size, remote.client.maxIo),
|
|
6498
|
+
remote.etag
|
|
6499
|
+
);
|
|
6500
|
+
},
|
|
6501
|
+
async write(fh, offset, bytes) {
|
|
6502
|
+
const remote = live(fh);
|
|
6503
|
+
const most = remote.client.maxIo;
|
|
6504
|
+
for (let at = 0; at < bytes.length; at += most) {
|
|
6505
|
+
await remote.client.write(remote.fh, offset + at, bytes.subarray(at, at + most));
|
|
6506
|
+
}
|
|
6507
|
+
},
|
|
6508
|
+
async release(fh) {
|
|
6509
|
+
const remote = handles.get(fh);
|
|
6510
|
+
if (!remote) throw fsError2("EBADF", `handle ${fh}`);
|
|
6511
|
+
handles.delete(fh);
|
|
6512
|
+
try {
|
|
6513
|
+
await fresh(remote).client.call("release", { fh: remote.fh }, true);
|
|
6514
|
+
} finally {
|
|
6515
|
+
if (remote.restore) await current().call("setattr", remote.restore);
|
|
6516
|
+
}
|
|
6517
|
+
},
|
|
6518
|
+
mkdir: async (path) => void await call("mkdir", { path: rel(path) }),
|
|
6519
|
+
rmdir: async (path) => void await call("rmdir", { path: rel(path) }),
|
|
6520
|
+
unlink: async (path) => void await call("unlink", { path: rel(path) }),
|
|
6521
|
+
rename: async (from, to) => void await call("rename", { from: rel(from), to: rel(to) }),
|
|
6522
|
+
symlink: async (target, path) => void await call("symlink", { target, path: rel(path) }),
|
|
6523
|
+
readlink: async (path) => (await call("readlink", { path: rel(path) })).target,
|
|
6524
|
+
setattr: async (path, change) => void await call("setattr", { path: rel(path), ...change }),
|
|
6525
|
+
statfs: () => call("statfs")
|
|
6526
|
+
};
|
|
6527
|
+
}
|
|
6528
|
+
function sleep(ms, signal) {
|
|
6529
|
+
return new Promise((resolve) => {
|
|
6530
|
+
const done4 = () => {
|
|
6531
|
+
clearTimeout(timer);
|
|
6532
|
+
signal.removeEventListener("abort", done4);
|
|
6533
|
+
resolve();
|
|
6534
|
+
};
|
|
6535
|
+
const timer = setTimeout(done4, ms);
|
|
6536
|
+
signal.addEventListener("abort", done4);
|
|
6537
|
+
});
|
|
6538
|
+
}
|
|
6539
|
+
function keepWatching(loop, signal) {
|
|
6540
|
+
const silenceMs = loop.silenceMs ?? 45e3;
|
|
6541
|
+
const [first, most] = loop.backoffMs ?? [1e3, 3e4];
|
|
6542
|
+
const ready2 = Promise.withResolvers();
|
|
6543
|
+
let wait = first;
|
|
6544
|
+
let up = false;
|
|
6545
|
+
let current;
|
|
6546
|
+
async function session() {
|
|
6547
|
+
const session2 = new AbortController();
|
|
6548
|
+
current = session2;
|
|
6549
|
+
const stop = () => session2.abort();
|
|
6550
|
+
signal.addEventListener("abort", stop);
|
|
6551
|
+
let timer;
|
|
6552
|
+
const quiet = () => {
|
|
6553
|
+
clearTimeout(timer);
|
|
6554
|
+
timer = setTimeout(stop, silenceMs);
|
|
6555
|
+
};
|
|
6556
|
+
let opened2 = false;
|
|
6557
|
+
try {
|
|
6558
|
+
const client = await loop.connect();
|
|
6559
|
+
await client.watch(
|
|
6560
|
+
session2.signal,
|
|
6561
|
+
(event) => {
|
|
6562
|
+
quiet();
|
|
6563
|
+
if (!event.ping) loop.event(event);
|
|
6564
|
+
},
|
|
6565
|
+
() => {
|
|
6566
|
+
quiet();
|
|
6567
|
+
opened2 = true;
|
|
6568
|
+
up = true;
|
|
6569
|
+
loop.up(client);
|
|
6570
|
+
ready2.resolve();
|
|
6571
|
+
}
|
|
6572
|
+
);
|
|
6573
|
+
return opened2;
|
|
6574
|
+
} catch {
|
|
6575
|
+
return opened2;
|
|
6576
|
+
} finally {
|
|
6577
|
+
clearTimeout(timer);
|
|
6578
|
+
signal.removeEventListener("abort", stop);
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
void (async () => {
|
|
6582
|
+
while (!signal.aborted) {
|
|
6583
|
+
const began = Date.now();
|
|
6584
|
+
const opened2 = await session();
|
|
6585
|
+
if (signal.aborted) return;
|
|
6586
|
+
if (opened2 && Date.now() - began >= first) {
|
|
6587
|
+
wait = first;
|
|
6588
|
+
continue;
|
|
6589
|
+
}
|
|
6590
|
+
if (!opened2 && up) {
|
|
6591
|
+
up = false;
|
|
6592
|
+
loop.down();
|
|
6593
|
+
}
|
|
6594
|
+
ready2.resolve();
|
|
6595
|
+
await sleep(wait, signal);
|
|
6596
|
+
wait = Math.min(wait * 2, most);
|
|
6597
|
+
}
|
|
6598
|
+
})();
|
|
6599
|
+
return { ready: ready2.promise, kick: () => current?.abort() };
|
|
6600
|
+
}
|
|
6601
|
+
function invalidation(event) {
|
|
6602
|
+
return event.paths ?? true;
|
|
6603
|
+
}
|
|
6604
|
+
async function openHostfs(spec, hook, fetch2, timing = {}) {
|
|
6605
|
+
const readonly = spec.options?.ro !== void 0;
|
|
6606
|
+
const { port1, port2 } = new MessageChannel();
|
|
6607
|
+
const slot = mediumSlot(
|
|
6608
|
+
async (err) => unreachable(err),
|
|
6609
|
+
() => watching.kick()
|
|
6610
|
+
);
|
|
6611
|
+
const stop = new AbortController();
|
|
6612
|
+
const grant = () => hook(spec.source, { readonly });
|
|
6613
|
+
let active;
|
|
6614
|
+
const handlers = hostfsHandlers(() => active);
|
|
6615
|
+
const served = serveFilesystem(port2, slot.handlers, {
|
|
6616
|
+
symlinks: true,
|
|
6617
|
+
chmod: true,
|
|
6618
|
+
listingStats: true,
|
|
6619
|
+
maxIo: HOSTFS_IO,
|
|
6620
|
+
...readonly ? { readonly: true } : {}
|
|
6621
|
+
});
|
|
6622
|
+
const watching = keepWatching(
|
|
6623
|
+
{
|
|
6624
|
+
connect: async () => new HostfsClient(await grant(), grant, fetch2),
|
|
6625
|
+
up(client) {
|
|
6626
|
+
active = client;
|
|
6627
|
+
slot.insert(client, handlers);
|
|
6628
|
+
served.invalidate(true);
|
|
6629
|
+
},
|
|
6630
|
+
event: (event) => served.invalidate(invalidation(event)),
|
|
6631
|
+
down() {
|
|
6632
|
+
slot.eject();
|
|
6633
|
+
served.invalidate(true);
|
|
6634
|
+
},
|
|
6635
|
+
...timing
|
|
6636
|
+
},
|
|
6637
|
+
stop.signal
|
|
6638
|
+
);
|
|
6639
|
+
await watching.ready;
|
|
6640
|
+
return {
|
|
6641
|
+
port: port1,
|
|
6642
|
+
present: slot.present,
|
|
6643
|
+
dispose() {
|
|
6644
|
+
stop.abort();
|
|
6645
|
+
port1.close();
|
|
6646
|
+
port2.close();
|
|
6300
6647
|
}
|
|
6301
6648
|
};
|
|
6302
6649
|
}
|
|
@@ -6396,6 +6743,7 @@ var DriverConnection = class _DriverConnection {
|
|
|
6396
6743
|
|
|
6397
6744
|
// src/mount/mount-fs.ts
|
|
6398
6745
|
var MAX_LINKS4 = 40;
|
|
6746
|
+
var STALE_RETRIES = 3;
|
|
6399
6747
|
var DEFAULT_IO = 8 * 1024 * 1024;
|
|
6400
6748
|
var UNITS = { "": 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3, t: 1024 ** 4 };
|
|
6401
6749
|
function parseSize(text) {
|
|
@@ -6462,8 +6810,8 @@ var MountTable = class {
|
|
|
6462
6810
|
best = mount;
|
|
6463
6811
|
}
|
|
6464
6812
|
if (!best) return void 0;
|
|
6465
|
-
const
|
|
6466
|
-
return { mount: best, rel:
|
|
6813
|
+
const rel2 = best.target === "/" ? path : path.slice(best.target.length);
|
|
6814
|
+
return { mount: best, rel: rel2 || "/" };
|
|
6467
6815
|
}
|
|
6468
6816
|
async mount(spec, fs) {
|
|
6469
6817
|
const target = normalizePath(spec.target);
|
|
@@ -6568,20 +6916,20 @@ var MountTable = class {
|
|
|
6568
6916
|
mount.lists.delete(parentOf4(path));
|
|
6569
6917
|
for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
|
|
6570
6918
|
}
|
|
6571
|
-
async getattr(mount, path,
|
|
6919
|
+
async getattr(mount, path, rel2) {
|
|
6572
6920
|
if (mount.conn.failure) throw mount.conn.failure;
|
|
6573
6921
|
const cached = mount.attrs.get(path);
|
|
6574
6922
|
if (cached && cached.expires > Date.now()) return cached.value;
|
|
6575
|
-
const attr = await mount.conn.call({ op: "getattr", path:
|
|
6923
|
+
const attr = await mount.conn.call({ op: "getattr", path: rel2 });
|
|
6576
6924
|
const ttl = mount.caps.attrTtl ?? 1e3;
|
|
6577
6925
|
if (ttl > 0) mount.attrs.set(path, { value: attr, expires: Date.now() + ttl });
|
|
6578
6926
|
return attr;
|
|
6579
6927
|
}
|
|
6580
|
-
async readdir(mount, path,
|
|
6928
|
+
async readdir(mount, path, rel2) {
|
|
6581
6929
|
if (mount.conn.failure) throw mount.conn.failure;
|
|
6582
6930
|
const cached = mount.lists.get(path);
|
|
6583
6931
|
if (cached && cached.expires > Date.now()) return cached.value;
|
|
6584
|
-
const entries = await mount.conn.call({ op: "readdir", path:
|
|
6932
|
+
const entries = await mount.conn.call({ op: "readdir", path: rel2 });
|
|
6585
6933
|
const ttl = mount.caps.entryTtl ?? mount.caps.attrTtl ?? 1e3;
|
|
6586
6934
|
if (ttl > 0) mount.lists.set(path, { value: entries, expires: Date.now() + ttl });
|
|
6587
6935
|
return entries;
|
|
@@ -6626,10 +6974,10 @@ var MountFs = class {
|
|
|
6626
6974
|
const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
|
|
6627
6975
|
return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
|
|
6628
6976
|
}
|
|
6629
|
-
async open(mount,
|
|
6977
|
+
async open(mount, rel2, write) {
|
|
6630
6978
|
return await mount.conn.call({
|
|
6631
6979
|
op: "open",
|
|
6632
|
-
path:
|
|
6980
|
+
path: rel2,
|
|
6633
6981
|
write,
|
|
6634
6982
|
create: write,
|
|
6635
6983
|
truncate: write,
|
|
@@ -6646,13 +6994,22 @@ var MountFs = class {
|
|
|
6646
6994
|
const real = await this.follow(path);
|
|
6647
6995
|
const found = this.at(real);
|
|
6648
6996
|
if (!found) return this.base.readFileBuffer(real);
|
|
6649
|
-
|
|
6650
|
-
|
|
6997
|
+
for (let attempt = 1; ; attempt++) {
|
|
6998
|
+
try {
|
|
6999
|
+
return await this.readWhole(found.mount, real, found.rel);
|
|
7000
|
+
} catch (err) {
|
|
7001
|
+
if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
|
|
7002
|
+
this.table.forget(found.mount, real);
|
|
7003
|
+
}
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
async readWhole(mount, real, rel2) {
|
|
7007
|
+
const attr = await this.table.getattr(mount, real, rel2);
|
|
6651
7008
|
if (attr.kind === "directory") throw errnoError("EISDIR", real);
|
|
6652
7009
|
if (mount.maxFile > 0 && attr.size > mount.maxFile) {
|
|
6653
7010
|
throw errnoError("EFBIG", `${real} is larger than this mount's maxfile (${mount.maxFile})`);
|
|
6654
7011
|
}
|
|
6655
|
-
const fh = await this.open(mount,
|
|
7012
|
+
const fh = await this.open(mount, rel2, false);
|
|
6656
7013
|
const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
|
|
6657
7014
|
const chunks = [];
|
|
6658
7015
|
let total = 0;
|
|
@@ -6678,7 +7035,7 @@ var MountFs = class {
|
|
|
6678
7035
|
const real = await this.follow(path, 0, true);
|
|
6679
7036
|
const found = this.at(real);
|
|
6680
7037
|
if (!found) return this.base.writeFile(real, content);
|
|
6681
|
-
const { mount, rel } = found;
|
|
7038
|
+
const { mount, rel: rel2 } = found;
|
|
6682
7039
|
this.writable(mount, real);
|
|
6683
7040
|
const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
6684
7041
|
if (mount.maxFile > 0 && bytes.length > mount.maxFile) {
|
|
@@ -6687,8 +7044,17 @@ var MountFs = class {
|
|
|
6687
7044
|
`${real} would be larger than this mount's maxfile (${mount.maxFile})`
|
|
6688
7045
|
);
|
|
6689
7046
|
}
|
|
7047
|
+
for (let attempt = 1; ; attempt++) {
|
|
7048
|
+
try {
|
|
7049
|
+
return await this.writeWhole(mount, real, rel2, bytes);
|
|
7050
|
+
} catch (err) {
|
|
7051
|
+
if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
|
|
7052
|
+
}
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
async writeWhole(mount, real, rel2, bytes) {
|
|
6690
7056
|
this.table.forget(mount, real);
|
|
6691
|
-
const fh = await this.open(mount,
|
|
7057
|
+
const fh = await this.open(mount, rel2, true);
|
|
6692
7058
|
const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
|
|
6693
7059
|
try {
|
|
6694
7060
|
for (let offset = 0; offset < bytes.length; offset += chunk2) {
|
|
@@ -6755,16 +7121,16 @@ var MountFs = class {
|
|
|
6755
7121
|
async mkdir(path, options = {}) {
|
|
6756
7122
|
const found = this.at(path);
|
|
6757
7123
|
if (!found) return this.base.mkdir(path, options);
|
|
6758
|
-
const { mount, rel } = found;
|
|
7124
|
+
const { mount, rel: rel2 } = found;
|
|
6759
7125
|
const real = normalizePath(path);
|
|
6760
7126
|
this.writable(mount, real);
|
|
6761
7127
|
if (!options.recursive) {
|
|
6762
|
-
if (
|
|
7128
|
+
if (rel2 === "/") throw errnoError("EEXIST", real);
|
|
6763
7129
|
this.table.forget(mount, real);
|
|
6764
|
-
await mount.conn.call({ op: "mkdir", path:
|
|
7130
|
+
await mount.conn.call({ op: "mkdir", path: rel2 });
|
|
6765
7131
|
return;
|
|
6766
7132
|
}
|
|
6767
|
-
const parts =
|
|
7133
|
+
const parts = rel2.split("/").filter(Boolean);
|
|
6768
7134
|
for (let n = 1; n <= parts.length; n++) {
|
|
6769
7135
|
const sub = `/${parts.slice(0, n).join("/")}`;
|
|
6770
7136
|
const full = normalizePath(`${mount.target}${sub}`);
|
|
@@ -6778,25 +7144,25 @@ var MountFs = class {
|
|
|
6778
7144
|
async rm(path, options = {}) {
|
|
6779
7145
|
const found = this.at(path);
|
|
6780
7146
|
if (!found) return this.base.rm(path, options);
|
|
6781
|
-
const { mount, rel } = found;
|
|
7147
|
+
const { mount, rel: rel2 } = found;
|
|
6782
7148
|
const real = normalizePath(path);
|
|
6783
|
-
if (
|
|
7149
|
+
if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
|
|
6784
7150
|
this.writable(mount, real);
|
|
6785
7151
|
let attr;
|
|
6786
7152
|
try {
|
|
6787
|
-
attr = await this.table.getattr(mount, real,
|
|
7153
|
+
attr = await this.table.getattr(mount, real, rel2);
|
|
6788
7154
|
} catch (err) {
|
|
6789
7155
|
if (options.force && err.code === "ENOENT") return;
|
|
6790
7156
|
throw err;
|
|
6791
7157
|
}
|
|
6792
7158
|
this.table.forget(mount, real);
|
|
6793
7159
|
if (attr.kind === "directory" && options.recursive) {
|
|
6794
|
-
for (const entry of await this.table.readdir(mount, real,
|
|
7160
|
+
for (const entry of await this.table.readdir(mount, real, rel2)) {
|
|
6795
7161
|
await this.rm(`${real}/${entry.name}`, { recursive: true, force: options.force ?? false });
|
|
6796
7162
|
}
|
|
6797
7163
|
this.table.forget(mount, real);
|
|
6798
7164
|
}
|
|
6799
|
-
await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path:
|
|
7165
|
+
await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path: rel2 });
|
|
6800
7166
|
this.table.forget(mount, real);
|
|
6801
7167
|
}
|
|
6802
7168
|
async rename(from, to) {
|
|
@@ -7258,6 +7624,7 @@ var Launcher = class {
|
|
|
7258
7624
|
media;
|
|
7259
7625
|
onMountPending;
|
|
7260
7626
|
removable = /* @__PURE__ */ new Map();
|
|
7627
|
+
hostfs;
|
|
7261
7628
|
inserted = /* @__PURE__ */ new Map();
|
|
7262
7629
|
openFiles = /* @__PURE__ */ new Set();
|
|
7263
7630
|
held = /* @__PURE__ */ new Set();
|
|
@@ -7275,6 +7642,7 @@ var Launcher = class {
|
|
|
7275
7642
|
this.createDriverWorker = options.createDriverWorker;
|
|
7276
7643
|
this.media = options.media ?? memoryMedia();
|
|
7277
7644
|
this.onMountPending = options.onMountPending;
|
|
7645
|
+
this.hostfs = options;
|
|
7278
7646
|
this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
|
|
7279
7647
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
7280
7648
|
this.env = { ...networkEnv(), ...options.env };
|
|
@@ -7540,6 +7908,17 @@ var Launcher = class {
|
|
|
7540
7908
|
}
|
|
7541
7909
|
async driver(type, spec) {
|
|
7542
7910
|
if (type === "fsa") return this.fsaDriver(spec);
|
|
7911
|
+
if (type === "hostfs") {
|
|
7912
|
+
const { hostfs, hostfsFetch, hostfsTiming } = this.hostfs;
|
|
7913
|
+
if (!hostfs)
|
|
7914
|
+
throw fsError("ENODEV", "this kernel has no hostfs hook (createKernel({ hostfs }))");
|
|
7915
|
+
return openHostfs(
|
|
7916
|
+
spec,
|
|
7917
|
+
hostfs,
|
|
7918
|
+
hostfsFetch ?? ((url, init) => fetch(url, init)),
|
|
7919
|
+
hostfsTiming
|
|
7920
|
+
);
|
|
7921
|
+
}
|
|
7543
7922
|
if (type === "tmpfs") {
|
|
7544
7923
|
const { port1, port2 } = new MessageChannel();
|
|
7545
7924
|
serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });
|
|
@@ -7796,6 +8175,7 @@ function serveKernel(port, deps) {
|
|
|
7796
8175
|
...remote ? { transport: remote } : {},
|
|
7797
8176
|
...media ? { media } : {},
|
|
7798
8177
|
onMountPending: (medium) => port.postMessage({ medium }),
|
|
8178
|
+
...req.hostfs ? { hostfs: askGrant } : {},
|
|
7799
8179
|
caStore: caStore(deps.metadata ? name2 : false)
|
|
7800
8180
|
});
|
|
7801
8181
|
await started.prepare();
|
|
@@ -7829,7 +8209,25 @@ function serveKernel(port, deps) {
|
|
|
7829
8209
|
onStderr: (bytes) => reply(req.id, { fd: 2, bytes })
|
|
7830
8210
|
});
|
|
7831
8211
|
}
|
|
8212
|
+
const grants = /* @__PURE__ */ new Map();
|
|
8213
|
+
let nextGrant = 0;
|
|
8214
|
+
const askGrant = (source, options) => {
|
|
8215
|
+
const id = ++nextGrant;
|
|
8216
|
+
const waiting = Promise.withResolvers();
|
|
8217
|
+
grants.set(id, waiting);
|
|
8218
|
+
port.postMessage({ hostfs: { id, source, ...options } });
|
|
8219
|
+
return waiting.promise;
|
|
8220
|
+
};
|
|
8221
|
+
function granted2({ id, grant, error }) {
|
|
8222
|
+
const waiting = grants.get(id);
|
|
8223
|
+
if (!waiting) return;
|
|
8224
|
+
grants.delete(id);
|
|
8225
|
+
if (grant) waiting.resolve(grant);
|
|
8226
|
+
else waiting.reject(Object.assign(new Error(error ?? "no grant"), { code: "EACCES" }));
|
|
8227
|
+
}
|
|
7832
8228
|
port.addEventListener("message", (event) => {
|
|
8229
|
+
const grant = event.data.hostfsGrant;
|
|
8230
|
+
if (grant) return granted2(grant);
|
|
7833
8231
|
if (event.data.net !== void 0) {
|
|
7834
8232
|
remote?.receive(event.data);
|
|
7835
8233
|
return;
|