sandboxedjs 0.1.47 → 0.1.49

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.
@@ -0,0 +1,865 @@
1
+ // src/runtime/python/host-abi.ts
2
+ var SBX_HOST_ABI_VERSION = 1;
3
+ var SBX_REQUEST_HEADER_BYTES = 16;
4
+ var SBX_RESPONSE_HEADER_BYTES = 20;
5
+ var Op = {
6
+ handshake: 0,
7
+ openat: 1,
8
+ close: 2,
9
+ read: 3,
10
+ write: 4,
11
+ pread: 5,
12
+ pwrite: 6,
13
+ lseek: 7,
14
+ fstat: 8,
15
+ statat: 9,
16
+ ftruncate: 10,
17
+ renameat: 11,
18
+ unlinkat: 12,
19
+ mkdirat: 13,
20
+ readlinkat: 14,
21
+ symlinkat: 15,
22
+ getdents: 16,
23
+ fsync: 17,
24
+ dup: 256,
25
+ dup2: 257,
26
+ get_flags: 258,
27
+ set_flags: 259,
28
+ pipe: 512,
29
+ poll: 768,
30
+ clock_gettime: 1024,
31
+ sleep: 1025,
32
+ getpid: 1280,
33
+ getcwd: 1281,
34
+ chdir: 1282,
35
+ environ: 1283,
36
+ getrandom: 1536
37
+ };
38
+ var Errno = {
39
+ EPERM: 1,
40
+ ENOENT: 2,
41
+ ESRCH: 3,
42
+ EINTR: 4,
43
+ EIO: 5,
44
+ ENXIO: 6,
45
+ E2BIG: 7,
46
+ EBADF: 9,
47
+ EAGAIN: 11,
48
+ ENOMEM: 12,
49
+ EACCES: 13,
50
+ EFAULT: 14,
51
+ EBUSY: 16,
52
+ EEXIST: 17,
53
+ EXDEV: 18,
54
+ ENODEV: 19,
55
+ ENOTDIR: 20,
56
+ EISDIR: 21,
57
+ EINVAL: 22,
58
+ ENFILE: 23,
59
+ EMFILE: 24,
60
+ ENOTTY: 25,
61
+ EFBIG: 27,
62
+ ENOSPC: 28,
63
+ ESPIPE: 29,
64
+ EROFS: 30,
65
+ EMLINK: 31,
66
+ EPIPE: 32,
67
+ ERANGE: 34,
68
+ ENAMETOOLONG: 36,
69
+ ENOSYS: 38,
70
+ ENOTEMPTY: 39,
71
+ ELOOP: 40,
72
+ ENODATA: 61,
73
+ EPROTO: 71,
74
+ EOVERFLOW: 75,
75
+ ETIMEDOUT: 110,
76
+ ECANCELED: 125,
77
+ EDQUOT: 122
78
+ };
79
+ var OP_NAMES = Object.fromEntries(
80
+ Object.entries(Op).map(([name, code]) => [code, name])
81
+ );
82
+ var ERRNO_NAMES = Object.fromEntries(
83
+ Object.entries(Errno).map(([name, code]) => [code, name])
84
+ );
85
+
86
+ // src/runtime/python/protocol.ts
87
+ var ProtocolError = class extends Error {
88
+ code = "ERR_SBX_ABI_PROTOCOL";
89
+ };
90
+ function encodeRequest(header, payload) {
91
+ const frame = new Uint8Array(SBX_REQUEST_HEADER_BYTES + payload.length);
92
+ const view = new DataView(frame.buffer);
93
+ view.setUint16(0, header.version, true);
94
+ view.setUint16(2, header.op, true);
95
+ view.setUint32(4, header.requestId, true);
96
+ view.setUint32(8, header.generation, true);
97
+ view.setUint32(12, payload.length, true);
98
+ frame.set(payload, SBX_REQUEST_HEADER_BYTES);
99
+ return frame;
100
+ }
101
+ function decodeResponse(frame, expected) {
102
+ if (frame.length < SBX_RESPONSE_HEADER_BYTES) {
103
+ throw new ProtocolError("the host ABI transport returned an incomplete frame");
104
+ }
105
+ const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
106
+ const header = {
107
+ version: view.getUint16(0, true),
108
+ op: view.getUint16(2, true),
109
+ requestId: view.getUint32(4, true),
110
+ generation: view.getUint32(8, true)
111
+ };
112
+ const status = view.getInt32(12, true);
113
+ const length = view.getUint32(16, true);
114
+ if (frame.length - SBX_RESPONSE_HEADER_BYTES < length) throw new ProtocolError("truncated response payload");
115
+ if (expected) {
116
+ if (header.requestId !== expected.requestId) throw new ProtocolError("stale response: request id mismatch");
117
+ if (header.generation !== expected.generation) throw new ProtocolError("stale response: process generation mismatch");
118
+ if (header.op !== expected.op) throw new ProtocolError("response answers a different operation");
119
+ }
120
+ if (header.version !== SBX_HOST_ABI_VERSION) {
121
+ throw new ProtocolError(`host ABI version ${header.version}, expected ${SBX_HOST_ABI_VERSION}`);
122
+ }
123
+ return {
124
+ header,
125
+ status,
126
+ payload: frame.subarray(SBX_RESPONSE_HEADER_BYTES, SBX_RESPONSE_HEADER_BYTES + length)
127
+ };
128
+ }
129
+ var Reader = class {
130
+ at = 0;
131
+ view;
132
+ bytes;
133
+ constructor(bytes) {
134
+ this.bytes = bytes;
135
+ this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
136
+ }
137
+ need(count) {
138
+ if (this.at + count > this.bytes.length) throw new ProtocolError("payload underrun");
139
+ const offset = this.at;
140
+ this.at += count;
141
+ return offset;
142
+ }
143
+ i32() {
144
+ return this.view.getInt32(this.need(4), true);
145
+ }
146
+ u32() {
147
+ return this.view.getUint32(this.need(4), true);
148
+ }
149
+ i64() {
150
+ return Number(this.view.getBigInt64(this.need(8), true));
151
+ }
152
+ u64() {
153
+ return Number(this.view.getBigUint64(this.need(8), true));
154
+ }
155
+ bytes32() {
156
+ const length = this.u32();
157
+ const offset = this.need(length);
158
+ return this.bytes.subarray(offset, offset + length);
159
+ }
160
+ string() {
161
+ return new TextDecoder().decode(this.bytes32());
162
+ }
163
+ get remaining() {
164
+ return this.bytes.length - this.at;
165
+ }
166
+ };
167
+ var Writer = class {
168
+ parts = [];
169
+ i32(value) {
170
+ const b = new Uint8Array(4);
171
+ new DataView(b.buffer).setInt32(0, value, true);
172
+ this.parts.push(b);
173
+ return this;
174
+ }
175
+ u32(value) {
176
+ const b = new Uint8Array(4);
177
+ new DataView(b.buffer).setUint32(0, value >>> 0, true);
178
+ this.parts.push(b);
179
+ return this;
180
+ }
181
+ i64(value) {
182
+ const b = new Uint8Array(8);
183
+ new DataView(b.buffer).setBigInt64(0, BigInt(Math.trunc(value)), true);
184
+ this.parts.push(b);
185
+ return this;
186
+ }
187
+ u64(value) {
188
+ const b = new Uint8Array(8);
189
+ new DataView(b.buffer).setBigUint64(0, BigInt(Math.trunc(value)), true);
190
+ this.parts.push(b);
191
+ return this;
192
+ }
193
+ bytes32(value) {
194
+ this.u32(value.length);
195
+ this.parts.push(value);
196
+ return this;
197
+ }
198
+ string(value) {
199
+ return this.bytes32(new TextEncoder().encode(value));
200
+ }
201
+ finish() {
202
+ const total = this.parts.reduce((sum, part) => sum + part.length, 0);
203
+ const out = new Uint8Array(total);
204
+ let at = 0;
205
+ for (const part of this.parts) {
206
+ out.set(part, at);
207
+ at += part.length;
208
+ }
209
+ return out;
210
+ }
211
+ };
212
+
213
+ // src/runtime/python/syscall-client.ts
214
+ var HostAbiError = class extends Error {
215
+ errno;
216
+ code;
217
+ constructor(errno, op) {
218
+ const name = ERRNO_NAMES[errno] ?? String(errno);
219
+ super(`${OP_NAMES[op] ?? `op ${op}`} failed: ${name}`);
220
+ this.errno = errno;
221
+ this.code = name;
222
+ }
223
+ };
224
+ var HostAbiClient = class {
225
+ transport;
226
+ generation;
227
+ nextRequestId = 1;
228
+ constructor(transport, generation) {
229
+ this.transport = transport;
230
+ this.generation = generation;
231
+ }
232
+ /** Send one frame, blocking. Returns the status and payload verbatim. */
233
+ callRaw(op, payload) {
234
+ const header = {
235
+ version: SBX_HOST_ABI_VERSION,
236
+ op,
237
+ requestId: this.nextRequestId++,
238
+ generation: this.generation
239
+ };
240
+ const frame = this.transport.call(encodeRequest(header, payload));
241
+ const response = decodeResponse(frame, header);
242
+ return { status: response.status, payload: response.payload };
243
+ }
244
+ /** As {@link callRaw}, but a negative status becomes an exception. */
245
+ call(op, payload) {
246
+ const result = this.callRaw(op, payload);
247
+ if (result.status < 0) throw new HostAbiError(-result.status, op);
248
+ return result;
249
+ }
250
+ handshake() {
251
+ const { payload } = this.call(Op.handshake, new Uint8Array());
252
+ const r = new Reader(payload);
253
+ const version = r.u32();
254
+ const count = r.u32();
255
+ const capabilities = {};
256
+ for (let i = 0; i < count; i += 1) capabilities[r.string()] = r.u32() === 1;
257
+ return { version, capabilities };
258
+ }
259
+ open(path, flags, mode = 438) {
260
+ return this.call(Op.openat, new Writer().string(path).u32(flags).u32(mode).finish()).status;
261
+ }
262
+ close(fd) {
263
+ this.call(Op.close, new Writer().i32(fd).finish());
264
+ }
265
+ read(fd, length) {
266
+ return this.call(Op.read, new Writer().i32(fd).u32(length).finish()).payload;
267
+ }
268
+ write(fd, data) {
269
+ return this.call(Op.write, new Writer().i32(fd).bytes32(data).finish()).status;
270
+ }
271
+ /** Write every byte, looping over short writes the way a guest's libc must. */
272
+ writeAll(fd, data) {
273
+ let at = 0;
274
+ while (at < data.length) at += this.write(fd, data.subarray(at));
275
+ }
276
+ pread(fd, length, offset) {
277
+ return this.call(Op.pread, new Writer().i32(fd).u32(length).u64(offset).finish()).payload;
278
+ }
279
+ pwrite(fd, data, offset) {
280
+ return this.call(Op.pwrite, new Writer().i32(fd).bytes32(data).u64(offset).finish()).status;
281
+ }
282
+ seek(fd, offset, whence) {
283
+ const { payload } = this.call(Op.lseek, new Writer().i32(fd).i64(offset).u32(whence).finish());
284
+ return new Reader(payload).i64();
285
+ }
286
+ fstat(fd) {
287
+ return decodeStat(this.call(Op.fstat, new Writer().i32(fd).finish()).payload);
288
+ }
289
+ stat(path, followLinks = true) {
290
+ return decodeStat(
291
+ this.call(Op.statat, new Writer().string(path).u32(followLinks ? 1 : 0).finish()).payload
292
+ );
293
+ }
294
+ ftruncate(fd, length) {
295
+ this.call(Op.ftruncate, new Writer().i32(fd).u64(length).finish());
296
+ }
297
+ rename(from, to) {
298
+ this.call(Op.renameat, new Writer().string(from).string(to).finish());
299
+ }
300
+ unlink(path, removeDirectory = false) {
301
+ this.call(Op.unlinkat, new Writer().string(path).u32(removeDirectory ? 1 : 0).finish());
302
+ }
303
+ mkdir(path, mode = 511) {
304
+ this.call(Op.mkdirat, new Writer().string(path).u32(mode).finish());
305
+ }
306
+ readdir(fd, bufferSize = 4096) {
307
+ const { payload } = this.call(Op.getdents, new Writer().i32(fd).u32(bufferSize).finish());
308
+ const text = new TextDecoder().decode(payload);
309
+ return text.split("\0").filter((name) => name.length > 0);
310
+ }
311
+ dup(fd, from = 0) {
312
+ return this.call(Op.dup, new Writer().i32(fd).u32(from).finish()).status;
313
+ }
314
+ dup2(fd, target) {
315
+ return this.call(Op.dup2, new Writer().i32(fd).i32(target).finish()).status;
316
+ }
317
+ getFlags(fd) {
318
+ const r = new Reader(this.call(Op.get_flags, new Writer().i32(fd).finish()).payload);
319
+ return { flags: r.u32(), cloexec: r.u32() === 1 };
320
+ }
321
+ setFlags(fd, flags, cloexec) {
322
+ this.call(Op.set_flags, new Writer().i32(fd).u32(flags).u32(cloexec ? 1 : 0).finish());
323
+ }
324
+ pipe(flags = 0) {
325
+ const r = new Reader(this.call(Op.pipe, new Writer().u32(flags).finish()).payload);
326
+ return [r.i32(), r.i32()];
327
+ }
328
+ poll(entries, timeoutMs) {
329
+ const w = new Writer().u32(entries.length);
330
+ for (const entry of entries) w.i32(entry.fd).u32(entry.events);
331
+ w.i64(timeoutMs);
332
+ const r = new Reader(this.call(Op.poll, w.finish()).payload);
333
+ const count = r.u32();
334
+ const out = [];
335
+ for (let i = 0; i < count; i += 1) out.push({ fd: r.i32(), revents: r.u32() });
336
+ return out;
337
+ }
338
+ clockGettime(monotonic) {
339
+ const r = new Reader(this.call(Op.clock_gettime, new Writer().u32(monotonic ? 1 : 0).finish()).payload);
340
+ return { seconds: r.i64(), nanos: r.u32() };
341
+ }
342
+ sleep(nanos) {
343
+ this.call(Op.sleep, new Writer().u64(nanos).finish());
344
+ }
345
+ identity() {
346
+ const r = new Reader(this.call(Op.getpid, new Uint8Array()).payload);
347
+ return { pid: r.i32(), ppid: r.i32(), uid: r.u32(), gid: r.u32(), umask: r.u32() };
348
+ }
349
+ getcwd() {
350
+ return new Reader(this.call(Op.getcwd, new Uint8Array()).payload).string();
351
+ }
352
+ chdir(path) {
353
+ this.call(Op.chdir, new Writer().string(path).finish());
354
+ }
355
+ environ() {
356
+ const joined = new Reader(this.call(Op.environ, new Uint8Array()).payload).string();
357
+ const out = {};
358
+ for (const entry of joined.split("\0")) {
359
+ if (!entry) continue;
360
+ const split = entry.indexOf("=");
361
+ out[entry.slice(0, split)] = entry.slice(split + 1);
362
+ }
363
+ return out;
364
+ }
365
+ getrandom(length) {
366
+ return this.call(Op.getrandom, new Writer().u32(length).finish()).payload;
367
+ }
368
+ };
369
+ function decodeStat(payload) {
370
+ const r = new Reader(payload);
371
+ return {
372
+ ino: r.u64(),
373
+ mode: r.u32(),
374
+ size: r.u64(),
375
+ uid: r.u32(),
376
+ gid: r.u32(),
377
+ nlink: r.u32(),
378
+ atimeNs: r.i64(),
379
+ mtimeNs: r.i64(),
380
+ ctimeNs: r.i64()
381
+ };
382
+ }
383
+
384
+ // src/runtime/python/sync-transport.ts
385
+ var STATE = 0;
386
+ var LENGTH = 1;
387
+ var MORE = 2;
388
+ var STATE_IDLE = 0;
389
+ var STATE_REQUEST = 1;
390
+ var STATE_RESPONSE = 2;
391
+ var STATE_CONTINUE = 3;
392
+ var STATE_CLOSED = 4;
393
+ var WAIT_SLICE_MS = 200;
394
+ var HostTransportError = class extends Error {
395
+ code = "ERR_SBX_HOST_TRANSPORT";
396
+ };
397
+ var HostCallClient = class {
398
+ control;
399
+ data;
400
+ capacity;
401
+ wake;
402
+ closed = false;
403
+ constructor(buffers, wake) {
404
+ this.control = new Int32Array(buffers.control);
405
+ this.data = new Uint8Array(buffers.data);
406
+ this.capacity = this.data.length;
407
+ this.wake = wake;
408
+ }
409
+ /** Send a frame and block until the whole answer is back. Never returns empty. */
410
+ call(request) {
411
+ if (this.closed || Atomics.load(this.control, STATE) === STATE_CLOSED) {
412
+ this.closed = true;
413
+ throw new HostTransportError("the host transport is closed");
414
+ }
415
+ this.send(request);
416
+ const response = this.receive();
417
+ if (response.length === 0) {
418
+ throw new HostTransportError("the host answered with no frame; the kernel side failed or went away");
419
+ }
420
+ return response;
421
+ }
422
+ send(request) {
423
+ let offset = 0;
424
+ for (; ; ) {
425
+ const size = Math.min(this.capacity, request.length - offset);
426
+ this.data.set(request.subarray(offset, offset + size), 0);
427
+ offset += size;
428
+ Atomics.store(this.control, LENGTH, size);
429
+ Atomics.store(this.control, MORE, offset < request.length ? 1 : 0);
430
+ this.publish(STATE_REQUEST);
431
+ const next = this.waitWhile(STATE_REQUEST);
432
+ if (next === STATE_RESPONSE) return;
433
+ if (next !== STATE_CONTINUE) {
434
+ this.closed = true;
435
+ throw new HostTransportError("the host went away mid-request");
436
+ }
437
+ }
438
+ }
439
+ receive() {
440
+ const parts = [];
441
+ let total = 0;
442
+ for (; ; ) {
443
+ const size = Atomics.load(this.control, LENGTH);
444
+ parts.push(this.data.slice(0, size));
445
+ total += size;
446
+ if (Atomics.load(this.control, MORE) === 0) break;
447
+ this.publish(STATE_CONTINUE);
448
+ if (this.waitWhile(STATE_CONTINUE) !== STATE_RESPONSE) {
449
+ this.closed = true;
450
+ throw new HostTransportError("the host went away mid-response");
451
+ }
452
+ }
453
+ Atomics.store(this.control, STATE, STATE_IDLE);
454
+ if (parts.length === 1) return parts[0];
455
+ const joined = new Uint8Array(total);
456
+ let at = 0;
457
+ for (const part of parts) {
458
+ joined.set(part, at);
459
+ at += part.length;
460
+ }
461
+ return joined;
462
+ }
463
+ publish(state) {
464
+ Atomics.store(this.control, STATE, state);
465
+ Atomics.notify(this.control, STATE);
466
+ this.wake();
467
+ }
468
+ waitWhile(state) {
469
+ for (; ; ) {
470
+ Atomics.wait(this.control, STATE, state, WAIT_SLICE_MS);
471
+ const current = Atomics.load(this.control, STATE);
472
+ if (current !== state) return current;
473
+ }
474
+ }
475
+ };
476
+
477
+ // src/runtime/python/sbxfs.ts
478
+ var S_IFDIR = 16384;
479
+ function createSbxFs(FS, client, errnoCodes) {
480
+ const translate = (canonical) => {
481
+ const name = ERRNO_NAMES[canonical];
482
+ const mapped = name ? errnoCodes[name] : void 0;
483
+ return mapped ?? errnoCodes.EIO ?? canonical;
484
+ };
485
+ const raise = (canonical) => {
486
+ throw new FS.ErrnoError(translate(canonical));
487
+ };
488
+ const fail = (error) => {
489
+ if (error instanceof HostAbiError) return raise(error.errno);
490
+ return raise(Errno.EIO);
491
+ };
492
+ const SBXFS = {
493
+ /** Where this mount sits in the guest's namespace, and where in the container. */
494
+ mount(mount) {
495
+ return SBXFS.createNode(null, "/", S_IFDIR | 511, 0);
496
+ },
497
+ createNode(parent, name, mode, dev) {
498
+ if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) raise(Errno.EINVAL);
499
+ const node = FS.createNode(parent, name, mode, dev);
500
+ node.node_ops = SBXFS.node_ops;
501
+ node.stream_ops = SBXFS.stream_ops;
502
+ return node;
503
+ },
504
+ /** The container path a guest node stands for. */
505
+ realPath(node) {
506
+ const parts = [];
507
+ let current = node;
508
+ while (current.parent !== current) {
509
+ parts.push(current.name);
510
+ current = current.parent;
511
+ }
512
+ parts.push(current.mount.opts.root);
513
+ parts.reverse();
514
+ return parts.join("/").replace(/\/+/g, "/");
515
+ },
516
+ node_ops: {
517
+ getattr(node) {
518
+ const path = SBXFS.realPath(node);
519
+ let stat;
520
+ try {
521
+ stat = client.stat(path, false);
522
+ } catch (error) {
523
+ return fail(error);
524
+ }
525
+ return {
526
+ dev: 1,
527
+ ino: stat.ino,
528
+ mode: stat.mode,
529
+ nlink: stat.nlink,
530
+ uid: stat.uid,
531
+ gid: stat.gid,
532
+ rdev: 0,
533
+ size: stat.size,
534
+ atime: new Date(stat.atimeNs / 1e6),
535
+ mtime: new Date(stat.mtimeNs / 1e6),
536
+ ctime: new Date(stat.ctimeNs / 1e6),
537
+ /* Emscripten sizes its own reads from this; a wrong block size shows
538
+ * up as short reads on large files rather than as an error. */
539
+ blksize: 4096,
540
+ blocks: Math.ceil(stat.size / 4096)
541
+ };
542
+ },
543
+ setattr(node, attr) {
544
+ const path = SBXFS.realPath(node);
545
+ try {
546
+ if (attr.size !== void 0) {
547
+ const fd = client.open(
548
+ path,
549
+ 1
550
+ /* O_WRONLY */
551
+ );
552
+ try {
553
+ client.ftruncate(fd, attr.size);
554
+ } finally {
555
+ client.close(fd);
556
+ }
557
+ }
558
+ } catch (error) {
559
+ fail(error);
560
+ }
561
+ },
562
+ lookup(parent, name) {
563
+ const path = `${SBXFS.realPath(parent)}/${name}`.replace(/\/+/g, "/");
564
+ let stat;
565
+ try {
566
+ stat = client.stat(path, false);
567
+ } catch (error) {
568
+ if (error instanceof HostAbiError && error.errno === Errno.ENOENT) raise(Errno.ENOENT);
569
+ return fail(error);
570
+ }
571
+ const node = SBXFS.createNode(parent, name, stat.mode, 0);
572
+ node.node_ops = SBXFS.node_ops;
573
+ node.stream_ops = SBXFS.stream_ops;
574
+ return node;
575
+ },
576
+ mknod(parent, name, mode, dev) {
577
+ const path = `${SBXFS.realPath(parent)}/${name}`.replace(/\/+/g, "/");
578
+ try {
579
+ if ((mode & S_IFDIR) === S_IFDIR) client.mkdir(path, mode & 511);
580
+ else {
581
+ const fd = client.open(path, 1 | 64, mode & 511);
582
+ client.close(fd);
583
+ }
584
+ } catch (error) {
585
+ fail(error);
586
+ }
587
+ return SBXFS.createNode(parent, name, mode, dev);
588
+ },
589
+ rename(node, newParent, newName) {
590
+ const from = SBXFS.realPath(node);
591
+ const to = `${SBXFS.realPath(newParent)}/${newName}`.replace(/\/+/g, "/");
592
+ try {
593
+ client.rename(from, to);
594
+ } catch (error) {
595
+ fail(error);
596
+ }
597
+ node.name = newName;
598
+ node.parent = newParent;
599
+ },
600
+ unlink(parent, name) {
601
+ const path = `${SBXFS.realPath(parent)}/${name}`.replace(/\/+/g, "/");
602
+ try {
603
+ client.unlink(path, false);
604
+ } catch (error) {
605
+ fail(error);
606
+ }
607
+ },
608
+ rmdir(parent, name) {
609
+ const path = `${SBXFS.realPath(parent)}/${name}`.replace(/\/+/g, "/");
610
+ try {
611
+ client.unlink(path, true);
612
+ } catch (error) {
613
+ fail(error);
614
+ }
615
+ },
616
+ readdir(node) {
617
+ const path = SBXFS.realPath(node);
618
+ try {
619
+ const fd = client.open(
620
+ path,
621
+ 0
622
+ /* O_RDONLY */
623
+ );
624
+ try {
625
+ const names = [];
626
+ for (; ; ) {
627
+ const batch = client.readdir(fd, 8192);
628
+ if (batch.length === 0) break;
629
+ names.push(...batch);
630
+ }
631
+ return names;
632
+ } finally {
633
+ client.close(fd);
634
+ }
635
+ } catch (error) {
636
+ return fail(error);
637
+ }
638
+ },
639
+ symlink() {
640
+ raise(Errno.ENOSYS);
641
+ },
642
+ readlink() {
643
+ return raise(Errno.EINVAL);
644
+ }
645
+ },
646
+ stream_ops: {
647
+ open(stream) {
648
+ const path = SBXFS.realPath(stream.node);
649
+ if (FS.isDir(stream.node.mode)) return;
650
+ try {
651
+ stream.sbxFd = client.open(
652
+ path,
653
+ stream.flags & ~524288
654
+ /* drop O_CLOEXEC */
655
+ );
656
+ } catch (error) {
657
+ fail(error);
658
+ }
659
+ },
660
+ close(stream) {
661
+ if (stream.sbxFd === void 0) return;
662
+ try {
663
+ client.close(stream.sbxFd);
664
+ } catch {
665
+ }
666
+ stream.sbxFd = void 0;
667
+ },
668
+ read(stream, buffer, offset, length, position) {
669
+ if (length === 0) return 0;
670
+ try {
671
+ const data = client.pread(stream.sbxFd, length, position);
672
+ buffer.set(data, offset);
673
+ return data.length;
674
+ } catch (error) {
675
+ return fail(error);
676
+ }
677
+ },
678
+ write(stream, buffer, offset, length, position) {
679
+ if (length === 0) return 0;
680
+ try {
681
+ return client.pwrite(stream.sbxFd, buffer.subarray(offset, offset + length), position);
682
+ } catch (error) {
683
+ return fail(error);
684
+ }
685
+ },
686
+ llseek(stream, offset, whence) {
687
+ let position = offset;
688
+ if (whence === 1) position += stream.position;
689
+ else if (whence === 2 && FS.isFile(stream.node.mode)) {
690
+ position += SBXFS.node_ops.getattr(stream.node).size;
691
+ }
692
+ if (position < 0) raise(Errno.EINVAL);
693
+ return position;
694
+ }
695
+ }
696
+ };
697
+ return SBXFS;
698
+ }
699
+
700
+ // src/runtime/python/worker-entry.ts
701
+ var INTERPRETER_OWNED = /* @__PURE__ */ new Set(["/lib", "/dev", "/proc", "/usr/local"]);
702
+ async function runPythonProcess(start, port) {
703
+ const transport = new HostCallClient(start.buffers, () => port.postMessage({ type: "wake" }));
704
+ const stampedCall = (request) => {
705
+ const stamped = request.slice();
706
+ new DataView(stamped.buffer).setUint32(8, start.generation, true);
707
+ return transport.call(stamped);
708
+ };
709
+ const client = new HostAbiClient(
710
+ { call: stampedCall },
711
+ start.generation
712
+ );
713
+ const factory = (await import(
714
+ /* @vite-ignore */
715
+ start.moduleUrl
716
+ )).default;
717
+ let exitCode = 0;
718
+ const config = {
719
+ /* Emscripten resolves `python.wasm` and `python.data` beside its loader;
720
+ * saying so explicitly survives a host that serves the loader from a
721
+ * different path than it was built at.
722
+ *
723
+ * Under Node the loader reads them with `fs`, which wants a path and not a
724
+ * `file://` URL — and reports the difference as a bare ENOENT naming the
725
+ * URL, which reads like a missing file rather than a wrong kind of name. */
726
+ locateFile: (file) => {
727
+ const resolved = new URL(file, start.moduleUrl);
728
+ if (resolved.protocol !== "file:") return resolved.href;
729
+ return decodeURIComponent(resolved.pathname);
730
+ },
731
+ arguments: start.argv,
732
+ /* Read by the `--wrap`ped `getpid`/`getppid` in library_sbx_posix.js. */
733
+ sbxPid: start.pid,
734
+ sbxPpid: start.ppid,
735
+ quit: (status) => {
736
+ exitCode = status;
737
+ }
738
+ };
739
+ config.preRun = [
740
+ () => {
741
+ mountContainer(config.FS, client, start.mounts, config.ERRNO_CODES ?? {});
742
+ bindStdio(config.FS, client, start.isTty);
743
+ for (const [key, value] of Object.entries(start.env)) config.ENV[key] = value;
744
+ try {
745
+ config.FS.chdir(start.cwd);
746
+ } catch {
747
+ }
748
+ }
749
+ ];
750
+ const module = await factory(config);
751
+ try {
752
+ exitCode = module.callMain(start.argv) ?? exitCode;
753
+ } catch (error) {
754
+ const status = error.status;
755
+ if (typeof status === "number") exitCode = status;
756
+ else {
757
+ port.postMessage({ type: "failed", message: String(error?.message ?? error) });
758
+ return;
759
+ }
760
+ }
761
+ port.postMessage({ type: "exit", code: exitCode });
762
+ }
763
+ function mountContainer(FS, client, mounts, errnoCodes) {
764
+ const SBXFS = createSbxFs(FS, client, errnoCodes);
765
+ for (const mount of mounts) {
766
+ if (INTERPRETER_OWNED.has(mount.guest)) continue;
767
+ try {
768
+ FS.mkdirTree(mount.guest);
769
+ } catch {
770
+ }
771
+ FS.mount(SBXFS, { root: mount.container }, mount.guest);
772
+ }
773
+ }
774
+ function bindStdio(FS, client, isTty) {
775
+ const readByte = () => {
776
+ const data = client.read(0, 1);
777
+ return data.length === 0 ? null : data[0];
778
+ };
779
+ if (!isTty) {
780
+ FS.init(readByte, writeThrough(client, 1), writeThrough(client, 2));
781
+ return;
782
+ }
783
+ FS.init(null, null, null);
784
+ for (const [fd, sink] of [[1, 1], [2, 2]]) {
785
+ const stream = FS.getStream(fd);
786
+ if (!stream) continue;
787
+ stream.stream_ops = {
788
+ ...stream.stream_ops,
789
+ write(_stream, buffer, offset, length) {
790
+ if (length > 0) client.writeAll(sink, buffer.subarray(offset, offset + length));
791
+ return length;
792
+ },
793
+ fsync() {
794
+ return 0;
795
+ }
796
+ };
797
+ }
798
+ const stdin = FS.getStream(0);
799
+ if (stdin) {
800
+ stdin.stream_ops = {
801
+ ...stdin.stream_ops,
802
+ read(_stream, buffer, offset, length) {
803
+ const data = client.read(0, length);
804
+ buffer.set(data, offset);
805
+ return data.length;
806
+ }
807
+ };
808
+ }
809
+ }
810
+ function writeThrough(client, fd) {
811
+ let pending = [];
812
+ return (charCode) => {
813
+ if (charCode === null) {
814
+ if (pending.length > 0) {
815
+ client.writeAll(fd, new Uint8Array(pending));
816
+ pending = [];
817
+ }
818
+ return;
819
+ }
820
+ pending.push(charCode);
821
+ if (charCode === 10 || pending.length >= 4096) {
822
+ client.writeAll(fd, new Uint8Array(pending));
823
+ pending = [];
824
+ }
825
+ };
826
+ }
827
+
828
+ // src/runtime/python/worker.ts
829
+ async function connect() {
830
+ const scope = globalThis;
831
+ if (typeof scope.postMessage === "function" && typeof scope.addEventListener === "function") {
832
+ const post = scope.postMessage.bind(globalThis);
833
+ const listen = scope.addEventListener.bind(globalThis);
834
+ return {
835
+ port: { postMessage: post },
836
+ onStart: (handler) => listen("message", (event) => {
837
+ const data = event.data;
838
+ if (data?.type === "start") handler(data);
839
+ })
840
+ };
841
+ }
842
+ const specifier = ["node", "worker_threads"].join(":");
843
+ const { parentPort } = await import(
844
+ /* @vite-ignore */
845
+ /* webpackIgnore: true */
846
+ specifier
847
+ );
848
+ return {
849
+ port: parentPort,
850
+ onStart: (handler) => parentPort.on("message", (message) => {
851
+ const data = message;
852
+ if (data?.type === "start") handler(data);
853
+ })
854
+ };
855
+ }
856
+ void connect().then(({ port, onStart }) => {
857
+ onStart((start) => {
858
+ void runPythonProcess(start, port).catch(
859
+ (error) => port.postMessage({ type: "failed", message: String(error?.message ?? error) })
860
+ );
861
+ });
862
+ port.postMessage({ type: "sandboxedjs:ready" });
863
+ });
864
+ //# sourceMappingURL=python-worker.js.map
865
+ //# sourceMappingURL=python-worker.js.map