@ai-ecoverse/slicc-kernel 1.37.0 → 1.39.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 +4 -2
- package/dist/driver-worker.js +123 -0
- package/dist/driver-worker.js.map +3 -3
- package/dist/index.js +309 -6
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +1028 -319
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +123 -0
- package/dist/node-driver-worker.js.map +3 -3
- package/dist/node-process-worker.js +399 -111
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +904 -116
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +399 -111
- package/dist/process-worker.js.map +4 -4
- package/package.json +1 -1
package/dist/node.js
CHANGED
|
@@ -1190,7 +1190,153 @@ async function readWithin(reader, ms) {
|
|
|
1190
1190
|
}
|
|
1191
1191
|
}
|
|
1192
1192
|
|
|
1193
|
+
// src/kernel/net/ws-queue.ts
|
|
1194
|
+
var INBOUND_LIMIT = 16 * 1024 * 1024;
|
|
1195
|
+
var MESSAGE_LIMIT = 64 * 1024 * 1024;
|
|
1196
|
+
var TOO_BIG = 1009;
|
|
1197
|
+
function payloadSize(message2) {
|
|
1198
|
+
return typeof message2 === "string" ? new TextEncoder().encode(message2).length : message2.length;
|
|
1199
|
+
}
|
|
1200
|
+
var MessageQueue = class {
|
|
1201
|
+
items = [];
|
|
1202
|
+
limit;
|
|
1203
|
+
waiter;
|
|
1204
|
+
size = 0;
|
|
1205
|
+
ended = false;
|
|
1206
|
+
overflow;
|
|
1207
|
+
taken;
|
|
1208
|
+
closed;
|
|
1209
|
+
settle;
|
|
1210
|
+
constructor(overflow, limit = INBOUND_LIMIT, taken) {
|
|
1211
|
+
this.overflow = overflow;
|
|
1212
|
+
this.limit = limit;
|
|
1213
|
+
this.taken = taken;
|
|
1214
|
+
this.closed = new Promise((resolve) => {
|
|
1215
|
+
this.settle = resolve;
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
push(message2) {
|
|
1219
|
+
if (this.ended) return;
|
|
1220
|
+
const size = payloadSize(message2);
|
|
1221
|
+
const huge = size > MESSAGE_LIMIT;
|
|
1222
|
+
if (huge || this.items.length > 0 && this.size + size > this.limit) {
|
|
1223
|
+
const reason = huge ? `a message is over ${MESSAGE_LIMIT} bytes` : `over ${this.limit} bytes of messages are waiting`;
|
|
1224
|
+
this.end({ code: TOO_BIG, reason });
|
|
1225
|
+
this.overflow();
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
this.size += size;
|
|
1229
|
+
this.items.push(message2);
|
|
1230
|
+
this.wake();
|
|
1231
|
+
}
|
|
1232
|
+
end(close) {
|
|
1233
|
+
if (this.ended) return;
|
|
1234
|
+
this.ended = true;
|
|
1235
|
+
this.settle(close);
|
|
1236
|
+
this.wake();
|
|
1237
|
+
}
|
|
1238
|
+
wake() {
|
|
1239
|
+
const waiter = this.waiter;
|
|
1240
|
+
this.waiter = void 0;
|
|
1241
|
+
waiter?.();
|
|
1242
|
+
}
|
|
1243
|
+
async *[Symbol.asyncIterator]() {
|
|
1244
|
+
for (; ; ) {
|
|
1245
|
+
const next = this.items.shift();
|
|
1246
|
+
if (next !== void 0) {
|
|
1247
|
+
const n = payloadSize(next);
|
|
1248
|
+
this.size -= n;
|
|
1249
|
+
this.taken?.(n);
|
|
1250
|
+
yield next;
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
if (this.ended) return;
|
|
1254
|
+
await new Promise((resolve) => {
|
|
1255
|
+
this.waiter = resolve;
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
// src/websocket-transport.ts
|
|
1262
|
+
var SENT_HEADERS_DROP = /* @__PURE__ */ new Set([
|
|
1263
|
+
"host",
|
|
1264
|
+
"connection",
|
|
1265
|
+
"upgrade",
|
|
1266
|
+
"sec-websocket-key",
|
|
1267
|
+
"sec-websocket-version",
|
|
1268
|
+
"sec-websocket-extensions",
|
|
1269
|
+
"sec-websocket-protocol"
|
|
1270
|
+
]);
|
|
1271
|
+
function headerRecord(headers) {
|
|
1272
|
+
const out = {};
|
|
1273
|
+
for (const [name2, value] of headers) {
|
|
1274
|
+
if (!SENT_HEADERS_DROP.has(name2.toLowerCase())) out[name2] = value;
|
|
1275
|
+
}
|
|
1276
|
+
return out;
|
|
1277
|
+
}
|
|
1278
|
+
function sendableClose(code) {
|
|
1279
|
+
return code === 1e3 || code !== void 0 && code >= 3e3 && code <= 4999 ? code : 1e3;
|
|
1280
|
+
}
|
|
1281
|
+
function blockedReason(url, e) {
|
|
1282
|
+
const name2 = e?.name;
|
|
1283
|
+
if (name2 === "SecurityError" && url.startsWith("ws:")) {
|
|
1284
|
+
return `${url}: ws:// is blocked from an https page; use wss:// or slicc-node`;
|
|
1285
|
+
}
|
|
1286
|
+
return `${url}: ${e instanceof Error ? e.message : String(e)}`;
|
|
1287
|
+
}
|
|
1288
|
+
function openWebSocket(Socket, request, withHeaders) {
|
|
1289
|
+
const { url, protocols, signal } = request;
|
|
1290
|
+
signal.throwIfAborted();
|
|
1291
|
+
let socket;
|
|
1292
|
+
try {
|
|
1293
|
+
socket = withHeaders ? new Socket(url, { protocols, headers: headerRecord(request.headers) }) : new Socket(url, protocols);
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
return Promise.reject(Object.assign(new Error(blockedReason(url, e)), { status: 502 }));
|
|
1296
|
+
}
|
|
1297
|
+
socket.binaryType = "arraybuffer";
|
|
1298
|
+
const queue = new MessageQueue(() => socket.close(1e3, "too many waiting messages"));
|
|
1299
|
+
return new Promise((resolve, reject) => {
|
|
1300
|
+
let open = false;
|
|
1301
|
+
const abort = () => {
|
|
1302
|
+
socket.close(1e3, "aborted");
|
|
1303
|
+
reject(signal.reason);
|
|
1304
|
+
};
|
|
1305
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1306
|
+
socket.addEventListener("open", () => {
|
|
1307
|
+
open = true;
|
|
1308
|
+
signal.removeEventListener("abort", abort);
|
|
1309
|
+
resolve({
|
|
1310
|
+
protocol: socket.protocol,
|
|
1311
|
+
get buffered() {
|
|
1312
|
+
return socket.bufferedAmount;
|
|
1313
|
+
},
|
|
1314
|
+
send: (data) => socket.send(data),
|
|
1315
|
+
messages: queue,
|
|
1316
|
+
close: (code, reason) => socket.close(sendableClose(code), reason),
|
|
1317
|
+
closed: queue.closed
|
|
1318
|
+
});
|
|
1319
|
+
});
|
|
1320
|
+
socket.addEventListener("message", (event) => {
|
|
1321
|
+
queue.push(typeof event.data === "string" ? event.data : new Uint8Array(event.data));
|
|
1322
|
+
});
|
|
1323
|
+
socket.addEventListener("close", (event) => {
|
|
1324
|
+
queue.end({ code: event.code, reason: event.reason });
|
|
1325
|
+
if (open) return;
|
|
1326
|
+
signal.removeEventListener("abort", abort);
|
|
1327
|
+
reject(
|
|
1328
|
+
Object.assign(new Error(`${url}: the WebSocket closed (${event.code}) before it opened`), {
|
|
1329
|
+
status: 502
|
|
1330
|
+
})
|
|
1331
|
+
);
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1193
1336
|
// src/transport.ts
|
|
1337
|
+
var SEND_BUFFER = 1024 * 1024;
|
|
1338
|
+
var CLOSE_WAIT_MS = 5e3;
|
|
1339
|
+
var NO_WEBSOCKET = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
|
|
1194
1340
|
var MAX_REQUEST_BODY = 64 * 1024 * 1024;
|
|
1195
1341
|
function joinHeaders(headers) {
|
|
1196
1342
|
const out = new Headers();
|
|
@@ -1215,13 +1361,17 @@ async function* chunks(body, idleMs) {
|
|
|
1215
1361
|
}
|
|
1216
1362
|
function fetchTransport(options = {}) {
|
|
1217
1363
|
const send = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1364
|
+
const Socket = options.webSocket === true ? globalThis.WebSocket : options.webSocket || void 0;
|
|
1365
|
+
const headers = options.webSocketHeaders === true;
|
|
1218
1366
|
return {
|
|
1219
1367
|
traits: {
|
|
1220
1368
|
manualRedirects: false,
|
|
1221
1369
|
encodedBodies: false,
|
|
1222
1370
|
maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
|
|
1223
|
-
crossOrigin: "cors"
|
|
1371
|
+
crossOrigin: "cors",
|
|
1372
|
+
...Socket ? { websocket: true } : {}
|
|
1224
1373
|
},
|
|
1374
|
+
...Socket ? { websocket: (request) => openWebSocket(Socket, request, headers) } : {},
|
|
1225
1375
|
async fetch(request) {
|
|
1226
1376
|
let response;
|
|
1227
1377
|
try {
|
|
@@ -1243,15 +1393,15 @@ function fetchTransport(options = {}) {
|
|
|
1243
1393
|
{ status: 502 }
|
|
1244
1394
|
);
|
|
1245
1395
|
}
|
|
1246
|
-
const
|
|
1396
|
+
const headers2 = [];
|
|
1247
1397
|
response.headers.forEach((value, name2) => {
|
|
1248
|
-
|
|
1398
|
+
headers2.push([name2, value]);
|
|
1249
1399
|
});
|
|
1250
1400
|
const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
|
|
1251
1401
|
return {
|
|
1252
1402
|
status: response.status,
|
|
1253
1403
|
statusText: response.statusText,
|
|
1254
|
-
headers,
|
|
1404
|
+
headers: headers2,
|
|
1255
1405
|
body,
|
|
1256
1406
|
cancel: async () => {
|
|
1257
1407
|
await body.return(void 0);
|
|
@@ -1261,7 +1411,7 @@ function fetchTransport(options = {}) {
|
|
|
1261
1411
|
}
|
|
1262
1412
|
};
|
|
1263
1413
|
}
|
|
1264
|
-
function serveTransport(peer, transport) {
|
|
1414
|
+
function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
1265
1415
|
const open = /* @__PURE__ */ new Map();
|
|
1266
1416
|
const fail2 = (nid, e) => {
|
|
1267
1417
|
open.delete(nid);
|
|
@@ -1302,6 +1452,96 @@ function serveTransport(peer, transport) {
|
|
|
1302
1452
|
fail2(call.nid, e);
|
|
1303
1453
|
}
|
|
1304
1454
|
};
|
|
1455
|
+
const sockets = /* @__PURE__ */ new Map();
|
|
1456
|
+
const credit = /* @__PURE__ */ new Map();
|
|
1457
|
+
const refill = (nid, n, closing = false) => {
|
|
1458
|
+
const flow = credit.get(nid);
|
|
1459
|
+
if (!flow) return;
|
|
1460
|
+
flow.inflight -= n;
|
|
1461
|
+
if (closing && !flow.closing) {
|
|
1462
|
+
flow.closing = true;
|
|
1463
|
+
flow.deadline();
|
|
1464
|
+
}
|
|
1465
|
+
flow.wake?.();
|
|
1466
|
+
};
|
|
1467
|
+
const openSocket = async (call) => {
|
|
1468
|
+
const abort = new AbortController();
|
|
1469
|
+
open.set(call.nid, { abort });
|
|
1470
|
+
try {
|
|
1471
|
+
if (!transport.websocket) throw new Error(NO_WEBSOCKET);
|
|
1472
|
+
const socket = await transport.websocket({
|
|
1473
|
+
url: call.url,
|
|
1474
|
+
protocols: call.protocols,
|
|
1475
|
+
headers: call.headers,
|
|
1476
|
+
signal: abort.signal
|
|
1477
|
+
});
|
|
1478
|
+
if (!open.delete(call.nid)) {
|
|
1479
|
+
socket.close(1e3, "cancelled");
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
sockets.set(call.nid, socket);
|
|
1483
|
+
peer.postMessage({ net: "ws-opened", nid: call.nid, protocol: socket.protocol });
|
|
1484
|
+
void relay(call.nid, socket);
|
|
1485
|
+
} catch (e) {
|
|
1486
|
+
fail2(call.nid, e);
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
const relay = async (nid, socket) => {
|
|
1490
|
+
let ending;
|
|
1491
|
+
let late;
|
|
1492
|
+
const unanswered = new Promise((resolve) => {
|
|
1493
|
+
late = () => resolve("late");
|
|
1494
|
+
});
|
|
1495
|
+
const flow = {
|
|
1496
|
+
inflight: 0,
|
|
1497
|
+
closing: false,
|
|
1498
|
+
deadline: () => AbortSignal.timeout(closeWaitMs).addEventListener("abort", late, { once: true })
|
|
1499
|
+
};
|
|
1500
|
+
credit.set(nid, flow);
|
|
1501
|
+
const timedOut = { code: 1006, reason: "the far end did not answer the close" };
|
|
1502
|
+
let oversize = false;
|
|
1503
|
+
try {
|
|
1504
|
+
const messages = socket.messages[Symbol.asyncIterator]();
|
|
1505
|
+
for (; ; ) {
|
|
1506
|
+
const next = await Promise.race([messages.next(), unanswered]);
|
|
1507
|
+
if (next === "late" || next.done) break;
|
|
1508
|
+
const data = next.value;
|
|
1509
|
+
if (flow.closing) continue;
|
|
1510
|
+
if (payloadSize(data) > MESSAGE_LIMIT) {
|
|
1511
|
+
oversize = true;
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
if (typeof data === "string") peer.postMessage({ net: "ws-message", nid, data });
|
|
1515
|
+
else {
|
|
1516
|
+
const bytes = data.slice();
|
|
1517
|
+
peer.postMessage({ net: "ws-message", nid, data: bytes }, [bytes.buffer]);
|
|
1518
|
+
}
|
|
1519
|
+
flow.inflight += payloadSize(data);
|
|
1520
|
+
while (flow.inflight > INBOUND_LIMIT && !flow.closing) {
|
|
1521
|
+
await new Promise((resolve) => {
|
|
1522
|
+
flow.wake = resolve;
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (oversize) socket.close(1e3, "a message is too big");
|
|
1527
|
+
ending = oversize ? { code: 1009, reason: `a message is over ${MESSAGE_LIMIT} bytes` } : await Promise.race([socket.closed, unanswered.then(() => timedOut)]);
|
|
1528
|
+
} catch (e) {
|
|
1529
|
+
socket.close(1e3, "the relay failed");
|
|
1530
|
+
ending = { code: 1011, reason: e instanceof Error ? e.message : String(e) };
|
|
1531
|
+
}
|
|
1532
|
+
sockets.delete(nid);
|
|
1533
|
+
credit.delete(nid);
|
|
1534
|
+
peer.postMessage({ net: "ws-closed", nid, ...ending });
|
|
1535
|
+
};
|
|
1536
|
+
const sendOn = async (call) => {
|
|
1537
|
+
const socket = sockets.get(call.nid);
|
|
1538
|
+
if (!socket) return;
|
|
1539
|
+
socket.send(call.data);
|
|
1540
|
+
while (socket.buffered > SEND_BUFFER && sockets.has(call.nid)) {
|
|
1541
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1542
|
+
}
|
|
1543
|
+
peer.postMessage({ net: "ws-acked", nid: call.nid, n: payloadSize(call.data) });
|
|
1544
|
+
};
|
|
1305
1545
|
const read = async (nid) => {
|
|
1306
1546
|
const body = open.get(nid)?.body;
|
|
1307
1547
|
if (!body) return;
|
|
@@ -1319,6 +1559,8 @@ function serveTransport(peer, transport) {
|
|
|
1319
1559
|
}
|
|
1320
1560
|
};
|
|
1321
1561
|
const drop = (nid) => {
|
|
1562
|
+
sockets.get(nid)?.close(1e3, "cancelled");
|
|
1563
|
+
refill(nid, 0, true);
|
|
1322
1564
|
const entry = open.get(nid);
|
|
1323
1565
|
open.delete(nid);
|
|
1324
1566
|
entry?.abort.abort();
|
|
@@ -1328,10 +1570,16 @@ function serveTransport(peer, transport) {
|
|
|
1328
1570
|
answer(call) {
|
|
1329
1571
|
if (call.net === "fetch") void start(call);
|
|
1330
1572
|
else if (call.net === "read") void read(call.nid);
|
|
1573
|
+
else if (call.net === "ws-open") void openSocket(call);
|
|
1574
|
+
else if (call.net === "ws-send") void sendOn(call);
|
|
1575
|
+
else if (call.net === "ws-close") {
|
|
1576
|
+
sockets.get(call.nid)?.close(call.code, call.reason);
|
|
1577
|
+
refill(call.nid, 0, true);
|
|
1578
|
+
} else if (call.net === "ws-consumed") refill(call.nid, call.n);
|
|
1331
1579
|
else drop(call.nid);
|
|
1332
1580
|
},
|
|
1333
1581
|
close() {
|
|
1334
|
-
for (const nid of [...open.keys()]) drop(nid);
|
|
1582
|
+
for (const nid of [...open.keys(), ...sockets.keys()]) drop(nid);
|
|
1335
1583
|
}
|
|
1336
1584
|
};
|
|
1337
1585
|
}
|
|
@@ -2417,6 +2665,10 @@ var Incoming = class {
|
|
|
2417
2665
|
this.buf = this.buf.subarray(n);
|
|
2418
2666
|
return out;
|
|
2419
2667
|
}
|
|
2668
|
+
async peek(signal) {
|
|
2669
|
+
if (this.buf.length === 0) await this.fill(signal);
|
|
2670
|
+
return this.buf[0];
|
|
2671
|
+
}
|
|
2420
2672
|
async some(max, signal) {
|
|
2421
2673
|
if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
|
|
2422
2674
|
if (this.eof) return new Uint8Array(0);
|
|
@@ -2614,7 +2866,15 @@ var CLOSE = {
|
|
|
2614
2866
|
error: 1011
|
|
2615
2867
|
};
|
|
2616
2868
|
var OP_TEXT = 1;
|
|
2617
|
-
var
|
|
2869
|
+
var OP_BINARY = 2;
|
|
2870
|
+
var OP = {
|
|
2871
|
+
continuation: 0,
|
|
2872
|
+
text: OP_TEXT,
|
|
2873
|
+
binary: OP_BINARY,
|
|
2874
|
+
close: 8,
|
|
2875
|
+
ping: 9,
|
|
2876
|
+
pong: 10
|
|
2877
|
+
};
|
|
2618
2878
|
var WsError = class extends Error {
|
|
2619
2879
|
code;
|
|
2620
2880
|
constructor(code, message2) {
|
|
@@ -2622,6 +2882,10 @@ var WsError = class extends Error {
|
|
|
2622
2882
|
this.code = code;
|
|
2623
2883
|
}
|
|
2624
2884
|
};
|
|
2885
|
+
function sendableCode(code) {
|
|
2886
|
+
if (code >= 3e3 && code <= 4999) return true;
|
|
2887
|
+
return code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006;
|
|
2888
|
+
}
|
|
2625
2889
|
function field(headers, name2) {
|
|
2626
2890
|
return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
|
|
2627
2891
|
}
|
|
@@ -2660,7 +2924,11 @@ function frame(opcode, payload) {
|
|
|
2660
2924
|
}
|
|
2661
2925
|
function closeFrame(code, reason = "") {
|
|
2662
2926
|
let bytes = new TextEncoder().encode(reason);
|
|
2663
|
-
if (bytes.length > 123)
|
|
2927
|
+
if (bytes.length > 123) {
|
|
2928
|
+
let end = 123;
|
|
2929
|
+
while ((bytes[end] & 192) === 128) end--;
|
|
2930
|
+
bytes = bytes.subarray(0, end);
|
|
2931
|
+
}
|
|
2664
2932
|
const payload = new Uint8Array(2 + bytes.length);
|
|
2665
2933
|
new DataView(payload.buffer).setUint16(0, code);
|
|
2666
2934
|
payload.set(bytes, 2);
|
|
@@ -2691,9 +2959,12 @@ var WsReader = class {
|
|
|
2691
2959
|
parts = [];
|
|
2692
2960
|
size = 0;
|
|
2693
2961
|
continuing = false;
|
|
2694
|
-
|
|
2962
|
+
binaryMessage = false;
|
|
2963
|
+
binary;
|
|
2964
|
+
constructor(incoming, max = MAX_MESSAGE, binary) {
|
|
2695
2965
|
this.incoming = incoming;
|
|
2696
2966
|
this.max = max;
|
|
2967
|
+
this.binary = binary;
|
|
2697
2968
|
}
|
|
2698
2969
|
async next(signal) {
|
|
2699
2970
|
for (; ; ) {
|
|
@@ -2723,6 +2994,20 @@ var WsReader = class {
|
|
|
2723
2994
|
if (event) return event;
|
|
2724
2995
|
}
|
|
2725
2996
|
}
|
|
2997
|
+
data(opcode, fin, payload) {
|
|
2998
|
+
if (opcode === OP.continuation !== this.continuing) {
|
|
2999
|
+
throw new WsError(CLOSE.protocol, "unexpected continuation frame");
|
|
3000
|
+
}
|
|
3001
|
+
if (opcode !== OP.continuation) this.binaryMessage = opcode === OP.binary;
|
|
3002
|
+
this.parts.push(payload);
|
|
3003
|
+
this.size += payload.length;
|
|
3004
|
+
this.continuing = !fin;
|
|
3005
|
+
if (!fin) return void 0;
|
|
3006
|
+
const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
|
|
3007
|
+
this.parts = [];
|
|
3008
|
+
this.size = 0;
|
|
3009
|
+
return this.binaryMessage ? { kind: "binary", bytes: whole } : { kind: "text", text: decode(whole) };
|
|
3010
|
+
}
|
|
2726
3011
|
async length(short, signal) {
|
|
2727
3012
|
if (short < 126) return short;
|
|
2728
3013
|
const ext = new Uint8Array(short === 126 ? 2 : 8);
|
|
@@ -2741,36 +3026,31 @@ var WsReader = class {
|
|
|
2741
3026
|
case OP.close: {
|
|
2742
3027
|
if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
|
|
2743
3028
|
const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
case OP.text:
|
|
2747
|
-
case OP.continuation: {
|
|
2748
|
-
if (opcode === OP.continuation !== this.continuing) {
|
|
2749
|
-
throw new WsError(CLOSE.protocol, "unexpected continuation frame");
|
|
3029
|
+
if (payload.length >= 2 && !sendableCode(code)) {
|
|
3030
|
+
throw new WsError(CLOSE.protocol, `close code ${code} may not be sent`);
|
|
2750
3031
|
}
|
|
2751
|
-
|
|
2752
|
-
this.size += payload.length;
|
|
2753
|
-
this.continuing = !fin;
|
|
2754
|
-
if (!fin) return void 0;
|
|
2755
|
-
const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
|
|
2756
|
-
this.parts = [];
|
|
2757
|
-
this.size = 0;
|
|
2758
|
-
return { kind: "text", text: decode(whole) };
|
|
3032
|
+
return { kind: "close", code, reason: decode(payload.subarray(2)) };
|
|
2759
3033
|
}
|
|
2760
3034
|
case OP.binary:
|
|
2761
|
-
throw new WsError(CLOSE.unsupported, "CDP messages are text");
|
|
3035
|
+
if (!this.binary) throw new WsError(CLOSE.unsupported, "CDP messages are text");
|
|
3036
|
+
return this.data(opcode, fin, payload);
|
|
3037
|
+
case OP.text:
|
|
3038
|
+
case OP.continuation:
|
|
3039
|
+
return this.data(opcode, fin, payload);
|
|
2762
3040
|
default:
|
|
2763
3041
|
throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
|
|
2764
3042
|
}
|
|
2765
3043
|
}
|
|
2766
3044
|
};
|
|
2767
|
-
function switchingHead(accept2) {
|
|
3045
|
+
function switchingHead(accept2, protocol = "") {
|
|
3046
|
+
const chosen = protocol ? `Sec-WebSocket-Protocol: ${protocol}\r
|
|
3047
|
+
` : "";
|
|
2768
3048
|
return latin1Bytes(
|
|
2769
3049
|
`HTTP/1.1 101 Switching Protocols\r
|
|
2770
3050
|
Upgrade: websocket\r
|
|
2771
3051
|
Connection: Upgrade\r
|
|
2772
3052
|
Sec-WebSocket-Accept: ${accept2}\r
|
|
2773
|
-
\r
|
|
3053
|
+
${chosen}\r
|
|
2774
3054
|
`
|
|
2775
3055
|
);
|
|
2776
3056
|
}
|
|
@@ -2778,7 +3058,7 @@ Sec-WebSocket-Accept: ${accept2}\r
|
|
|
2778
3058
|
// src/cdp/facade.ts
|
|
2779
3059
|
var MAX_HEAD = 64 * 1024;
|
|
2780
3060
|
var LIST_TIMEOUT = 1e4;
|
|
2781
|
-
var
|
|
3061
|
+
var CLOSE_WAIT_MS2 = 1e3;
|
|
2782
3062
|
function respond(status, body, type = "text/plain; charset=utf-8") {
|
|
2783
3063
|
const bytes = new TextEncoder().encode(body);
|
|
2784
3064
|
const head = latin1Bytes(
|
|
@@ -2926,7 +3206,7 @@ var CdpFacade = class {
|
|
|
2926
3206
|
const host = await this.host(runtime);
|
|
2927
3207
|
const limits = {
|
|
2928
3208
|
max: this.options.maxMessage ?? MAX_MESSAGE,
|
|
2929
|
-
closeWaitMs: this.options.closeWaitMs ??
|
|
3209
|
+
closeWaitMs: this.options.closeWaitMs ?? CLOSE_WAIT_MS2
|
|
2930
3210
|
};
|
|
2931
3211
|
await new Relay(conn, incoming, host, limits, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
|
|
2932
3212
|
}
|
|
@@ -3394,13 +3674,193 @@ function withCommandDirs(fs, names) {
|
|
|
3394
3674
|
};
|
|
3395
3675
|
}
|
|
3396
3676
|
|
|
3677
|
+
// src/fs/unlinked.ts
|
|
3678
|
+
var UNLINKED_DIR = ".slicc-unlinked";
|
|
3679
|
+
function webLocks() {
|
|
3680
|
+
return globalThis.navigator?.locks;
|
|
3681
|
+
}
|
|
3682
|
+
var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
3683
|
+
var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
3684
|
+
var lockName = (instance) => `slicc-unlinked:${instance}`;
|
|
3685
|
+
var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
|
|
3686
|
+
var PathGate = class {
|
|
3687
|
+
tails = /* @__PURE__ */ new Map();
|
|
3688
|
+
async acquire(path) {
|
|
3689
|
+
const prev = this.tails.get(path) ?? Promise.resolve();
|
|
3690
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
3691
|
+
const tail = prev.then(() => promise);
|
|
3692
|
+
this.tails.set(path, tail);
|
|
3693
|
+
await prev;
|
|
3694
|
+
return () => {
|
|
3695
|
+
resolve();
|
|
3696
|
+
if (this.tails.get(path) === tail) this.tails.delete(path);
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
async run(path, op) {
|
|
3700
|
+
const release = await this.acquire(path);
|
|
3701
|
+
try {
|
|
3702
|
+
return await op();
|
|
3703
|
+
} finally {
|
|
3704
|
+
release();
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
};
|
|
3708
|
+
var UnlinkedKeeper = class {
|
|
3709
|
+
fs;
|
|
3710
|
+
holders;
|
|
3711
|
+
rootOf;
|
|
3712
|
+
locks;
|
|
3713
|
+
busy;
|
|
3714
|
+
waiting = /* @__PURE__ */ new Set();
|
|
3715
|
+
instance = crypto.randomUUID();
|
|
3716
|
+
gate = new PathGate();
|
|
3717
|
+
queue = Promise.resolve();
|
|
3718
|
+
count = 0;
|
|
3719
|
+
constructor(fs, holders, rootOf, locks, busy = () => false) {
|
|
3720
|
+
this.fs = fs;
|
|
3721
|
+
this.holders = holders;
|
|
3722
|
+
this.rootOf = rootOf;
|
|
3723
|
+
this.locks = locks;
|
|
3724
|
+
this.busy = busy;
|
|
3725
|
+
void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
|
|
3726
|
+
}
|
|
3727
|
+
serial(op) {
|
|
3728
|
+
const next = this.queue.then(op);
|
|
3729
|
+
this.queue = next.catch(() => void 0);
|
|
3730
|
+
return next;
|
|
3731
|
+
}
|
|
3732
|
+
wrap() {
|
|
3733
|
+
const fs = this.fs;
|
|
3734
|
+
const keeper = this;
|
|
3735
|
+
const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
|
|
3736
|
+
return {
|
|
3737
|
+
...fs,
|
|
3738
|
+
async rm(path, options) {
|
|
3739
|
+
const target = fs.resolvePath("/", path);
|
|
3740
|
+
const pinned = await keeper.pin(target);
|
|
3741
|
+
if (pinned.moved.has(target)) pinned.commit();
|
|
3742
|
+
else await keeper.settle(pinned, () => fs.rm(path, options));
|
|
3743
|
+
},
|
|
3744
|
+
async rename(from, to) {
|
|
3745
|
+
const pinned = await keeper.pin(fs.resolvePath("/", to));
|
|
3746
|
+
await keeper.settle(pinned, () => fs.rename(from, to));
|
|
3747
|
+
},
|
|
3748
|
+
readdir: async (path) => visible(await fs.readdir(path)),
|
|
3749
|
+
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
async settle(pinned, op) {
|
|
3753
|
+
try {
|
|
3754
|
+
await op();
|
|
3755
|
+
} catch (err) {
|
|
3756
|
+
await pinned.rollback();
|
|
3757
|
+
throw err;
|
|
3758
|
+
}
|
|
3759
|
+
pinned.commit();
|
|
3760
|
+
}
|
|
3761
|
+
async pin(target) {
|
|
3762
|
+
const held = /* @__PURE__ */ new Map();
|
|
3763
|
+
for (const holder of this.holders) {
|
|
3764
|
+
if (holder.owns(target)) continue;
|
|
3765
|
+
for (const path of holder.opens.keys()) {
|
|
3766
|
+
if (!within2(path, target) || holder.unlinked.has(path)) continue;
|
|
3767
|
+
held.set(path, [...held.get(path) ?? [], holder]);
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
const paths = [...held.keys()].sort();
|
|
3771
|
+
const releases = [];
|
|
3772
|
+
for (const path of paths) releases.push(await this.gate.acquire(path));
|
|
3773
|
+
const hidden = /* @__PURE__ */ new Map();
|
|
3774
|
+
for (const path of paths) {
|
|
3775
|
+
const at = await this.hide(path);
|
|
3776
|
+
if (at !== void 0) hidden.set(path, at);
|
|
3777
|
+
}
|
|
3778
|
+
const done4 = () => {
|
|
3779
|
+
for (const release of releases) release();
|
|
3780
|
+
};
|
|
3781
|
+
return {
|
|
3782
|
+
moved: new Set(hidden.keys()),
|
|
3783
|
+
commit: () => {
|
|
3784
|
+
for (const [path, at] of hidden) {
|
|
3785
|
+
const holders = held.get(path);
|
|
3786
|
+
const file = this.keptFile(at, { refs: holders.length });
|
|
3787
|
+
for (const holder of holders) holder.unlinked.set(path, file);
|
|
3788
|
+
}
|
|
3789
|
+
done4();
|
|
3790
|
+
},
|
|
3791
|
+
rollback: async () => {
|
|
3792
|
+
for (const [path, at] of hidden) {
|
|
3793
|
+
await this.serial(async () => {
|
|
3794
|
+
await Promise.allSettled([this.fs.rename(at, path)]);
|
|
3795
|
+
await this.tidy(parentOf3(at));
|
|
3796
|
+
});
|
|
3797
|
+
}
|
|
3798
|
+
done4();
|
|
3799
|
+
}
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3802
|
+
keptFile(hidden, entry) {
|
|
3803
|
+
return {
|
|
3804
|
+
hidden,
|
|
3805
|
+
release: () => {
|
|
3806
|
+
if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
|
|
3807
|
+
}
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3810
|
+
hide(path) {
|
|
3811
|
+
return this.serial(async () => {
|
|
3812
|
+
const dir = dirOf(this.rootOf(path));
|
|
3813
|
+
const hidden = `${dir}/${this.instance}.${++this.count}`;
|
|
3814
|
+
try {
|
|
3815
|
+
await this.fs.mkdir(dir, { recursive: true });
|
|
3816
|
+
await this.fs.rename(path, hidden);
|
|
3817
|
+
return hidden;
|
|
3818
|
+
} catch {
|
|
3819
|
+
await this.tidy(dir);
|
|
3820
|
+
return void 0;
|
|
3821
|
+
}
|
|
3822
|
+
});
|
|
3823
|
+
}
|
|
3824
|
+
closed(path) {
|
|
3825
|
+
if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
|
|
3826
|
+
}
|
|
3827
|
+
async drop(hidden) {
|
|
3828
|
+
if (this.busy(hidden)) {
|
|
3829
|
+
this.waiting.add(hidden);
|
|
3830
|
+
return;
|
|
3831
|
+
}
|
|
3832
|
+
await this.fs.rm(hidden).catch(() => void 0);
|
|
3833
|
+
await this.tidy(parentOf3(hidden));
|
|
3834
|
+
}
|
|
3835
|
+
async tidy(dir) {
|
|
3836
|
+
const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
|
|
3837
|
+
if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
|
|
3838
|
+
}
|
|
3839
|
+
sweep(root) {
|
|
3840
|
+
const query = this.locks?.query?.bind(this.locks);
|
|
3841
|
+
if (!query) return Promise.resolve();
|
|
3842
|
+
return this.serial(async () => {
|
|
3843
|
+
const dir = dirOf(root);
|
|
3844
|
+
const names = await this.fs.readdir(dir).catch(() => void 0);
|
|
3845
|
+
if (!names) return;
|
|
3846
|
+
const held = new Set(((await query()).held ?? []).map((lock) => lock.name));
|
|
3847
|
+
for (const name2 of names) {
|
|
3848
|
+
const owner = name2.slice(0, name2.lastIndexOf("."));
|
|
3849
|
+
if (owner === this.instance || held.has(lockName(owner))) continue;
|
|
3850
|
+
await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
|
|
3851
|
+
}
|
|
3852
|
+
await this.tidy(dir);
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
};
|
|
3856
|
+
|
|
3397
3857
|
// src/fs/watch.ts
|
|
3398
3858
|
var WATCH_BATCH_LIMIT = 1e3;
|
|
3399
3859
|
var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
3400
|
-
var
|
|
3860
|
+
var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
3401
3861
|
function touches(watcher, changed) {
|
|
3402
3862
|
return watcher.paths.some(
|
|
3403
|
-
(watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) :
|
|
3863
|
+
(watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf4(changed) === watched)
|
|
3404
3864
|
);
|
|
3405
3865
|
}
|
|
3406
3866
|
var FsWatchers = class {
|
|
@@ -3782,7 +4242,7 @@ function parseSize(text) {
|
|
|
3782
4242
|
if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
|
|
3783
4243
|
return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
|
|
3784
4244
|
}
|
|
3785
|
-
var
|
|
4245
|
+
var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
3786
4246
|
var HeldSet = class extends Set {
|
|
3787
4247
|
idle;
|
|
3788
4248
|
constructor(idle) {
|
|
@@ -3795,18 +4255,47 @@ var HeldSet = class extends Set {
|
|
|
3795
4255
|
}
|
|
3796
4256
|
delete(holds) {
|
|
3797
4257
|
const removed = super.delete(holds);
|
|
3798
|
-
for (const path of holds.keys()) this.idle(path);
|
|
4258
|
+
for (const path of /* @__PURE__ */ new Set([...holds.keys(), ...holds.opens.keys()])) this.idle(path);
|
|
4259
|
+
for (const kept of holds.unlinked.values()) kept.release();
|
|
4260
|
+
holds.unlinked.clear();
|
|
3799
4261
|
return removed;
|
|
3800
4262
|
}
|
|
3801
4263
|
};
|
|
3802
4264
|
function heldUnder(held, target) {
|
|
3803
|
-
for (const paths of held) for (const path of paths.keys()) if (
|
|
4265
|
+
for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
|
|
3804
4266
|
return false;
|
|
3805
4267
|
}
|
|
3806
4268
|
var HeldPaths = class extends Map {
|
|
3807
4269
|
revoked = /* @__PURE__ */ new Set();
|
|
4270
|
+
opens = /* @__PURE__ */ new Map();
|
|
4271
|
+
unlinked = /* @__PURE__ */ new Map();
|
|
4272
|
+
aliases = /* @__PURE__ */ new Map();
|
|
4273
|
+
owning;
|
|
3808
4274
|
onIdle;
|
|
3809
|
-
|
|
4275
|
+
keyOf(path) {
|
|
4276
|
+
return this.aliases.get(path) ?? path;
|
|
4277
|
+
}
|
|
4278
|
+
keptAt(path) {
|
|
4279
|
+
return this.unlinked.get(this.keyOf(path));
|
|
4280
|
+
}
|
|
4281
|
+
holding(path) {
|
|
4282
|
+
return this.opens.has(this.keyOf(path));
|
|
4283
|
+
}
|
|
4284
|
+
hold(path, on, open = false, real) {
|
|
4285
|
+
if (open) {
|
|
4286
|
+
const key = real ?? this.keyOf(path);
|
|
4287
|
+
if (key !== path) this.aliases.set(path, key);
|
|
4288
|
+
const count2 = (this.opens.get(key) ?? 0) + (on ? 1 : -1);
|
|
4289
|
+
if (count2 > 0) this.opens.set(key, count2);
|
|
4290
|
+
else {
|
|
4291
|
+
this.opens.delete(key);
|
|
4292
|
+
this.unlinked.get(key)?.release();
|
|
4293
|
+
this.unlinked.delete(key);
|
|
4294
|
+
for (const [alias, target] of this.aliases) if (target === key) this.aliases.delete(alias);
|
|
4295
|
+
this.onIdle?.(key);
|
|
4296
|
+
}
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
3810
4299
|
const count = (this.get(path) ?? 0) + (on ? 1 : -1);
|
|
3811
4300
|
if (count > 0) this.set(path, count);
|
|
3812
4301
|
else {
|
|
@@ -3815,23 +4304,40 @@ var HeldPaths = class extends Map {
|
|
|
3815
4304
|
this.onIdle?.(path);
|
|
3816
4305
|
}
|
|
3817
4306
|
}
|
|
4307
|
+
async own(path, op) {
|
|
4308
|
+
this.owning = path;
|
|
4309
|
+
try {
|
|
4310
|
+
return await op();
|
|
4311
|
+
} finally {
|
|
4312
|
+
this.owning = void 0;
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
owns(path) {
|
|
4316
|
+
return this.owning === path;
|
|
4317
|
+
}
|
|
3818
4318
|
revoke(prefix) {
|
|
3819
|
-
for (const path of this.keys()) if (
|
|
4319
|
+
for (const path of this.keys()) if (within3(path, prefix)) this.revoked.add(path);
|
|
3820
4320
|
}
|
|
3821
4321
|
renamed(from, to) {
|
|
3822
4322
|
for (const [path, count] of [...this]) {
|
|
3823
|
-
if (!
|
|
4323
|
+
if (!within3(path, from)) continue;
|
|
3824
4324
|
const moved2 = to + path.slice(from.length);
|
|
3825
4325
|
this.delete(path);
|
|
3826
4326
|
this.set(moved2, (this.get(moved2) ?? 0) + count);
|
|
3827
4327
|
if (this.revoked.delete(path)) this.revoked.add(moved2);
|
|
3828
4328
|
}
|
|
4329
|
+
for (const [path, count] of [...this.opens]) {
|
|
4330
|
+
if (!within3(path, from)) continue;
|
|
4331
|
+
const moved2 = to + path.slice(from.length);
|
|
4332
|
+
this.opens.delete(path);
|
|
4333
|
+
this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
|
|
4334
|
+
}
|
|
3829
4335
|
}
|
|
3830
4336
|
isRevoked(path) {
|
|
3831
4337
|
return this.revoked.has(path);
|
|
3832
4338
|
}
|
|
3833
4339
|
};
|
|
3834
|
-
var
|
|
4340
|
+
var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
3835
4341
|
var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
|
|
3836
4342
|
function statOf(attr, path, mount) {
|
|
3837
4343
|
const mtime = new Date(attr.mtime);
|
|
@@ -3920,7 +4426,7 @@ var MountTable = class {
|
|
|
3920
4426
|
locate(path) {
|
|
3921
4427
|
let best;
|
|
3922
4428
|
for (const mount of this.mounts.values()) {
|
|
3923
|
-
if (
|
|
4429
|
+
if (within3(path, mount.target) && (!best || mount.target.length > best.target.length))
|
|
3924
4430
|
best = mount;
|
|
3925
4431
|
}
|
|
3926
4432
|
if (!best) return void 0;
|
|
@@ -4059,7 +4565,7 @@ var MountTable = class {
|
|
|
4059
4565
|
async commitUnder(prefix) {
|
|
4060
4566
|
const at = normalizePath(prefix);
|
|
4061
4567
|
await Promise.all(
|
|
4062
|
-
[...this.sessions.keys()].filter((p) =>
|
|
4568
|
+
[...this.sessions.keys()].filter((p) => within3(p, at)).map((p) => this.commit(p))
|
|
4063
4569
|
);
|
|
4064
4570
|
}
|
|
4065
4571
|
async commitOverlapping(real, offset, length2) {
|
|
@@ -4093,10 +4599,10 @@ var MountTable = class {
|
|
|
4093
4599
|
this.deps.changed?.(...full);
|
|
4094
4600
|
}
|
|
4095
4601
|
forget(mount, path) {
|
|
4096
|
-
for (const key of [...mount.attrs.keys()]) if (
|
|
4602
|
+
for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
|
|
4097
4603
|
mount.lists.delete(path);
|
|
4098
|
-
mount.lists.delete(
|
|
4099
|
-
for (const key of [...mount.lists.keys()]) if (
|
|
4604
|
+
mount.lists.delete(parentOf5(path));
|
|
4605
|
+
for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
|
|
4100
4606
|
}
|
|
4101
4607
|
async getattr(mount, path, rel2) {
|
|
4102
4608
|
if (mount.conn.failure) throw mount.conn.failure;
|
|
@@ -4161,7 +4667,7 @@ var MountFs = class {
|
|
|
4161
4667
|
if (attr.kind !== "symlink") return real;
|
|
4162
4668
|
if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
|
|
4163
4669
|
const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
|
|
4164
|
-
return this.follow(this.base.resolvePath(
|
|
4670
|
+
return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
|
|
4165
4671
|
}
|
|
4166
4672
|
async open(mount, rel2, write) {
|
|
4167
4673
|
return await mount.conn.call({
|
|
@@ -4675,6 +5181,8 @@ async function ranged(fs, path, req) {
|
|
|
4675
5181
|
else await ops.truncate(path, req.length ?? 0);
|
|
4676
5182
|
return done;
|
|
4677
5183
|
}
|
|
5184
|
+
var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
|
|
5185
|
+
var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
|
|
4678
5186
|
async function dispatchSyncFs(req) {
|
|
4679
5187
|
const entry = resolveSyncFsToken(req.token);
|
|
4680
5188
|
if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
|
|
@@ -4683,11 +5191,22 @@ async function dispatchSyncFs(req) {
|
|
|
4683
5191
|
const path = fs.resolvePath(cwd, req.path);
|
|
4684
5192
|
if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
|
|
4685
5193
|
if (req.op === "hold") {
|
|
4686
|
-
|
|
5194
|
+
const [on, open] = [((req.mode ?? 0) & 1) === 1, ((req.mode ?? 0) & 2) === 2];
|
|
5195
|
+
const real = on && open ? await fs.realpath?.(path, true).catch(() => path) : void 0;
|
|
5196
|
+
entry.hold?.(path, on, open, real);
|
|
4687
5197
|
return done;
|
|
4688
5198
|
}
|
|
4689
5199
|
if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
|
|
4690
|
-
|
|
5200
|
+
if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
|
|
5201
|
+
const serve = async () => {
|
|
5202
|
+
const kept = entry.unlinked?.(path);
|
|
5203
|
+
return run(entry, kept && !await fs.exists(path) ? kept.hidden : path, req);
|
|
5204
|
+
};
|
|
5205
|
+
return await (entry.gate ? entry.gate(path, serve) : serve());
|
|
5206
|
+
}
|
|
5207
|
+
if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
|
|
5208
|
+
const target = req.op === "rename" ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
|
|
5209
|
+
return await entry.own(target, () => run(entry, path, req));
|
|
4691
5210
|
} catch (err) {
|
|
4692
5211
|
return toErrno(err);
|
|
4693
5212
|
}
|
|
@@ -5037,12 +5556,17 @@ function ownNames(hostname) {
|
|
|
5037
5556
|
}
|
|
5038
5557
|
|
|
5039
5558
|
// src/kernel/net/proxy-service.ts
|
|
5559
|
+
var SEND_BUFFER2 = 1024 * 1024;
|
|
5560
|
+
var TLS_HANDSHAKE = 22;
|
|
5561
|
+
var TOKEN2 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
5562
|
+
var NO_WEBSOCKET2 = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
|
|
5040
5563
|
var REALM_PROXY_PORT = 3128;
|
|
5041
5564
|
var DEFAULT_LIMITS = {
|
|
5042
5565
|
maxConnections: 64,
|
|
5043
5566
|
maxHead: 64 * 1024,
|
|
5044
5567
|
idleMs: 12e4,
|
|
5045
|
-
bodyBudget: 128 * 1024 * 1024
|
|
5568
|
+
bodyBudget: 128 * 1024 * 1024,
|
|
5569
|
+
closeWaitMs: 5e3
|
|
5046
5570
|
};
|
|
5047
5571
|
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
5048
5572
|
"connection",
|
|
@@ -5233,11 +5757,27 @@ function plainResponse(status, message2) {
|
|
|
5233
5757
|
cancel: async () => void 0
|
|
5234
5758
|
};
|
|
5235
5759
|
}
|
|
5236
|
-
function timedSource(conn,
|
|
5760
|
+
function timedSource(conn, idle, stop) {
|
|
5237
5761
|
return {
|
|
5238
|
-
read: (max) =>
|
|
5762
|
+
read: (max, signal) => {
|
|
5763
|
+
const own = signal ? [stop, signal] : [stop];
|
|
5764
|
+
if (Number.isFinite(idle.ms)) own.push(AbortSignal.timeout(idle.ms));
|
|
5765
|
+
return conn.read(max, AbortSignal.any(own));
|
|
5766
|
+
}
|
|
5239
5767
|
};
|
|
5240
5768
|
}
|
|
5769
|
+
function webSocketTarget(req) {
|
|
5770
|
+
return { ...req, target: req.target.replace(/^ws(s?):/i, (_, s) => `http${s.toLowerCase()}:`) };
|
|
5771
|
+
}
|
|
5772
|
+
function wireCode(code) {
|
|
5773
|
+
if (code === 1005) return 1e3;
|
|
5774
|
+
return sendableCode(code) ? code : 1011;
|
|
5775
|
+
}
|
|
5776
|
+
function quietly(ms) {
|
|
5777
|
+
return new Promise((resolve) => {
|
|
5778
|
+
AbortSignal.timeout(ms).addEventListener("abort", () => resolve(), { once: true });
|
|
5779
|
+
});
|
|
5780
|
+
}
|
|
5241
5781
|
var RealmProxy = class {
|
|
5242
5782
|
options;
|
|
5243
5783
|
listener;
|
|
@@ -5246,6 +5786,7 @@ var RealmProxy = class {
|
|
|
5246
5786
|
slots;
|
|
5247
5787
|
bodies;
|
|
5248
5788
|
connections = /* @__PURE__ */ new Set();
|
|
5789
|
+
warnedWebSocket = false;
|
|
5249
5790
|
closed;
|
|
5250
5791
|
port;
|
|
5251
5792
|
constructor(options) {
|
|
@@ -5294,9 +5835,10 @@ var RealmProxy = class {
|
|
|
5294
5835
|
}
|
|
5295
5836
|
async serve(conn) {
|
|
5296
5837
|
this.connections.add(conn);
|
|
5297
|
-
const
|
|
5838
|
+
const idle = { ms: this.limits.idleMs };
|
|
5839
|
+
const incoming = new Incoming(timedSource(conn, idle, this.stop.signal));
|
|
5298
5840
|
try {
|
|
5299
|
-
await this.requests({ sink: conn, incoming, conn, socket: conn });
|
|
5841
|
+
await this.requests({ sink: conn, incoming, conn, socket: conn, idle });
|
|
5300
5842
|
} finally {
|
|
5301
5843
|
this.connections.delete(conn);
|
|
5302
5844
|
conn.close();
|
|
@@ -5323,7 +5865,11 @@ var RealmProxy = class {
|
|
|
5323
5865
|
const req = parseRequestHead(head);
|
|
5324
5866
|
if (req.method === "CONNECT") {
|
|
5325
5867
|
if (!ctx.conn) throw new HttpError(400, "CONNECT inside a tunnel");
|
|
5326
|
-
await this.connect(ctx.conn, incoming, req);
|
|
5868
|
+
await this.connect(ctx.conn, incoming, req, ctx.idle);
|
|
5869
|
+
return false;
|
|
5870
|
+
}
|
|
5871
|
+
if (req.method === "GET" && isUpgrade(req.headers)) {
|
|
5872
|
+
await this.websocket(ctx, req);
|
|
5327
5873
|
return false;
|
|
5328
5874
|
}
|
|
5329
5875
|
const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
|
|
@@ -5418,33 +5964,149 @@ var RealmProxy = class {
|
|
|
5418
5964
|
if (chunked2) await conn.write(LAST_CHUNK, signal);
|
|
5419
5965
|
return keep;
|
|
5420
5966
|
}
|
|
5421
|
-
async connect(conn, incoming, req) {
|
|
5967
|
+
async connect(conn, incoming, req, idle) {
|
|
5422
5968
|
const target = tunnelTarget(req);
|
|
5423
|
-
const
|
|
5424
|
-
if (
|
|
5425
|
-
throw new HttpError(
|
|
5426
|
-
501,
|
|
5427
|
-
`CONNECT ${target.host}:${target.port}: no tunnels through this proxy`
|
|
5428
|
-
);
|
|
5429
|
-
}
|
|
5430
|
-
await this.options.tunnelReady?.().catch((e) => {
|
|
5431
|
-
throw new HttpError(
|
|
5432
|
-
501,
|
|
5433
|
-
`CONNECT ${target.host}:${target.port}: ${e instanceof Error ? e.message : String(e)}`
|
|
5434
|
-
);
|
|
5435
|
-
});
|
|
5969
|
+
const refused = target.port === 443 ? await this.tunnelReady(target) : void 0;
|
|
5970
|
+
if (refused) throw refused;
|
|
5436
5971
|
await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
|
|
5437
5972
|
try {
|
|
5973
|
+
const first = await incoming.peek(this.stop.signal);
|
|
5974
|
+
if (first === void 0) return;
|
|
5975
|
+
if (first !== TLS_HANDSHAKE) {
|
|
5976
|
+
const origin = `http://${target.port === 80 ? target.host : `${target.host}:${target.port}`}`;
|
|
5977
|
+
await this.requests({ sink: conn, incoming, origin, socket: conn, idle });
|
|
5978
|
+
return;
|
|
5979
|
+
}
|
|
5980
|
+
const tunnel = this.options.tunnel;
|
|
5981
|
+
if (!tunnel || target.port !== 443 && await this.tunnelReady(target)) return;
|
|
5438
5982
|
await tunnel(
|
|
5439
5983
|
conn,
|
|
5440
5984
|
incoming,
|
|
5441
5985
|
target,
|
|
5442
5986
|
this.stop.signal,
|
|
5443
|
-
(source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn })
|
|
5987
|
+
(source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn, idle })
|
|
5444
5988
|
);
|
|
5445
5989
|
} catch {
|
|
5446
5990
|
}
|
|
5447
5991
|
}
|
|
5992
|
+
async tunnelReady(target) {
|
|
5993
|
+
const name2 = `CONNECT ${target.host}:${target.port}`;
|
|
5994
|
+
if (!this.options.tunnel) return new HttpError(501, `${name2}: no tunnels through this proxy`);
|
|
5995
|
+
try {
|
|
5996
|
+
await this.options.tunnelReady?.();
|
|
5997
|
+
return void 0;
|
|
5998
|
+
} catch (e) {
|
|
5999
|
+
return new HttpError(501, `${name2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
async websocket(ctx, req) {
|
|
6003
|
+
const href = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(webSocketTarget(req));
|
|
6004
|
+
const { transport } = this.options;
|
|
6005
|
+
if (!transport.traits.websocket || !transport.websocket) {
|
|
6006
|
+
if (!this.warnedWebSocket) {
|
|
6007
|
+
this.warnedWebSocket = true;
|
|
6008
|
+
console.warn(`slicc-kernel: a program asked for a WebSocket: ${NO_WEBSOCKET2}`);
|
|
6009
|
+
}
|
|
6010
|
+
throw new HttpError(501, NO_WEBSOCKET2);
|
|
6011
|
+
}
|
|
6012
|
+
const accept2 = await acceptKey(req.headers);
|
|
6013
|
+
const protocols = fieldValues(req.headers, "sec-websocket-protocol").flatMap((v) => v.split(",")).map((t) => t.trim()).filter((t) => t !== "");
|
|
6014
|
+
const hangup = new AbortController();
|
|
6015
|
+
const waiting = new AbortController();
|
|
6016
|
+
void watchHangup(ctx.socket, hangup, waiting.signal);
|
|
6017
|
+
let socket;
|
|
6018
|
+
try {
|
|
6019
|
+
socket = await transport.websocket({
|
|
6020
|
+
url: toHostLoopback(href).replace(/^http/, "ws"),
|
|
6021
|
+
protocols,
|
|
6022
|
+
headers: forwardRequestHeaders(req.headers),
|
|
6023
|
+
signal: AbortSignal.any([this.stop.signal, hangup.signal])
|
|
6024
|
+
});
|
|
6025
|
+
} catch (e) {
|
|
6026
|
+
waiting.abort();
|
|
6027
|
+
throw new HttpError(502, e instanceof Error ? e.message : String(e));
|
|
6028
|
+
}
|
|
6029
|
+
ctx.idle.ms = Number.POSITIVE_INFINITY;
|
|
6030
|
+
try {
|
|
6031
|
+
if (hangup.signal.aborted) throw hangup.signal.reason;
|
|
6032
|
+
if (socket.protocol && !(protocols.includes(socket.protocol) && TOKEN2.test(socket.protocol))) {
|
|
6033
|
+
throw new HttpError(502, "the far end chose a subprotocol the client did not offer");
|
|
6034
|
+
}
|
|
6035
|
+
await ctx.sink.write(switchingHead(accept2, socket.protocol), this.stop.signal);
|
|
6036
|
+
await this.bridge(ctx, socket, hangup.signal);
|
|
6037
|
+
} catch (e) {
|
|
6038
|
+
socket.close(1001, "the client went away");
|
|
6039
|
+
throw e;
|
|
6040
|
+
} finally {
|
|
6041
|
+
waiting.abort();
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
async bridge(ctx, socket, hangup) {
|
|
6045
|
+
const stop = this.stop.signal;
|
|
6046
|
+
let chain = Promise.resolve();
|
|
6047
|
+
const toGuest = (bytes) => {
|
|
6048
|
+
chain = chain.then(() => ctx.sink.write(bytes, stop));
|
|
6049
|
+
return chain;
|
|
6050
|
+
};
|
|
6051
|
+
const done4 = new AbortController();
|
|
6052
|
+
const reading = AbortSignal.any([stop, done4.signal, hangup]);
|
|
6053
|
+
const stopping = () => socket.close(1001, "the proxy is stopping");
|
|
6054
|
+
stop.addEventListener("abort", stopping, { once: true });
|
|
6055
|
+
let guestDone = false;
|
|
6056
|
+
let closeSent = false;
|
|
6057
|
+
const outbound2 = (async () => {
|
|
6058
|
+
for await (const data of socket.messages) {
|
|
6059
|
+
if (guestDone) continue;
|
|
6060
|
+
const text = typeof data === "string";
|
|
6061
|
+
await toGuest(
|
|
6062
|
+
frame(text ? OP_TEXT : OP_BINARY, text ? new TextEncoder().encode(data) : data)
|
|
6063
|
+
);
|
|
6064
|
+
}
|
|
6065
|
+
const closed = await socket.closed;
|
|
6066
|
+
if (guestDone) return;
|
|
6067
|
+
closeSent = true;
|
|
6068
|
+
await toGuest(closeFrame(wireCode(closed.code), closed.reason));
|
|
6069
|
+
AbortSignal.timeout(this.limits.closeWaitMs).addEventListener("abort", () => done4.abort(), {
|
|
6070
|
+
once: true
|
|
6071
|
+
});
|
|
6072
|
+
})().catch(() => done4.abort());
|
|
6073
|
+
const reader = new WsReader(ctx.incoming, MESSAGE_LIMIT, true);
|
|
6074
|
+
try {
|
|
6075
|
+
for (; ; ) {
|
|
6076
|
+
const event = await reader.next(reading);
|
|
6077
|
+
if (!event) break;
|
|
6078
|
+
if (event.kind === "ping") {
|
|
6079
|
+
await toGuest(pongFrame(event.payload));
|
|
6080
|
+
continue;
|
|
6081
|
+
}
|
|
6082
|
+
if (event.kind === "close") {
|
|
6083
|
+
guestDone = true;
|
|
6084
|
+
if (closeSent) break;
|
|
6085
|
+
const code = wireCode(event.code);
|
|
6086
|
+
socket.close(code, event.reason);
|
|
6087
|
+
const reply = await Promise.race([
|
|
6088
|
+
socket.closed,
|
|
6089
|
+
quietly(this.limits.closeWaitMs).then(() => ({ code, reason: event.reason }))
|
|
6090
|
+
]);
|
|
6091
|
+
await toGuest(closeFrame(wireCode(reply.code), reply.reason));
|
|
6092
|
+
break;
|
|
6093
|
+
}
|
|
6094
|
+
while (socket.buffered > SEND_BUFFER2 && !reading.aborted) await quietly(10);
|
|
6095
|
+
socket.send(event.kind === "text" ? event.text : event.bytes);
|
|
6096
|
+
}
|
|
6097
|
+
} catch (e) {
|
|
6098
|
+
if (e instanceof WsError) {
|
|
6099
|
+
guestDone = true;
|
|
6100
|
+
socket.close(e.code, e.message);
|
|
6101
|
+
await toGuest(closeFrame(e.code, e.message)).catch(() => void 0);
|
|
6102
|
+
}
|
|
6103
|
+
} finally {
|
|
6104
|
+
guestDone = true;
|
|
6105
|
+
socket.close(1e3, "the client went away");
|
|
6106
|
+
stop.removeEventListener("abort", stopping);
|
|
6107
|
+
await Promise.race([outbound2, quietly(this.limits.closeWaitMs)]);
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
5448
6110
|
};
|
|
5449
6111
|
|
|
5450
6112
|
// src/kernel/net/x509.ts
|
|
@@ -6266,7 +6928,7 @@ function parsePrefix(text) {
|
|
|
6266
6928
|
if (length2 < 0 || length2 > max) return void 0;
|
|
6267
6929
|
return { ...parsed, length: length2 };
|
|
6268
6930
|
}
|
|
6269
|
-
function
|
|
6931
|
+
function within4(prefix, addr) {
|
|
6270
6932
|
if (prefix.v6 !== addr.v6) return false;
|
|
6271
6933
|
const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
|
|
6272
6934
|
return addr.bits >> shift === prefix.bits >> shift;
|
|
@@ -6306,9 +6968,9 @@ var Routes = class {
|
|
|
6306
6968
|
if (!addr) return "unreachable";
|
|
6307
6969
|
if (addr.v6 && addr.bits === 1n) return "loopback";
|
|
6308
6970
|
if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
|
|
6309
|
-
if (RESERVED.some((p) =>
|
|
6971
|
+
if (RESERVED.some((p) => within4(p, addr))) return "reserved";
|
|
6310
6972
|
if (addr.v6 && !this.ipv6) return "unreachable";
|
|
6311
|
-
if (this.exit || this.table.some((p) =>
|
|
6973
|
+
if (this.exit || this.table.some((p) => within4(p, addr))) return "uplink";
|
|
6312
6974
|
return "unreachable";
|
|
6313
6975
|
}
|
|
6314
6976
|
};
|
|
@@ -7277,7 +7939,7 @@ function isMissing(err) {
|
|
|
7277
7939
|
if (err?.code === "ENOENT") return true;
|
|
7278
7940
|
return err instanceof Error && err.message.startsWith("ENOENT");
|
|
7279
7941
|
}
|
|
7280
|
-
function
|
|
7942
|
+
function within5(path, root) {
|
|
7281
7943
|
return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
|
|
7282
7944
|
}
|
|
7283
7945
|
var VfsNode = class {
|
|
@@ -7482,7 +8144,7 @@ var VfsNodes = class {
|
|
|
7482
8144
|
return run2;
|
|
7483
8145
|
}
|
|
7484
8146
|
async entryKey(path) {
|
|
7485
|
-
const parent =
|
|
8147
|
+
const parent = parentOf6(path);
|
|
7486
8148
|
const dir = await this.real(parent, true, () => realDir(this.fs, parent));
|
|
7487
8149
|
return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
|
|
7488
8150
|
}
|
|
@@ -7530,12 +8192,12 @@ var VfsNodes = class {
|
|
|
7530
8192
|
return node;
|
|
7531
8193
|
}
|
|
7532
8194
|
holds(prefix) {
|
|
7533
|
-
for (const path of this.byPath.keys()) if (
|
|
8195
|
+
for (const path of this.byPath.keys()) if (within5(path, prefix)) return true;
|
|
7534
8196
|
return false;
|
|
7535
8197
|
}
|
|
7536
8198
|
writes(prefix) {
|
|
7537
8199
|
for (const [path, node] of this.byPath)
|
|
7538
|
-
if (node.writers > 0 &&
|
|
8200
|
+
if (node.writers > 0 && within5(path, prefix)) return true;
|
|
7539
8201
|
return false;
|
|
7540
8202
|
}
|
|
7541
8203
|
closed(node, writable = false) {
|
|
@@ -7549,24 +8211,24 @@ var VfsNodes = class {
|
|
|
7549
8211
|
this.onClosed?.(node.path);
|
|
7550
8212
|
}
|
|
7551
8213
|
async flush(path) {
|
|
7552
|
-
for (const [p, node] of this.byPath) if (
|
|
8214
|
+
for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
|
|
7553
8215
|
}
|
|
7554
8216
|
async unlinking(path) {
|
|
7555
8217
|
for (const [held, node] of [...this.byPath]) {
|
|
7556
|
-
if (!
|
|
8218
|
+
if (!within5(held, path)) continue;
|
|
7557
8219
|
await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
|
|
7558
8220
|
}
|
|
7559
8221
|
}
|
|
7560
8222
|
revoke(prefix) {
|
|
7561
8223
|
for (const [path, node] of this.byPath) {
|
|
7562
|
-
if (!
|
|
8224
|
+
if (!within5(path, prefix)) continue;
|
|
7563
8225
|
node.revoke();
|
|
7564
8226
|
this.byPath.delete(path);
|
|
7565
8227
|
}
|
|
7566
8228
|
}
|
|
7567
8229
|
unlinked(path) {
|
|
7568
8230
|
for (const [held, node] of [...this.byPath]) {
|
|
7569
|
-
if (!
|
|
8231
|
+
if (!within5(held, path)) continue;
|
|
7570
8232
|
node.orphaned = true;
|
|
7571
8233
|
this.byPath.delete(held);
|
|
7572
8234
|
}
|
|
@@ -7575,8 +8237,8 @@ var VfsNodes = class {
|
|
|
7575
8237
|
if (from === to) return;
|
|
7576
8238
|
const moved2 = [];
|
|
7577
8239
|
for (const [p, node] of this.byPath) {
|
|
7578
|
-
if (
|
|
7579
|
-
else if (
|
|
8240
|
+
if (within5(p, from)) moved2.push(node);
|
|
8241
|
+
else if (within5(p, to)) {
|
|
7580
8242
|
node.orphaned = true;
|
|
7581
8243
|
this.byPath.delete(p);
|
|
7582
8244
|
}
|
|
@@ -7599,7 +8261,7 @@ async function present(fs, path, follow = false) {
|
|
|
7599
8261
|
}
|
|
7600
8262
|
}
|
|
7601
8263
|
var MAX_LINKS5 = 40;
|
|
7602
|
-
function
|
|
8264
|
+
function parentOf6(path) {
|
|
7603
8265
|
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
7604
8266
|
}
|
|
7605
8267
|
async function linkOf(fs, path) {
|
|
@@ -7612,7 +8274,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
|
|
|
7612
8274
|
const part = queue.shift();
|
|
7613
8275
|
if (part === ".") continue;
|
|
7614
8276
|
if (part === "..") {
|
|
7615
|
-
at =
|
|
8277
|
+
at = parentOf6(at);
|
|
7616
8278
|
continue;
|
|
7617
8279
|
}
|
|
7618
8280
|
const next = at === "/" ? `/${part}` : `${at}/${part}`;
|
|
@@ -7636,7 +8298,7 @@ function joinName(dir, name2) {
|
|
|
7636
8298
|
}
|
|
7637
8299
|
async function realTarget(fs, path) {
|
|
7638
8300
|
const budget = { hops: 0 };
|
|
7639
|
-
let dir = await realDir(fs,
|
|
8301
|
+
let dir = await realDir(fs, parentOf6(path), budget);
|
|
7640
8302
|
let name2 = path.slice(path.lastIndexOf("/") + 1);
|
|
7641
8303
|
for (; ; ) {
|
|
7642
8304
|
const link2 = await linkOf(fs, joinName(dir, name2));
|
|
@@ -7644,13 +8306,13 @@ async function realTarget(fs, path) {
|
|
|
7644
8306
|
if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
|
|
7645
8307
|
budget.hops++;
|
|
7646
8308
|
const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
|
|
7647
|
-
dir = await realDir(fs,
|
|
8309
|
+
dir = await realDir(fs, parentOf6(target), budget);
|
|
7648
8310
|
name2 = target.slice(target.lastIndexOf("/") + 1);
|
|
7649
8311
|
}
|
|
7650
8312
|
return joinName(dir, name2);
|
|
7651
8313
|
}
|
|
7652
8314
|
async function targetDir(fs, path) {
|
|
7653
|
-
return
|
|
8315
|
+
return parentOf6(await realTarget(fs, path).catch(() => path));
|
|
7654
8316
|
}
|
|
7655
8317
|
async function refuseReadonly(fs, path, flags, opts = {}) {
|
|
7656
8318
|
const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
|
|
@@ -8039,7 +8701,7 @@ var WasmProcess = class {
|
|
|
8039
8701
|
const fd = this.fds.install(heldFile(), 3);
|
|
8040
8702
|
try {
|
|
8041
8703
|
const nodes = this.nodes;
|
|
8042
|
-
const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : await nodes.targetKey(req.path);
|
|
8704
|
+
const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : req.create ? await nodes.targetKey(req.path) : await this.keptOr(await nodes.targetKey(req.path));
|
|
8043
8705
|
const open = { ...req, path };
|
|
8044
8706
|
if (req.exclusive) {
|
|
8045
8707
|
await nodes.createExclusive(path);
|
|
@@ -8195,6 +8857,11 @@ var WasmProcess = class {
|
|
|
8195
8857
|
return { ok: true, kind: "json", json: await file.stat() };
|
|
8196
8858
|
}
|
|
8197
8859
|
}
|
|
8860
|
+
keptPath;
|
|
8861
|
+
async keptOr(path) {
|
|
8862
|
+
const kept = this.keptPath?.(path);
|
|
8863
|
+
return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
|
|
8864
|
+
}
|
|
8198
8865
|
async promote(req) {
|
|
8199
8866
|
if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
|
|
8200
8867
|
if (req.share !== void 0) {
|
|
@@ -8202,7 +8869,7 @@ var WasmProcess = class {
|
|
|
8202
8869
|
return;
|
|
8203
8870
|
}
|
|
8204
8871
|
if (req.path === void 0) throw new KernelError("EINVAL");
|
|
8205
|
-
const path = req.orphan ? req.path : await this.nodes.targetKey(req.path);
|
|
8872
|
+
const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path));
|
|
8206
8873
|
if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
|
|
8207
8874
|
const file = vfsFile(
|
|
8208
8875
|
this.options.fs,
|
|
@@ -8534,7 +9201,42 @@ var Dying = class {
|
|
|
8534
9201
|
return [typeof reported === "number" ? reported : CRASHED, void 0];
|
|
8535
9202
|
}
|
|
8536
9203
|
};
|
|
8537
|
-
function
|
|
9204
|
+
function notifiers(header3) {
|
|
9205
|
+
return {
|
|
9206
|
+
onPending(sig) {
|
|
9207
|
+
Atomics.or(header3, SAB_I_SIGNALS, sigbit(sig));
|
|
9208
|
+
Atomics.notify(header3, SAB_I_SIGNALS);
|
|
9209
|
+
},
|
|
9210
|
+
onAsync() {
|
|
9211
|
+
Atomics.add(header3, SAB_I_ASYNC, 1);
|
|
9212
|
+
Atomics.notify(header3, SAB_I_ASYNC);
|
|
9213
|
+
}
|
|
9214
|
+
};
|
|
9215
|
+
}
|
|
9216
|
+
function publishStops(process, header3) {
|
|
9217
|
+
process.onState((state) => {
|
|
9218
|
+
Atomics.store(header3, SAB_I_STOP, state === "stopped" ? 1 : 0);
|
|
9219
|
+
Atomics.notify(header3, SAB_I_STOP);
|
|
9220
|
+
});
|
|
9221
|
+
}
|
|
9222
|
+
function transferOf(opts, lane) {
|
|
9223
|
+
const transfer = opts.fork ? [opts.fork.memory.buffer] : [];
|
|
9224
|
+
return lane ? [...transfer, lane.port] : transfer;
|
|
9225
|
+
}
|
|
9226
|
+
function openLane(token, dispatch2) {
|
|
9227
|
+
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
9228
|
+
const { port1, port2 } = new MessageChannel();
|
|
9229
|
+
const responder = attachSyncSabResponder(port1, sab, token, { dispatch: dispatch2 });
|
|
9230
|
+
port1.start();
|
|
9231
|
+
return {
|
|
9232
|
+
lane: { sab, port: port2 },
|
|
9233
|
+
dispose() {
|
|
9234
|
+
responder.dispose();
|
|
9235
|
+
port1.close();
|
|
9236
|
+
}
|
|
9237
|
+
};
|
|
9238
|
+
}
|
|
9239
|
+
function processInit(opts, sab, umask, lane) {
|
|
8538
9240
|
const base = {
|
|
8539
9241
|
type: WASM_PROCESS_INIT,
|
|
8540
9242
|
pid: opts.shownPid ?? opts.pid,
|
|
@@ -8547,7 +9249,12 @@ function processInit(opts, sab, umask) {
|
|
|
8547
9249
|
...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
|
|
8548
9250
|
};
|
|
8549
9251
|
if (opts.program.abi === "js") {
|
|
8550
|
-
return {
|
|
9252
|
+
return {
|
|
9253
|
+
...base,
|
|
9254
|
+
program: opts.program,
|
|
9255
|
+
fds: inheritedFds(opts.fds),
|
|
9256
|
+
lane
|
|
9257
|
+
};
|
|
8551
9258
|
}
|
|
8552
9259
|
return {
|
|
8553
9260
|
...base,
|
|
@@ -8561,6 +9268,19 @@ function inherited(opts) {
|
|
|
8561
9268
|
...opts.umask !== void 0 ? { umask: opts.umask } : {}
|
|
8562
9269
|
};
|
|
8563
9270
|
}
|
|
9271
|
+
function holdingToken(opts, holds) {
|
|
9272
|
+
return mintSyncFsToken({
|
|
9273
|
+
fs: opts.fs,
|
|
9274
|
+
cwd: opts.cwd,
|
|
9275
|
+
...opts.statfs ? { statfs: opts.statfs } : {},
|
|
9276
|
+
hold: (path, on, open, real) => holds.hold(path, on, open, real),
|
|
9277
|
+
unlinked: (path) => holds.keptAt(path),
|
|
9278
|
+
gate: (path, op) => opts.unlinkGate && holds.holding(path) ? opts.unlinkGate.run(holds.keyOf(path), op) : op(),
|
|
9279
|
+
own: (path, op) => holds.own(path, op),
|
|
9280
|
+
revoked: (path) => holds.isRevoked(path),
|
|
9281
|
+
renamed: (from, to) => holds.renamed(from, to)
|
|
9282
|
+
});
|
|
9283
|
+
}
|
|
8564
9284
|
function spawnWasmProcess(opts) {
|
|
8565
9285
|
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
8566
9286
|
const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
|
|
@@ -8586,37 +9306,22 @@ function spawnWasmProcess(opts) {
|
|
|
8586
9306
|
locks: opts.locks,
|
|
8587
9307
|
onReap: opts.onReap,
|
|
8588
9308
|
resolver: opts.resolver,
|
|
8589
|
-
|
|
8590
|
-
Atomics.or(header3, SAB_I_SIGNALS, sigbit(sig));
|
|
8591
|
-
Atomics.notify(header3, SAB_I_SIGNALS);
|
|
8592
|
-
},
|
|
8593
|
-
onAsync: () => {
|
|
8594
|
-
Atomics.add(header3, SAB_I_ASYNC, 1);
|
|
8595
|
-
Atomics.notify(header3, SAB_I_ASYNC);
|
|
8596
|
-
},
|
|
9309
|
+
...notifiers(header3),
|
|
8597
9310
|
onTimer: (which) => void Atomics.or(header3, SAB_I_TIMERS, 1 << which),
|
|
8598
9311
|
hasPending: () => Atomics.load(header3, SAB_I_SIGNALS) !== 0 || Atomics.load(header3, SAB_I_TIMERS) !== 0,
|
|
8599
9312
|
raise: (sig) => signal(sig),
|
|
8600
9313
|
...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
|
|
8601
9314
|
});
|
|
8602
|
-
process
|
|
8603
|
-
Atomics.store(header3, SAB_I_STOP, state === "stopped" ? 1 : 0);
|
|
8604
|
-
Atomics.notify(header3, SAB_I_STOP);
|
|
8605
|
-
});
|
|
9315
|
+
publishStops(process, header3);
|
|
8606
9316
|
const holds = new HeldPaths();
|
|
8607
9317
|
opts.held?.add(holds);
|
|
8608
|
-
const token =
|
|
8609
|
-
|
|
8610
|
-
cwd: opts.cwd,
|
|
8611
|
-
...opts.statfs ? { statfs: opts.statfs } : {},
|
|
8612
|
-
hold: (path, on) => holds.hold(path, on),
|
|
8613
|
-
revoked: (path) => holds.isRevoked(path),
|
|
8614
|
-
renamed: (from, to) => holds.renamed(from, to)
|
|
8615
|
-
});
|
|
9318
|
+
const token = holdingToken(opts, holds);
|
|
9319
|
+
process.keptPath = (path) => holds.keptAt(path)?.hidden;
|
|
8616
9320
|
const worker = opts.createWorker();
|
|
8617
9321
|
const dying = new Dying();
|
|
8618
9322
|
const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
|
|
8619
9323
|
const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
|
|
9324
|
+
const lane = opts.program.abi === "js" ? openLane(token, dispatch2) : void 0;
|
|
8620
9325
|
const { promise: exited, resolve: settle } = Promise.withResolvers();
|
|
8621
9326
|
let done4 = false;
|
|
8622
9327
|
let endedBy;
|
|
@@ -8629,6 +9334,7 @@ function spawnWasmProcess(opts) {
|
|
|
8629
9334
|
worker.removeEventListener("error", onError);
|
|
8630
9335
|
for (const tid of [...threads.keys()]) endThread(tid);
|
|
8631
9336
|
responder.dispose();
|
|
9337
|
+
lane?.dispose();
|
|
8632
9338
|
revokeSyncFsToken(token);
|
|
8633
9339
|
opts.held?.delete(holds);
|
|
8634
9340
|
worker.terminate();
|
|
@@ -8699,8 +9405,10 @@ function spawnWasmProcess(opts) {
|
|
|
8699
9405
|
};
|
|
8700
9406
|
tw.postMessage(tinit);
|
|
8701
9407
|
};
|
|
8702
|
-
|
|
8703
|
-
|
|
9408
|
+
worker.postMessage(
|
|
9409
|
+
processInit(opts, sab, process.umask, lane?.lane),
|
|
9410
|
+
transferOf(opts, lane?.lane)
|
|
9411
|
+
);
|
|
8704
9412
|
const signal = (sig) => {
|
|
8705
9413
|
if (process.signal(sig) !== "terminate") return;
|
|
8706
9414
|
const code = sig === SIG.KILL ? 137 : 128 + sig;
|
|
@@ -10261,6 +10969,7 @@ var Launcher = class {
|
|
|
10261
10969
|
deciding = /* @__PURE__ */ new Map();
|
|
10262
10970
|
identities = { asked: 0, timedOut: 0 };
|
|
10263
10971
|
nodes;
|
|
10972
|
+
unlinked;
|
|
10264
10973
|
held = new HeldSet((path) => this.released(path));
|
|
10265
10974
|
processMounts;
|
|
10266
10975
|
booting = new AbortController();
|
|
@@ -10305,7 +11014,15 @@ var Launcher = class {
|
|
|
10305
11014
|
const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
10306
11015
|
this.nodes = new VfsNodes(fs, (path) => this.released(path));
|
|
10307
11016
|
this.openFiles.add(this.nodes);
|
|
10308
|
-
this.
|
|
11017
|
+
this.unlinked = new UnlinkedKeeper(
|
|
11018
|
+
fs,
|
|
11019
|
+
this.held,
|
|
11020
|
+
(path) => this.mountRoot(path),
|
|
11021
|
+
webLocks(),
|
|
11022
|
+
(path) => [...this.openFiles].some((nodes) => nodes.holds(path))
|
|
11023
|
+
);
|
|
11024
|
+
this.fs = keepingOpen(this.unlinked.wrap(), this.nodes);
|
|
11025
|
+
void this.unlinked.sweep("/");
|
|
10309
11026
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
10310
11027
|
this.catalog = void 0;
|
|
10311
11028
|
this.binfmts = void 0;
|
|
@@ -10515,6 +11232,7 @@ var Launcher = class {
|
|
|
10515
11232
|
openFiles: this.openFiles,
|
|
10516
11233
|
nodes: this.nodes,
|
|
10517
11234
|
held: this.held,
|
|
11235
|
+
unlinkGate: this.unlinked.gate,
|
|
10518
11236
|
statfs: (path) => this.mounts.statfs(path),
|
|
10519
11237
|
mounts: () => this.mounts.list(),
|
|
10520
11238
|
mount: (call, signal) => this.processMount(pid, call, signal),
|
|
@@ -10672,6 +11390,7 @@ var Launcher = class {
|
|
|
10672
11390
|
}
|
|
10673
11391
|
released(path) {
|
|
10674
11392
|
const at = normalizePath(path);
|
|
11393
|
+
this.unlinked.closed(at);
|
|
10675
11394
|
const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
|
|
10676
11395
|
if (!open) void this.mounts.commit(at).catch(() => void 0);
|
|
10677
11396
|
}
|
|
@@ -10767,10 +11486,23 @@ var Launcher = class {
|
|
|
10767
11486
|
}
|
|
10768
11487
|
};
|
|
10769
11488
|
}
|
|
10770
|
-
mount(spec) {
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
11489
|
+
async mount(spec) {
|
|
11490
|
+
const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
|
|
11491
|
+
const entry = await this.mounts.mount(
|
|
11492
|
+
fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
|
|
11493
|
+
this.fs
|
|
11494
|
+
);
|
|
11495
|
+
void this.unlinked.sweep(entry.target);
|
|
11496
|
+
return entry;
|
|
11497
|
+
}
|
|
11498
|
+
mountRoot(path) {
|
|
11499
|
+
let root = "/";
|
|
11500
|
+
for (const { target } of this.mounts.list()) {
|
|
11501
|
+
if (target.length > root.length && (path === target || path.startsWith(`${target}/`))) {
|
|
11502
|
+
root = target;
|
|
11503
|
+
}
|
|
11504
|
+
}
|
|
11505
|
+
return root;
|
|
10774
11506
|
}
|
|
10775
11507
|
async insert(target, handle, source) {
|
|
10776
11508
|
const at = normalizePath(target);
|
|
@@ -10784,6 +11516,7 @@ var Launcher = class {
|
|
|
10784
11516
|
await this.media.put(slot.id, handle);
|
|
10785
11517
|
slot.medium.insert(handle);
|
|
10786
11518
|
slot.served.invalidate(true);
|
|
11519
|
+
void this.unlinked.sweep(at);
|
|
10787
11520
|
}
|
|
10788
11521
|
umount(target, detach = false) {
|
|
10789
11522
|
const at = normalizePath(target);
|
|
@@ -11153,12 +11886,15 @@ var RemoteTransport = class {
|
|
|
11153
11886
|
port;
|
|
11154
11887
|
waiting = /* @__PURE__ */ new Map();
|
|
11155
11888
|
nextId = 0;
|
|
11889
|
+
sockets = /* @__PURE__ */ new Map();
|
|
11156
11890
|
failure;
|
|
11157
11891
|
constructor(port, traits) {
|
|
11158
11892
|
this.port = port;
|
|
11159
11893
|
this.traits = traits;
|
|
11160
11894
|
}
|
|
11161
11895
|
receive(reply) {
|
|
11896
|
+
const socket = this.sockets.get(reply.nid);
|
|
11897
|
+
if (socket && this.toSocket(reply, socket)) return;
|
|
11162
11898
|
const waiter = this.waiting.get(reply.nid);
|
|
11163
11899
|
if (!waiter) return;
|
|
11164
11900
|
this.waiting.delete(reply.nid);
|
|
@@ -11166,10 +11902,22 @@ var RemoteTransport = class {
|
|
|
11166
11902
|
waiter.reject(new TransportError(reply.message, reply.status, reply.code));
|
|
11167
11903
|
} else waiter.resolve(reply);
|
|
11168
11904
|
}
|
|
11905
|
+
toSocket(reply, socket) {
|
|
11906
|
+
if (reply.net === "ws-message") socket.queue.push(reply.data);
|
|
11907
|
+
else if (reply.net === "ws-acked") socket.acked(reply.n);
|
|
11908
|
+
else if (reply.net === "ws-closed") {
|
|
11909
|
+
this.sockets.delete(reply.nid);
|
|
11910
|
+
socket.queue.end({ code: reply.code, reason: reply.reason });
|
|
11911
|
+
} else return false;
|
|
11912
|
+
return true;
|
|
11913
|
+
}
|
|
11169
11914
|
fail(error) {
|
|
11170
11915
|
this.failure = error;
|
|
11171
11916
|
for (const waiter of this.waiting.values()) waiter.reject(error);
|
|
11172
11917
|
this.waiting.clear();
|
|
11918
|
+
for (const socket of this.sockets.values())
|
|
11919
|
+
socket.queue.end({ code: 1011, reason: error.message });
|
|
11920
|
+
this.sockets.clear();
|
|
11173
11921
|
}
|
|
11174
11922
|
ask(call) {
|
|
11175
11923
|
if (this.failure) return Promise.reject(this.failure);
|
|
@@ -11248,6 +11996,46 @@ var RemoteTransport = class {
|
|
|
11248
11996
|
}
|
|
11249
11997
|
};
|
|
11250
11998
|
}
|
|
11999
|
+
async websocket(request) {
|
|
12000
|
+
const nid = ++this.nextId;
|
|
12001
|
+
const { signal } = request;
|
|
12002
|
+
signal.throwIfAborted();
|
|
12003
|
+
const abort = () => this.cancel(nid);
|
|
12004
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
12005
|
+
let opened2;
|
|
12006
|
+
try {
|
|
12007
|
+
opened2 = await this.ask({
|
|
12008
|
+
net: "ws-open",
|
|
12009
|
+
nid,
|
|
12010
|
+
url: request.url,
|
|
12011
|
+
protocols: request.protocols,
|
|
12012
|
+
headers: request.headers
|
|
12013
|
+
});
|
|
12014
|
+
} finally {
|
|
12015
|
+
signal.removeEventListener("abort", abort);
|
|
12016
|
+
}
|
|
12017
|
+
const close = (code, reason) => this.port.postMessage({ net: "ws-close", nid, code, reason });
|
|
12018
|
+
const queue = new MessageQueue(
|
|
12019
|
+
() => close(1e3, "a message is too big"),
|
|
12020
|
+
Number.POSITIVE_INFINITY,
|
|
12021
|
+
(n) => this.port.postMessage({ net: "ws-consumed", nid, n })
|
|
12022
|
+
);
|
|
12023
|
+
let unacked = 0;
|
|
12024
|
+
this.sockets.set(nid, { queue, acked: (n) => void (unacked -= n) });
|
|
12025
|
+
return {
|
|
12026
|
+
protocol: opened2.protocol,
|
|
12027
|
+
get buffered() {
|
|
12028
|
+
return unacked;
|
|
12029
|
+
},
|
|
12030
|
+
send: (data) => {
|
|
12031
|
+
unacked += payloadSize(data);
|
|
12032
|
+
this.port.postMessage({ net: "ws-send", nid, data });
|
|
12033
|
+
},
|
|
12034
|
+
messages: queue,
|
|
12035
|
+
close,
|
|
12036
|
+
closed: queue.closed
|
|
12037
|
+
};
|
|
12038
|
+
}
|
|
11251
12039
|
};
|
|
11252
12040
|
|
|
11253
12041
|
// src/serve.ts
|
|
@@ -11869,7 +12657,7 @@ function hostfsFetchOf(options) {
|
|
|
11869
12657
|
};
|
|
11870
12658
|
}
|
|
11871
12659
|
function nodeTransport() {
|
|
11872
|
-
const transport = fetchTransport();
|
|
12660
|
+
const transport = fetchTransport({ webSocket: true, webSocketHeaders: true });
|
|
11873
12661
|
return { ...transport, traits: { ...transport.traits, crossOrigin: "any" } };
|
|
11874
12662
|
}
|
|
11875
12663
|
var TERMINATED = "the kernel is terminated";
|