@ai-ecoverse/slicc-kernel 1.73.3 → 1.75.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 +16 -2
- package/dist/index.js +56 -8
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +320 -32
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +198 -35
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +298 -43
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +198 -35
- package/dist/process-worker.js.map +3 -3
- package/package.json +2 -2
package/dist/kernel-worker.js
CHANGED
|
@@ -2255,9 +2255,11 @@ var KernelPipe = class {
|
|
|
2255
2255
|
// src/kernel/fd-table.ts
|
|
2256
2256
|
var KernelError = class extends Error {
|
|
2257
2257
|
code;
|
|
2258
|
-
|
|
2258
|
+
detail;
|
|
2259
|
+
constructor(code, detail) {
|
|
2259
2260
|
super(code);
|
|
2260
2261
|
this.code = code;
|
|
2262
|
+
if (detail !== void 0) this.detail = detail;
|
|
2261
2263
|
}
|
|
2262
2264
|
};
|
|
2263
2265
|
function pollFile(file) {
|
|
@@ -2400,6 +2402,7 @@ function kernelFdKind(file) {
|
|
|
2400
2402
|
if (file.heldMeta && "device" in file.heldMeta) return "device";
|
|
2401
2403
|
if (file.tty) return "tty";
|
|
2402
2404
|
if (file.sealed) return "sealed";
|
|
2405
|
+
if (file.clipboard) return "clipboard";
|
|
2403
2406
|
return file.seek ? "file" : "stream";
|
|
2404
2407
|
}
|
|
2405
2408
|
var FdTable = class _FdTable {
|
|
@@ -4190,6 +4193,203 @@ function serveClient(port, host) {
|
|
|
4190
4193
|
return { detach, closed: ended.promise };
|
|
4191
4194
|
}
|
|
4192
4195
|
|
|
4196
|
+
// src/kernel/clipboard.ts
|
|
4197
|
+
var CLIPBOARD_MAX_BYTES = 1024 * 1024;
|
|
4198
|
+
var CLIPBOARD_TIMEOUT_MS = 1e4;
|
|
4199
|
+
var encoder = new TextEncoder();
|
|
4200
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
4201
|
+
var READY = { readable: true, writable: true, hangup: false };
|
|
4202
|
+
function lineOf(err) {
|
|
4203
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
4204
|
+
return (text.split("\n")[0] ?? "").slice(0, 200);
|
|
4205
|
+
}
|
|
4206
|
+
function clipboardFailure(err) {
|
|
4207
|
+
if (err instanceof KernelError) return err;
|
|
4208
|
+
const name2 = err?.name;
|
|
4209
|
+
return new KernelError(name2 === "NotAllowedError" ? "EACCES" : "EIO", lineOf(err));
|
|
4210
|
+
}
|
|
4211
|
+
function joined2(chunks, size) {
|
|
4212
|
+
if (chunks.length === 1) return chunks[0];
|
|
4213
|
+
const out = new Uint8Array(size);
|
|
4214
|
+
let at = 0;
|
|
4215
|
+
for (const chunk2 of chunks) {
|
|
4216
|
+
out.set(chunk2, at);
|
|
4217
|
+
at += chunk2.length;
|
|
4218
|
+
}
|
|
4219
|
+
return out;
|
|
4220
|
+
}
|
|
4221
|
+
async function interruptible(work, signal) {
|
|
4222
|
+
if (!signal) return work;
|
|
4223
|
+
if (signal.aborted) throw new KernelError("EINTR");
|
|
4224
|
+
let stop = () => {
|
|
4225
|
+
};
|
|
4226
|
+
const interrupted2 = new Promise((_, reject) => {
|
|
4227
|
+
stop = () => reject(new KernelError("EINTR"));
|
|
4228
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
4229
|
+
});
|
|
4230
|
+
try {
|
|
4231
|
+
return await Promise.race([work, interrupted2]);
|
|
4232
|
+
} finally {
|
|
4233
|
+
signal.removeEventListener("abort", stop);
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
var ClipboardChannel = class {
|
|
4237
|
+
stamp;
|
|
4238
|
+
access;
|
|
4239
|
+
broker;
|
|
4240
|
+
snapshot;
|
|
4241
|
+
taken;
|
|
4242
|
+
offset = 0;
|
|
4243
|
+
chunks = [];
|
|
4244
|
+
size = 0;
|
|
4245
|
+
wrote = false;
|
|
4246
|
+
failed = false;
|
|
4247
|
+
constructor(broker, access2, stamp) {
|
|
4248
|
+
this.broker = broker;
|
|
4249
|
+
this.access = access2;
|
|
4250
|
+
this.stamp = stamp;
|
|
4251
|
+
}
|
|
4252
|
+
get unread() {
|
|
4253
|
+
return this.taken === void 0 && this.snapshot === void 0;
|
|
4254
|
+
}
|
|
4255
|
+
get settled() {
|
|
4256
|
+
return this.taken !== void 0;
|
|
4257
|
+
}
|
|
4258
|
+
async read(max, signal) {
|
|
4259
|
+
if (this.access === "write") throw new KernelError("EBADF");
|
|
4260
|
+
if (!this.taken) {
|
|
4261
|
+
if (!this.snapshot) {
|
|
4262
|
+
const stamp = this.stamp;
|
|
4263
|
+
const asked = this.broker.ask(async (hook) => {
|
|
4264
|
+
const text = await hook.read(stamp);
|
|
4265
|
+
if (typeof text !== "string") throw new KernelError("EIO", "the clipboard gave no text");
|
|
4266
|
+
return encoder.encode(text);
|
|
4267
|
+
});
|
|
4268
|
+
this.snapshot = asked;
|
|
4269
|
+
asked.then(
|
|
4270
|
+
(bytes) => {
|
|
4271
|
+
if (this.snapshot === asked) this.taken = bytes;
|
|
4272
|
+
},
|
|
4273
|
+
() => {
|
|
4274
|
+
if (this.snapshot === asked) this.snapshot = void 0;
|
|
4275
|
+
}
|
|
4276
|
+
);
|
|
4277
|
+
}
|
|
4278
|
+
this.taken = await interruptible(this.snapshot, signal);
|
|
4279
|
+
}
|
|
4280
|
+
const out = this.taken.subarray(this.offset, this.offset + max);
|
|
4281
|
+
this.offset += out.length;
|
|
4282
|
+
return out;
|
|
4283
|
+
}
|
|
4284
|
+
write(bytes) {
|
|
4285
|
+
if (this.access === "read") throw new KernelError("EBADF");
|
|
4286
|
+
this.wrote = true;
|
|
4287
|
+
if (this.failed) throw new KernelError("EFBIG");
|
|
4288
|
+
if (this.size + bytes.length > CLIPBOARD_MAX_BYTES) {
|
|
4289
|
+
this.failed = true;
|
|
4290
|
+
this.chunks = [];
|
|
4291
|
+
this.size = 0;
|
|
4292
|
+
throw new KernelError("EFBIG");
|
|
4293
|
+
}
|
|
4294
|
+
if (bytes.length > 0) this.chunks.push(bytes.slice());
|
|
4295
|
+
this.size += bytes.length;
|
|
4296
|
+
return bytes.length;
|
|
4297
|
+
}
|
|
4298
|
+
poll() {
|
|
4299
|
+
return READY;
|
|
4300
|
+
}
|
|
4301
|
+
async close() {
|
|
4302
|
+
this.snapshot = void 0;
|
|
4303
|
+
this.taken = void 0;
|
|
4304
|
+
const commits = this.access === "write" || this.access === "both" && this.wrote;
|
|
4305
|
+
if (!commits || this.failed) return;
|
|
4306
|
+
const bytes = joined2(this.chunks, this.size);
|
|
4307
|
+
this.chunks = [];
|
|
4308
|
+
this.size = 0;
|
|
4309
|
+
let text;
|
|
4310
|
+
try {
|
|
4311
|
+
text = decoder.decode(bytes);
|
|
4312
|
+
} catch {
|
|
4313
|
+
throw new KernelError("EILSEQ", "the text is not valid UTF-8");
|
|
4314
|
+
}
|
|
4315
|
+
const stamp = this.stamp;
|
|
4316
|
+
await this.broker.ask(async (hook) => {
|
|
4317
|
+
await hook.write(text, stamp);
|
|
4318
|
+
});
|
|
4319
|
+
}
|
|
4320
|
+
};
|
|
4321
|
+
var ClipboardBroker = class {
|
|
4322
|
+
hook;
|
|
4323
|
+
users;
|
|
4324
|
+
timeoutMs;
|
|
4325
|
+
tagOf;
|
|
4326
|
+
constructor(hook, users, timeoutMs = CLIPBOARD_TIMEOUT_MS, tagOf = () => void 0) {
|
|
4327
|
+
this.hook = hook;
|
|
4328
|
+
this.users = users;
|
|
4329
|
+
this.timeoutMs = timeoutMs;
|
|
4330
|
+
this.tagOf = tagOf;
|
|
4331
|
+
}
|
|
4332
|
+
async ask(work) {
|
|
4333
|
+
const hook = this.hook;
|
|
4334
|
+
if (!hook) throw new KernelError("ENOTSUP", "no clipboard in this embedder");
|
|
4335
|
+
let timer;
|
|
4336
|
+
const late = new Promise((_, reject) => {
|
|
4337
|
+
timer = setTimeout(
|
|
4338
|
+
() => reject(new KernelError("EIO", "clipboard timed out")),
|
|
4339
|
+
this.timeoutMs
|
|
4340
|
+
);
|
|
4341
|
+
});
|
|
4342
|
+
const asked = Promise.resolve().then(() => work(hook)).catch((err) => {
|
|
4343
|
+
throw clipboardFailure(err);
|
|
4344
|
+
});
|
|
4345
|
+
try {
|
|
4346
|
+
return await Promise.race([asked, late]);
|
|
4347
|
+
} finally {
|
|
4348
|
+
clearTimeout(timer);
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
open(access2, stamp) {
|
|
4352
|
+
const channel = new ClipboardChannel(this, access2, stamp);
|
|
4353
|
+
return new OpenFile({
|
|
4354
|
+
read: (max, signal) => channel.read(max, signal),
|
|
4355
|
+
write: async (bytes) => channel.write(bytes),
|
|
4356
|
+
poll: () => channel.poll(),
|
|
4357
|
+
close: () => channel.close(),
|
|
4358
|
+
clipboard: channel
|
|
4359
|
+
});
|
|
4360
|
+
}
|
|
4361
|
+
};
|
|
4362
|
+
|
|
4363
|
+
// src/clipboard-relay.ts
|
|
4364
|
+
function clipboardAsker(port) {
|
|
4365
|
+
const asks = /* @__PURE__ */ new Map();
|
|
4366
|
+
let next2 = 0;
|
|
4367
|
+
const ask = (op, stamp, text) => {
|
|
4368
|
+
const id2 = ++next2;
|
|
4369
|
+
const waiting = Promise.withResolvers();
|
|
4370
|
+
asks.set(id2, waiting);
|
|
4371
|
+
const timer = setTimeout(() => {
|
|
4372
|
+
if (asks.delete(id2)) waiting.reject(new Error("clipboard timed out"));
|
|
4373
|
+
}, CLIPBOARD_TIMEOUT_MS + 1e3);
|
|
4374
|
+
port.postMessage({ clipboardAsk: { id: id2, op, stamp, ...text !== void 0 ? { text } : {} } });
|
|
4375
|
+
return waiting.promise.finally(() => clearTimeout(timer));
|
|
4376
|
+
};
|
|
4377
|
+
const hook = {
|
|
4378
|
+
read: async (stamp) => await ask("read", stamp) ?? "",
|
|
4379
|
+
write: async (text, stamp) => {
|
|
4380
|
+
await ask("write", stamp, text);
|
|
4381
|
+
}
|
|
4382
|
+
};
|
|
4383
|
+
const answer = ({ id: id2, text, error }) => {
|
|
4384
|
+
const waiting = asks.get(id2);
|
|
4385
|
+
if (!waiting) return;
|
|
4386
|
+
asks.delete(id2);
|
|
4387
|
+
if (error) waiting.reject(Object.assign(new Error(error.message), { name: error.name }));
|
|
4388
|
+
else waiting.resolve(text);
|
|
4389
|
+
};
|
|
4390
|
+
return { hook, answer };
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4193
4393
|
// src/kernel/net/loopback-names.ts
|
|
4194
4394
|
var HOST_LOOPBACK = "host.slicc.internal";
|
|
4195
4395
|
var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
|
|
@@ -9191,20 +9391,20 @@ var SAB_DEFAULT_WINDOW_BYTES = 1024 * 1024;
|
|
|
9191
9391
|
var SAB_MIN_BYTES = SAB_HEADER_BYTES + 4096;
|
|
9192
9392
|
var SYNC_SAB_REQ_MSG = "sync-sab-req";
|
|
9193
9393
|
var SYNC_SAB_NEXT_MSG = "sync-sab-next";
|
|
9194
|
-
var
|
|
9195
|
-
var
|
|
9394
|
+
var encoder2 = new TextEncoder();
|
|
9395
|
+
var decoder2 = new TextDecoder();
|
|
9196
9396
|
function encodeSabResult(result) {
|
|
9197
9397
|
if (!result.ok) {
|
|
9198
9398
|
return {
|
|
9199
9399
|
status: SAB_STATUS_ERRNO,
|
|
9200
|
-
payload:
|
|
9400
|
+
payload: encoder2.encode(JSON.stringify({ errno: result.errno, message: result.message }))
|
|
9201
9401
|
};
|
|
9202
9402
|
}
|
|
9203
9403
|
switch (result.kind) {
|
|
9204
9404
|
case "bytes":
|
|
9205
9405
|
return { status: SAB_STATUS_BYTES, payload: result.bytes };
|
|
9206
9406
|
case "json":
|
|
9207
|
-
return { status: SAB_STATUS_JSON, payload:
|
|
9407
|
+
return { status: SAB_STATUS_JSON, payload: encoder2.encode(JSON.stringify(result.json)) };
|
|
9208
9408
|
default:
|
|
9209
9409
|
return { status: SAB_STATUS_VOID, payload: new Uint8Array(0) };
|
|
9210
9410
|
}
|
|
@@ -10678,7 +10878,7 @@ function defaultTermios() {
|
|
|
10678
10878
|
c_cc: cCc
|
|
10679
10879
|
};
|
|
10680
10880
|
}
|
|
10681
|
-
var
|
|
10881
|
+
var encoder3 = new TextEncoder();
|
|
10682
10882
|
function isContinuation(byte) {
|
|
10683
10883
|
return (byte & 192) === 128;
|
|
10684
10884
|
}
|
|
@@ -10800,7 +11000,7 @@ var KernelTty = class {
|
|
|
10800
11000
|
this.line.push(byte);
|
|
10801
11001
|
this.readable.push(Uint8Array.from(this.line));
|
|
10802
11002
|
this.line = [];
|
|
10803
|
-
if (echo) this.output(
|
|
11003
|
+
if (echo) this.output(encoder3.encode("\n"));
|
|
10804
11004
|
} else {
|
|
10805
11005
|
this.line.push(byte);
|
|
10806
11006
|
if (echo) this.echo(byte);
|
|
@@ -10825,7 +11025,7 @@ var KernelTty = class {
|
|
|
10825
11025
|
const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
|
|
10826
11026
|
const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
|
|
10827
11027
|
if (cells > 0 && this.termios.c_lflag & ECHO) {
|
|
10828
|
-
this.output(
|
|
11028
|
+
this.output(encoder3.encode("\b \b".repeat(cells)), false);
|
|
10829
11029
|
}
|
|
10830
11030
|
}
|
|
10831
11031
|
echo(byte) {
|
|
@@ -11175,7 +11375,14 @@ async function runChild(req, deps) {
|
|
|
11175
11375
|
];
|
|
11176
11376
|
try {
|
|
11177
11377
|
pid = await deps.children.spawn(
|
|
11178
|
-
{
|
|
11378
|
+
{
|
|
11379
|
+
file: req.file,
|
|
11380
|
+
argv: req.argv,
|
|
11381
|
+
env: req.env,
|
|
11382
|
+
cwd: req.cwd,
|
|
11383
|
+
...req.pgid !== void 0 ? { pgid: req.pgid } : {},
|
|
11384
|
+
...req.session ? { session: req.session } : {}
|
|
11385
|
+
},
|
|
11179
11386
|
stdio
|
|
11180
11387
|
);
|
|
11181
11388
|
} catch (e) {
|
|
@@ -11307,7 +11514,7 @@ async function socketSyscall(req, proc) {
|
|
|
11307
11514
|
dialing?.catch(() => void 0);
|
|
11308
11515
|
throw new KernelError("EINPROGRESS");
|
|
11309
11516
|
}
|
|
11310
|
-
if (dialing !== void 0) await
|
|
11517
|
+
if (dialing !== void 0) await interruptible2(dialing, proc.blocking());
|
|
11311
11518
|
return ok();
|
|
11312
11519
|
}
|
|
11313
11520
|
case "sock-shutdown":
|
|
@@ -11331,7 +11538,7 @@ async function socketSyscall(req, proc) {
|
|
|
11331
11538
|
}
|
|
11332
11539
|
}
|
|
11333
11540
|
}
|
|
11334
|
-
function
|
|
11541
|
+
function interruptible2(dialing, signal) {
|
|
11335
11542
|
return new Promise((resolve, reject) => {
|
|
11336
11543
|
const abort = () => reject(new KernelError("EINTR"));
|
|
11337
11544
|
signal.addEventListener("abort", abort, { once: true });
|
|
@@ -11998,6 +12205,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11998
12205
|
"fd-tty-names",
|
|
11999
12206
|
"fd-tty-owner",
|
|
12000
12207
|
"fd-open-sealed",
|
|
12208
|
+
"fd-open-clipboard",
|
|
12001
12209
|
"tty-get",
|
|
12002
12210
|
"tty-set",
|
|
12003
12211
|
"tty-winsz",
|
|
@@ -12128,8 +12336,9 @@ var WasmProcess = class {
|
|
|
12128
12336
|
return "deliver";
|
|
12129
12337
|
}
|
|
12130
12338
|
async spawn(req) {
|
|
12131
|
-
const { file, argv, env, cwd, stdio, inherit: inherit2, exec } = req;
|
|
12132
|
-
const
|
|
12339
|
+
const { file, argv, env, cwd, stdio, inherit: inherit2, exec, pgid, session } = req;
|
|
12340
|
+
const group = { ...pgid !== void 0 ? { pgid } : {}, ...session ? { session } : {} };
|
|
12341
|
+
const spawned = { file, argv, env, cwd, ...group, ...exec ? { exec } : {} };
|
|
12133
12342
|
const pid = await this.children.spawn(spawned, stdio, inherit2);
|
|
12134
12343
|
if (!exec) return pid;
|
|
12135
12344
|
if (this.killed) void Promise.allSettled([this.options.kill?.(pid, SIG.KILL)]);
|
|
@@ -12239,7 +12448,7 @@ var WasmProcess = class {
|
|
|
12239
12448
|
return await this.procSyscall(req, cancelled);
|
|
12240
12449
|
} catch (e) {
|
|
12241
12450
|
if (e instanceof KernelError || e instanceof SpawnError) {
|
|
12242
|
-
return { ok: false, errno: e.code, message: e.code };
|
|
12451
|
+
return { ok: false, errno: e.code, message: e.detail ?? e.code };
|
|
12243
12452
|
}
|
|
12244
12453
|
const code = e?.code;
|
|
12245
12454
|
if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
|
|
@@ -12282,6 +12491,7 @@ var WasmProcess = class {
|
|
|
12282
12491
|
const read = req.peek ? file.peek : file.read;
|
|
12283
12492
|
if (!read) throw new KernelError(req.peek && file.read ? "EOPNOTSUPP" : "EBADF");
|
|
12284
12493
|
if (file.tty) this.checkForeground(file.tty);
|
|
12494
|
+
if (file.clipboard) return this.readClipboard(file.clipboard, req, cancelled);
|
|
12285
12495
|
if (req.max <= 0) return new Uint8Array(0);
|
|
12286
12496
|
if (file instanceof KernelSocket && file.unconnected) throw new KernelError("ENOTCONN");
|
|
12287
12497
|
const ready2 = pollFile(file).readable;
|
|
@@ -12289,6 +12499,13 @@ var WasmProcess = class {
|
|
|
12289
12499
|
const signal = ready2 ? this.interrupt.signal : this.blockingSignal(cancelled);
|
|
12290
12500
|
return read.call(file, Math.max(0, Math.min(req.max, MAX_READ)), signal);
|
|
12291
12501
|
}
|
|
12502
|
+
async readClipboard(channel, req, cancelled) {
|
|
12503
|
+
if (channel.access === "write") throw new KernelError("EBADF");
|
|
12504
|
+
if (req.max <= 0) return new Uint8Array(0);
|
|
12505
|
+
if (channel.unread) channel.stamp = await this.clipboardStamp();
|
|
12506
|
+
const signal = channel.settled ? this.interrupt.signal : this.blockingSignal(cancelled);
|
|
12507
|
+
return channel.read(Math.min(req.max, MAX_READ), signal);
|
|
12508
|
+
}
|
|
12292
12509
|
async write(fd, body, nonblock = false, cancelled) {
|
|
12293
12510
|
const file = this.fds.get(fd).file;
|
|
12294
12511
|
if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
|
|
@@ -12392,6 +12609,36 @@ var WasmProcess = class {
|
|
|
12392
12609
|
if (!broker) throw new KernelError("ENOENT");
|
|
12393
12610
|
return this.fds.install(broker.open(), 3);
|
|
12394
12611
|
}
|
|
12612
|
+
async openClipboard(flags2) {
|
|
12613
|
+
const broker = this.options.clipboard;
|
|
12614
|
+
if (!broker) throw new KernelError("ENOENT");
|
|
12615
|
+
const mode = flags2 & WRITE_ACCESS;
|
|
12616
|
+
const access2 = mode === 0 ? "read" : mode === 1 ? "write" : "both";
|
|
12617
|
+
return this.fds.install(broker.open(access2, await this.clipboardStamp()), 3);
|
|
12618
|
+
}
|
|
12619
|
+
async clipboardStamp() {
|
|
12620
|
+
const shown = await this.options.identity?.();
|
|
12621
|
+
const broker = this.options.clipboard;
|
|
12622
|
+
const tag = broker.tagOf(this.pid);
|
|
12623
|
+
return {
|
|
12624
|
+
pid: shown?.pid ?? this.pid,
|
|
12625
|
+
uid: this.cred.euid,
|
|
12626
|
+
gid: this.cred.egid,
|
|
12627
|
+
users: broker.users,
|
|
12628
|
+
...tag !== void 0 ? { tag } : {}
|
|
12629
|
+
};
|
|
12630
|
+
}
|
|
12631
|
+
async stampClipboards() {
|
|
12632
|
+
if (!this.options.clipboard) return;
|
|
12633
|
+
const open = this.fds.numbers().filter((fd) => this.fds.get(fd).file.clipboard);
|
|
12634
|
+
if (open.length === 0) return;
|
|
12635
|
+
const stamp = await this.clipboardStamp().catch(() => void 0);
|
|
12636
|
+
if (!stamp) return;
|
|
12637
|
+
for (const fd of open) {
|
|
12638
|
+
const channel = this.fds.get(fd).file.clipboard;
|
|
12639
|
+
if (channel) channel.stamp = stamp;
|
|
12640
|
+
}
|
|
12641
|
+
}
|
|
12395
12642
|
async sealedCaller() {
|
|
12396
12643
|
const shown = await this.options.identity?.();
|
|
12397
12644
|
const broker = this.options.sealed;
|
|
@@ -12466,10 +12713,13 @@ var WasmProcess = class {
|
|
|
12466
12713
|
kind: "json",
|
|
12467
12714
|
json: await this.write(req.fd, req.body, req.nonblock, cancelled)
|
|
12468
12715
|
};
|
|
12469
|
-
case "fd-close":
|
|
12716
|
+
case "fd-close": {
|
|
12470
12717
|
this.options.locks?.closed(this.pid, req.fd);
|
|
12718
|
+
const channel = this.fds.get(req.fd).file.clipboard;
|
|
12719
|
+
if (channel) channel.stamp = await this.clipboardStamp();
|
|
12471
12720
|
await Promise.resolve(this.fds.close(req.fd));
|
|
12472
12721
|
return { ok: true, kind: "void" };
|
|
12722
|
+
}
|
|
12473
12723
|
case "fd-pipe": {
|
|
12474
12724
|
const pipe = openPipe();
|
|
12475
12725
|
const read = this.fds.install(pipe.read, 3);
|
|
@@ -12527,6 +12777,8 @@ var WasmProcess = class {
|
|
|
12527
12777
|
return { ok: true, kind: "json", json: this.options.jobs?.terminalNames() ?? [] };
|
|
12528
12778
|
case "fd-open-sealed":
|
|
12529
12779
|
return { ok: true, kind: "json", json: this.openSealed() };
|
|
12780
|
+
case "fd-open-clipboard":
|
|
12781
|
+
return { ok: true, kind: "json", json: await this.openClipboard(req.flags) };
|
|
12530
12782
|
case "fd-tty-owner":
|
|
12531
12783
|
return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
|
|
12532
12784
|
case "fd-select": {
|
|
@@ -12978,6 +13230,7 @@ var WasmProcess = class {
|
|
|
12978
13230
|
this.options.locks?.release(this.pid);
|
|
12979
13231
|
this.asyncOps.close();
|
|
12980
13232
|
for (const pid of this.children.pids()) this.options.onReap?.(pid);
|
|
13233
|
+
await this.stampClipboards();
|
|
12981
13234
|
await this.fds.closeAll();
|
|
12982
13235
|
if (!this.options.nodes) this.options.openFiles?.delete(this.nodes);
|
|
12983
13236
|
await this.options.http?.closeAll();
|
|
@@ -13160,6 +13413,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
13160
13413
|
...inherited(opts),
|
|
13161
13414
|
...opts.owners ? { owners: true } : {},
|
|
13162
13415
|
...opts.sealed ? { sealed: opts.sealed } : {},
|
|
13416
|
+
...opts.clipboard ? { clipboard: opts.clipboard } : {},
|
|
13163
13417
|
...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
|
|
13164
13418
|
...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
|
|
13165
13419
|
...opts.identity ? { identity: opts.identity } : {},
|
|
@@ -13423,6 +13677,9 @@ var JobTable = class {
|
|
|
13423
13677
|
member.pgid = pgid;
|
|
13424
13678
|
member.sid = sid;
|
|
13425
13679
|
}
|
|
13680
|
+
lead(pid) {
|
|
13681
|
+
this.member(pid).pgid = this.idOf(pid);
|
|
13682
|
+
}
|
|
13426
13683
|
pgidOf(pid) {
|
|
13427
13684
|
return this.members.get(pid)?.pgid;
|
|
13428
13685
|
}
|
|
@@ -13661,14 +13918,14 @@ var SEALED_OPS = /* @__PURE__ */ new Set([
|
|
|
13661
13918
|
"cancel",
|
|
13662
13919
|
"materialize"
|
|
13663
13920
|
]);
|
|
13664
|
-
var
|
|
13665
|
-
var
|
|
13921
|
+
var encoder4 = new TextEncoder();
|
|
13922
|
+
var decoder3 = new TextDecoder("utf-8", { fatal: true });
|
|
13666
13923
|
function parseSealed(bytes) {
|
|
13667
13924
|
try {
|
|
13668
13925
|
if (bytes.length > SEALED_MAX_BYTES) throw new KernelError("EMSGSIZE");
|
|
13669
13926
|
let parsed2;
|
|
13670
13927
|
try {
|
|
13671
|
-
parsed2 = JSON.parse(
|
|
13928
|
+
parsed2 = JSON.parse(decoder3.decode(bytes));
|
|
13672
13929
|
} catch {
|
|
13673
13930
|
throw new KernelError("EINVAL");
|
|
13674
13931
|
}
|
|
@@ -13694,13 +13951,13 @@ function serialized(reply) {
|
|
|
13694
13951
|
const text = JSON.stringify(reply);
|
|
13695
13952
|
plain = text === void 0 ? void 0 : JSON.parse(text);
|
|
13696
13953
|
} catch (err) {
|
|
13697
|
-
return JSON.stringify(errorReply("EIO",
|
|
13954
|
+
return JSON.stringify(errorReply("EIO", lineOf2(err)));
|
|
13698
13955
|
}
|
|
13699
13956
|
if (!isRecord(plain)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
|
|
13700
13957
|
const { v: _, ...rest2 } = plain;
|
|
13701
13958
|
return JSON.stringify({ v: 1, ...rest2 });
|
|
13702
13959
|
}
|
|
13703
|
-
function
|
|
13960
|
+
function lineOf2(err) {
|
|
13704
13961
|
const text = err instanceof Error ? err.message : String(err);
|
|
13705
13962
|
return (text.split("\n")[0] ?? "").slice(0, 200);
|
|
13706
13963
|
}
|
|
@@ -13730,7 +13987,7 @@ var SealedChannel = class {
|
|
|
13730
13987
|
gone.removeEventListener("abort", withdraw);
|
|
13731
13988
|
if (this.inflight !== flight) return;
|
|
13732
13989
|
this.inflight = void 0;
|
|
13733
|
-
if (reply) this.reply =
|
|
13990
|
+
if (reply) this.reply = encoder4.encode(serialized(reply));
|
|
13734
13991
|
});
|
|
13735
13992
|
this.inflight = flight;
|
|
13736
13993
|
return length2;
|
|
@@ -13744,7 +14001,7 @@ var SealedChannel = class {
|
|
|
13744
14001
|
if (signal.aborted) return void 0;
|
|
13745
14002
|
const asked = Promise.resolve().then(() => hook(req, { signal })).then(
|
|
13746
14003
|
(reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
|
|
13747
|
-
(err) => errorReply("EIO",
|
|
14004
|
+
(err) => errorReply("EIO", lineOf2(err))
|
|
13748
14005
|
);
|
|
13749
14006
|
const reply = await Promise.race([asked, stopped]);
|
|
13750
14007
|
if (!signal.aborted) return reply;
|
|
@@ -14467,13 +14724,13 @@ var HostfsClient = class {
|
|
|
14467
14724
|
const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
|
|
14468
14725
|
onOpen();
|
|
14469
14726
|
const reader = response.body.getReader();
|
|
14470
|
-
const
|
|
14727
|
+
const decoder4 = new TextDecoder();
|
|
14471
14728
|
let buffered = "";
|
|
14472
14729
|
try {
|
|
14473
14730
|
for (; ; ) {
|
|
14474
14731
|
const { done: done4, value } = await reader.read();
|
|
14475
14732
|
if (done4) return;
|
|
14476
|
-
buffered +=
|
|
14733
|
+
buffered += decoder4.decode(value, { stream: true });
|
|
14477
14734
|
let end = buffered.indexOf("\n");
|
|
14478
14735
|
while (end >= 0) {
|
|
14479
14736
|
const line = buffered.slice(0, end).trim();
|
|
@@ -15852,7 +16109,7 @@ var LAYOUT = [
|
|
|
15852
16109
|
];
|
|
15853
16110
|
var LOAD_SAMPLE_MS = 5e3;
|
|
15854
16111
|
var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
|
|
15855
|
-
var
|
|
16112
|
+
var encoder5 = new TextEncoder();
|
|
15856
16113
|
var USERS_MODES = ["off", "report", "enforce"];
|
|
15857
16114
|
function usersMode(mode = "off") {
|
|
15858
16115
|
if (USERS_MODES.includes(mode)) return mode;
|
|
@@ -16009,6 +16266,7 @@ var Launcher = class {
|
|
|
16009
16266
|
usersMode;
|
|
16010
16267
|
dac;
|
|
16011
16268
|
sealed;
|
|
16269
|
+
clipboard;
|
|
16012
16270
|
rootFs;
|
|
16013
16271
|
fileWatches;
|
|
16014
16272
|
nextPid = 1e3;
|
|
@@ -16019,6 +16277,12 @@ var Launcher = class {
|
|
|
16019
16277
|
this.rootFs = options.fs;
|
|
16020
16278
|
this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
|
|
16021
16279
|
if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
|
|
16280
|
+
this.clipboard = new ClipboardBroker(
|
|
16281
|
+
options.clipboard,
|
|
16282
|
+
this.usersMode,
|
|
16283
|
+
void 0,
|
|
16284
|
+
(pid) => this.tagOf(pid)
|
|
16285
|
+
);
|
|
16022
16286
|
this.sealed = new SealedBroker(
|
|
16023
16287
|
options.sealed,
|
|
16024
16288
|
this.usersMode,
|
|
@@ -16408,7 +16672,7 @@ var Launcher = class {
|
|
|
16408
16672
|
start(req) {
|
|
16409
16673
|
this.sampling();
|
|
16410
16674
|
if (req.pgid !== void 0 && this.jobs.sessionOf(req.pgid) === void 0) {
|
|
16411
|
-
throw
|
|
16675
|
+
throw new SpawnError("ESRCH");
|
|
16412
16676
|
}
|
|
16413
16677
|
const pid = this.nextPid++;
|
|
16414
16678
|
const stack = this.stackFor(req.tag);
|
|
@@ -16435,6 +16699,7 @@ var Launcher = class {
|
|
|
16435
16699
|
cred: req.cred,
|
|
16436
16700
|
...this.owners ? { owners: true } : {},
|
|
16437
16701
|
sealed: this.sealed,
|
|
16702
|
+
clipboard: this.clipboard,
|
|
16438
16703
|
...this.dac ? { dac: this.dac } : {},
|
|
16439
16704
|
program: req.program,
|
|
16440
16705
|
argv0: req.argv0,
|
|
@@ -16483,6 +16748,8 @@ var Launcher = class {
|
|
|
16483
16748
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
16484
16749
|
if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
|
|
16485
16750
|
if (req.pgid !== void 0) this.jobs.join(pid, req.pgid);
|
|
16751
|
+
if (req.group === "lead") this.jobs.lead(pid);
|
|
16752
|
+
if (req.group === "session") this.jobs.setsid(pid);
|
|
16486
16753
|
this.described.set(pid, {
|
|
16487
16754
|
argv: [req.argv0, ...req.args],
|
|
16488
16755
|
tty: terminal?.name ?? null,
|
|
@@ -16644,6 +16911,11 @@ var Launcher = class {
|
|
|
16644
16911
|
await fds.closeAll();
|
|
16645
16912
|
throw new SpawnError("EACCES");
|
|
16646
16913
|
}
|
|
16914
|
+
const group = this.childGroup(ppid, req);
|
|
16915
|
+
if (typeof group === "string") {
|
|
16916
|
+
await fds.closeAll();
|
|
16917
|
+
throw new SpawnError(group);
|
|
16918
|
+
}
|
|
16647
16919
|
const cred = grant ? grant.cred : this.credOf(ppid);
|
|
16648
16920
|
const scope = grant ? "" : await this.scopeOf(cred.euid);
|
|
16649
16921
|
const planned = await this.plan(req.file, req.argv, req.cwd, scope);
|
|
@@ -16670,6 +16942,7 @@ var Launcher = class {
|
|
|
16670
16942
|
umask: this.umaskOf(ppid),
|
|
16671
16943
|
cred,
|
|
16672
16944
|
tag: this.tagOf(ppid),
|
|
16945
|
+
...group,
|
|
16673
16946
|
...exec
|
|
16674
16947
|
});
|
|
16675
16948
|
deciding?.attach();
|
|
@@ -16677,6 +16950,17 @@ var Launcher = class {
|
|
|
16677
16950
|
return childHandle(handle);
|
|
16678
16951
|
};
|
|
16679
16952
|
}
|
|
16953
|
+
childGroup(ppid, req) {
|
|
16954
|
+
const { pgid, session } = req;
|
|
16955
|
+
if (pgid === void 0 && session === void 0) return {};
|
|
16956
|
+
const valid = pgid === void 0 || Number.isInteger(pgid) && pgid >= 0;
|
|
16957
|
+
if (req.exec || !valid || session !== void 0 && session !== true) return "EINVAL";
|
|
16958
|
+
if (session) return pgid === void 0 ? { group: "session" } : "EINVAL";
|
|
16959
|
+
if (pgid === 0) return { group: "lead" };
|
|
16960
|
+
const sid = this.jobs.sessionOf(pgid);
|
|
16961
|
+
if (sid === void 0) return "ESRCH";
|
|
16962
|
+
return sid === this.jobs.sidOf(ppid) ? { pgid } : "EPERM";
|
|
16963
|
+
}
|
|
16680
16964
|
undecided(ppid) {
|
|
16681
16965
|
const decided = Promise.withResolvers();
|
|
16682
16966
|
return {
|
|
@@ -16707,7 +16991,7 @@ var Launcher = class {
|
|
|
16707
16991
|
}, 0);
|
|
16708
16992
|
}
|
|
16709
16993
|
forker(ppid, parent) {
|
|
16710
|
-
const { exec: _exec, ...image } = parent;
|
|
16994
|
+
const { exec: _exec, pgid: _pgid, group: _group, ...image } = parent;
|
|
16711
16995
|
return async (state, fds) => childHandle(
|
|
16712
16996
|
this.start({
|
|
16713
16997
|
...image,
|
|
@@ -17176,7 +17460,7 @@ var Launcher = class {
|
|
|
17176
17460
|
},
|
|
17177
17461
|
account
|
|
17178
17462
|
);
|
|
17179
|
-
const report = (message3) => options.onData(
|
|
17463
|
+
const report = (message3) => options.onData(encoder5.encode(`${message3}\r
|
|
17180
17464
|
`));
|
|
17181
17465
|
const handle = await this.launch(planned, {
|
|
17182
17466
|
env,
|
|
@@ -17213,7 +17497,7 @@ var Launcher = class {
|
|
|
17213
17497
|
};
|
|
17214
17498
|
const stdout = collect(out, options.onStdout);
|
|
17215
17499
|
const stderr = collect(err, options.onStderr);
|
|
17216
|
-
const report = (message3) => stderr(
|
|
17500
|
+
const report = (message3) => stderr(encoder5.encode(`${message3}
|
|
17217
17501
|
`));
|
|
17218
17502
|
if (!planned) {
|
|
17219
17503
|
report(`${file}: command not found`);
|
|
@@ -17401,13 +17685,14 @@ function usersOp(l, req) {
|
|
|
17401
17685
|
if (req.op === "groups-remove-member") return l.users.removeMember(req.group, req.user);
|
|
17402
17686
|
return l.users.list();
|
|
17403
17687
|
}
|
|
17404
|
-
function hostOptions(req, port, sealed) {
|
|
17688
|
+
function hostOptions(req, port, sealed, clipboard) {
|
|
17405
17689
|
return {
|
|
17406
17690
|
...req.hostname !== void 0 ? { hostname: req.hostname } : {},
|
|
17407
17691
|
...req.users !== void 0 ? { users: req.users } : {},
|
|
17408
17692
|
...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
|
|
17409
17693
|
...req.protect ? { protect: protectInit(req.protect) } : {},
|
|
17410
|
-
...req.sealed ? { sealed } : {}
|
|
17694
|
+
...req.sealed ? { sealed } : {},
|
|
17695
|
+
...req.clipboard ? { clipboard } : {}
|
|
17411
17696
|
};
|
|
17412
17697
|
}
|
|
17413
17698
|
function policyAsker(port) {
|
|
@@ -17437,6 +17722,7 @@ function serveKernel(port, deps) {
|
|
|
17437
17722
|
const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
|
|
17438
17723
|
const clients = /* @__PURE__ */ new Set();
|
|
17439
17724
|
const sealed = sealedAsker(port);
|
|
17725
|
+
const clipboard = clipboardAsker(port);
|
|
17440
17726
|
const approvals = sudoApprovals(port);
|
|
17441
17727
|
const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
|
|
17442
17728
|
let holding;
|
|
@@ -17501,7 +17787,7 @@ function serveKernel(port, deps) {
|
|
|
17501
17787
|
...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
|
|
17502
17788
|
...req.modules ? { modules: req.modules } : {},
|
|
17503
17789
|
...req.env ? { env: req.env } : {},
|
|
17504
|
-
...hostOptions(req, port, sealed.hook),
|
|
17790
|
+
...hostOptions(req, port, sealed.hook, clipboard.hook),
|
|
17505
17791
|
...req.approve ? { approve: approvals.ask } : {},
|
|
17506
17792
|
...net,
|
|
17507
17793
|
...media ? { media } : {},
|
|
@@ -17570,6 +17856,8 @@ function serveKernel(port, deps) {
|
|
|
17570
17856
|
port.addEventListener("message", (event) => {
|
|
17571
17857
|
const sealedReply = event.data.sealedReply;
|
|
17572
17858
|
if (sealedReply) return sealed.answer(sealedReply);
|
|
17859
|
+
const clipboardReply = event.data.clipboardReply;
|
|
17860
|
+
if (clipboardReply) return clipboard.answer(clipboardReply);
|
|
17573
17861
|
const grant = event.data.hostfsGrant;
|
|
17574
17862
|
if (grant) return granted2(grant);
|
|
17575
17863
|
if (allowed(event.data)) return;
|