@nimbus-sh/worker 0.2.0 → 0.2.2
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/dist/facets/manager.d.ts +47 -2
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +139 -15
- package/dist/loaders/generated-workers.d.ts +2 -2
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +3 -3
- package/dist/runtime/bash-repl.d.ts +1 -1
- package/dist/runtime/bash-repl.d.ts.map +1 -1
- package/dist/runtime/bash-repl.js +3 -2
- package/dist/runtime/cpython-resident.d.ts +27 -0
- package/dist/runtime/cpython-resident.d.ts.map +1 -0
- package/dist/runtime/cpython-resident.js +133 -0
- package/dist/runtime/facet-loader-host.d.ts +16 -1
- package/dist/runtime/facet-loader-host.d.ts.map +1 -1
- package/dist/runtime/facet-loader-host.js +42 -0
- package/dist/runtime/package-manager.d.ts +3 -58
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +6 -143
- package/dist/runtime/python-repl.d.ts +1 -1
- package/dist/runtime/python-repl.d.ts.map +1 -1
- package/dist/runtime/python-repl.js +2 -2
- package/dist/runtime/ruby-repl.js +1 -1
- package/dist/runtime/ruby-resident.d.ts +20 -0
- package/dist/runtime/ruby-resident.d.ts.map +1 -0
- package/dist/runtime/ruby-resident.js +278 -0
- package/dist/runtime/runtime-catalog.d.ts +4 -80
- package/dist/runtime/runtime-catalog.d.ts.map +1 -1
- package/dist/runtime/runtime-catalog.js +4 -67
- package/dist/session/init.d.ts +16 -7
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +210 -249
- package/dist/session/keys.d.ts +13 -0
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +13 -0
- package/dist/session/nimbus-session.d.ts +30 -2
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +49 -58
- package/dist/session/programmatic.d.ts +3 -3
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +3 -2
- package/dist/session/routes.js +1 -1
- package/dist/session/supervisor-rpc.d.ts +6 -13
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +7 -17
- package/package.json +2 -2
- package/scripts/bundle-facet-workers.mjs +9 -8
- package/scripts/bundle-runtime.mjs +276 -44
- package/dist/npm/tarball-stream.d.ts +0 -93
- package/dist/npm/tarball-stream.d.ts.map +0 -1
- package/dist/npm/tarball-stream.js +0 -235
- package/dist/npm/tarball.d.ts +0 -34
- package/dist/npm/tarball.d.ts.map +0 -1
- package/dist/npm/tarball.js +0 -97
- package/dist/runtime/bash-runner.d.ts +0 -63
- package/dist/runtime/bash-runner.d.ts.map +0 -1
- package/dist/runtime/bash-runner.generated.d.ts +0 -14
- package/dist/runtime/bash-runner.generated.d.ts.map +0 -1
- package/dist/runtime/bash-runner.generated.js +0 -13
- package/dist/runtime/bash-runner.js +0 -291
- package/dist/runtime/clang-runner.d.ts +0 -37
- package/dist/runtime/clang-runner.d.ts.map +0 -1
- package/dist/runtime/clang-runner.js +0 -865
- package/dist/runtime/cpython-runner.d.ts +0 -71
- package/dist/runtime/cpython-runner.d.ts.map +0 -1
- package/dist/runtime/cpython-runner.js +0 -535
- package/dist/runtime/python-pip.d.ts +0 -38
- package/dist/runtime/python-pip.d.ts.map +0 -1
- package/dist/runtime/python-pip.js +0 -1063
- package/dist/runtime/ruby-gems.d.ts +0 -30
- package/dist/runtime/ruby-gems.d.ts.map +0 -1
- package/dist/runtime/ruby-gems.js +0 -636
- package/dist/runtime/ruby-runner.d.ts +0 -75
- package/dist/runtime/ruby-runner.d.ts.map +0 -1
- package/dist/runtime/ruby-runner.js +0 -1577
- package/dist/runtime/runtime-registry.d.ts +0 -162
- package/dist/runtime/runtime-registry.d.ts.map +0 -1
- package/dist/runtime/runtime-registry.js +0 -364
- package/dist/runtime/virtual-socket-kernel.generated.d.ts +0 -14
- package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +0 -1
- package/dist/runtime/virtual-socket-kernel.generated.js +0 -13
- package/dist/runtime/wasm-runner.d.ts +0 -70
- package/dist/runtime/wasm-runner.d.ts.map +0 -1
- package/dist/runtime/wasm-runner.js +0 -664
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* npm-tarball-stream.ts — pure streaming tar primitives.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from npm-tarball.ts so these helpers can be esbuild-bundled
|
|
5
|
-
* WITHOUT dragging in NpmCache / NpmResolver / SqliteVFS. Consumed by
|
|
6
|
-
* scripts/bundle-facet-workers.mjs which emits a string constant the
|
|
7
|
-
* NimbusLoaderPool uses to inject the tar parser into dynamic workers.
|
|
8
|
-
*
|
|
9
|
-
* Zero dependencies. Works identically on the supervisor and inside a
|
|
10
|
-
* facet isolate. Never buffers the full decompressed tarball — peak
|
|
11
|
-
* transient heap is one file's bytes plus a 512-byte carry.
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* Maximum size of a single file inside a tarball. Larger entries are skipped.
|
|
15
|
-
*
|
|
16
|
-
* History: 5 MB was too low — it silently dropped `esbuild-wasm/esbuild.wasm`
|
|
17
|
-
* (11.35 MB on v0.24.2), which made Nimbus-in-Nimbus `npm run dev` fail with
|
|
18
|
-
* `No such module "esbuild-wasm/esbuild.wasm"` since the missing file caused
|
|
19
|
-
* esbuild's VFS plugin to mark the import `external`, and workerd's LOADER
|
|
20
|
-
* has no entry for that specifier. 20 MB covers esbuild-wasm with headroom
|
|
21
|
-
* while keeping per-facet peak heap bounded for the streaming extractor.
|
|
22
|
-
*/
|
|
23
|
-
export const MAX_FILE_BYTES = 20_000_000;
|
|
24
|
-
/**
|
|
25
|
-
* Read one tar header (USTAR) out of `block`. Returns parsed fields or
|
|
26
|
-
* `null` for an end-of-archive block (all zeros).
|
|
27
|
-
*/
|
|
28
|
-
/**
|
|
29
|
-
* Collapse "."/".." segments in a tar entry's package-relative path.
|
|
30
|
-
* Returns the canonical relative path, or '' when the entry escapes its
|
|
31
|
-
* package root (a leading ".." that pops above the root) — the caller
|
|
32
|
-
* treats '' as a no-name entry and skips it. Mirrors the segment logic in
|
|
33
|
-
* w7-frame's canonicalPath so joined write paths are always accepted.
|
|
34
|
-
*/
|
|
35
|
-
export function canonicalTarName(name) {
|
|
36
|
-
const out = [];
|
|
37
|
-
for (const seg of name.split('/')) {
|
|
38
|
-
if (seg === '..') {
|
|
39
|
-
if (out.length === 0)
|
|
40
|
-
return '';
|
|
41
|
-
out.pop();
|
|
42
|
-
}
|
|
43
|
-
else if (seg !== '' && seg !== '.') {
|
|
44
|
-
out.push(seg);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return out.join('/');
|
|
48
|
-
}
|
|
49
|
-
export function parseTarHeader(block) {
|
|
50
|
-
if (block[0] === 0)
|
|
51
|
-
return null;
|
|
52
|
-
let name = '';
|
|
53
|
-
for (let i = 0; i < 100 && block[i] !== 0; i++) {
|
|
54
|
-
name += String.fromCharCode(block[i]);
|
|
55
|
-
}
|
|
56
|
-
let prefix = '';
|
|
57
|
-
for (let i = 345; i < 500 && block[i] !== 0; i++) {
|
|
58
|
-
prefix += String.fromCharCode(block[i]);
|
|
59
|
-
}
|
|
60
|
-
if (prefix)
|
|
61
|
-
name = prefix + '/' + name;
|
|
62
|
-
// Strip the npm `package/` convention.
|
|
63
|
-
name = name.replace(/^package\//, '');
|
|
64
|
-
// Canonicalize the entry-relative path. npm tarballs legitimately carry
|
|
65
|
-
// entries like "./dist/index.js" (agent-base, http-proxy-agent,
|
|
66
|
-
// protobufjs, ...); left as-is the "./" survives into the VFS write path
|
|
67
|
-
// and the w7-frame writer rejects the noncanonical path, failing the
|
|
68
|
-
// whole shared install wave and dropping shard-mates' completion markers.
|
|
69
|
-
// Collapsing here (the one place entry names are assembled) keeps every
|
|
70
|
-
// downstream join canonical. An entry that escapes its package root via
|
|
71
|
-
// ".." is dropped to '' → skipped as a no-name entry.
|
|
72
|
-
name = canonicalTarName(name);
|
|
73
|
-
let sizeStr = '';
|
|
74
|
-
for (let i = 124; i < 136 && block[i] !== 0; i++) {
|
|
75
|
-
sizeStr += String.fromCharCode(block[i]);
|
|
76
|
-
}
|
|
77
|
-
const size = parseInt(sizeStr.trim(), 8) || 0;
|
|
78
|
-
const typeFlag = block[156];
|
|
79
|
-
return { name, size, typeFlag };
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Wrap a `ReadableStream<Uint8Array>` as an async iterable. Workerd and
|
|
83
|
-
* Node both support `Symbol.asyncIterator` on ReadableStream, but we
|
|
84
|
-
* spell the reader loop out so we don't depend on ambient lib typings.
|
|
85
|
-
*/
|
|
86
|
-
export async function* readableStreamToAsyncIterable(rs) {
|
|
87
|
-
const reader = rs.getReader();
|
|
88
|
-
try {
|
|
89
|
-
while (true) {
|
|
90
|
-
const { value, done } = await reader.read();
|
|
91
|
-
if (done)
|
|
92
|
-
return;
|
|
93
|
-
if (value && value.length > 0)
|
|
94
|
-
yield value;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
finally {
|
|
98
|
-
try {
|
|
99
|
-
reader.releaseLock();
|
|
100
|
-
}
|
|
101
|
-
catch { /* ignore */ }
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Streaming tar extractor.
|
|
106
|
-
*
|
|
107
|
-
* Consumes an async iterable of Uint8Array chunks (the decompressed tar
|
|
108
|
-
* byte stream) and yields one `{ name, data }` entry per regular file,
|
|
109
|
-
* as each file completes.
|
|
110
|
-
*
|
|
111
|
-
* Memory invariant: holds at most one pending file's bytes (≤ MAX_FILE_BYTES)
|
|
112
|
-
* plus a small carry buffer for the tar header being assembled.
|
|
113
|
-
*
|
|
114
|
-
* Skips: symlinks, directories, hardlinks, long-name extensions (PaxHeader),
|
|
115
|
-
* and any file whose declared size exceeds MAX_FILE_BYTES.
|
|
116
|
-
*
|
|
117
|
-
* If `onSkip` is provided, it is invoked for each skipped entry with the
|
|
118
|
-
* name, declared size, and reason code. Callers that need to surface
|
|
119
|
-
* dropped-file warnings to users should pass one; legacy callers that
|
|
120
|
-
* omit the arg still behave exactly as before (silent skip).
|
|
121
|
-
*/
|
|
122
|
-
export async function* streamTarEntries(source, onSkip) {
|
|
123
|
-
let carry = new Uint8Array(0);
|
|
124
|
-
let state = { kind: 'header' };
|
|
125
|
-
function concat(a, b) {
|
|
126
|
-
if (a.length === 0)
|
|
127
|
-
return b;
|
|
128
|
-
if (b.length === 0)
|
|
129
|
-
return a;
|
|
130
|
-
const out = new Uint8Array(a.length + b.length);
|
|
131
|
-
out.set(a, 0);
|
|
132
|
-
out.set(b, a.length);
|
|
133
|
-
return out;
|
|
134
|
-
}
|
|
135
|
-
for await (const chunkRaw of source) {
|
|
136
|
-
let buf = concat(carry, chunkRaw);
|
|
137
|
-
let cursor = 0;
|
|
138
|
-
while (true) {
|
|
139
|
-
if (state.kind === 'header') {
|
|
140
|
-
if (buf.length - cursor < 512)
|
|
141
|
-
break;
|
|
142
|
-
const header = buf.subarray(cursor, cursor + 512);
|
|
143
|
-
const parsed = parseTarHeader(header);
|
|
144
|
-
cursor += 512;
|
|
145
|
-
if (!parsed)
|
|
146
|
-
return; // end-of-archive
|
|
147
|
-
const { name, size, typeFlag } = parsed;
|
|
148
|
-
const pad = size === 0 ? 0 : (512 - (size % 512)) % 512;
|
|
149
|
-
const isRegularFile = (typeFlag === 48 /* '0' */ || typeFlag === 0);
|
|
150
|
-
if (size === 0) {
|
|
151
|
-
if (isRegularFile && name) {
|
|
152
|
-
yield { name, data: new Uint8Array(0) };
|
|
153
|
-
}
|
|
154
|
-
state = { kind: 'header' };
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (!isRegularFile || !name || size > MAX_FILE_BYTES) {
|
|
158
|
-
if (onSkip) {
|
|
159
|
-
// Classify before entering the skip state so the reason is
|
|
160
|
-
// exact. Precedence matches the condition order above:
|
|
161
|
-
// non-regular first (directories/symlinks/PaxHeaders are
|
|
162
|
-
// skipped regardless of size), then no-name, then too-large.
|
|
163
|
-
const reason = !isRegularFile
|
|
164
|
-
? 'non-regular'
|
|
165
|
-
: !name
|
|
166
|
-
? 'no-name'
|
|
167
|
-
: 'too-large';
|
|
168
|
-
try {
|
|
169
|
-
onSkip(name, size, reason);
|
|
170
|
-
}
|
|
171
|
-
catch { /* best-effort */ }
|
|
172
|
-
}
|
|
173
|
-
state = { kind: 'skip', remaining: size + pad };
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
state = {
|
|
177
|
-
kind: 'file',
|
|
178
|
-
name,
|
|
179
|
-
remaining: size,
|
|
180
|
-
fileBuf: new Uint8Array(size),
|
|
181
|
-
fileOffset: 0,
|
|
182
|
-
pad,
|
|
183
|
-
skip: false,
|
|
184
|
-
};
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
if (state.kind === 'file') {
|
|
188
|
-
const avail = buf.length - cursor;
|
|
189
|
-
if (avail === 0)
|
|
190
|
-
break;
|
|
191
|
-
if (state.remaining > 0) {
|
|
192
|
-
const take = Math.min(state.remaining, avail);
|
|
193
|
-
state.fileBuf.set(buf.subarray(cursor, cursor + take), state.fileOffset);
|
|
194
|
-
state.fileOffset += take;
|
|
195
|
-
state.remaining -= take;
|
|
196
|
-
cursor += take;
|
|
197
|
-
if (state.remaining > 0)
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
if (state.pad > 0) {
|
|
201
|
-
const avail2 = buf.length - cursor;
|
|
202
|
-
if (avail2 === 0)
|
|
203
|
-
break;
|
|
204
|
-
const take = Math.min(state.pad, avail2);
|
|
205
|
-
state.pad -= take;
|
|
206
|
-
cursor += take;
|
|
207
|
-
if (state.pad > 0)
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
yield { name: state.name, data: state.fileBuf };
|
|
211
|
-
state = { kind: 'header' };
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
// state.kind === 'skip'
|
|
215
|
-
const avail = buf.length - cursor;
|
|
216
|
-
if (avail === 0)
|
|
217
|
-
break;
|
|
218
|
-
const take = Math.min(state.remaining, avail);
|
|
219
|
-
cursor += take;
|
|
220
|
-
state.remaining -= take;
|
|
221
|
-
if (state.remaining > 0)
|
|
222
|
-
break;
|
|
223
|
-
state = { kind: 'header' };
|
|
224
|
-
}
|
|
225
|
-
if (cursor >= buf.length) {
|
|
226
|
-
carry = new Uint8Array(0);
|
|
227
|
-
}
|
|
228
|
-
else if (cursor === 0) {
|
|
229
|
-
carry = buf;
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
carry = buf.slice(cursor);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
package/dist/npm/tarball.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* npm-tarball.ts — streaming tarball extraction (gunzip + tar).
|
|
3
|
-
*
|
|
4
|
-
* `extractTarball` / `extractTarballFromResponse` walk a gzipped tar stream
|
|
5
|
-
* entry-by-entry, holding at most one file's bytes at a time. Used by the
|
|
6
|
-
* install-batch facet (whose own 128 MiB envelope, not the supervisor's,
|
|
7
|
-
* holds the bytes) and the ruby-gems runtime.
|
|
8
|
-
*
|
|
9
|
-
* The legacy supervisor-resident `fetchWaves` / `buildBatchPayload` and the
|
|
10
|
-
* `buildCacheRestorePayload` fast path were removed — they ran in supervisor
|
|
11
|
-
* heap and held tarball bytes long enough to OOM the DO on large installs.
|
|
12
|
-
* The single batch-facet path (install-batch-facet.ts) supersedes them and
|
|
13
|
-
* consults the shared L2/L3 tarball cache directly.
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* Streaming extractor driven by a `Response` body.
|
|
17
|
-
*
|
|
18
|
-
* Pipes `resp.body` through `DecompressionStream('gzip')` (npm tarballs are
|
|
19
|
-
* always gzipped) and walks the tar stream entry-by-entry. Never buffers the
|
|
20
|
-
* full decompressed tarball — peak transient heap is one file's bytes plus a
|
|
21
|
-
* small carry buffer.
|
|
22
|
-
*
|
|
23
|
-
* The returned Map is per-file Uint8Arrays. If the response has no body
|
|
24
|
-
* (unusual but possible with some proxies), we fall back to `arrayBuffer()`
|
|
25
|
-
* + `extractTarball` so we still make progress.
|
|
26
|
-
*/
|
|
27
|
-
export declare function extractTarballFromResponse(resp: Response): Promise<Map<string, Uint8Array>>;
|
|
28
|
-
/**
|
|
29
|
-
* Legacy buffered extractor. Kept for code paths that receive a fully-buffered
|
|
30
|
-
* tarball (e.g. the tarball cache restore path, which already stores bytes in
|
|
31
|
-
* SQLite as Uint8Arrays). New install paths use `streamTarEntries` instead.
|
|
32
|
-
*/
|
|
33
|
-
export declare function extractTarball(tarball: ArrayBuffer): Promise<Map<string, Uint8Array>>;
|
|
34
|
-
//# sourceMappingURL=tarball.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tarball.d.ts","sourceRoot":"","sources":["../../src/npm/tarball.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH;;;;;;;;;;;GAWG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAmClC;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAsBlC"}
|
package/dist/npm/tarball.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* npm-tarball.ts — streaming tarball extraction (gunzip + tar).
|
|
3
|
-
*
|
|
4
|
-
* `extractTarball` / `extractTarballFromResponse` walk a gzipped tar stream
|
|
5
|
-
* entry-by-entry, holding at most one file's bytes at a time. Used by the
|
|
6
|
-
* install-batch facet (whose own 128 MiB envelope, not the supervisor's,
|
|
7
|
-
* holds the bytes) and the ruby-gems runtime.
|
|
8
|
-
*
|
|
9
|
-
* The legacy supervisor-resident `fetchWaves` / `buildBatchPayload` and the
|
|
10
|
-
* `buildCacheRestorePayload` fast path were removed — they ran in supervisor
|
|
11
|
-
* heap and held tarball bytes long enough to OOM the DO on large installs.
|
|
12
|
-
* The single batch-facet path (install-batch-facet.ts) supersedes them and
|
|
13
|
-
* consults the shared L2/L3 tarball cache directly.
|
|
14
|
-
*/
|
|
15
|
-
import { streamTarEntries, readableStreamToAsyncIterable, } from './tarball-stream.js';
|
|
16
|
-
// ── Tarball extraction ──────────────────────────────────────────────────
|
|
17
|
-
//
|
|
18
|
-
// The streaming primitives (parseTarHeader, streamTarEntries,
|
|
19
|
-
// readableStreamToAsyncIterable, MAX_FILE_BYTES) live in
|
|
20
|
-
// ./npm-tarball-stream.ts — a dependency-free leaf so
|
|
21
|
-
// scripts/bundle-facet-workers.mjs can esbuild it into a string the facet
|
|
22
|
-
// pool injects into dynamic workers. Import them directly from that module.
|
|
23
|
-
/**
|
|
24
|
-
* Streaming extractor driven by a `Response` body.
|
|
25
|
-
*
|
|
26
|
-
* Pipes `resp.body` through `DecompressionStream('gzip')` (npm tarballs are
|
|
27
|
-
* always gzipped) and walks the tar stream entry-by-entry. Never buffers the
|
|
28
|
-
* full decompressed tarball — peak transient heap is one file's bytes plus a
|
|
29
|
-
* small carry buffer.
|
|
30
|
-
*
|
|
31
|
-
* The returned Map is per-file Uint8Arrays. If the response has no body
|
|
32
|
-
* (unusual but possible with some proxies), we fall back to `arrayBuffer()`
|
|
33
|
-
* + `extractTarball` so we still make progress.
|
|
34
|
-
*/
|
|
35
|
-
export async function extractTarballFromResponse(resp) {
|
|
36
|
-
const files = new Map();
|
|
37
|
-
const body = resp.body;
|
|
38
|
-
if (!body) {
|
|
39
|
-
// Fallback: no streamable body (shouldn't happen, but handle it).
|
|
40
|
-
const buf = await resp.arrayBuffer();
|
|
41
|
-
return extractTarball(buf);
|
|
42
|
-
}
|
|
43
|
-
// Time-bound the whole extraction to detect hung gunzip streams.
|
|
44
|
-
const controller = new AbortController();
|
|
45
|
-
const timer = setTimeout(() => controller.abort(), 60_000);
|
|
46
|
-
try {
|
|
47
|
-
const decompressed = body.pipeThrough(new DecompressionStream('gzip'));
|
|
48
|
-
const source = readableStreamToAsyncIterable(decompressed);
|
|
49
|
-
for await (const entry of streamTarEntries(source)) {
|
|
50
|
-
if (controller.signal.aborted)
|
|
51
|
-
throw new Error('tarball extract timeout');
|
|
52
|
-
files.set(entry.name, entry.data);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch (e) {
|
|
56
|
-
// On any error, just return whatever we managed to extract. Callers
|
|
57
|
-
// treat `files.size === 0` as a failed fetch and mark the package failed.
|
|
58
|
-
// If partial data is present (some files succeeded before the stream
|
|
59
|
-
// broke), we propagate it; the installer will re-fetch on mismatch.
|
|
60
|
-
if (files.size === 0) {
|
|
61
|
-
clearTimeout(timer);
|
|
62
|
-
throw e;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
finally {
|
|
66
|
-
clearTimeout(timer);
|
|
67
|
-
}
|
|
68
|
-
return files;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Legacy buffered extractor. Kept for code paths that receive a fully-buffered
|
|
72
|
-
* tarball (e.g. the tarball cache restore path, which already stores bytes in
|
|
73
|
-
* SQLite as Uint8Arrays). New install paths use `streamTarEntries` instead.
|
|
74
|
-
*/
|
|
75
|
-
export async function extractTarball(tarball) {
|
|
76
|
-
const files = new Map();
|
|
77
|
-
const raw = new Uint8Array(tarball);
|
|
78
|
-
// Adapter: wrap the single buffer as an async iterable. If gzipped, pipe
|
|
79
|
-
// through DecompressionStream so the streaming parser still sees tar bytes.
|
|
80
|
-
let source;
|
|
81
|
-
if (raw[0] === 0x1f && raw[1] === 0x8b) {
|
|
82
|
-
const rs = new Blob([tarball]).stream().pipeThrough(new DecompressionStream('gzip'));
|
|
83
|
-
source = readableStreamToAsyncIterable(rs);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
source = (async function* () { yield raw; })();
|
|
87
|
-
}
|
|
88
|
-
try {
|
|
89
|
-
for await (const entry of streamTarEntries(source)) {
|
|
90
|
-
files.set(entry.name, entry.data);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return files;
|
|
95
|
-
}
|
|
96
|
-
return files;
|
|
97
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* bash-runner — real GNU bash 5.2.37 (wasm32-wasi, asyncified) in a
|
|
3
|
-
* dedicated facet.
|
|
4
|
-
*
|
|
5
|
-
* bash CANNOT run through the stock JSPI wasm-runner: the binary is
|
|
6
|
-
* asyncify-instrumented (fork/setjmp/blocking-pipe unwinds) and needs
|
|
7
|
-
* 15 `nimbus_proc` imports plus MULTIPLE instances per facet (fork).
|
|
8
|
-
* This runner embeds the proven multi-instance fork/pipe/exec/setjmp
|
|
9
|
-
* scheduler (packages/worker/wasm/bash/run-bash-fork.mjs — the local
|
|
10
|
-
* acid-test driver, itself a port of the PROVEN-LIVE fork M1/M2/M3
|
|
11
|
-
* mechanisms) as a facet preamble.
|
|
12
|
-
*
|
|
13
|
-
* Architecture (mirrors ruby-runner's facet dispatch):
|
|
14
|
-
* - bash.async.wasm + the coreutil exec targets ride the LOADER
|
|
15
|
-
* modules map (compiled by workerd at module-load; exposed on
|
|
16
|
-
* globalThis.__NIMBUS_WASM).
|
|
17
|
-
* - The preamble defines __bashBoot / __bashFeed. Boot instantiates
|
|
18
|
-
* bash, pumps the scheduler until the process tree exits or the
|
|
19
|
-
* root parks on a terminal stdin read; each feed delivers stdin
|
|
20
|
-
* bytes and pumps again. Facet state persists across submits on
|
|
21
|
-
* the warm isolate (same mechanism as __rubyInstance caching).
|
|
22
|
-
* - stdout/stderr accumulate per pump slice and stream back to the
|
|
23
|
-
* CommandContext; VFS writes come back as a WasiFsDiff on exit.
|
|
24
|
-
*/
|
|
25
|
-
import type { RuntimeManifest } from './runtime-catalog.js';
|
|
26
|
-
import type { CredentialedVfs, SqliteVFS } from '@nimbus-sh/core/vfs/sqlite-vfs.js';
|
|
27
|
-
import type { FacetManager } from '../facets/manager.js';
|
|
28
|
-
import type { Command } from '@nimbus-sh/core/substrate/lifo/commands/types.js';
|
|
29
|
-
import type { BashSlice } from '@nimbus-sh/core/runtime/bash/types.js';
|
|
30
|
-
type BashRunnerFactory = (manifest: RuntimeManifest, installRoot: string, binName: string, binKind: string | undefined) => Command;
|
|
31
|
-
export interface BashFacetSession {
|
|
32
|
-
readonly initial: BashSlice;
|
|
33
|
-
push(data: string, eof?: boolean): Promise<BashSlice>;
|
|
34
|
-
close(): Promise<void>;
|
|
35
|
-
}
|
|
36
|
-
export declare function createBashFacetSession(deps: {
|
|
37
|
-
facetMgr: FacetManager;
|
|
38
|
-
vfs: CredentialedVfs;
|
|
39
|
-
manifest: RuntimeManifest;
|
|
40
|
-
installRoot: string;
|
|
41
|
-
argv: string[];
|
|
42
|
-
env: Record<string, string>;
|
|
43
|
-
cwd: string;
|
|
44
|
-
stdinData?: string;
|
|
45
|
-
stdinClosed: boolean;
|
|
46
|
-
stdinTty: boolean;
|
|
47
|
-
extraRoots?: string[];
|
|
48
|
-
}): Promise<BashFacetSession>;
|
|
49
|
-
export declare function makeBashRunnerFactory(deps: {
|
|
50
|
-
facetMgr: FacetManager;
|
|
51
|
-
vfs: SqliteVFS;
|
|
52
|
-
}): BashRunnerFactory;
|
|
53
|
-
/**
|
|
54
|
-
* Source string injected as the loader-pool `preamble`. The facet's module init
|
|
55
|
-
* evaluates it verbatim so `__bashBoot` / `__bashFeed` are in scope when the
|
|
56
|
-
* user fn runs. Self-contained — no closure captures, no imports.
|
|
57
|
-
*
|
|
58
|
-
* The scheduler itself lives in `bash/preamble.ts` as real TypeScript; the build
|
|
59
|
-
* bundles it into `bash-runner.generated.ts`.
|
|
60
|
-
*/
|
|
61
|
-
export declare const BASH_RUNNER_PREAMBLE: string;
|
|
62
|
-
export {};
|
|
63
|
-
//# sourceMappingURL=bash-runner.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bash-runner.d.ts","sourceRoot":"","sources":["../../src/runtime/bash-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAsC,MAAM,kDAAkD,CAAC;AAGpH,OAAO,KAAK,EAA8B,SAAS,EAAE,MAAM,uCAAuC,CAAC;AAOnG,KAAK,iBAAiB,GAAG,CACvB,QAAQ,EAAE,eAAe,EACzB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAAG,SAAS,KACxB,OAAO,CAAC;AAsDb,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACtD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,QAAQ,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,eAAe,CAAC;IACrB,QAAQ,EAAE,eAAe,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyG5B;AA6BD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,SAAS,CAAC;CAChB,GAAG,iBAAiB,CAsFpB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAA6B,CAAC"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* bash-runner.generated.ts — AUTO-GENERATED. DO NOT EDIT.
|
|
3
|
-
*
|
|
4
|
-
* Produced by scripts/bundle-facet-workers.mjs from:
|
|
5
|
-
* - @nimbus-sh/core src/runtime/bash/preamble.ts
|
|
6
|
-
*
|
|
7
|
-
* The facet-side bash scheduler as a self-contained IIFE that installs
|
|
8
|
-
* globalThis.__bashBoot / globalThis.__bashFeed. bash-runner.ts re-exports it
|
|
9
|
-
* as BASH_RUNNER_PREAMBLE and passes it as the loader-pool preamble.
|
|
10
|
-
*
|
|
11
|
-
* Size: 58.56 KiB
|
|
12
|
-
*/
|
|
13
|
-
export declare const BASH_RUNNER_BODY_SRC: string;
|
|
14
|
-
//# sourceMappingURL=bash-runner.generated.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bash-runner.generated.d.ts","sourceRoot":"","sources":["../../src/runtime/bash-runner.generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,oBAAoB,EAAE,MAA8i5D,CAAC"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* bash-runner.generated.ts — AUTO-GENERATED. DO NOT EDIT.
|
|
3
|
-
*
|
|
4
|
-
* Produced by scripts/bundle-facet-workers.mjs from:
|
|
5
|
-
* - @nimbus-sh/core src/runtime/bash/preamble.ts
|
|
6
|
-
*
|
|
7
|
-
* The facet-side bash scheduler as a self-contained IIFE that installs
|
|
8
|
-
* globalThis.__bashBoot / globalThis.__bashFeed. bash-runner.ts re-exports it
|
|
9
|
-
* as BASH_RUNNER_PREAMBLE and passes it as the loader-pool preamble.
|
|
10
|
-
*
|
|
11
|
-
* Size: 58.56 KiB
|
|
12
|
-
*/
|
|
13
|
-
export const BASH_RUNNER_BODY_SRC = "\"use strict\";\n(() => {\n // ../core/src/runtime/bash/preamble.ts\n var PAGE = 65536, te = new TextEncoder(), td = new TextDecoder();\n var MAIN_SIZE = 8 << 20, SLOT_SIZE = 256 << 10, NSLOT = 32;\n var E = { ACCES: 2, BADF: 8, EXIST: 20, INVAL: 28, ISDIR: 31, LOOP: 32, NOENT: 44, NOSYS: 52, NOTDIR: 54, NOTEMPTY: 55, PERM: 63, SPIPE: 70 };\n var CLOCK_REALTIME = 0, CLOCK_MONOTONIC = 1, CLOCK_PROCESS_CPUTIME = 2, CLOCK_THREAD_CPUTIME = 3;\n function realtimeNs() {\n return BigInt(Date.now()) * 1000000n;\n }\n function monotonicNs() {\n const ms = typeof performance !== \"undefined\" && performance.now ? performance.now() : Date.now();\n return BigInt(Math.floor(ms * 1e3)) * 1000n;\n }\n function clockNs(id) {\n if (id === CLOCK_REALTIME) return realtimeNs();\n if (id === CLOCK_MONOTONIC || id === CLOCK_PROCESS_CPUTIME || id === CLOCK_THREAD_CPUTIME) return monotonicNs();\n return null;\n }\n var Exit = class {\n constructor(c) {\n this.code = c;\n }\n };\n var S = null;\n function norm(p) {\n const parts = [];\n for (const seg of String(p).split(\"/\")) {\n if (!seg || seg === \".\") continue;\n if (seg === \"..\") {\n parts.pop();\n continue;\n }\n parts.push(seg);\n }\n return parts.join(\"/\");\n }\n function b64ToBytes(b64) {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n function bytesToB64(bytes) {\n let bin = \"\";\n const CH = 32768;\n for (let i = 0; i < bytes.length; i += CH) bin += String.fromCharCode.apply(null, bytes.subarray(i, i + CH));\n return btoa(bin);\n }\n function newSession(args) {\n const wasmTable = globalThis.__NIMBUS_WASM || {};\n const mod = wasmTable[\"bash.async.wasm\"];\n if (!mod) throw new Error(\"bash.async.wasm missing from __NIMBUS_WASM\");\n const coreutils = /* @__PURE__ */ new Map();\n for (const key of Object.keys(wasmTable)) {\n if (key.startsWith(\"cu_\") && key.endsWith(\".wasm\")) coreutils.set(key.slice(3, -5), wasmTable[key]);\n }\n const busybox = wasmTable[\"cu_busybox.wasm\"];\n if (busybox) for (const name of args.busyboxApplets || []) coreutils.set(name, busybox);\n const fs = {\n files: /* @__PURE__ */ new Map(),\n dirs: /* @__PURE__ */ new Set(),\n modes: /* @__PURE__ */ new Map(),\n written: /* @__PURE__ */ new Set(),\n deleted: /* @__PURE__ */ new Set(),\n dirsCreated: /* @__PURE__ */ new Set(),\n dirsDeleted: /* @__PURE__ */ new Set(),\n modesChanged: /* @__PURE__ */ new Map(),\n // vfsPath → mtime ns. The snapshot carries no times, so an inode the\n // session did not touch reports sessionNs: older than anything written\n // here, which is what makes 'is the target newer than its source' — the\n // question make(1) and every incremental build asks — answerable.\n times: /* @__PURE__ */ new Map(),\n sessionNs: realtimeNs(),\n // vfsPath → target string, stored verbatim per POSIX: a symlink is a dumb\n // string resolved at lookup time, not a pointer to an inode.\n symlinks: /* @__PURE__ */ new Map(),\n symlinksCreated: /* @__PURE__ */ new Map()\n };\n const snap = args.fsSnapshot || { files: {}, dirs: [], modes: {} };\n for (const [path, b64] of Object.entries(snap.files || {})) fs.files.set(norm(path), { bytes: b64ToBytes(b64) });\n for (const d of snap.dirs || []) fs.dirs.add(norm(d));\n for (const [path, m] of Object.entries(snap.modes || {})) fs.modes.set(norm(path), Number(m) & 7);\n for (const seed of [\"etc\", \"dev\", \"bin\", \"usr\", \"usr/bin\"]) {\n fs.dirs.add(seed);\n if (!fs.modes.has(seed)) fs.modes.set(seed, 5);\n }\n if (!fs.files.has(\"etc/nimbus.bashrc\")) {\n fs.files.set(\"etc/nimbus.bashrc\", { bytes: te.encode('command cd \"${NIMBUS_PWD:-/}\" 2>/dev/null\\n') });\n fs.modes.set(\"etc/nimbus.bashrc\", 4);\n }\n const home = norm((args.environ.find((e) => e.startsWith(\"HOME=\")) || \"HOME=/home/user\").slice(5));\n if (args.stdinTty && home && !fs.files.has(home + \"/.bashrc\")) {\n fs.files.set(home + \"/.bashrc\", { bytes: te.encode(\". /etc/nimbus.bashrc\\n\") });\n if (!fs.modes.has(home + \"/.bashrc\")) fs.modes.set(home + \"/.bashrc\", 6);\n }\n const cwd = norm((args.environ.find((e) => e.startsWith(\"NIMBUS_PWD=\")) || \"NIMBUS_PWD=/\").slice(11));\n return {\n mod,\n coreutils,\n fs,\n cwd,\n argv: args.argv,\n environ: args.environ,\n stdinTty: !!args.stdinTty,\n stdin: { chunks: args.stdinData ? [te.encode(args.stdinData)] : [], queued: 0, closed: !!args.stdinClosed, waiters: [] },\n procs: /* @__PURE__ */ new Map(),\n pipes: /* @__PURE__ */ new Map(),\n runnable: [],\n exitStatus: /* @__PURE__ */ new Map(),\n waiters: [],\n pidNext: 100,\n pipeNext: 1,\n rootPid: 0,\n rootExit: null,\n steps: 0,\n out: \"\",\n err: \"\",\n missingWasi: /* @__PURE__ */ new Set(),\n stats: { instances: 0, memPeak: 0, mainHi: 0, slotHi: 0 },\n error: null\n };\n }\n function initStdinQueued(s) {\n s.stdin.queued = s.stdin.chunks.reduce((a, c) => a + c.length, 0);\n }\n function newPipe(s) {\n const id = s.pipeNext++;\n s.pipes.set(id, { chunks: [], queued: 0, readers: 1, writers: 1, readW: [] });\n return id;\n }\n function lowestFd(proc) {\n let fd = 0;\n while (proc.fds.has(fd) || fd === 3) fd++;\n return fd;\n }\n function bumpPipe(s, e, d) {\n const pp = s.pipes.get(e.pipeId);\n if (e.end === \"r\") pp.readers += d;\n else pp.writers += d;\n }\n function closeFd(s, proc, fd) {\n const e = proc.fds.get(fd);\n if (!e) return;\n proc.fds.delete(fd);\n if (e.kind === \"pipe\") {\n bumpPipe(s, e, -1);\n wakePipe(s, s.pipes.get(e.pipeId));\n }\n }\n function takeUpTo(src, max) {\n let need = max;\n const parts = [];\n while (need > 0 && src.chunks.length) {\n const ch = src.chunks[0];\n if (ch.length <= need) {\n parts.push(ch);\n need -= ch.length;\n src.chunks.shift();\n } else {\n parts.push(ch.subarray(0, need));\n src.chunks[0] = ch.subarray(need);\n need = 0;\n }\n }\n const total = max - need;\n src.queued -= total;\n const o = new Uint8Array(total);\n let x = 0;\n for (const p of parts) {\n o.set(p, x);\n x += p.length;\n }\n return o;\n }\n var EMPTY_IOV = { list: [], total: 0 };\n function readIovs(dv, iovs, n) {\n const list = [];\n let total = 0;\n for (let i = 0; i < n; i++) {\n const ptr = dv.getUint32(iovs + i * 8, true), len = dv.getUint32(iovs + i * 8 + 4, true);\n if (len > 0) {\n list.push({ ptr, len });\n total += len;\n }\n }\n return { list, total };\n }\n function scatter(u8, iov, bytes) {\n let off = 0;\n for (const b of iov.list) {\n if (off >= bytes.length) break;\n const n = Math.min(b.len, bytes.length - off);\n u8.set(bytes.subarray(off, off + n), b.ptr);\n off += n;\n }\n return off;\n }\n function wakePipe(s, pp) {\n while (pp.readW.length && (pp.queued > 0 || pp.writers === 0)) {\n const w = pp.readW.shift();\n const proc = w.proc;\n const req = proc.ctx.pipeReq;\n const bytes = pp.queued > 0 ? takeUpTo(pp, req.iov.total) : new Uint8Array(0);\n proc.pendingRead = { iov: req.iov, bytes, nreadPtr: req.nreadPtr, pollUserdata: req.pollUserdata };\n resumeProc(proc);\n }\n }\n function wakeStdin(s) {\n const st = s.stdin;\n while (st.waiters.length && (st.queued > 0 || st.closed)) {\n const w = st.waiters.shift();\n const proc = w.proc;\n const req = proc.ctx.pipeReq;\n const bytes = st.queued > 0 ? takeUpTo(st, req.iov.total) : new Uint8Array(0);\n proc.pendingRead = { iov: req.iov, bytes, nreadPtr: req.nreadPtr, pollUserdata: req.pollUserdata };\n resumeProc(proc);\n }\n }\n function fileLookup(s, path) {\n return s.fs.files.get(path) || null;\n }\n function isDir(s, path) {\n return path === \"\" || s.fs.dirs.has(path);\n }\n function isCoreutil(s, path) {\n if (!path.startsWith(\"bin/\") && !path.startsWith(\"usr/bin/\")) return false;\n return s.coreutils.has(path.split(\"/\").pop());\n }\n function fileExists(s, path) {\n return s.fs.files.has(path) || s.fs.modes.has(path) && !s.fs.dirs.has(path);\n }\n function inodeExists(s, path) {\n return s.fs.files.has(path) || s.fs.dirs.has(path) || s.fs.modes.has(path);\n }\n function effMode(s, path) {\n if (path === \"\") return 7;\n const m = s.fs.modes.get(path);\n if (m !== void 0) return m;\n if (isCoreutil(s, path)) return 5;\n return inodeExists(s, path) ? 0 : 7;\n }\n function parentOf(path) {\n const i = path.lastIndexOf(\"/\");\n return i < 0 ? \"\" : path.slice(0, i);\n }\n function followPath(s, path) {\n let cur = path;\n for (let hops = 0; hops < 32; hops++) {\n if (!s.fs.symlinks.size) return { path: cur, errno: 0 };\n const parts = cur.split(\"/\");\n let prefix = \"\";\n let rewrote = false;\n for (let i = 0; i < parts.length; i++) {\n prefix = prefix ? prefix + \"/\" + parts[i] : parts[i];\n const target = s.fs.symlinks.get(prefix);\n if (target === void 0) continue;\n const base = target.startsWith(\"/\") ? norm(target) : norm(parentOf(prefix) + \"/\" + target);\n const rest = parts.slice(i + 1).join(\"/\");\n cur = rest ? norm(base + \"/\" + rest) : base;\n rewrote = true;\n break;\n }\n if (!rewrote) return { path: cur, errno: 0 };\n }\n return { path: cur, errno: E.LOOP };\n }\n function followParents(s, path) {\n const parent = parentOf(path);\n if (!parent) return { path, errno: 0 };\n const r = followPath(s, parent);\n if (r.errno) return r;\n const name = path.slice(parent.length + 1);\n return { path: r.path ? r.path + \"/\" + name : name, errno: 0 };\n }\n function checkTraversal(s, path) {\n const parts = path.split(\"/\");\n let anc = \"\";\n for (let i = 0; i < parts.length - 1; i++) {\n anc = anc ? anc + \"/\" + parts[i] : parts[i];\n if (!isDir(s, anc)) return inodeExists(s, anc) ? E.NOTDIR : E.NOENT;\n if ((effMode(s, anc) & 1) === 0) return E.ACCES;\n }\n return 0;\n }\n function checkParentWritable(s, path) {\n const parent = parentOf(path);\n if (parent && (effMode(s, parent) & 3) !== 3) return E.ACCES;\n return 0;\n }\n function recordDirAdded(s, path) {\n s.fs.dirs.add(path);\n s.fs.dirsDeleted.delete(path);\n s.fs.dirsCreated.add(path);\n if (!s.fs.modes.has(path)) s.fs.modes.set(path, 7);\n touchPath(s, path);\n }\n function recordDirRemoved(s, path) {\n s.fs.dirs.delete(path);\n s.fs.modes.delete(path);\n s.fs.times.delete(path);\n if (s.fs.dirsCreated.has(path)) s.fs.dirsCreated.delete(path);\n else s.fs.dirsDeleted.add(path);\n }\n function recordFileRemoved(s, path) {\n s.fs.files.delete(path);\n s.fs.written.delete(path);\n s.fs.modes.delete(path);\n s.fs.times.delete(path);\n s.fs.symlinks.delete(path);\n s.fs.symlinksCreated.delete(path);\n s.fs.deleted.add(path);\n }\n function markWritten(s, path) {\n s.fs.written.add(path);\n s.fs.deleted.delete(path);\n if (!s.fs.modes.has(path)) s.fs.modes.set(path, 6);\n touchPath(s, path);\n const parts = path.split(\"/\");\n for (let i = 1; i < parts.length; i++) {\n const dir = parts.slice(0, i).join(\"/\");\n if (!s.fs.dirs.has(dir)) recordDirAdded(s, dir);\n }\n }\n function fileWrite(s, entry, path, pos, bytes) {\n const need = pos + bytes.length;\n if (need > entry.bytes.length) {\n const grown = new Uint8Array(need);\n grown.set(entry.bytes);\n entry.bytes = grown;\n }\n entry.bytes.set(bytes, pos);\n markWritten(s, path);\n }\n function mtimeNs(s, path) {\n const t = s.fs.times.get(path);\n return t === void 0 ? s.fs.sessionNs : t;\n }\n function touchPath(s, path) {\n s.fs.times.set(path, realtimeNs());\n }\n function statBuf(dv, u8, buf, filetype, size, timeNs) {\n u8.fill(0, buf, buf + 64);\n dv.setUint8(buf + 16, filetype);\n dv.setBigUint64(buf + 24, 1n, true);\n dv.setBigUint64(buf + 32, BigInt(size), true);\n dv.setBigUint64(buf + 40, timeNs, true);\n dv.setBigUint64(buf + 48, timeNs, true);\n dv.setBigUint64(buf + 56, timeNs, true);\n }\n function isUnderAbsRoot(p, absRoots) {\n if (!absRoots) return false;\n for (const a of absRoots) {\n if (p === a) return true;\n if (a !== \"\" && p.startsWith(a + \"/\")) return true;\n }\n return false;\n }\n function makeResolve(s, pathBase, absRoots) {\n return (raw) => {\n const p = norm(raw);\n if (!pathBase) return p;\n if (isUnderAbsRoot(p, absRoots)) return p;\n const joined = norm(pathBase + \"/\" + p);\n if (inodeExists(s, joined)) return joined;\n if (inodeExists(s, p)) return p;\n if (isDir(s, parentOf(joined))) return joined;\n if (isDir(s, parentOf(p))) return p;\n return joined;\n };\n }\n function makeUnsupported(s) {\n const nosys = (name) => () => {\n s.missingWasi.add(name);\n return E.NOSYS;\n };\n return {\n // Every write is already durable in the session's own heap, so there is\n // nothing to flush and success is the honest answer.\n fd_sync: () => 0,\n fd_datasync: () => 0,\n fd_advise: () => 0,\n // One process runs at a time under this scheduler; a yield has nothing to\n // yield to that the caller has not already reached.\n sched_yield: () => 0,\n fd_pread: nosys(\"fd_pread\"),\n fd_pwrite: nosys(\"fd_pwrite\"),\n fd_allocate: nosys(\"fd_allocate\"),\n fd_filestat_set_size: nosys(\"fd_filestat_set_size\"),\n fd_filestat_set_times: nosys(\"fd_filestat_set_times\"),\n fd_fdstat_set_rights: nosys(\"fd_fdstat_set_rights\"),\n proc_raise: nosys(\"proc_raise\"),\n sock_send: nosys(\"sock_send\"),\n sock_recv: nosys(\"sock_recv\"),\n sock_shutdown: nosys(\"sock_shutdown\")\n };\n }\n function makeWasiFs(s, proc, DV, U8, io, pathBase, absRoots) {\n const resolve = makeResolve(s, pathBase, absRoots);\n return {\n ...makeUnsupported(s),\n fd_prestat_get: (fd, buf) => {\n if (fd === 3) {\n DV().setUint8(buf, 0);\n DV().setUint32(buf + 4, 1, true);\n return 0;\n }\n return E.BADF;\n },\n // The buffer length is a contract, not decoration: fd_prestat_get reports\n // the name is 1 byte, and a guest that offers less must not be written to.\n fd_prestat_dir_name: (fd, path, plen) => {\n if (fd !== 3) return E.BADF;\n if (plen < 1) return E.INVAL;\n U8()[path] = 47;\n return 0;\n },\n path_open: (dirfd, dirflags, pathPtr, pathLen, oflags, rightsBase, _ri, fdflags, retPtr) => {\n const raw = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n const r = (dirflags & 1) !== 0 ? followPath(s, raw) : followParents(s, raw);\n if (r.errno) return r.errno;\n const path = r.path;\n const dv = DV();\n if (path === \"dev/null\" || path === \"dev/tty\") {\n const fd2 = lowestFd(proc);\n proc.fds.set(fd2, { kind: path === \"dev/tty\" ? \"tty\" : \"null\" });\n dv.setUint32(retPtr, fd2, true);\n return 0;\n }\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n const wantDir = (oflags & 2) !== 0;\n if (isDir(s, path)) {\n if ((effMode(s, path) & 1) === 0) return E.ACCES;\n const fd2 = lowestFd(proc);\n proc.fds.set(fd2, { kind: \"dir\", path });\n dv.setUint32(retPtr, fd2, true);\n return 0;\n }\n if (wantDir) return fileExists(s, path) ? E.NOTDIR : E.NOENT;\n const rights = typeof rightsBase === \"bigint\" ? rightsBase : BigInt(rightsBase >>> 0);\n let need = 0;\n if ((rights & 2n) !== 0n) need |= 4;\n if ((rights & 64n) !== 0n) need |= 2;\n if ((oflags & 8) !== 0) need |= 2;\n let entry = fileLookup(s, path);\n const exists = entry !== null || fileExists(s, path);\n if (exists && oflags & 4) return E.EXIST;\n if (!exists) {\n if (!(oflags & 1)) return E.NOENT;\n const denied = checkParentWritable(s, path);\n if (denied) return denied;\n entry = { bytes: new Uint8Array(0) };\n s.fs.files.set(path, entry);\n markWritten(s, path);\n } else {\n if (need && (effMode(s, path) & need) !== need) return E.ACCES;\n if (!entry) {\n entry = { bytes: new Uint8Array(0) };\n s.fs.files.set(path, entry);\n }\n if (oflags & 8) {\n entry.bytes = new Uint8Array(0);\n markWritten(s, path);\n }\n }\n const fd = lowestFd(proc);\n proc.fds.set(fd, { kind: \"file\", path, pos: 0, append: (fdflags & 1) !== 0 });\n dv.setUint32(retPtr, fd, true);\n return 0;\n },\n fd_filestat_get: (fd, buf) => {\n const e = proc.fds.get(fd);\n if (!e) return E.BADF;\n if (e.kind === \"file\") {\n const f = fileLookup(s, e.path);\n statBuf(DV(), U8(), buf, 4, f ? f.bytes.length : 0, mtimeNs(s, e.path));\n return 0;\n }\n if (e && (e.kind === \"dir\" || e.kind === \"preopen\")) {\n statBuf(DV(), U8(), buf, 3, 0, mtimeNs(s, e.kind === \"preopen\" ? \"\" : e.path));\n return 0;\n }\n statBuf(DV(), U8(), buf, e && (e.kind === \"stdin\" || e.kind === \"stdout\" || e.kind === \"stderr\" || e.kind === \"tty\") ? 2 : 0, 0, realtimeNs());\n return 0;\n },\n path_filestat_get: (dirfd, flags, pathPtr, pathLen, buf) => {\n const raw = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n if (raw === \"dev/null\" || raw === \"dev/tty\") {\n statBuf(DV(), U8(), buf, 2, 0, realtimeNs());\n return 0;\n }\n const follow = (flags & 1) !== 0;\n if (!follow && s.fs.symlinks.has(raw)) {\n statBuf(DV(), U8(), buf, 7, te.encode(s.fs.symlinks.get(raw)).length, mtimeNs(s, raw));\n return 0;\n }\n const r = followPath(s, raw);\n if (r.errno) return r.errno;\n const path = r.path;\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n const f = fileLookup(s, path);\n if (f) {\n statBuf(DV(), U8(), buf, 4, f.bytes.length, mtimeNs(s, path));\n return 0;\n }\n if (isCoreutil(s, path)) {\n statBuf(DV(), U8(), buf, 4, 1024, mtimeNs(s, path));\n return 0;\n }\n if (isDir(s, path)) {\n statBuf(DV(), U8(), buf, 3, 0, mtimeNs(s, path));\n return 0;\n }\n if (fileExists(s, path)) {\n statBuf(DV(), U8(), buf, 4, 0, mtimeNs(s, path));\n return 0;\n }\n return E.NOENT;\n },\n // fstflags: ATIM=1, ATIM_NOW=2, MTIM=4, MTIM_NOW=8. Only the mtime half is\n // recorded — this layer keeps one timestamp per inode, and mtime is the\n // one every build tool reads.\n path_filestat_set_times: (dirfd, _flags, pathPtr, pathLen, _atim, mtim, fstflags) => {\n const path = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n if (!inodeExists(s, path) && !s.fs.symlinks.has(path)) return E.NOENT;\n if (fstflags & 8) s.fs.times.set(path, realtimeNs());\n else if (fstflags & 4) s.fs.times.set(path, typeof mtim === \"bigint\" ? mtim : BigInt(mtim >>> 0));\n return 0;\n },\n path_unlink_file: (dirfd, pathPtr, pathLen) => {\n const r = followParents(s, resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen))));\n if (r.errno) return r.errno;\n const path = r.path;\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n if (isDir(s, path)) return E.ISDIR;\n if (!fileExists(s, path) && !s.fs.symlinks.has(path)) return E.NOENT;\n const denied = checkParentWritable(s, path);\n if (denied) return denied;\n recordFileRemoved(s, path);\n return 0;\n },\n path_rename: (fd1, oldPtr, oldLen, fd2, newPtr, newLen) => {\n const from = resolve(td.decode(U8().subarray(oldPtr, oldPtr + oldLen)));\n const to = resolve(td.decode(U8().subarray(newPtr, newPtr + newLen)));\n let trav = checkTraversal(s, from);\n if (trav) return trav;\n trav = checkTraversal(s, to);\n if (trav) return trav;\n const fromIsDir = from !== \"\" && isDir(s, from);\n const f = fileLookup(s, from);\n if (!fromIsDir && !f && !fileExists(s, from)) return E.NOENT;\n if (from === to) return 0;\n let denied = checkParentWritable(s, from);\n if (!denied) denied = checkParentWritable(s, to);\n if (denied) return denied;\n const moveMode = (a, b) => {\n const m = s.fs.modes.get(a);\n s.fs.modes.delete(a);\n if (m !== void 0) s.fs.modes.set(b, m);\n };\n if (isDir(s, to)) {\n if (!fromIsDir) return E.ISDIR;\n const prefix = to + \"/\";\n for (const p of s.fs.files.keys()) if (p.startsWith(prefix)) return E.NOTEMPTY;\n for (const p of s.fs.dirs) if (p.startsWith(prefix)) return E.NOTEMPTY;\n recordDirRemoved(s, to);\n } else if (fileExists(s, to)) {\n if (fromIsDir) return E.NOTDIR;\n recordFileRemoved(s, to);\n }\n if (fromIsDir) {\n const mode2 = s.fs.modes.get(from);\n recordDirRemoved(s, from);\n recordDirAdded(s, to);\n if (mode2 !== void 0) s.fs.modes.set(to, mode2);\n const prefix = from + \"/\";\n const rebase = (key) => to + \"/\" + key.slice(prefix.length);\n for (const key of [...s.fs.files.keys()]) {\n if (!key.startsWith(prefix)) continue;\n const nk = rebase(key);\n const entry2 = s.fs.files.get(key);\n recordFileRemoved(s, key);\n moveMode(key, nk);\n s.fs.files.set(nk, entry2);\n s.fs.written.add(nk);\n s.fs.deleted.delete(nk);\n if (!s.fs.modes.has(nk)) s.fs.modes.set(nk, 6);\n }\n for (const key of [...s.fs.dirs]) {\n if (!key.startsWith(prefix)) continue;\n const nk = rebase(key);\n const mode22 = s.fs.modes.get(key);\n recordDirRemoved(s, key);\n recordDirAdded(s, nk);\n if (mode22 !== void 0) s.fs.modes.set(nk, mode22);\n }\n return 0;\n }\n const entry = f || { bytes: new Uint8Array(0) };\n const mode = s.fs.modes.get(from);\n recordFileRemoved(s, from);\n s.fs.files.set(to, entry);\n markWritten(s, to);\n if (mode !== void 0) s.fs.modes.set(to, mode);\n return 0;\n },\n path_create_directory: (dirfd, pathPtr, pathLen) => {\n const path = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n if (isDir(s, path) || fileExists(s, path)) return E.EXIST;\n const denied = checkParentWritable(s, path);\n if (denied) return denied;\n recordDirAdded(s, path);\n return 0;\n },\n // path_readlink(fd, path, path_len, buf, buf_len, *bufused). Never follows\n // the last component — reading a link is the one op that is about the link.\n path_readlink: (dirfd, pathPtr, pathLen, bufPtr, bufLen, bufUsedPtr) => {\n const r = followParents(s, resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen))));\n if (r.errno) return r.errno;\n const target = s.fs.symlinks.get(r.path);\n if (target === void 0) return inodeExists(s, r.path) ? E.INVAL : E.NOENT;\n const bytes = te.encode(target);\n const n = Math.min(bytes.length, bufLen);\n U8().set(bytes.subarray(0, n), bufPtr);\n DV().setUint32(bufUsedPtr, n, true);\n return 0;\n },\n // path_symlink(old_path, old_path_len, fd, new_path, new_path_len) — the\n // target comes FIRST and is not a path in this filesystem: it is stored\n // verbatim and may dangle, exactly as ln -s allows.\n path_symlink: (oldPtr, oldLen, newFd, newPtr, newLen) => {\n const target = td.decode(U8().subarray(oldPtr, oldPtr + oldLen));\n const r = followParents(s, resolve(td.decode(U8().subarray(newPtr, newPtr + newLen))));\n if (r.errno) return r.errno;\n const link = r.path;\n const trav = checkTraversal(s, link);\n if (trav) return trav;\n if (inodeExists(s, link) || s.fs.symlinks.has(link)) return E.EXIST;\n const denied = checkParentWritable(s, link);\n if (denied) return denied;\n s.fs.symlinks.set(link, target);\n s.fs.symlinksCreated.set(link, target);\n s.fs.modes.set(link, 7);\n s.fs.deleted.delete(link);\n touchPath(s, link);\n return 0;\n },\n path_link: (fd1, _lookupFlags, oldPtr, oldLen, fd2, newPtr, newLen) => {\n const from = resolve(td.decode(U8().subarray(oldPtr, oldPtr + oldLen)));\n const to = resolve(td.decode(U8().subarray(newPtr, newPtr + newLen)));\n let trav = checkTraversal(s, from);\n if (trav) return trav;\n trav = checkTraversal(s, to);\n if (trav) return trav;\n if (isDir(s, from)) return E.PERM;\n const f = fileLookup(s, from);\n if (!f) return fileExists(s, from) ? E.ACCES : E.NOENT;\n if (fileExists(s, to) || isDir(s, to)) return E.EXIST;\n const denied = checkParentWritable(s, to);\n if (denied) return denied;\n s.fs.files.set(to, f);\n markWritten(s, to);\n s.fs.modes.set(to, effMode(s, from));\n return 0;\n },\n path_remove_directory: (dirfd, pathPtr, pathLen) => {\n const path = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n if (path === \"\" || path === \"etc\" || path === \"dev\" || path === \"bin\" || path === \"usr\" || path === \"usr/bin\") return E.ACCES;\n if (!isDir(s, path)) return fileExists(s, path) ? E.NOTDIR : E.NOENT;\n const denied = checkParentWritable(s, path);\n if (denied) return denied;\n const prefix = path + \"/\";\n for (const p of s.fs.files.keys()) if (p.startsWith(prefix)) return E.NOTEMPTY;\n for (const p of s.fs.dirs) if (p.startsWith(prefix)) return E.NOTEMPTY;\n for (const p of s.fs.modes.keys()) if (p.startsWith(prefix)) return E.NOTEMPTY;\n recordDirRemoved(s, path);\n return 0;\n },\n fd_renumber: (from, to) => {\n const e = proc.fds.get(from);\n if (!e) return E.BADF;\n if (from !== to) {\n closeFd(s, proc, to);\n proc.fds.set(to, e);\n proc.fds.delete(from);\n }\n return 0;\n },\n fd_readdir: (fd, buf, bufLen, cookie, retPtr) => {\n const e = proc.fds.get(fd);\n if (!e || e.kind !== \"dir\" && e.kind !== \"preopen\") return E.BADF;\n const dir = e.kind === \"preopen\" ? \"\" : e.path;\n if ((effMode(s, dir) & 4) === 0) return E.ACCES;\n const prefix = dir ? dir + \"/\" : \"\";\n const seen = /* @__PURE__ */ new Set([\".\", \"..\"]);\n const children = [];\n const push = (name, type) => {\n if (!seen.has(name)) {\n seen.add(name);\n children.push({ name, type });\n }\n };\n for (const p of s.fs.dirs) {\n if (p !== dir && p.startsWith(prefix) && !p.slice(prefix.length).includes(\"/\")) push(p.slice(prefix.length), 3);\n }\n for (const p of s.fs.files.keys()) {\n if (p.startsWith(prefix) && !p.slice(prefix.length).includes(\"/\")) push(p.slice(prefix.length), 4);\n }\n for (const p of s.fs.symlinks.keys()) {\n if (p.startsWith(prefix) && !p.slice(prefix.length).includes(\"/\")) push(p.slice(prefix.length), 7);\n }\n for (const p of s.fs.modes.keys()) {\n if (p !== dir && p.startsWith(prefix) && !p.slice(prefix.length).includes(\"/\") && !s.fs.dirs.has(p)) push(p.slice(prefix.length), 4);\n }\n if (dir === \"bin\" || dir === \"usr/bin\") for (const name of s.coreutils.keys()) push(name, 4);\n children.sort((a, b) => a.name < b.name ? -1 : 1);\n const entries = [{ name: \".\", type: 3 }, { name: \"..\", type: 3 }, ...children];\n const dv = DV(), u8 = U8();\n let off = 0;\n for (let i = Number(cookie); i < entries.length; i++) {\n const nb = te.encode(entries[i].name);\n const need = 24 + nb.length;\n const record = new Uint8Array(need);\n const rdv = new DataView(record.buffer);\n rdv.setBigUint64(0, BigInt(i + 1), true);\n rdv.setBigUint64(8, 1n, true);\n rdv.setUint32(16, nb.length, true);\n record[20] = entries[i].type;\n record.set(nb, 24);\n const room = bufLen - off;\n if (need > room) {\n u8.set(record.subarray(0, room), buf + off);\n off = bufLen;\n break;\n }\n u8.set(record, buf + off);\n off += need;\n }\n dv.setUint32(retPtr, off, true);\n return 0;\n },\n // An fd that was never opened is EBADF everywhere below. It used to be\n // ESPIPE from fd_seek and plain SUCCESS from fd_tell and fd_filestat_get,\n // while fd_close, fd_fdstat_get and fd_write in this same table already\n // answered EBADF — one descriptor table, five different opinions about what\n // a closed descriptor is. ESPIPE in particular is a yes: it means \"open, and\n // not seekable\", so a caller using lseek to test validity read it as valid.\n fd_seek: (fd, offset, whence, retPtr) => {\n const e = proc.fds.get(fd);\n if (!e) return E.BADF;\n if (e.kind === \"file\") {\n const f = fileLookup(s, e.path);\n const len = f ? f.bytes.length : 0;\n const off = Number(offset);\n const next = whence === 0 ? off : whence === 1 ? e.pos + off : len + off;\n if (next < 0) return E.INVAL;\n e.pos = next;\n DV().setBigUint64(retPtr, BigInt(next), true);\n return 0;\n }\n DV().setBigUint64(retPtr, 0n, true);\n return E.SPIPE;\n },\n fd_tell: (fd, retPtr) => {\n const e = proc.fds.get(fd);\n if (!e) return E.BADF;\n DV().setBigUint64(retPtr, BigInt(e.kind === \"file\" ? e.pos : 0), true);\n return 0;\n },\n fd_close: (fd) => {\n if (!proc.fds.has(fd)) return E.BADF;\n closeFd(s, proc, fd);\n return 0;\n },\n fd_fdstat_get: (fd, st) => {\n const dv = DV();\n const e = proc.fds.get(fd);\n let ft = 4;\n if (e && e.kind === \"pipe\") ft = 0;\n else if (e && (e.kind === \"stdin\" || e.kind === \"stdout\" || e.kind === \"stderr\")) ft = s.stdinTty || fd !== 0 ? 2 : 0;\n else if (e && e.kind === \"tty\") ft = 2;\n else if (e && (e.kind === \"dir\" || e.kind === \"preopen\")) ft = 3;\n else if (!e) return E.BADF;\n dv.setUint8(st, ft);\n dv.setUint16(st + 2, 0, true);\n dv.setBigUint64(st + 8, 0xffffffffffffffffn, true);\n dv.setBigUint64(st + 16, 0xffffffffffffffffn, true);\n return 0;\n },\n fd_fdstat_set_flags: () => 0,\n fd_read: (fd, iovs, n, nread) => io.read(fd, readIovs(DV(), iovs, n), nread),\n fd_write: (fd, iovs, n, nw) => {\n if (!proc.fds.has(fd)) return E.BADF;\n const dv = DV(), u8 = U8();\n let w = 0;\n for (let i = 0; i < n; i++) {\n const p = dv.getUint32(iovs + i * 8, true), l = dv.getUint32(iovs + i * 8 + 4, true);\n const n2 = io.write(fd, u8.subarray(p, p + l));\n if (n2 === null) return E.BADF;\n w += n2;\n }\n dv.setUint32(nw, w, true);\n return 0;\n },\n poll_oneoff: (inPtr, outPtr, nsubs, retPtr) => io.poll(inPtr, outPtr, nsubs, retPtr),\n clock_time_get: (id, _pr, t) => {\n const ns = clockNs(id);\n if (ns === null) return E.INVAL;\n DV().setBigUint64(t, ns, true);\n return 0;\n },\n clock_res_get: (id, r) => {\n if (clockNs(id) === null) return E.INVAL;\n DV().setBigUint64(r, id === CLOCK_REALTIME ? 1000000n : 1000n, true);\n return 0;\n },\n // getRandomValues caps at 65536 bytes per call; a guest asking for more\n // gets the same CSPRNG, chunked, never a weaker one.\n random_get: (b, l) => {\n const u = U8();\n for (let off = 0; off < l; off += 65536) {\n crypto.getRandomValues(u.subarray(b + off, b + off + Math.min(l - off, 65536)));\n }\n return 0;\n },\n proc_exit: (code) => {\n throw new Exit(code);\n }\n };\n }\n function writeThroughFd(s, proc, fd, bytes) {\n const e = proc.fds.get(fd);\n if (e && e.kind === \"pipe\") {\n if (e.end === \"r\") return null;\n const pp = s.pipes.get(e.pipeId);\n pp.chunks.push(bytes.slice());\n pp.queued += bytes.length;\n wakePipe(s, pp);\n return bytes.length;\n }\n if (e && e.kind === \"file\") {\n const f = fileLookup(s, e.path) || { bytes: new Uint8Array(0) };\n if (!s.fs.files.has(e.path)) s.fs.files.set(e.path, f);\n const pos = e.append ? f.bytes.length : e.pos;\n fileWrite(s, f, e.path, pos, bytes);\n e.pos = pos + bytes.length;\n return bytes.length;\n }\n if (e && (e.kind === \"null\" || e.kind === \"tty\")) return bytes.length;\n const text = td.decode(bytes);\n if (e.kind === \"stderr\") s.err += text;\n else s.out += text;\n return bytes.length;\n }\n function tryReadFd(s, proc, fd, dv, u8, iov, nreadPtr) {\n const e = proc.fds.get(fd);\n const deliver = (bytes) => {\n dv.setUint32(nreadPtr, scatter(u8, iov, bytes), true);\n return { errno: 0 };\n };\n if (e && e.kind === \"file\") {\n const f = fileLookup(s, e.path);\n const bytes = f ? f.bytes.subarray(e.pos, e.pos + iov.total) : new Uint8Array(0);\n e.pos += bytes.length;\n return deliver(bytes);\n }\n if (e && (e.kind === \"null\" || e.kind === \"tty\")) {\n dv.setUint32(nreadPtr, 0, true);\n return { errno: 0 };\n }\n if (e && e.kind === \"pipe\") {\n const pp = s.pipes.get(e.pipeId);\n if (pp.queued > 0) return deliver(takeUpTo(pp, iov.total));\n if (pp.writers === 0) {\n dv.setUint32(nreadPtr, 0, true);\n return { errno: 0 };\n }\n return null;\n }\n if (e && e.kind === \"stdin\") {\n const st = s.stdin;\n if (st.queued > 0) return deliver(takeUpTo(st, iov.total));\n if (st.closed) {\n dv.setUint32(nreadPtr, 0, true);\n return { errno: 0 };\n }\n return null;\n }\n if (e) {\n dv.setUint32(nreadPtr, 0, true);\n return { errno: 0 };\n }\n return { errno: E.BADF };\n }\n function readSubs(dv, inPtr, nsubs) {\n const subs = [];\n for (let i = 0; i < nsubs; i++) {\n const base = inPtr + i * 48;\n const userdata = dv.getBigUint64(base, true);\n const tag = dv.getUint8(base + 8);\n if (tag === 0) {\n const id = dv.getUint32(base + 16, true);\n const timeout = dv.getBigUint64(base + 24, true);\n const abs = (dv.getUint16(base + 40, true) & 1) !== 0;\n const now = clockNs(id);\n subs.push(now === null ? { tag, userdata, id, bad: true } : { tag, userdata, id, deadline: abs ? timeout : now + timeout });\n } else {\n subs.push({ tag, userdata, fd: dv.getUint32(base + 16, true) });\n }\n }\n return subs;\n }\n function clockExpired(sub) {\n const now = clockNs(sub.id);\n return now !== null && now >= sub.deadline;\n }\n function writeEvent(dv, outPtr, slot, sub, errno, nbytes) {\n const ev = outPtr + slot * 32;\n dv.setBigUint64(ev, sub.userdata, true);\n dv.setUint16(ev + 8, errno, true);\n dv.setUint8(ev + 10, sub.tag);\n dv.setBigUint64(ev + 16, BigInt(nbytes), true);\n dv.setUint16(ev + 24, 0, true);\n }\n function fdReadReady(s, proc, fd) {\n const e = proc.fds.get(fd);\n if (!e) return null;\n if (e.kind === \"pipe\") {\n const pp = s.pipes.get(e.pipeId);\n return { ready: pp.queued > 0 || pp.writers === 0, avail: pp.queued };\n }\n if (e.kind === \"stdin\") return { ready: s.stdin.queued > 0 || s.stdin.closed, avail: s.stdin.queued };\n return { ready: true, avail: 0 };\n }\n function emitReady(s, proc, dv, outPtr, subs) {\n let n = 0;\n for (const sub of subs) {\n if (sub.tag === 0) {\n if (sub.bad) {\n writeEvent(dv, outPtr, n++, sub, E.INVAL, 0);\n continue;\n }\n if (clockExpired(sub)) writeEvent(dv, outPtr, n++, sub, 0, 0);\n continue;\n }\n const st = fdReadReady(s, proc, sub.fd);\n if (!st) {\n writeEvent(dv, outPtr, n++, sub, E.BADF, 0);\n continue;\n }\n if (sub.tag === 2 || st.ready) writeEvent(dv, outPtr, n++, sub, 0, st.avail);\n }\n return n;\n }\n var FROZEN_PROBE = 2e5;\n function waitForDeadline(s, proc, subs) {\n const clocks = subs.filter((x) => x.tag === 0 && !x.bad);\n if (!clocks.length) return;\n const startedNs = realtimeNs();\n let spins = 0;\n while (!clocks.some(clockExpired)) {\n if (s.rootExit !== null) return;\n if (subs.some((x) => x.tag === 1 && (fdReadReady(s, proc, x.fd) || { ready: true }).ready)) return;\n if (s.runnable.length) {\n pumpOne(s);\n continue;\n }\n if (++spins > FROZEN_PROBE && realtimeNs() === startedNs) return;\n }\n }\n function emitClocks(dv, outPtr, subs) {\n let n = 0;\n for (const sub of subs) if (sub.tag === 0 && !sub.bad) writeEvent(dv, outPtr, n++, sub, 0, 0);\n return n;\n }\n function blockTarget(s, proc, fd) {\n const e = proc.fds.get(fd);\n if (e && e.kind === \"pipe\") return { list: s.pipes.get(e.pipeId).readW, wake: () => wakePipe(s, s.pipes.get(e.pipeId)) };\n if (e && e.kind === \"stdin\") return { list: s.stdin.waiters, wake: () => wakeStdin(s) };\n return null;\n }\n function makeProc(s, pid, ppid, fds) {\n const proc = {\n pid,\n ppid,\n fds,\n inst: null,\n __s: s,\n ctx: { reason: null, rewinding: false, captureEnv: 0, ljEnv: 0, ljVal: 0, nextSlot: 0, resume: 0 },\n MAIN_BUF: 0,\n SLOT0: 0,\n pendingRead: null,\n slotByEnv: /* @__PURE__ */ new Map(),\n freeSlots: []\n };\n const DV = () => new DataView(proc.inst.exports.memory.buffer);\n const U8 = () => new Uint8Array(proc.inst.exports.memory.buffer);\n proc.DV = DV;\n proc.U8 = U8;\n const slotAddr = (i) => proc.SLOT0 + i * SLOT_SIZE;\n proc.slotAddr = slotAddr;\n const initHdr = (a, sz) => {\n const dv = DV();\n dv.setUint32(a, a + 8, true);\n dv.setUint32(a + 4, a + sz, true);\n };\n proc.initHdr = initHdr;\n const wstr = (p, str) => {\n const b = te.encode(str);\n U8().set(b, p);\n return b.length;\n };\n const c = proc.ctx;\n const io = {\n read: (fd, iov, nread) => {\n if (proc.pendingRead) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n const pr = proc.pendingRead;\n proc.pendingRead = null;\n DV().setUint32(pr.nreadPtr, scatter(U8(), pr.iov, pr.bytes), true);\n return 0;\n }\n const dv = DV();\n const sync = tryReadFd(s, proc, fd, dv, U8(), iov, nread);\n if (sync) return sync.errno;\n dv.setUint32(nread, 0, true);\n c.reason = \"blockread\";\n c.pipeReq = { fd, iov, nreadPtr: nread };\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n },\n write: (fd, bytes) => writeThroughFd(s, proc, fd, bytes),\n poll: (inPtr, outPtr, nsubs, retPtr) => {\n if (proc.pendingRead) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n const pr = proc.pendingRead;\n proc.pendingRead = null;\n const dv2 = DV();\n dv2.setBigUint64(outPtr, pr.pollUserdata ?? 0n, true);\n dv2.setUint16(outPtr + 8, 0, true);\n dv2.setUint8(outPtr + 10, 1);\n dv2.setBigUint64(outPtr + 16, BigInt(pr.bytes.length), true);\n dv2.setUint16(outPtr + 24, 0, true);\n dv2.setUint32(retPtr, 1, true);\n return 0;\n }\n const dv = DV();\n const subs = readSubs(dv, inPtr, nsubs);\n let emitted = emitReady(s, proc, dv, outPtr, subs);\n if (emitted > 0) {\n dv.setUint32(retPtr, emitted, true);\n return 0;\n }\n const blockSub = subs.find((x) => x.tag === 1 && blockTarget(s, proc, x.fd));\n if (!blockSub) {\n waitForDeadline(s, proc, subs);\n emitted = emitReady(s, proc, dv, outPtr, subs) || emitClocks(dv, outPtr, subs);\n dv.setUint32(retPtr, emitted, true);\n return 0;\n }\n dv.setUint32(retPtr, 0, true);\n c.reason = \"blockread\";\n c.pipeReq = { fd: blockSub.fd, iov: EMPTY_IOV, nreadPtr: 0, pollUserdata: blockSub.userdata };\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n }\n };\n const wasiBase = makeWasiFs(s, proc, DV, U8, io);\n const wasi = {\n ...wasiBase,\n args_sizes_get: (a, b) => {\n const dv = DV();\n dv.setUint32(a, s.argv.length, true);\n dv.setUint32(b, s.argv.reduce((x, v) => x + te.encode(v).length + 1, 0), true);\n return 0;\n },\n args_get: (ptrs, buf) => {\n const dv = DV();\n let p = buf;\n for (const a of s.argv) {\n dv.setUint32(ptrs, p, true);\n ptrs += 4;\n p += wstr(p, a);\n U8()[p++] = 0;\n }\n return 0;\n },\n environ_sizes_get: (a, b) => {\n const dv = DV();\n dv.setUint32(a, s.environ.length, true);\n dv.setUint32(b, s.environ.reduce((x, v) => x + te.encode(v).length + 1, 0), true);\n return 0;\n },\n environ_get: (ptrs, buf) => {\n const dv = DV();\n let p = buf;\n for (const v of s.environ) {\n dv.setUint32(ptrs, p, true);\n ptrs += 4;\n p += wstr(p, v);\n U8()[p++] = 0;\n }\n return 0;\n }\n };\n const allocSlot = (env) => {\n const prev = proc.slotByEnv.get(env);\n if (prev !== void 0) {\n proc.slotByEnv.delete(env);\n proc.freeSlots.push(prev);\n }\n let idx;\n if (proc.freeSlots.length > 1) idx = proc.freeSlots.shift();\n else if (c.nextSlot < NSLOT) idx = c.nextSlot++;\n else idx = proc.freeSlots.shift();\n if (idx === void 0) throw new Error(\"bash-runner: setjmp slot budget exceeded (\" + NSLOT + \")\");\n proc.slotByEnv.set(env, idx);\n return idx;\n };\n const nimbus_proc = {\n setjmp: (env) => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n return;\n }\n c.reason = \"capture\";\n c.captureEnv = env;\n const idx = allocSlot(env);\n const dv = DV();\n dv.setInt32(env, idx, true);\n dv.setInt32(env + 4, 0, true);\n initHdr(slotAddr(idx), SLOT_SIZE);\n proc.inst.exports.asyncify_start_unwind(slotAddr(idx));\n },\n longjmp: (env, val) => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n return;\n }\n c.reason = \"longjmp\";\n c.ljEnv = env;\n c.ljVal = val;\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n },\n fork: () => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n return c.resume;\n }\n c.reason = \"fork\";\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n },\n vfork: () => nimbus_proc.fork(),\n waitpid: (pid2, statusPtr, _opt) => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n if (c.waitStatusPtr != null) DV().setInt32(c.waitStatusPtr, c.resumeStatus, true);\n return c.resume;\n }\n c.reason = \"waitpid\";\n c.waitTarget = pid2;\n c.waitStatusPtr = statusPtr;\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n },\n execve: (pathPtr, argvFlatPtr, argvLen, envFlatPtr, envLen) => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n return c.resume;\n }\n const u8 = U8();\n let e = pathPtr;\n while (u8[e]) e++;\n c.reason = \"exec\";\n c.execPath = td.decode(u8.subarray(pathPtr, e));\n c.execArgv = td.decode(u8.subarray(argvFlatPtr, argvFlatPtr + argvLen)).split(\"\\0\").filter((x) => x.length);\n c.execEnv = td.decode(u8.subarray(envFlatPtr, envFlatPtr + envLen)).split(\"\\0\").filter((x) => x.length);\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n },\n pipe: (fdsPtr) => {\n const id = newPipe(s);\n const rfd = lowestFd(proc);\n proc.fds.set(rfd, { kind: \"pipe\", pipeId: id, end: \"r\" });\n const wfd = lowestFd(proc);\n proc.fds.set(wfd, { kind: \"pipe\", pipeId: id, end: \"w\" });\n const dv = DV();\n dv.setInt32(fdsPtr, rfd, true);\n dv.setInt32(fdsPtr + 4, wfd, true);\n return 0;\n },\n dup: (o) => {\n const e = proc.fds.get(o);\n if (!e) return -E.BADF;\n const nf = lowestFd(proc);\n proc.fds.set(nf, { ...e });\n if (e.kind === \"pipe\") bumpPipe(s, e, 1);\n return nf;\n },\n dup2: (o, n) => {\n const e = proc.fds.get(o);\n if (!e) return -E.BADF;\n if (o === n) return n;\n if (proc.fds.has(n)) closeFd(s, proc, n);\n proc.fds.set(n, { ...e });\n if (e.kind === \"pipe\") bumpPipe(s, e, 1);\n return n;\n },\n kill: () => 0,\n setpgid: () => 0,\n getpgid: () => proc.pid,\n getppid: () => proc.ppid,\n tcsetpgrp: () => 0,\n tcgetpgrp: () => proc.pid,\n tcgetattr: () => -1,\n tcsetattr: () => 0\n };\n const envImports = {\n getpid: () => proc.pid,\n getuid: () => 0,\n geteuid: () => 0,\n getgid: () => 0,\n getegid: () => 0,\n setuid: () => 0,\n setgid: () => 0,\n umask: () => 18,\n gethostname: (p, _l) => {\n U8().set(te.encode(\"nimbus\"), p);\n return 0;\n },\n dlopen: () => 0,\n dlsym: () => 0,\n dlclose: () => 0,\n dlerror: () => 0\n };\n proc.inst = new WebAssembly.Instance(s.mod, { wasi_snapshot_preview1: wasi, nimbus_proc, env: envImports });\n s.stats.instances++;\n s.procs.set(pid, proc);\n return proc;\n }\n function setupArena(proc) {\n const base = proc.inst.exports.memory.buffer.byteLength;\n const need = MAIN_SIZE + NSLOT * SLOT_SIZE;\n proc.inst.exports.memory.grow(Math.ceil(need / PAGE));\n proc.MAIN_BUF = base;\n proc.SLOT0 = proc.MAIN_BUF + MAIN_SIZE;\n }\n function resumeProc(proc) {\n proc.ctx.rewinding = true;\n proc.inst.exports.asyncify_start_rewind(proc.MAIN_BUF);\n proc.__s.runnable.push(proc);\n }\n function trackArena(s, proc, bufAddr, size, isSlot) {\n const used = proc.DV().getUint32(bufAddr, true) - (bufAddr + 8);\n if (isSlot) {\n if (used > s.stats.slotHi) s.stats.slotHi = used;\n } else if (used > s.stats.mainHi) s.stats.mainHi = used;\n }\n function step(s, proc) {\n const c = proc.ctx, ex = proc.inst.exports;\n try {\n ex._start();\n } catch (e) {\n if (e instanceof Exit) {\n finishProc(s, proc, e.code);\n return;\n }\n throw e;\n }\n if (c.reason === null) {\n finishProc(s, proc, 0);\n return;\n }\n ex.asyncify_stop_unwind();\n const r = c.reason;\n c.reason = null;\n const dv = proc.DV();\n const mem = proc.inst.exports.memory.buffer.byteLength;\n if (mem > s.stats.memPeak) s.stats.memPeak = mem;\n if (r === \"capture\") {\n const idx = dv.getInt32(c.captureEnv, true);\n trackArena(s, proc, proc.slotAddr(idx), SLOT_SIZE, true);\n dv.setUint32(c.captureEnv + 8, dv.getUint32(proc.slotAddr(idx), true), true);\n c.rewinding = true;\n ex.asyncify_start_rewind(proc.slotAddr(idx));\n s.runnable.push(proc);\n } else if (r === \"longjmp\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n const idx = dv.getInt32(c.ljEnv, true), hw = dv.getUint32(c.ljEnv + 8, true);\n dv.setInt32(c.ljEnv + 4, c.ljVal, true);\n dv.setUint32(proc.slotAddr(idx), hw, true);\n c.rewinding = true;\n ex.asyncify_start_rewind(proc.slotAddr(idx));\n s.runnable.push(proc);\n } else if (r === \"fork\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n doFork(s, proc);\n } else if (r === \"waitpid\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n doWait(s, proc);\n } else if (r === \"blockread\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n const target = blockTarget(s, proc, c.pipeReq.fd);\n if (!target) {\n proc.pendingRead = { iov: c.pipeReq.iov, bytes: new Uint8Array(0), nreadPtr: c.pipeReq.nreadPtr, pollUserdata: c.pipeReq.pollUserdata };\n resumeProc(proc);\n } else {\n target.list.push({ proc });\n target.wake();\n }\n } else if (r === \"exec\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n doExec(s, proc);\n } else {\n throw new Error(\"bash-runner: unknown unwind reason \" + r);\n }\n }\n function pumpOne(s) {\n if (!s.runnable.length) return false;\n step(s, s.runnable.shift());\n return true;\n }\n function doExec(s, proc) {\n const name = proc.ctx.execPath.split(\"/\").pop();\n const env = proc.ctx.execEnv && proc.ctx.execEnv.length ? proc.ctx.execEnv : s.environ;\n const pwdVar = env.find((x) => x.startsWith(\"PWD=\"));\n const childCwd = pwdVar ? norm(pwdVar.slice(4)) : s.cwd;\n const path = makeResolve(s, childCwd)(proc.ctx.execPath);\n const viaPath = path.startsWith(\"bin/\") || path.startsWith(\"usr/bin/\");\n const m = viaPath ? s.coreutils.get(name) : null;\n if (!m) {\n proc.ctx.resume = fileLookup(s, path) ? -45 : fileExists(s, path) ? -2 : -44;\n proc.ctx.rewinding = true;\n proc.inst.exports.asyncify_start_rewind(proc.MAIN_BUF);\n s.runnable.push(proc);\n return;\n }\n let inst2;\n const DV = () => new DataView(inst2.exports.memory.buffer);\n const U8 = () => new Uint8Array(inst2.exports.memory.buffer);\n const tv = proc.ctx.execArgv;\n const absRoots = /* @__PURE__ */ new Set();\n for (let i = 1; i < tv.length; i++) if (tv[i].startsWith(\"/\")) absRoots.add(norm(tv[i]));\n const wstr = (p, str) => {\n const b = te.encode(str);\n U8().set(b, p);\n return b.length;\n };\n let code = 0;\n const io = {\n read: (fd, iov, nread) => {\n const dv = DV();\n for (; ; ) {\n const sync = tryReadFd(s, proc, fd, dv, U8(), iov, nread);\n if (sync) return sync.errno;\n if (!pumpOne(s)) {\n dv.setUint32(nread, 0, true);\n return 0;\n }\n }\n },\n write: (fd, bytes) => writeThroughFd(s, proc, fd, bytes),\n // An exec'd tool cannot park, so a wait it cannot satisfy locally is spent\n // pumping the scheduler — the same discipline its blocking reads use.\n poll: (inPtr, outPtr, nsubs, retPtr) => {\n const dv = DV();\n const subs = readSubs(dv, inPtr, nsubs);\n let emitted = emitReady(s, proc, dv, outPtr, subs);\n if (emitted === 0) {\n waitForDeadline(s, proc, subs);\n emitted = emitReady(s, proc, dv, outPtr, subs) || emitClocks(dv, outPtr, subs);\n }\n dv.setUint32(retPtr, emitted, true);\n return 0;\n }\n };\n const base = makeWasiFs(s, proc, DV, U8, io, childCwd, absRoots);\n const w = {\n ...base,\n args_sizes_get: (a, b) => {\n const dv = DV();\n dv.setUint32(a, tv.length, true);\n dv.setUint32(b, tv.reduce((x, v) => x + te.encode(v).length + 1, 0), true);\n return 0;\n },\n args_get: (ptrs, buf) => {\n const dv = DV();\n let p = buf;\n for (const a of tv) {\n dv.setUint32(ptrs, p, true);\n ptrs += 4;\n p += wstr(p, a);\n U8()[p++] = 0;\n }\n return 0;\n },\n // POSIX environment inheritance: the child env bash passed to execve.\n environ_sizes_get: (a, b) => {\n const dv = DV();\n dv.setUint32(a, env.length, true);\n dv.setUint32(b, env.reduce((x, v) => x + te.encode(v).length + 1, 0), true);\n return 0;\n },\n environ_get: (ptrs, buf) => {\n const dv = DV();\n let p = buf;\n for (const v of env) {\n dv.setUint32(ptrs, p, true);\n ptrs += 4;\n p += wstr(p, v);\n U8()[p++] = 0;\n }\n return 0;\n },\n proc_exit: (ec) => {\n code = ec;\n throw new Exit(ec);\n }\n };\n const chmodResolve = makeResolve(s, childCwd, absRoots);\n const nimbus_proc = {\n chmod: (pathPtr, pathLen, mode) => {\n const p = chmodResolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n const trav = checkTraversal(s, p);\n if (trav) return trav;\n if (!inodeExists(s, p) && !(p !== \"\" && isDir(s, p))) return E.NOENT;\n const bits = mode & 511;\n s.fs.modes.set(p, bits >> 6 & 7);\n s.fs.modesChanged.set(p, bits);\n return 0;\n }\n };\n inst2 = new WebAssembly.Instance(m, { wasi_snapshot_preview1: w, nimbus_proc });\n s.stats.instances++;\n const mem2 = inst2.exports.memory.buffer.byteLength;\n if (mem2 > s.stats.memPeak) s.stats.memPeak = mem2;\n try {\n inst2.exports._start();\n } catch (e) {\n if (e instanceof Exit) code = e.code;\n else throw e;\n }\n finishProc(s, proc, code);\n }\n function doFork(s, parent) {\n const childPid = s.pidNext++;\n const childFds = /* @__PURE__ */ new Map();\n for (const [fd, e] of parent.fds) {\n childFds.set(fd, { ...e });\n if (e.kind === \"pipe\") bumpPipe(s, e, 1);\n }\n const child = makeProc(s, childPid, parent.pid, childFds);\n const pmem = parent.inst.exports.memory, cmem = child.inst.exports.memory;\n if (cmem.buffer.byteLength < pmem.buffer.byteLength) cmem.grow((pmem.buffer.byteLength - cmem.buffer.byteLength) / PAGE);\n new Uint8Array(cmem.buffer).set(new Uint8Array(pmem.buffer));\n for (const [k, v] of Object.entries(parent.inst.exports)) if (v instanceof WebAssembly.Global) child.inst.exports[k].value = v.value;\n child.MAIN_BUF = parent.MAIN_BUF;\n child.SLOT0 = parent.SLOT0;\n child.ctx.nextSlot = parent.ctx.nextSlot;\n child.slotByEnv = new Map(parent.slotByEnv);\n child.freeSlots = parent.freeSlots.slice();\n const total = s.procs.size;\n if (total * cmem.buffer.byteLength > s.stats.memPeak) s.stats.memPeak = total * cmem.buffer.byteLength;\n child.ctx.resume = 0;\n child.ctx.rewinding = true;\n child.inst.exports.asyncify_start_rewind(child.MAIN_BUF);\n s.runnable.push(child);\n parent.ctx.resume = childPid;\n parent.ctx.rewinding = true;\n parent.inst.exports.asyncify_start_rewind(parent.MAIN_BUF);\n s.runnable.push(parent);\n }\n function doWait(s, proc) {\n const t = proc.ctx.waitTarget;\n let pid = null;\n if (t > 0) {\n const e = s.exitStatus.get(t);\n if (e && e.ppid === proc.pid) pid = t;\n } else {\n for (const [p, e] of s.exitStatus) {\n if (e.ppid === proc.pid) {\n pid = p;\n break;\n }\n }\n }\n if (pid != null) {\n const st = s.exitStatus.get(pid).status;\n s.exitStatus.delete(pid);\n proc.ctx.resume = pid;\n proc.ctx.resumeStatus = st;\n resumeProc(proc);\n } else {\n s.waiters.push({ proc, targetPid: t });\n }\n }\n function finishProc(s, proc, code) {\n const st = (code & 255) << 8;\n s.procs.delete(proc.pid);\n for (const fd of [...proc.fds.keys()]) closeFd(s, proc, fd);\n if (proc.ppid === 0) s.rootExit = code;\n s.exitStatus.set(proc.pid, { status: st, ppid: proc.ppid });\n for (let i = 0; i < s.waiters.length; i++) {\n const w = s.waiters[i];\n if (w.proc.pid === proc.ppid && (w.targetPid === proc.pid || w.targetPid <= 0)) {\n s.waiters.splice(i, 1);\n w.proc.ctx.resume = proc.pid;\n w.proc.ctx.resumeStatus = st;\n s.exitStatus.delete(proc.pid);\n resumeProc(w.proc);\n break;\n }\n }\n }\n function composeFsDiff(s) {\n const filesWritten = {};\n for (const path of s.fs.written) {\n if (path.startsWith(\"etc/nimbus.bashrc\")) continue;\n const f = s.fs.files.get(path);\n if (f) filesWritten[path] = bytesToB64(f.bytes);\n }\n return {\n filesWritten,\n filesDeleted: [...s.fs.deleted],\n dirsCreated: [...s.fs.dirsCreated],\n // Deepest-first so the flush rmdirs children before their parents.\n dirsDeleted: [...s.fs.dirsDeleted].sort((a, b) => b.length - a.length),\n modesChanged: Object.fromEntries(s.fs.modesChanged),\n symlinksCreated: Object.fromEntries(s.fs.symlinksCreated)\n };\n }\n function pump(s) {\n try {\n while (s.runnable.length) {\n if (++s.steps > 5e6) throw new Error(\"bash-runner: runaway scheduler (>5M steps)\");\n step(s, s.runnable.shift());\n if (s.rootExit !== null) break;\n }\n } catch (e) {\n s.error = String(e && e.stack || e && e.message || e);\n }\n const out = s.out, err = s.err;\n s.out = \"\";\n s.err = \"\";\n const stats = { ...s.stats, steps: s.steps, missingWasi: [...s.missingWasi] };\n if (s.error) {\n S = null;\n return { state: \"error\", exitCode: 1, stdout: out, stderr: err, error: s.error, stats };\n }\n if (s.rootExit !== null || s.procs.size === 0) {\n const code = s.rootExit === null ? 0 : s.rootExit;\n const fsDiff = composeFsDiff(s);\n S = null;\n return { state: \"exited\", exitCode: code, stdout: out, stderr: err, fsDiff, stats };\n }\n if (s.stdin.waiters.length > 0) {\n return { state: \"need-input\", exitCode: 0, stdout: out, stderr: err, stats };\n }\n S = null;\n return { state: \"error\", exitCode: 1, stdout: out, stderr: err, error: \"bash-runner: deadlock \\u2014 live procs with empty run queue\", stats };\n }\n globalThis.__bashBoot = function __bashBoot(args) {\n try {\n S = newSession(args);\n initStdinQueued(S);\n const root = makeProc(S, S.pidNext++, 0, /* @__PURE__ */ new Map([\n [0, { kind: \"stdin\" }],\n [1, { kind: \"stdout\" }],\n [2, { kind: \"stderr\" }],\n [3, { kind: \"preopen\" }]\n ]));\n S.rootPid = root.pid;\n setupArena(root);\n S.runnable.push(root);\n return pump(S);\n } catch (e) {\n S = null;\n return { state: \"error\", exitCode: 1, stdout: \"\", stderr: \"\", error: \"boot failed: \" + String(e && e.message || e) };\n }\n };\n globalThis.__bashFeed = function __bashFeed(args) {\n if (!S) {\n return { state: \"error\", exitCode: 1, stdout: \"\", stderr: \"\", error: \"bash facet has no active session (warm isolate recycled?)\" };\n }\n try {\n if (args.data) {\n const b = te.encode(args.data);\n S.stdin.chunks.push(b);\n S.stdin.queued += b.length;\n }\n if (args.eof) S.stdin.closed = true;\n wakeStdin(S);\n return pump(S);\n } catch (e) {\n const s = S;\n S = null;\n return { state: \"error\", exitCode: 1, stdout: s ? s.out : \"\", stderr: s ? s.err : \"\", error: \"feed failed: \" + String(e && e.message || e) };\n }\n };\n})();\n";
|