@nimbus-sh/worker 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +5 -0
- package/README.md +10 -0
- package/dist/_shared/session-router.d.ts +7 -0
- package/dist/_shared/session-router.d.ts.map +1 -1
- package/dist/_shared/session-router.js +7 -0
- package/dist/bindings/body.d.ts +6 -0
- package/dist/bindings/body.d.ts.map +1 -0
- package/dist/bindings/body.js +37 -0
- package/dist/bindings/kv.d.ts +0 -2
- package/dist/bindings/kv.d.ts.map +1 -1
- package/dist/bindings/kv.js +3 -42
- package/dist/bindings/r2.d.ts +0 -2
- package/dist/bindings/r2.d.ts.map +1 -1
- package/dist/bindings/r2.js +4 -44
- package/dist/esbuild-cli-artifact.generated.d.ts +18 -0
- package/dist/esbuild-cli-artifact.generated.d.ts.map +1 -0
- package/dist/esbuild-cli-artifact.generated.js +17 -0
- package/dist/esbuild-wasm-bundle.generated.d.ts +24 -24
- package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -1
- package/dist/esbuild-wasm-bundle.generated.js +24 -24
- package/dist/facets/cirrus-real.d.ts.map +1 -1
- package/dist/facets/cirrus-real.js +11 -6
- package/dist/facets/compose.d.ts +11 -7
- package/dist/facets/compose.d.ts.map +1 -1
- package/dist/facets/compose.js +8 -31
- package/dist/facets/data-plan.d.ts +79 -0
- package/dist/facets/data-plan.d.ts.map +1 -0
- package/dist/facets/data-plan.js +477 -0
- package/dist/facets/esbuild-bundle-pool.d.ts +1 -1
- package/dist/facets/esbuild-bundle-pool.js +3 -3
- package/dist/facets/esbuild-transform.d.ts +38 -8
- package/dist/facets/esbuild-transform.d.ts.map +1 -1
- package/dist/facets/esbuild-transform.js +219 -19
- package/dist/facets/exec-telemetry.d.ts +7 -3
- package/dist/facets/exec-telemetry.d.ts.map +1 -1
- package/dist/facets/manager.d.ts +198 -69
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +1276 -652
- package/dist/facets/opencode-staging.d.ts +0 -2
- package/dist/facets/opencode-staging.d.ts.map +1 -1
- package/dist/facets/opencode-staging.js +5 -11
- package/dist/facets/process.d.ts +4 -0
- package/dist/facets/process.d.ts.map +1 -1
- package/dist/facets/process.js +3 -0
- package/dist/facets/read-profile.d.ts +149 -0
- package/dist/facets/read-profile.d.ts.map +1 -0
- package/dist/facets/read-profile.js +483 -0
- package/dist/facets/real-vite-hmr.d.ts +2 -0
- package/dist/facets/real-vite-hmr.d.ts.map +1 -1
- package/dist/facets/real-vite-hmr.js +3 -2
- package/dist/facets/resident-identity.d.ts +0 -1
- package/dist/facets/resident-identity.d.ts.map +1 -1
- package/dist/facets/resident-identity.js +0 -3
- package/dist/facets/wasm-swap-registry.d.ts +34 -25
- package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
- package/dist/facets/wasm-swap-registry.js +62 -56
- package/dist/facets/ws-terminal.d.ts +4 -4
- package/dist/facets/ws-terminal.d.ts.map +1 -1
- package/dist/facets/ws-terminal.js +4 -5
- package/dist/git/commands.d.ts +33 -10
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +1891 -197
- package/dist/git/network-facet.d.ts +12 -2
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +69 -18
- package/dist/git/unified-diff.d.ts +74 -0
- package/dist/git/unified-diff.d.ts.map +1 -0
- package/dist/git/unified-diff.js +676 -0
- package/dist/git-bundle.generated.d.ts +7 -11
- package/dist/git-bundle.generated.d.ts.map +1 -1
- package/dist/git-bundle.generated.js +7 -11
- package/dist/hosted/commands.d.ts.map +1 -1
- package/dist/hosted/commands.js +64 -245
- package/dist/hosted/runtime.d.ts +22 -15
- package/dist/hosted/runtime.d.ts.map +1 -1
- package/dist/hosted/runtime.js +72 -14
- package/dist/hosted/services.d.ts +8 -1
- package/dist/hosted/services.d.ts.map +1 -1
- package/dist/hosted/services.js +12 -13
- package/dist/hosted/session.d.ts +165 -0
- package/dist/hosted/session.d.ts.map +1 -0
- package/dist/hosted/session.js +132 -0
- package/dist/loaders/generated-workers.d.ts +6 -3
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +8 -5
- package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
- package/dist/loaders/npm-resolve-preamble.js +0 -27
- package/dist/loaders/pre-bundle-preamble.d.ts +6 -3
- package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -1
- package/dist/loaders/pre-bundle-preamble.js +13 -8
- package/dist/node-shims-artifact.generated.d.ts +13 -4
- package/dist/node-shims-artifact.generated.d.ts.map +1 -1
- package/dist/node-shims-artifact.generated.js +16 -7
- package/dist/npm/bin-links.d.ts +3 -1
- package/dist/npm/bin-links.d.ts.map +1 -1
- package/dist/npm/bin-links.js +32 -8
- package/dist/npm/cache.d.ts +11 -1
- package/dist/npm/cache.d.ts.map +1 -1
- package/dist/npm/cache.js +21 -7
- package/dist/npm/install-batch-facet.d.ts +4 -2
- package/dist/npm/install-batch-facet.d.ts.map +1 -1
- package/dist/npm/install-batch-facet.js +32 -18
- package/dist/npm/installer.d.ts +23 -3
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +330 -131
- package/dist/npm/npx-install.d.ts +3 -1
- package/dist/npm/npx-install.d.ts.map +1 -1
- package/dist/npm/npx-install.js +4 -1
- package/dist/npm/package-lock.d.ts +32 -0
- package/dist/npm/package-lock.d.ts.map +1 -0
- package/dist/npm/package-lock.js +65 -0
- package/dist/npm/placement.d.ts +16 -0
- package/dist/npm/placement.d.ts.map +1 -0
- package/dist/npm/placement.js +33 -0
- package/dist/npm/pre-bundle-facet.d.ts +1 -30
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
- package/dist/npm/pre-bundle-facet.js +3 -5
- package/dist/npm/r2-cache.d.ts +12 -6
- package/dist/npm/r2-cache.d.ts.map +1 -1
- package/dist/npm/r2-cache.js +34 -16
- package/dist/npm/resolve-one-facet.d.ts +3 -0
- package/dist/npm/resolve-one-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.js +15 -3
- package/dist/npm/resolver.d.ts +11 -13
- package/dist/npm/resolver.d.ts.map +1 -1
- package/dist/npm/resolver.js +12 -25
- package/dist/opencode-artifact.generated.js +2 -2
- package/dist/replica/routing.d.ts +2 -38
- package/dist/replica/routing.d.ts.map +1 -1
- package/dist/replica/routing.js +3 -55
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +15 -2
- package/dist/router/public-directory.d.ts +18 -0
- package/dist/router/public-directory.d.ts.map +1 -1
- package/dist/router/public-directory.js +1 -1
- package/dist/router/remote-api.d.ts +3 -1
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +42 -79
- package/dist/runtime/bash-repl.js +2 -2
- package/dist/runtime/bun-repl.d.ts.map +1 -1
- package/dist/runtime/bun-repl.js +2 -13
- package/dist/runtime/cpython-resident.d.ts.map +1 -1
- package/dist/runtime/cpython-resident.js +2 -10
- package/dist/runtime/esbuild-wasm-bytes.d.ts +37 -18
- package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/esbuild-wasm-bytes.js +66 -92
- package/dist/runtime/facet-loader-host.d.ts.map +1 -1
- package/dist/runtime/facet-loader-host.js +0 -7
- package/dist/runtime/git-bundle-artifact.d.ts +17 -0
- package/dist/runtime/git-bundle-artifact.d.ts.map +1 -0
- package/dist/runtime/git-bundle-artifact.js +25 -0
- package/dist/runtime/node-repl.d.ts.map +1 -1
- package/dist/runtime/node-repl.js +2 -13
- package/dist/runtime/node-shims-artifact.d.ts +22 -23
- package/dist/runtime/node-shims-artifact.d.ts.map +1 -1
- package/dist/runtime/node-shims-artifact.js +48 -78
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +2358 -996
- package/dist/runtime/opencode-artifact.d.ts.map +1 -1
- package/dist/runtime/opencode-artifact.js +17 -53
- package/dist/runtime/opencode-facet-runner.d.ts +3 -6
- package/dist/runtime/opencode-facet-runner.d.ts.map +1 -1
- package/dist/runtime/opencode-facet-runner.js +45 -10
- package/dist/runtime/opentui-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/opentui-wasm-bytes.js +16 -49
- package/dist/runtime/package-manager.d.ts +0 -2
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +0 -3
- package/dist/runtime/python-repl.d.ts.map +1 -1
- package/dist/runtime/python-repl.js +4 -35
- package/dist/runtime/repl-session.d.ts +10 -0
- package/dist/runtime/repl-session.d.ts.map +1 -1
- package/dist/runtime/repl-session.js +7 -0
- package/dist/runtime/ruby-repl.d.ts.map +1 -1
- package/dist/runtime/ruby-repl.js +2 -2
- package/dist/runtime/ruby-resident.d.ts.map +1 -1
- package/dist/runtime/ruby-resident.js +41 -51
- package/dist/runtime/runtime-catalog.d.ts +10 -3
- package/dist/runtime/runtime-catalog.d.ts.map +1 -1
- package/dist/runtime/runtime-catalog.js +125 -15
- package/dist/runtime/sqlite-wasm-bytes.d.ts +4 -4
- package/dist/runtime/sqlite-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/sqlite-wasm-bytes.js +21 -57
- package/dist/runtime/staged-source.d.ts +67 -0
- package/dist/runtime/staged-source.d.ts.map +1 -0
- package/dist/runtime/staged-source.js +113 -0
- package/dist/runtime/wasi/preamble.d.ts +0 -22
- package/dist/runtime/wasi/preamble.d.ts.map +1 -1
- package/dist/runtime/wasi/preamble.js +98 -1425
- package/dist/runtime-catalog.generated.js +1 -1
- package/dist/session/agent.d.ts +14 -2
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/agent.js +57 -19
- package/dist/session/ai.d.ts +0 -11
- package/dist/session/ai.d.ts.map +1 -1
- package/dist/session/ai.js +0 -4
- package/dist/session/fs-watch.d.ts +0 -11
- package/dist/session/fs-watch.d.ts.map +1 -1
- package/dist/session/fs-watch.js +0 -21
- package/dist/session/hibernation.d.ts +68 -2
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +95 -1
- package/dist/session/init-phases.d.ts +1 -2
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +1 -2
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +7 -20
- package/dist/session/keys.d.ts +0 -3
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +0 -3
- package/dist/session/legacy-reset.d.ts +11 -0
- package/dist/session/legacy-reset.d.ts.map +1 -0
- package/dist/session/legacy-reset.js +16 -0
- package/dist/session/nimbus-session.d.ts +50 -16
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +77 -71
- package/dist/session/npm-install-port.d.ts.map +1 -1
- package/dist/session/npm-install-port.js +2 -0
- package/dist/session/programmatic.d.ts +13 -18
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +53 -39
- package/dist/session/replica-routes.d.ts +2 -3
- package/dist/session/replica-routes.d.ts.map +1 -1
- package/dist/session/replica-routes.js +3 -10
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +11 -12
- package/dist/session/rpc.d.ts +108 -21
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +194 -39
- package/dist/session/start-real-vite.js +2 -2
- package/dist/session/state-store.d.ts +1 -17
- package/dist/session/state-store.d.ts.map +1 -1
- package/dist/session/state-store.js +3 -53
- package/dist/session/supervisor-op.d.ts +7 -0
- package/dist/session/supervisor-op.d.ts.map +1 -1
- package/dist/session/supervisor-op.js +6 -1
- package/dist/session/supervisor-rpc.d.ts +116 -19
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +194 -84
- package/dist/session/vite-command.d.ts.map +1 -1
- package/dist/session/vite-command.js +17 -14
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +20 -8
- package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
- package/dist/shell/npm-bin-entrypoints.js +30 -5
- package/dist/vfs/facet-resident-limits.d.ts +33 -0
- package/dist/vfs/facet-resident-limits.d.ts.map +1 -0
- package/dist/vfs/facet-resident-limits.js +33 -0
- package/dist/vfs/facet-resident-store.d.ts +2 -10
- package/dist/vfs/facet-resident-store.d.ts.map +1 -1
- package/dist/vfs/facet-resident-store.js +1594 -219
- package/dist/workspace-host.d.ts +3 -1
- package/dist/workspace-host.d.ts.map +1 -1
- package/dist/workspace-host.js +2 -0
- package/dist/wrangler/nimbus-wrangler.d.ts +0 -2
- package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -1
- package/dist/wrangler/nimbus-wrangler.js +8 -22
- package/package.json +34 -7
- package/public/_assets/esbuild-0.24.2.js +2380 -0
- package/public/_assets/opencode/1.16.2/index-attach.js +2 -2
- package/public/_assets/runtime/esbuild-cli-c4fda380d52befc1.js +6892 -0
- package/public/_assets/runtime/git-de3e60c9f3ff006a.js +103 -0
- package/public/_assets/runtime/{node-shims-acaa037e6d26ec0d.js → node-shims-06565b121555c194.js} +2879 -992
- package/public/_assets/runtime/resident-store-6ffb1939c53d982f.js +2264 -0
- package/public/_assets/runtime/vfs-write-ledger-e501e8fc793bc2c1.js +889 -0
- package/scripts/build-opencode-attach-entry.mjs +51 -1
- package/scripts/bundle-esbuild-wasm.mjs +55 -39
- package/scripts/bundle-facet-workers.mjs +159 -10
- package/scripts/bundle-git.mjs +88 -27
- package/scripts/bundle-node-shims.mjs +72 -46
- package/scripts/bundle-runtime.mjs +18 -421
- package/scripts/cf-git-patch.mjs +137 -0
- package/scripts/patch-install-deps.mjs +49 -96
- package/scripts/runtime-specs.mjs +435 -0
- package/vendor/git-http.generated.d.ts +82 -0
- package/vendor/git-upstream.generated.d.ts +4378 -0
- package/vendor/git.LICENSE +7 -0
- package/vendor/git.generated.d.mts +21 -0
- package/vendor/git.generated.mjs +103 -0
- package/dist/replica/suspension.d.ts +0 -36
- package/dist/replica/suspension.d.ts.map +0 -1
- package/dist/replica/suspension.js +0 -49
- package/dist/runtime/static-server.d.ts +0 -3
- package/dist/runtime/static-server.d.ts.map +0 -1
- package/dist/runtime/static-server.js +0 -133
- package/scripts/clean-dist.mjs +0 -7
|
@@ -88,49 +88,28 @@ catch (__e) {
|
|
|
88
88
|
class __WasiExit {
|
|
89
89
|
constructor(code) { this.code = code | 0; }
|
|
90
90
|
}
|
|
91
|
-
//
|
|
91
|
+
// The device a pathless descriptor (stdio, a socket) reports in fd_filestat_get.
|
|
92
|
+
// Above any device the authority can hand out, so a fd-numbered inode on it
|
|
93
|
+
// never collides with a file's (dev, ino).
|
|
94
|
+
const __WASI_STREAM_DEV = 1n << 32n;
|
|
95
|
+
// ─── Filesystem state ───────────────────────────────────────────────────
|
|
92
96
|
//
|
|
93
|
-
// __wasiInitFS({ root, preopens,
|
|
94
|
-
//
|
|
95
|
-
// root string — canonical session root, e.g. 'home/user
|
|
97
|
+
// __wasiInitFS({ root, preopens, residentFileCap? }) installs a per-call
|
|
98
|
+
// filesystem view:
|
|
99
|
+
// root string — canonical session root, e.g. 'home/user'.
|
|
96
100
|
// preopens Array<{ wasiPath, vfsPath }> — fd>=3 preopens (in order).
|
|
97
|
-
// files Record<vfsPath, base64 string> — initial file contents.
|
|
98
|
-
// dirs Array<vfsPath> — initial directory list.
|
|
99
|
-
// modes Record<vfsPath, effective rwx bits> — access already resolved
|
|
100
|
-
// for the invoking credential by the supervisor.
|
|
101
|
-
// times Record<vfsPath, {mtime, atime, ctime}> | undefined
|
|
102
|
-
// — per-path nanosecond timestamps (WASI socket and polling support B1). Optional;
|
|
103
|
-
// paths without entries default to "now" at init time.
|
|
104
|
-
// Values are decimal strings to survive JSON.serialize
|
|
105
|
-
// (BigInt is not JSON-safe); converted to BigInt internally.
|
|
106
|
-
// symlinks Record<vfsPath, targetPath> | undefined
|
|
107
|
-
// — WASI socket and polling support B3 in-memory symlink table. Optional; defaults
|
|
108
|
-
// to empty. Target paths are canonicalized at insertion.
|
|
109
101
|
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
102
|
+
// There is no in-facet copy of the filesystem. Every file and directory
|
|
103
|
+
// syscall is answered by the authority codec (wasi/filesystem.ts) through the
|
|
104
|
+
// supervisor stub __wasiAdoptSupervisor installs, reading and writing the same
|
|
105
|
+
// inodes the shell does. What this module keeps is the descriptor table's
|
|
106
|
+
// non-file kinds: stdio, sockets, listeners and the preopen roots.
|
|
113
107
|
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
// them. Eight other places DID check, and answered null/0/false, so the same
|
|
118
|
-
// pre-init read was a TypeError down one path and a plausible wrong answer down
|
|
119
|
-
// another. wasm-runner worked around it by calling __wasiInitFS with an empty
|
|
120
|
-
// seed purely so "__wasiFS isn't null when WASI fns are called".
|
|
121
|
-
//
|
|
122
|
-
// Starting empty makes the invariant structural instead of a caller's duty: a
|
|
123
|
-
// syscall before init now answers ENOENT against an empty filesystem, which is
|
|
124
|
-
// true, rather than trapping the guest or being individually guarded 113 times.
|
|
108
|
+
// Starting empty rather than null keeps the invariant structural: a syscall
|
|
109
|
+
// before init answers EBADF against an empty descriptor table, which is true,
|
|
110
|
+
// rather than trapping the guest.
|
|
125
111
|
function __wasiEmptyFS() {
|
|
126
|
-
return {
|
|
127
|
-
root: '',
|
|
128
|
-
files: new Map(), dirs: new Set(), times: new Map(), symlinks: new Map(),
|
|
129
|
-
modes: new Map(), sizes: new Map(), origFiles: new Map(),
|
|
130
|
-
residentFileCap: WASI_RESIDENT_FILE_CAP_BYTES,
|
|
131
|
-
enumeratedRoots: [],
|
|
132
|
-
revision: null,
|
|
133
|
-
};
|
|
112
|
+
return { root: '', residentFileCap: WASI_RESIDENT_FILE_CAP_BYTES };
|
|
134
113
|
}
|
|
135
114
|
let __wasiFS = __wasiEmptyFS();
|
|
136
115
|
let __wasiPreopens = [];
|
|
@@ -145,186 +124,21 @@ let __wasiPreopens = [];
|
|
|
145
124
|
let __wasiThreads = null;
|
|
146
125
|
// ── Live backing store ───────────────────────────────────────────────────
|
|
147
126
|
//
|
|
148
|
-
// The supervisor stub
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
127
|
+
// The supervisor stub. Every filesystem call the guest makes is answered by
|
|
128
|
+
// the authority codec (wasi/filesystem.ts) through it; absent (unit tests,
|
|
129
|
+
// compute-only instances) the guest has stdio, sockets and clocks but no
|
|
130
|
+
// files, and a file syscall answers EBADF.
|
|
152
131
|
let __wasiSup = null;
|
|
153
|
-
const __wasiPersistQ = { pending: [], tail: Promise.resolve(), failures: [] };
|
|
154
132
|
// Adopting is idempotent and never downgrades. A resident process re-enters
|
|
155
133
|
// through routed fetch/handleHttpRequest hops that resolve the entrypoint
|
|
156
134
|
// WITHOUT a supervisor in env; clearing the live stub on those hops would
|
|
157
|
-
// strand the process
|
|
135
|
+
// strand the process.
|
|
158
136
|
export function __wasiAdoptSupervisor(sup) {
|
|
159
137
|
if (sup)
|
|
160
138
|
__wasiSup = sup;
|
|
161
139
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
* supervisor to back it. Only manifestVfs stamps a revision, so this is
|
|
165
|
-
* exactly "the producer expected this filesystem to be live" — as opposed to
|
|
166
|
-
* a sealed instance, whose seed IS the whole filesystem by design.
|
|
167
|
-
*/
|
|
168
|
-
function __wasiExpectsLiveBacking() {
|
|
169
|
-
return __wasiFS.revision !== null && __wasiFS.revision !== undefined;
|
|
170
|
-
}
|
|
171
|
-
/** Mirror one mutation to the session VFS. Cache is already updated. */
|
|
172
|
-
function __wasiEnqueue(op, run) {
|
|
173
|
-
if (!__wasiSup) {
|
|
174
|
-
// A sealed instance keeps its mutations in memory on purpose. One that was
|
|
175
|
-
// seeded as a CACHE and has no supervisor cannot: the write is already
|
|
176
|
-
// gone. Recording it makes the next drain throw, which is the whole reason
|
|
177
|
-
// the exit-time diff could be deleted — silence here would put the data
|
|
178
|
-
// loss back, just without the second implementation.
|
|
179
|
-
if (__wasiExpectsLiveBacking()) {
|
|
180
|
-
__wasiPersistQ.failures.push(op + ': no supervisor adopted for a live-backed filesystem');
|
|
181
|
-
}
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
// Captured HERE rather than read when the continuation runs. __wasiInitFS
|
|
185
|
-
// nulls __wasiSup and resets the queue tail, but resetting the tail does not
|
|
186
|
-
// cancel continuations already chained to the old one — so a pooled isolate
|
|
187
|
-
// that re-inits with work in flight used to hand the previous program's op a
|
|
188
|
-
// null stub, lose that mutation, and then throw the resulting TypeError out
|
|
189
|
-
// of the NEXT program's drain. An op belongs to the process that queued it.
|
|
190
|
-
const sup = __wasiSup;
|
|
191
|
-
const entry = { op };
|
|
192
|
-
__wasiPersistQ.pending.push(entry);
|
|
193
|
-
__wasiPersistQ.tail = __wasiPersistQ.tail.then(async () => {
|
|
194
|
-
try {
|
|
195
|
-
await run(sup);
|
|
196
|
-
}
|
|
197
|
-
catch (e) {
|
|
198
|
-
// A failed write-back is data loss and must be visible, not swallowed.
|
|
199
|
-
__wasiPersistQ.failures.push(op + ': ' + ((e && e.message) || String(e)));
|
|
200
|
-
}
|
|
201
|
-
finally {
|
|
202
|
-
const i = __wasiPersistQ.pending.indexOf(entry);
|
|
203
|
-
if (i >= 0)
|
|
204
|
-
__wasiPersistQ.pending.splice(i, 1);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
// Paths with a write-back already queued. The queued op reads the cache when
|
|
209
|
-
// it runs, so a burst of fd_writes to one file coalesces into one round trip
|
|
210
|
-
// instead of one per write.
|
|
211
|
-
const __wasiDirty = new Set();
|
|
212
|
-
// Paths a live stat has already reported absent. Cleared whenever anything is
|
|
213
|
-
// created or the cache is revalidated, so a negative never outlives the fact.
|
|
214
|
-
const __wasiNegative = new Set();
|
|
215
|
-
function __wasiPersistFile(vfsPath) {
|
|
216
|
-
// No supervisor check here: __wasiEnqueue is the one place that decides what
|
|
217
|
-
// a missing one means. Marking the path dirty first also means a burst of
|
|
218
|
-
// writes to it reports at most once.
|
|
219
|
-
if (__wasiDirty.has(vfsPath))
|
|
220
|
-
return;
|
|
221
|
-
__wasiDirty.add(vfsPath);
|
|
222
|
-
// The bytes are read when the op RUNS, which is what coalesces a burst of
|
|
223
|
-
// fd_writes into one round trip. The filesystem they are read from is pinned
|
|
224
|
-
// here, though: reading the global instead meant a re-init between the queue
|
|
225
|
-
// and the drain looked up the path in the NEXT program's empty cache, found
|
|
226
|
-
// nothing, and returned successfully having written nothing at all.
|
|
227
|
-
const fs = __wasiFS;
|
|
228
|
-
__wasiEnqueue('writeFile ' + vfsPath, async (sup) => {
|
|
229
|
-
__wasiDirty.delete(vfsPath);
|
|
230
|
-
const bytes = fs && fs.files.get(vfsPath);
|
|
231
|
-
if (!bytes)
|
|
232
|
-
return;
|
|
233
|
-
await sup.writeFile(vfsPath, bytes);
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Mirror an explicit utimensat. Deliberately NOT reached from the implicit
|
|
238
|
-
* mtime bump every write does — writeFile already sets it, and a round trip
|
|
239
|
-
* per fd_write would be one syscall's cost paid by all of them.
|
|
240
|
-
*/
|
|
241
|
-
function __wasiPersistTimes(vfsPath) {
|
|
242
|
-
const t = __wasiFS.times.get(vfsPath);
|
|
243
|
-
if (!t)
|
|
244
|
-
return;
|
|
245
|
-
const atimeMs = Number(t.atime / 1000000n);
|
|
246
|
-
const mtimeMs = Number(t.mtime / 1000000n);
|
|
247
|
-
__wasiEnqueue('utimes ' + vfsPath, (sup) => sup.utimes(vfsPath, atimeMs, mtimeMs));
|
|
248
|
-
}
|
|
249
|
-
function __wasiPersistSymlink(target, vfsPath) {
|
|
250
|
-
__wasiEnqueue('symlink ' + vfsPath, (sup) => sup.symlink(target, vfsPath));
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Drop cached content that matches what the supervisor already has, turning
|
|
254
|
-
* those inodes back into manifest entries so the next read refetches. Content
|
|
255
|
-
* with a write still queued is never dropped. Returns the paths evicted.
|
|
256
|
-
*
|
|
257
|
-
* This is what makes the cache a cache: memory pressure and staleness are both
|
|
258
|
-
* answered by forgetting, never by serving something known to be old.
|
|
259
|
-
*/
|
|
260
|
-
function __wasiEvictCleanContent() {
|
|
261
|
-
if (!__wasiFS || !__wasiSup)
|
|
262
|
-
return [];
|
|
263
|
-
const evicted = [];
|
|
264
|
-
for (const [path, bytes] of [...__wasiFS.files]) {
|
|
265
|
-
if (__wasiDirty.has(path))
|
|
266
|
-
continue;
|
|
267
|
-
const orig = __wasiFS.origFiles.get(path);
|
|
268
|
-
if (!orig || orig.length !== bytes.length)
|
|
269
|
-
continue;
|
|
270
|
-
let same = true;
|
|
271
|
-
for (let i = 0; i < orig.length; i++) {
|
|
272
|
-
if (orig[i] !== bytes[i]) {
|
|
273
|
-
same = false;
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
if (!same)
|
|
278
|
-
continue;
|
|
279
|
-
__wasiFS.files.delete(path);
|
|
280
|
-
__wasiFS.sizes.set(path, bytes.length);
|
|
281
|
-
evicted.push(path);
|
|
282
|
-
}
|
|
283
|
-
return evicted;
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Re-sync the cache with the session VFS. A resident process (a server) must
|
|
287
|
-
* see files that other processes created or changed after it spawned, so the
|
|
288
|
-
* cached content is dropped and metadata re-read on next access.
|
|
289
|
-
*/
|
|
290
|
-
export async function __wasiRevalidateFS() {
|
|
291
|
-
if (!__wasiSup || !__wasiFS)
|
|
292
|
-
return [];
|
|
293
|
-
await __wasiDrainPersist();
|
|
294
|
-
// One round trip decides it. An unchanged subtree means the cache is still
|
|
295
|
-
// exactly right, so a resident server parking between requests pays a single
|
|
296
|
-
// revision check rather than re-reading everything it had already loaded.
|
|
297
|
-
if (typeof __wasiSup.fsRevision === 'function') {
|
|
298
|
-
let revision;
|
|
299
|
-
try {
|
|
300
|
-
revision = await __wasiSup.fsRevision(__wasiFS.root);
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
revision = null;
|
|
304
|
-
}
|
|
305
|
-
if (revision !== null && revision === __wasiFS.revision)
|
|
306
|
-
return [];
|
|
307
|
-
__wasiFS.revision = revision;
|
|
308
|
-
}
|
|
309
|
-
__wasiNegative.clear();
|
|
310
|
-
return __wasiEvictCleanContent();
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Await every queued mutation. Callers drain before returning a result to the
|
|
314
|
-
* supervisor and before any live read, so the supervisor's view always
|
|
315
|
-
* includes this process's own writes.
|
|
316
|
-
*/
|
|
317
|
-
export async function __wasiDrainPersist() {
|
|
318
|
-
await __wasiPersistQ.tail;
|
|
319
|
-
if (__wasiPersistQ.failures.length > 0) {
|
|
320
|
-
const failures = __wasiPersistQ.failures.slice();
|
|
321
|
-
__wasiPersistQ.failures.length = 0;
|
|
322
|
-
throw new Error('wasi persist failed: ' + failures.join('; '));
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
// All paths are stored in canonical form: no leading '/', no '..', no
|
|
326
|
-
// double slashes. The wasm program sees '/foo/bar.txt' (with leading
|
|
327
|
-
// slash) but the VFS keys are 'home/user/.../foo/bar.txt'.
|
|
140
|
+
// A guest-visible path in canonical form: no leading '/', no '..', no double
|
|
141
|
+
// slashes. The socket path prefixes are matched against it.
|
|
328
142
|
function __wasiCanonicalize(p) {
|
|
329
143
|
if (!p)
|
|
330
144
|
return '';
|
|
@@ -342,120 +156,15 @@ function __wasiCanonicalize(p) {
|
|
|
342
156
|
}
|
|
343
157
|
return out.join('/');
|
|
344
158
|
}
|
|
345
|
-
// Wall-clock at init, used as the default mtime/atime/ctime for any path
|
|
346
|
-
// that arrives without an explicit times entry. Captured once per call so
|
|
347
|
-
// all "default-init" timestamps within a call share a value (mtime ==
|
|
348
|
-
// atime == ctime), matching what a real cold-load would produce.
|
|
349
|
-
// One device for this filesystem, and a stable distinct inode per path.
|
|
350
|
-
//
|
|
351
|
-
// A guest is entitled to treat (st_dev, st_ino) as an identity: LLVM's
|
|
352
|
-
// FileManager keys its directory cache on exactly that pair, GNU make and
|
|
353
|
-
// find -samefile compare it, and rsync uses it to detect hardlinks. Emitting
|
|
354
|
-
// a constant zero collapses every inode into one — clang then searched a
|
|
355
|
-
// single include directory and reported every system header missing. FNV-1a
|
|
356
|
-
// over the canonical path is stable across calls within a run and across
|
|
357
|
-
// processes seeded from the same tree, which is what those callers need.
|
|
358
|
-
const __WASI_DEV = 1n;
|
|
359
|
-
const __WASI_INO_MASK = 0xffffffffffffffffn;
|
|
360
|
-
function __wasiInode(path) {
|
|
361
|
-
let h = 14695981039346656037n;
|
|
362
|
-
const p = String(path === undefined || path === null ? '' : path);
|
|
363
|
-
for (let i = 0; i < p.length; i++) {
|
|
364
|
-
h = (h ^ BigInt(p.charCodeAt(i) & 0xff)) * 1099511628211n & __WASI_INO_MASK;
|
|
365
|
-
}
|
|
366
|
-
// 0 is the "no inode" value a caller may test for; never hand it out.
|
|
367
|
-
return h === 0n ? 1n : h;
|
|
368
|
-
}
|
|
369
|
-
function __wasiNowNs() {
|
|
370
|
-
// Date.now() is ms since epoch; multiply by 1e6 → ns.
|
|
371
|
-
return BigInt(Date.now()) * 1000000n;
|
|
372
|
-
}
|
|
373
159
|
export function __wasiInitFS(opts) {
|
|
374
|
-
// A fresh init is a fresh process: no supervisor adopted yet
|
|
375
|
-
// the previous tenant's
|
|
376
|
-
//
|
|
377
|
-
// this would let one program's state answer another program's syscalls.
|
|
160
|
+
// A fresh init is a fresh process: no supervisor adopted yet. Pools reuse an
|
|
161
|
+
// isolate across calls, so the previous tenant's stub must not answer the
|
|
162
|
+
// next program's syscalls.
|
|
378
163
|
__wasiSup = null;
|
|
379
|
-
__wasiDirty.clear();
|
|
380
|
-
__wasiNegative.clear();
|
|
381
|
-
__wasiPersistQ.pending.length = 0;
|
|
382
|
-
__wasiPersistQ.failures.length = 0;
|
|
383
|
-
__wasiPersistQ.tail = Promise.resolve();
|
|
384
|
-
const files = new Map(); // canonicalVfsPath → Uint8Array
|
|
385
|
-
const dirs = new Set(); // canonicalVfsPath
|
|
386
|
-
// WASI socket and polling support B1: parallel timestamp + symlink maps.
|
|
387
|
-
const times = new Map(); // canonicalVfsPath → {mtime: BigInt, atime: BigInt, ctime: BigInt}
|
|
388
|
-
const symlinks = new Map(); // canonicalVfsPath → targetPath (canonical)
|
|
389
|
-
const modes = new Map(); // canonicalVfsPath → effective rwx bits
|
|
390
|
-
// Mirror of the content the seed carried (and of anything demand-loaded),
|
|
391
|
-
// so the evictor can tell a cached-but-unchanged file from a dirty one.
|
|
392
|
-
const origFiles = new Map();
|
|
393
|
-
const nowNs = __wasiNowNs();
|
|
394
|
-
for (const [path, b64] of Object.entries(opts.files || {})) {
|
|
395
|
-
const canon = __wasiCanonicalize(path);
|
|
396
|
-
const bin = atob(b64);
|
|
397
|
-
const u8 = new Uint8Array(bin.length);
|
|
398
|
-
for (let i = 0; i < bin.length; i++)
|
|
399
|
-
u8[i] = bin.charCodeAt(i);
|
|
400
|
-
files.set(canon, u8);
|
|
401
|
-
origFiles.set(canon, u8.slice()); // copy so subsequent mutations detect change
|
|
402
|
-
// Default timestamps (overwritten below if opts.times has this path).
|
|
403
|
-
times.set(canon, { mtime: nowNs, atime: nowNs, ctime: nowNs });
|
|
404
|
-
}
|
|
405
|
-
for (const path of opts.dirs || []) {
|
|
406
|
-
const canon = __wasiCanonicalize(path);
|
|
407
|
-
dirs.add(canon);
|
|
408
|
-
if (!times.has(canon))
|
|
409
|
-
times.set(canon, { mtime: nowNs, atime: nowNs, ctime: nowNs });
|
|
410
|
-
}
|
|
411
|
-
for (const [path, mode] of Object.entries(opts.modes)) {
|
|
412
|
-
modes.set(__wasiCanonicalize(path), Number(mode) & 7);
|
|
413
|
-
}
|
|
414
|
-
// WASI socket and polling support B1: load explicit per-path timestamps if supervisor provided them.
|
|
415
|
-
// Values arrive as decimal strings (BigInt → JSON.stringify safe) or numbers.
|
|
416
|
-
for (const [path, t] of Object.entries(opts.times || {})) {
|
|
417
|
-
const canon = __wasiCanonicalize(path);
|
|
418
|
-
const cur = times.get(canon);
|
|
419
|
-
if (!cur)
|
|
420
|
-
continue; // path not yet in files/dirs; skip
|
|
421
|
-
const mtime = (t && t.mtime !== undefined) ? BigInt(t.mtime) : cur.mtime;
|
|
422
|
-
const atime = (t && t.atime !== undefined) ? BigInt(t.atime) : cur.atime;
|
|
423
|
-
const ctime = (t && t.ctime !== undefined) ? BigInt(t.ctime) : cur.ctime;
|
|
424
|
-
times.set(canon, { mtime, atime, ctime });
|
|
425
|
-
}
|
|
426
|
-
// WASI socket and polling support B3: load explicit symlinks if supervisor provided them.
|
|
427
|
-
for (const [path, target] of Object.entries(opts.symlinks || {})) {
|
|
428
|
-
const canon = __wasiCanonicalize(path);
|
|
429
|
-
symlinks.set(canon, String(target));
|
|
430
|
-
}
|
|
431
|
-
// Metadata-only entries: the manifest knows the file and its size, the seed
|
|
432
|
-
// did not carry its bytes. Content arrives on first read via the supervisor.
|
|
433
|
-
// A path here but absent from the files map is a cache miss, never ENOENT.
|
|
434
|
-
const sizes = new Map();
|
|
435
|
-
for (const [path, size] of Object.entries(opts.sizes || {})) {
|
|
436
|
-
const canon = __wasiCanonicalize(path);
|
|
437
|
-
sizes.set(canon, Number(size));
|
|
438
|
-
if (!times.has(canon))
|
|
439
|
-
times.set(canon, { mtime: nowNs, atime: nowNs, ctime: nowNs });
|
|
440
|
-
}
|
|
441
164
|
__wasiFS = {
|
|
442
165
|
root: __wasiCanonicalize(opts.root || ''),
|
|
443
|
-
|
|
444
|
-
origFiles,
|
|
445
|
-
// Files at or above this size are never held whole; reads window through
|
|
446
|
-
// the supervisor instead. Keeps a 200 MiB blob from ending the isolate.
|
|
166
|
+
// Largest regular file the codec answers from a resident copy.
|
|
447
167
|
residentFileCap: Number(opts.residentFileCap ?? WASI_RESIDENT_FILE_CAP_BYTES),
|
|
448
|
-
// Roots the seed claims to have listed COMPLETELY. Only a producer that
|
|
449
|
-
// walked a subtree without exclusions may claim one. Inside such a root a
|
|
450
|
-
// path the manifest lacks is genuinely absent, so the miss is answered
|
|
451
|
-
// here instead of costing a round trip — which is most of the traffic a
|
|
452
|
-
// language runtime generates ($LOAD_PATH / sys.path probing walks
|
|
453
|
-
// candidate names that mostly do not exist).
|
|
454
|
-
enumeratedRoots: (opts.enumeratedRoots || []).map(__wasiCanonicalize),
|
|
455
|
-
// Supervisor revision the seed was built against; a park re-checks it and
|
|
456
|
-
// only then drops cached content. Without this the cache could serve
|
|
457
|
-
// content another process has since changed.
|
|
458
|
-
revision: opts.revision ?? null,
|
|
459
168
|
};
|
|
460
169
|
// Reset fd table baseline; install preopens as fd 3, 4, 5, ...
|
|
461
170
|
// Emptied rather than replaced: the authority codec and the socket helpers
|
|
@@ -472,10 +181,6 @@ export function __wasiInitFS(opts) {
|
|
|
472
181
|
const vfsPath = __wasiCanonicalize(po.vfsPath);
|
|
473
182
|
fdTable.set(fd, { kind: 'preopen', wasiPath: po.wasiPath, vfsPath });
|
|
474
183
|
__wasiPreopens.push({ fd, wasiPath: po.wasiPath, vfsPath });
|
|
475
|
-
if (!dirs.has(vfsPath))
|
|
476
|
-
dirs.add(vfsPath);
|
|
477
|
-
if (!times.has(vfsPath))
|
|
478
|
-
times.set(vfsPath, { mtime: nowNs, atime: nowNs, ctime: nowNs });
|
|
479
184
|
}
|
|
480
185
|
}
|
|
481
186
|
// The guest-visible absolute path a path_* call names. wasi-libc resolves an
|
|
@@ -701,207 +406,26 @@ function __wasiAdoptSocket(socket, fdflags) {
|
|
|
701
406
|
});
|
|
702
407
|
return fd;
|
|
703
408
|
}
|
|
704
|
-
// WASI socket and polling support B1+B2 helpers: update tracked timestamps for a path. Idempotent.
|
|
705
|
-
// Caller passes nanosecond BigInt(s); pass null for fields to keep unchanged.
|
|
706
|
-
function __wasiTouchTimes(canonPath, mtimeNs, atimeNs, ctimeNs) {
|
|
707
|
-
const cur = __wasiFS.times.get(canonPath);
|
|
708
|
-
if (cur) {
|
|
709
|
-
if (mtimeNs !== null)
|
|
710
|
-
cur.mtime = mtimeNs;
|
|
711
|
-
if (atimeNs !== null)
|
|
712
|
-
cur.atime = atimeNs;
|
|
713
|
-
if (ctimeNs !== null)
|
|
714
|
-
cur.ctime = ctimeNs;
|
|
715
|
-
}
|
|
716
|
-
else {
|
|
717
|
-
const now = __wasiNowNs();
|
|
718
|
-
__wasiFS.times.set(canonPath, {
|
|
719
|
-
mtime: mtimeNs !== null ? mtimeNs : now,
|
|
720
|
-
atime: atimeNs !== null ? atimeNs : now,
|
|
721
|
-
ctime: ctimeNs !== null ? ctimeNs : now,
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
// Convenience: bump mtime + ctime to "now" on a write/create.
|
|
726
|
-
function __wasiBumpMtime(canonPath) {
|
|
727
|
-
const now = __wasiNowNs();
|
|
728
|
-
__wasiTouchTimes(canonPath, now, null, now);
|
|
729
|
-
}
|
|
730
|
-
/**
|
|
731
|
-
* Read named files back out as base64.
|
|
732
|
-
*
|
|
733
|
-
* A process with a supervisor has already written everything through as it
|
|
734
|
-
* happened and never calls this. A SEALED one — no supervisor, by design,
|
|
735
|
-
* because it must not be able to reach the session VFS at all — has no other
|
|
736
|
-
* channel, so its caller names the paths it wants and gets exactly those.
|
|
737
|
-
* A path with no file is simply absent from the result.
|
|
738
|
-
*/
|
|
739
|
-
export function __wasiReadFilesB64(paths) {
|
|
740
|
-
const out = {};
|
|
741
|
-
for (const path of paths) {
|
|
742
|
-
const canon = __wasiCanonicalize(path);
|
|
743
|
-
const bytes = __wasiFS.files.get(canon);
|
|
744
|
-
if (!bytes)
|
|
745
|
-
continue;
|
|
746
|
-
const CHUNK = 0x8000;
|
|
747
|
-
let s = '';
|
|
748
|
-
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
749
|
-
s += String.fromCharCode.apply(null, bytes.subarray(i, Math.min(i + CHUNK, bytes.length)));
|
|
750
|
-
}
|
|
751
|
-
out[path] = btoa(s);
|
|
752
|
-
}
|
|
753
|
-
return out;
|
|
754
|
-
}
|
|
755
409
|
// ─── fd table ──────────────────────────────────────────────────────────
|
|
756
410
|
//
|
|
757
|
-
// Entry shapes:
|
|
411
|
+
// Entry shapes this module owns:
|
|
758
412
|
// { kind: 'stdin' | 'stdout' | 'stderr' }
|
|
759
413
|
// { kind: 'preopen', wasiPath, vfsPath, rights? }
|
|
760
|
-
// { kind: '
|
|
761
|
-
//
|
|
762
|
-
// { kind: '
|
|
414
|
+
// { kind: 'socket' | 'listener', socket, reader, writer, readBuf, ... }
|
|
415
|
+
// and the authority codec's (wasi/filesystem.ts), which shares the table:
|
|
416
|
+
// { kind: 'authority', handle, type, rights, ... } a live descriptor
|
|
417
|
+
// { kind: 'resident', stat, bytes, position, ... } a read-only copy
|
|
763
418
|
//
|
|
764
419
|
// WASI socket and polling support B6: 'rights' is an optional BigInt mask. When set, fd_fdstat_get
|
|
765
420
|
// returns it (instead of the wide-open default). fd_fdstat_set_rights
|
|
766
|
-
// writes to it. The mask is advisory
|
|
767
|
-
// rights
|
|
421
|
+
// writes to it. The mask is advisory for this module's own kinds; the codec
|
|
422
|
+
// enforces rights on its descriptors.
|
|
768
423
|
export const fdTable = new Map();
|
|
769
424
|
let nextFd = 3;
|
|
770
425
|
// The only source of descriptor numbers. A second counter — one for files, one
|
|
771
426
|
// for sockets — hands the same number out twice and the later open silently
|
|
772
427
|
// destroys the earlier fd's entry.
|
|
773
428
|
function __wasiAllocateFd() { return nextFd++; }
|
|
774
|
-
// Resolve a WASI path against a preopen fd; returns the canonical VFS path
|
|
775
|
-
// WITHOUT following symlinks. WASI socket and polling support kept this name compatible with all
|
|
776
|
-
// existing call sites (~10 of them inside the imports object).
|
|
777
|
-
//
|
|
778
|
-
// wasi-path-fix: a single preopen { wasiPath: '/', vfsPath: 'home/user' }
|
|
779
|
-
// exposes the user's home as the wasm-side root. wasi-libc strips '/'
|
|
780
|
-
// from absolute paths and hands the rest to path_open. For "/tmp/x" the
|
|
781
|
-
// stripped path "tmp/x" + vfsPath "home/user" yields "home/user/tmp/x"
|
|
782
|
-
// (correct). For "/home/user/x" the stripped path "home/user/x" +
|
|
783
|
-
// vfsPath "home/user" yields "home/user/home/user/x" — the file lands
|
|
784
|
-
// at the wrong location. The user mental model is that "/home/user/x"
|
|
785
|
-
// IS the user's file at home/user/x in the VFS.
|
|
786
|
-
//
|
|
787
|
-
// Fix: in __wasiResolvePath, when baseFd is a preopen AND the supplied
|
|
788
|
-
// pathStr already starts with the preopen's vfsPath (followed by '/' or
|
|
789
|
-
// end-of-string), strip the vfsPath prefix BEFORE prepending it. This
|
|
790
|
-
// makes "/home/user/x" → wasi-libc strip → "home/user/x" → resolver
|
|
791
|
-
// strip vfsPath → "x" → final canonical "home/user/x" ✓. Paths that
|
|
792
|
-
// don't share the vfsPath prefix (like "tmp/x") follow the legacy path.
|
|
793
|
-
//
|
|
794
|
-
// Skip the strip when vfsPath is empty (no chroot mismatch possible).
|
|
795
|
-
// Be careful with segment boundaries: vfsPath "home/user" must NOT
|
|
796
|
-
// strip from path "home/userfoo/bar" (it's a different directory).
|
|
797
|
-
function __wasiResolvePath(baseFd, pathStr) {
|
|
798
|
-
const entry = fdTable.get(baseFd);
|
|
799
|
-
if (!entry)
|
|
800
|
-
return null;
|
|
801
|
-
let baseVfs;
|
|
802
|
-
if (entry.kind === 'preopen' || entry.kind === 'dir')
|
|
803
|
-
baseVfs = entry.vfsPath;
|
|
804
|
-
else
|
|
805
|
-
return null;
|
|
806
|
-
// Strip leading './' or '/' segments (POSIX legacy from wasi-libc).
|
|
807
|
-
let trimmed = pathStr.replace(/^\.?\/+/, '').replace(/^\/+/, '');
|
|
808
|
-
// wasi-path-fix: chroot-collision strip. Only applies to preopens (not
|
|
809
|
-
// 'dir' fds — those are opened via path_open with already-resolved
|
|
810
|
-
// paths). Only when baseVfs is non-empty AND trimmed starts with
|
|
811
|
-
// baseVfs at a segment boundary.
|
|
812
|
-
if (entry.kind === 'preopen' && baseVfs.length > 0) {
|
|
813
|
-
if (trimmed === baseVfs) {
|
|
814
|
-
trimmed = '';
|
|
815
|
-
}
|
|
816
|
-
else if (trimmed.length > baseVfs.length &&
|
|
817
|
-
trimmed.charCodeAt(baseVfs.length) === 47 /* '/' */ &&
|
|
818
|
-
trimmed.substring(0, baseVfs.length) === baseVfs) {
|
|
819
|
-
trimmed = trimmed.substring(baseVfs.length + 1);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
return __wasiCanonicalize(baseVfs + '/' + trimmed);
|
|
823
|
-
}
|
|
824
|
-
// WASI socket and polling support B3: resolve a path WITH optional symlink-follow + POSIX loop
|
|
825
|
-
// detection. Returns {path: canonical, isSymlink: bool, err?: errno}.
|
|
826
|
-
// - If followFlag is true (default — matches LOOKUPFLAGS_SYMLINK_FOLLOW),
|
|
827
|
-
// walks the symlink chain up to __WASI_SYMLOOP_MAX depth. ELOOP if exceeded.
|
|
828
|
-
// - If followFlag is false, returns the bare resolved path; if the path
|
|
829
|
-
// itself is in __wasiFS.symlinks, isSymlink=true so callers know to
|
|
830
|
-
// open it as a symlink fd rather than a regular file.
|
|
831
|
-
// Errors return {path: '', isSymlink: false, err: __WASI_E...}.
|
|
832
|
-
function __wasiResolvePathFull(baseFd, pathStr, followFlag) {
|
|
833
|
-
let p = __wasiResolvePath(baseFd, pathStr);
|
|
834
|
-
if (p === null)
|
|
835
|
-
return { path: '', isSymlink: false, err: __WASI_EBADF };
|
|
836
|
-
if (!followFlag) {
|
|
837
|
-
return { path: p, isSymlink: __wasiFS.symlinks.has(p), err: __WASI_ESUCCESS };
|
|
838
|
-
}
|
|
839
|
-
// Follow symlinks with bounded loop counter.
|
|
840
|
-
let depth = 0;
|
|
841
|
-
while (__wasiFS.symlinks.has(p)) {
|
|
842
|
-
if (depth++ >= __WASI_SYMLOOP_MAX) {
|
|
843
|
-
return { path: p, isSymlink: true, err: __WASI_ELOOP };
|
|
844
|
-
}
|
|
845
|
-
const target = __wasiFS.symlinks.get(p);
|
|
846
|
-
// Target may be relative or absolute. Absolute targets (leading '/')
|
|
847
|
-
// resolve against the same preopen root as the original path; relative
|
|
848
|
-
// targets resolve against the directory containing the symlink.
|
|
849
|
-
let next;
|
|
850
|
-
if (target.startsWith('/')) {
|
|
851
|
-
// Resolve against the preopen of baseFd.
|
|
852
|
-
const e = fdTable.get(baseFd);
|
|
853
|
-
const baseVfs = (e && (e.kind === 'preopen' || e.kind === 'dir')) ? e.vfsPath : '';
|
|
854
|
-
next = __wasiCanonicalize(baseVfs + '/' + target);
|
|
855
|
-
}
|
|
856
|
-
else {
|
|
857
|
-
const lastSlash = p.lastIndexOf('/');
|
|
858
|
-
const parent = lastSlash >= 0 ? p.substring(0, lastSlash) : '';
|
|
859
|
-
next = __wasiCanonicalize(parent + '/' + target);
|
|
860
|
-
}
|
|
861
|
-
p = next;
|
|
862
|
-
}
|
|
863
|
-
return { path: p, isSymlink: false, err: __WASI_ESUCCESS };
|
|
864
|
-
}
|
|
865
|
-
function __wasiEffectiveMode(path) {
|
|
866
|
-
const mode = __wasiFS.modes.get(path);
|
|
867
|
-
if (mode !== undefined)
|
|
868
|
-
return mode;
|
|
869
|
-
return __wasiInodeExists(path) ? 0 : 7;
|
|
870
|
-
}
|
|
871
|
-
function __wasiInodeExists(path) {
|
|
872
|
-
return (__wasiFS.files.has(path) ||
|
|
873
|
-
__wasiFS.dirs.has(path) ||
|
|
874
|
-
__wasiFS.symlinks.has(path) ||
|
|
875
|
-
__wasiFS.modes.has(path));
|
|
876
|
-
}
|
|
877
|
-
function __wasiCheckTraversal(baseFd, resolved) {
|
|
878
|
-
const entry = fdTable.get(baseFd);
|
|
879
|
-
if (!entry || (entry.kind !== 'preopen' && entry.kind !== 'dir'))
|
|
880
|
-
return __WASI_EBADF;
|
|
881
|
-
const base = entry.vfsPath;
|
|
882
|
-
if (resolved === base)
|
|
883
|
-
return __WASI_ESUCCESS;
|
|
884
|
-
const prefix = base ? base + '/' : '';
|
|
885
|
-
const relative = resolved.startsWith(prefix) ? resolved.substring(prefix.length) : resolved;
|
|
886
|
-
const parts = relative.split('/').filter(Boolean);
|
|
887
|
-
let ancestor = base;
|
|
888
|
-
for (let i = 0; i < parts.length; i++) {
|
|
889
|
-
if (!__wasiFS.dirs.has(ancestor)) {
|
|
890
|
-
return __wasiInodeExists(ancestor) ? __WASI_ENOTDIR : __WASI_ENOENT;
|
|
891
|
-
}
|
|
892
|
-
if ((__wasiEffectiveMode(ancestor) & 1) === 0)
|
|
893
|
-
return __WASI_EACCES;
|
|
894
|
-
if (i === parts.length - 1)
|
|
895
|
-
break;
|
|
896
|
-
ancestor = ancestor ? ancestor + '/' + parts[i] : parts[i];
|
|
897
|
-
}
|
|
898
|
-
return __WASI_ESUCCESS;
|
|
899
|
-
}
|
|
900
|
-
function __wasiCheckMode(path, requested) {
|
|
901
|
-
return (__wasiEffectiveMode(path) & requested) === requested
|
|
902
|
-
? __WASI_ESUCCESS
|
|
903
|
-
: __WASI_EACCES;
|
|
904
|
-
}
|
|
905
429
|
// ─── makeImports ────────────────────────────────────────────────────────
|
|
906
430
|
// ── Park watchdog ────────────────────────────────────────────────────────
|
|
907
431
|
//
|
|
@@ -1004,35 +528,6 @@ export function __wasiMakeImports(opts) {
|
|
|
1004
528
|
const bytes = u8().subarray(ptr, ptr + len);
|
|
1005
529
|
return utf8dec.decode(bytes);
|
|
1006
530
|
}
|
|
1007
|
-
// The single filestat emitter for both ABIs. preview1 is 64 bytes with an
|
|
1008
|
-
// eight-byte nlink at +24; preview0 is 56 with a four-byte nlink at +20, so
|
|
1009
|
-
// every field from nlink onward shifts. A caller decoding the wrong layout
|
|
1010
|
-
// reads st_size out of the nlink slot and sees 1 — silently, since the
|
|
1011
|
-
// function signature is identical either way.
|
|
1012
|
-
function writeFilestat(statPtr, path, ftype, size, t) {
|
|
1013
|
-
const dv = view();
|
|
1014
|
-
writeU64LE(statPtr, __WASI_DEV);
|
|
1015
|
-
writeU64LE(statPtr + 8, __wasiInode(path));
|
|
1016
|
-
dv.setUint8(statPtr + 16, ftype);
|
|
1017
|
-
if (preview0) {
|
|
1018
|
-
for (let i = 17; i < 20; i++)
|
|
1019
|
-
dv.setUint8(statPtr + i, 0);
|
|
1020
|
-
dv.setUint32(statPtr + 20, 1, true); // nlink u32
|
|
1021
|
-
writeU64LE(statPtr + 24, size);
|
|
1022
|
-
writeU64LE(statPtr + 32, t.atime);
|
|
1023
|
-
writeU64LE(statPtr + 40, t.mtime);
|
|
1024
|
-
writeU64LE(statPtr + 48, t.ctime);
|
|
1025
|
-
return __WASI_ESUCCESS;
|
|
1026
|
-
}
|
|
1027
|
-
for (let i = 17; i < 24; i++)
|
|
1028
|
-
dv.setUint8(statPtr + i, 0);
|
|
1029
|
-
writeU64LE(statPtr + 24, 1n); // nlink u64
|
|
1030
|
-
writeU64LE(statPtr + 32, size);
|
|
1031
|
-
writeU64LE(statPtr + 40, t.atime);
|
|
1032
|
-
writeU64LE(statPtr + 48, t.mtime);
|
|
1033
|
-
writeU64LE(statPtr + 56, t.ctime);
|
|
1034
|
-
return __WASI_ESUCCESS;
|
|
1035
|
-
}
|
|
1036
531
|
let stdoutBuf = '';
|
|
1037
532
|
let stderrBuf = '';
|
|
1038
533
|
// A sink and the readable buffer are mutually exclusive: with a sink the
|
|
@@ -1054,219 +549,6 @@ export function __wasiMakeImports(opts) {
|
|
|
1054
549
|
stderrBuf += s;
|
|
1055
550
|
}
|
|
1056
551
|
}
|
|
1057
|
-
// ── Helpers operating on the VFS state ───────────────────────────────
|
|
1058
|
-
function getFile(vfsPath) {
|
|
1059
|
-
return __wasiFS.files.get(vfsPath) || null;
|
|
1060
|
-
}
|
|
1061
|
-
/** Known to exist — resident content or a manifest entry. */
|
|
1062
|
-
function fileKnown(vfsPath) {
|
|
1063
|
-
return __wasiFS.files.has(vfsPath) || __wasiFS.sizes.has(vfsPath);
|
|
1064
|
-
}
|
|
1065
|
-
/** Size without forcing a load. */
|
|
1066
|
-
function fileSize(vfsPath) {
|
|
1067
|
-
const resident = __wasiFS.files.get(vfsPath);
|
|
1068
|
-
if (resident)
|
|
1069
|
-
return resident.length;
|
|
1070
|
-
return __wasiFS.sizes.get(vfsPath) ?? 0;
|
|
1071
|
-
}
|
|
1072
|
-
/**
|
|
1073
|
-
* Bytes for [offset, offset+length) of a file. Returns synchronously when
|
|
1074
|
-
* the content is resident — JSPI passes a plain value through with no
|
|
1075
|
-
* suspender, so sync-only guests are unaffected by the live path existing.
|
|
1076
|
-
* Returns a Promise only on a genuine cache miss.
|
|
1077
|
-
*/
|
|
1078
|
-
function readRange(vfsPath, offset, length) {
|
|
1079
|
-
const resident = __wasiFS.files.get(vfsPath);
|
|
1080
|
-
if (resident) {
|
|
1081
|
-
const start = Math.min(offset, resident.length);
|
|
1082
|
-
return resident.subarray(start, Math.min(resident.length, start + length));
|
|
1083
|
-
}
|
|
1084
|
-
const size = __wasiFS.sizes.get(vfsPath);
|
|
1085
|
-
if (size === undefined)
|
|
1086
|
-
return new Uint8Array(0);
|
|
1087
|
-
// A manifest entry says "this file exists and has N bytes"; the bytes live
|
|
1088
|
-
// in the session VFS. With no supervisor there is no way to get them, and
|
|
1089
|
-
// answering with zero bytes is not a degraded read — it is a DIFFERENT
|
|
1090
|
-
// FILE, reported as a success. Ruby's require took that empty content,
|
|
1091
|
-
// defined nothing, and failed later as an undefined constant with nothing
|
|
1092
|
-
// pointing back here. Callers turn null into EIO.
|
|
1093
|
-
if (!__wasiSup)
|
|
1094
|
-
return null;
|
|
1095
|
-
// Queued writes must be visible to our own live read.
|
|
1096
|
-
return (async () => {
|
|
1097
|
-
await __wasiDrainPersist();
|
|
1098
|
-
if (size <= __wasiFS.residentFileCap) {
|
|
1099
|
-
const whole = await __wasiSup.fsReadRange(vfsPath, 0, size);
|
|
1100
|
-
if (whole === null)
|
|
1101
|
-
throw new Error('ENOENT: ' + vfsPath);
|
|
1102
|
-
const bytes = whole;
|
|
1103
|
-
__wasiFS.files.set(vfsPath, bytes);
|
|
1104
|
-
// The diff-back mirror must agree, or a demand-loaded file would be
|
|
1105
|
-
// reported as newly written by every runner still using the diff.
|
|
1106
|
-
__wasiFS.origFiles.set(vfsPath, bytes.slice());
|
|
1107
|
-
const start = Math.min(offset, bytes.length);
|
|
1108
|
-
return bytes.subarray(start, Math.min(bytes.length, start + length));
|
|
1109
|
-
}
|
|
1110
|
-
// Too large to hold: window straight through, nothing cached.
|
|
1111
|
-
const want = Math.max(0, Math.min(length, size - offset));
|
|
1112
|
-
if (want === 0)
|
|
1113
|
-
return new Uint8Array(0);
|
|
1114
|
-
const win = await __wasiSup.fsReadRange(vfsPath, offset, want);
|
|
1115
|
-
if (win === null)
|
|
1116
|
-
throw new Error('ENOENT: ' + vfsPath);
|
|
1117
|
-
return win;
|
|
1118
|
-
})();
|
|
1119
|
-
}
|
|
1120
|
-
/** Scatter the given bytes across the iovec list; returns bytes consumed. */
|
|
1121
|
-
function scatterIovs(bytes, iovsPtr, iovsLen, dv, memU8) {
|
|
1122
|
-
let done = 0;
|
|
1123
|
-
for (let i = 0; i < iovsLen && done < bytes.length; i++) {
|
|
1124
|
-
const iov = iovsPtr + i * 8;
|
|
1125
|
-
const bufPtr = dv.getUint32(iov, true);
|
|
1126
|
-
const bufLen = dv.getUint32(iov + 4, true);
|
|
1127
|
-
const n = Math.min(bufLen, bytes.length - done);
|
|
1128
|
-
if (n <= 0)
|
|
1129
|
-
break;
|
|
1130
|
-
memU8.set(bytes.subarray(done, done + n), bufPtr);
|
|
1131
|
-
done += n;
|
|
1132
|
-
if (n < bufLen)
|
|
1133
|
-
break;
|
|
1134
|
-
}
|
|
1135
|
-
return done;
|
|
1136
|
-
}
|
|
1137
|
-
/**
|
|
1138
|
-
* Resolve a path the manifest does not know. Files created after this
|
|
1139
|
-
* process spawned are invisible to a seed, so a miss asks the supervisor
|
|
1140
|
-
* once and records the answer — including the negative, so a program that
|
|
1141
|
-
* probes the same absent path in a loop pays a single round trip.
|
|
1142
|
-
*/
|
|
1143
|
-
/** True when the seed listed this path's subtree exhaustively. */
|
|
1144
|
-
function underEnumeratedRoot(vfsPath) {
|
|
1145
|
-
for (const root of __wasiFS.enumeratedRoots) {
|
|
1146
|
-
if (root === '' || vfsPath === root || vfsPath.startsWith(root + '/'))
|
|
1147
|
-
return true;
|
|
1148
|
-
}
|
|
1149
|
-
return false;
|
|
1150
|
-
}
|
|
1151
|
-
function statLive(vfsPath) {
|
|
1152
|
-
if (!__wasiSup || __wasiNegative.has(vfsPath))
|
|
1153
|
-
return false;
|
|
1154
|
-
// The manifest is authoritative inside a root it fully enumerated, so an
|
|
1155
|
-
// absent path there is absent — no round trip. Revalidation at a park is
|
|
1156
|
-
// what lets files created after spawn become visible.
|
|
1157
|
-
if (underEnumeratedRoot(vfsPath))
|
|
1158
|
-
return false;
|
|
1159
|
-
return (async () => {
|
|
1160
|
-
await __wasiDrainPersist();
|
|
1161
|
-
const st = await __wasiSup.stat(vfsPath);
|
|
1162
|
-
if (!st) {
|
|
1163
|
-
__wasiNegative.add(vfsPath);
|
|
1164
|
-
return false;
|
|
1165
|
-
}
|
|
1166
|
-
ensureParentDirs(vfsPath);
|
|
1167
|
-
if (st.type === 'directory') {
|
|
1168
|
-
__wasiFS.dirs.add(vfsPath);
|
|
1169
|
-
__wasiFS.modes.set(vfsPath, 7);
|
|
1170
|
-
}
|
|
1171
|
-
else {
|
|
1172
|
-
__wasiFS.sizes.set(vfsPath, Number(st.size ?? 0));
|
|
1173
|
-
__wasiFS.modes.set(vfsPath, 6);
|
|
1174
|
-
}
|
|
1175
|
-
const ns = BigInt(st.mtime ?? Date.now()) * 1000000n;
|
|
1176
|
-
__wasiFS.times.set(vfsPath, { mtime: ns, atime: ns, ctime: ns });
|
|
1177
|
-
return true;
|
|
1178
|
-
})();
|
|
1179
|
-
}
|
|
1180
|
-
/** Total capacity of an iovec list. */
|
|
1181
|
-
function iovsCapacity(iovsPtr, iovsLen, dv) {
|
|
1182
|
-
let total = 0;
|
|
1183
|
-
for (let i = 0; i < iovsLen; i++)
|
|
1184
|
-
total += dv.getUint32(iovsPtr + i * 8 + 4, true);
|
|
1185
|
-
return total;
|
|
1186
|
-
}
|
|
1187
|
-
function hasDir(vfsPath) {
|
|
1188
|
-
if (__wasiFS.dirs.has(vfsPath))
|
|
1189
|
-
return true;
|
|
1190
|
-
// Root preopens implicitly exist as dirs.
|
|
1191
|
-
return false;
|
|
1192
|
-
}
|
|
1193
|
-
function setFile(vfsPath, bytes) {
|
|
1194
|
-
__wasiFS.files.set(vfsPath, bytes);
|
|
1195
|
-
__wasiFS.sizes.delete(vfsPath);
|
|
1196
|
-
__wasiNegative.delete(vfsPath);
|
|
1197
|
-
if (!__wasiFS.modes.has(vfsPath))
|
|
1198
|
-
__wasiFS.modes.set(vfsPath, 6);
|
|
1199
|
-
// WASI socket and polling support B1: bump mtime+ctime on every write. atime stays as-is
|
|
1200
|
-
// (read paths bump atime explicitly via touchAccess()).
|
|
1201
|
-
__wasiBumpMtime(vfsPath);
|
|
1202
|
-
__wasiPersistFile(vfsPath);
|
|
1203
|
-
}
|
|
1204
|
-
function unsetFile(vfsPath) {
|
|
1205
|
-
__wasiFS.files.delete(vfsPath);
|
|
1206
|
-
__wasiFS.sizes.delete(vfsPath);
|
|
1207
|
-
__wasiFS.times.delete(vfsPath);
|
|
1208
|
-
__wasiFS.modes.delete(vfsPath);
|
|
1209
|
-
__wasiEnqueue('unlink ' + vfsPath, (sup) => sup.unlink(vfsPath));
|
|
1210
|
-
}
|
|
1211
|
-
function touchAccess(vfsPath) {
|
|
1212
|
-
// WASI socket and polling support B1: bump atime on a read. mtime/ctime unchanged.
|
|
1213
|
-
const cur = __wasiFS.times.get(vfsPath);
|
|
1214
|
-
if (cur)
|
|
1215
|
-
cur.atime = __wasiNowNs();
|
|
1216
|
-
}
|
|
1217
|
-
function ensureParentDirs(vfsPath) {
|
|
1218
|
-
// Ensure all ancestor dirs of vfsPath exist (they should, but mkdir -p
|
|
1219
|
-
// semantics are handled in path_create_directory by callers).
|
|
1220
|
-
const parts = vfsPath.split('/');
|
|
1221
|
-
if (parts.length <= 1)
|
|
1222
|
-
return;
|
|
1223
|
-
let p = '';
|
|
1224
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1225
|
-
p = p ? (p + '/' + parts[i]) : parts[i];
|
|
1226
|
-
__wasiFS.dirs.add(p);
|
|
1227
|
-
if (!__wasiFS.modes.has(p))
|
|
1228
|
-
__wasiFS.modes.set(p, 7);
|
|
1229
|
-
// WASI socket and polling support B1: ensure ancestor dirs have a times entry.
|
|
1230
|
-
if (!__wasiFS.times.has(p)) {
|
|
1231
|
-
const now = __wasiNowNs();
|
|
1232
|
-
__wasiFS.times.set(p, { mtime: now, atime: now, ctime: now });
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
function readdirChildren(vfsPath) {
|
|
1237
|
-
const out = [];
|
|
1238
|
-
const seen = new Set();
|
|
1239
|
-
const prefix = vfsPath === '' ? '' : vfsPath + '/';
|
|
1240
|
-
// Files
|
|
1241
|
-
for (const path of __wasiFS.files.keys()) {
|
|
1242
|
-
if (path.startsWith(prefix)) {
|
|
1243
|
-
const rest = path.substring(prefix.length);
|
|
1244
|
-
if (rest && rest.indexOf('/') === -1) {
|
|
1245
|
-
out.push({ name: rest, type: __WASI_FT_REGULAR_FILE });
|
|
1246
|
-
seen.add(rest);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
// Dirs
|
|
1251
|
-
for (const path of __wasiFS.dirs) {
|
|
1252
|
-
if (path !== vfsPath && path.startsWith(prefix)) {
|
|
1253
|
-
const rest = path.substring(prefix.length);
|
|
1254
|
-
if (rest && rest.indexOf('/') === -1) {
|
|
1255
|
-
out.push({ name: rest, type: __WASI_FT_DIRECTORY });
|
|
1256
|
-
seen.add(rest);
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
for (const path of __wasiFS.modes.keys()) {
|
|
1261
|
-
if (path.startsWith(prefix)) {
|
|
1262
|
-
const rest = path.substring(prefix.length);
|
|
1263
|
-
if (rest && rest.indexOf('/') === -1 && !seen.has(rest)) {
|
|
1264
|
-
out.push({ name: rest, type: __WASI_FT_REGULAR_FILE });
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
return out;
|
|
1269
|
-
}
|
|
1270
552
|
const imports = {
|
|
1271
553
|
// ── args / env ──
|
|
1272
554
|
args_get(argvPtr, argvBufPtr) {
|
|
@@ -1352,32 +634,9 @@ export function __wasiMakeImports(opts) {
|
|
|
1352
634
|
if (entry.kind === 'listener') {
|
|
1353
635
|
return __wasiAcceptRead(entry, iovsPtr, iovsLen, nreadPtr, writeU32LE, view, u8);
|
|
1354
636
|
}
|
|
1355
|
-
if (entry.kind === '
|
|
637
|
+
if (entry.kind === 'preopen')
|
|
1356
638
|
return __WASI_EISDIR;
|
|
1357
|
-
|
|
1358
|
-
return __WASI_EBADF;
|
|
1359
|
-
if (!fileKnown(entry.vfsPath))
|
|
1360
|
-
return __WASI_ENOENT;
|
|
1361
|
-
const want = iovsCapacity(iovsPtr, iovsLen, view());
|
|
1362
|
-
const take = Math.min(want, Math.max(0, fileSize(entry.vfsPath) - entry.offset));
|
|
1363
|
-
if (take === 0) {
|
|
1364
|
-
writeU32LE(nreadPtr, 0);
|
|
1365
|
-
return __WASI_ESUCCESS;
|
|
1366
|
-
}
|
|
1367
|
-
const chunk = readRange(entry.vfsPath, entry.offset, take);
|
|
1368
|
-
// Re-read memory views after a possible suspension: the guest may have
|
|
1369
|
-
// grown its memory while we were parked, detaching the old buffer.
|
|
1370
|
-
const deliver = (bytes) => {
|
|
1371
|
-
// Null is "the content exists but is unreachable" — never zero bytes,
|
|
1372
|
-
// which the guest cannot tell from an empty file.
|
|
1373
|
-
if (bytes === null)
|
|
1374
|
-
return __WASI_EIO;
|
|
1375
|
-
const n = scatterIovs(bytes, iovsPtr, iovsLen, view(), u8());
|
|
1376
|
-
entry.offset += n;
|
|
1377
|
-
writeU32LE(nreadPtr, n);
|
|
1378
|
-
return __WASI_ESUCCESS;
|
|
1379
|
-
};
|
|
1380
|
-
return (chunk && typeof chunk.then === 'function') ? chunk.then(deliver) : deliver(chunk);
|
|
639
|
+
return __WASI_EBADF;
|
|
1381
640
|
},
|
|
1382
641
|
fd_write(fd, iovsPtr, iovsLen, nwrittenPtr) {
|
|
1383
642
|
// wasi-libc maps write(2) to fd_write for every fd kind; a socket fd
|
|
@@ -1386,6 +645,8 @@ export function __wasiMakeImports(opts) {
|
|
|
1386
645
|
if (sockEntry && sockEntry.kind === 'socket') {
|
|
1387
646
|
return __rawSockSend(fd, iovsPtr, iovsLen, 0, nwrittenPtr);
|
|
1388
647
|
}
|
|
648
|
+
if (fd !== 1 && fd !== 2)
|
|
649
|
+
return __WASI_EBADF;
|
|
1389
650
|
const dv = view();
|
|
1390
651
|
const memU8 = u8();
|
|
1391
652
|
// Gather all iov bytes
|
|
@@ -1412,79 +673,27 @@ export function __wasiMakeImports(opts) {
|
|
|
1412
673
|
off += p.length;
|
|
1413
674
|
}
|
|
1414
675
|
}
|
|
1415
|
-
|
|
1416
|
-
appendStream(fd === 1 ? 'stdout' : 'stderr', combined);
|
|
1417
|
-
writeU32LE(nwrittenPtr, total);
|
|
1418
|
-
return __WASI_ESUCCESS;
|
|
1419
|
-
}
|
|
1420
|
-
const entry = fdTable.get(fd);
|
|
1421
|
-
if (!entry || entry.kind !== 'file')
|
|
1422
|
-
return __WASI_EBADF;
|
|
1423
|
-
const file = getFile(entry.vfsPath) || new Uint8Array(0);
|
|
1424
|
-
// O_APPEND: each write atomically seeks to EOF, ignoring the current
|
|
1425
|
-
// offset, then advances the offset past the written bytes (POSIX).
|
|
1426
|
-
const appendMode = (entry.fdflags & __WASI_FDFLAGS_APPEND) !== 0;
|
|
1427
|
-
const writeAt = appendMode ? file.length : entry.offset;
|
|
1428
|
-
const newLen = Math.max(file.length, writeAt + total);
|
|
1429
|
-
const next = new Uint8Array(newLen);
|
|
1430
|
-
next.set(file, 0);
|
|
1431
|
-
next.set(combined, writeAt);
|
|
1432
|
-
setFile(entry.vfsPath, next);
|
|
1433
|
-
entry.offset = writeAt + total;
|
|
676
|
+
appendStream(fd === 1 ? 'stdout' : 'stderr', combined);
|
|
1434
677
|
writeU32LE(nwrittenPtr, total);
|
|
1435
678
|
return __WASI_ESUCCESS;
|
|
1436
679
|
},
|
|
1437
|
-
fd_seek(fd,
|
|
1438
|
-
//
|
|
1439
|
-
// (
|
|
1440
|
-
|
|
1441
|
-
if (fd === 0 || fd === 1 || fd === 2) {
|
|
1442
|
-
// Seeking a pipe/tty is ESPIPE — lets guests detect non-seekable
|
|
1443
|
-
// stdio (POSIX lseek on a pipe).
|
|
680
|
+
fd_seek(fd, _offsetArg, _whence, _newOffsetPtr) {
|
|
681
|
+
// Seeking a pipe/tty is ESPIPE — lets guests detect non-seekable
|
|
682
|
+
// stdio (POSIX lseek on a pipe). A socket is a non-seekable stream too.
|
|
683
|
+
if (fd === 0 || fd === 1 || fd === 2)
|
|
1444
684
|
return __WASI_ESPIPE;
|
|
1445
|
-
}
|
|
1446
685
|
const entry = fdTable.get(fd);
|
|
1447
|
-
// A socket is a non-seekable stream, same as stdio: ESPIPE, not EBADF.
|
|
1448
686
|
if (entry && (entry.kind === 'socket' || entry.kind === 'listener'))
|
|
1449
687
|
return __WASI_ESPIPE;
|
|
1450
|
-
|
|
1451
|
-
return __WASI_EBADF;
|
|
1452
|
-
const delta = typeof offsetArg === 'bigint' ? offsetArg : BigInt(offsetArg | 0);
|
|
1453
|
-
const cur = BigInt(entry.offset);
|
|
1454
|
-
// fileSize, not the resident buffer: a manifest entry is a file whose
|
|
1455
|
-
// bytes have not been demand-loaded yet, and measuring it by what
|
|
1456
|
-
// happens to be in memory calls it empty. SEEK_END then lands at 0, and
|
|
1457
|
-
// every guest that sizes a file by seeking to its end — zipimport does
|
|
1458
|
-
// exactly that before looking for the end-of-central-directory record —
|
|
1459
|
-
// reads the head of the file and concludes it is not an archive.
|
|
1460
|
-
const fileLen = BigInt(fileSize(entry.vfsPath));
|
|
1461
|
-
let next;
|
|
1462
|
-
if (whence === WHENCE_SET)
|
|
1463
|
-
next = delta;
|
|
1464
|
-
else if (whence === WHENCE_CUR)
|
|
1465
|
-
next = cur + delta;
|
|
1466
|
-
else if (whence === WHENCE_END)
|
|
1467
|
-
next = fileLen + delta;
|
|
1468
|
-
else
|
|
1469
|
-
return __WASI_EINVAL;
|
|
1470
|
-
if (next < 0n)
|
|
1471
|
-
return __WASI_EINVAL;
|
|
1472
|
-
entry.offset = Number(next);
|
|
1473
|
-
writeU64LE(newOffsetPtr, next);
|
|
1474
|
-
return __WASI_ESUCCESS;
|
|
688
|
+
return __WASI_EBADF;
|
|
1475
689
|
},
|
|
1476
|
-
fd_tell(fd,
|
|
1477
|
-
if (fd === 0 || fd === 1 || fd === 2)
|
|
1478
|
-
// ftell on a pipe/tty is ESPIPE, same non-seekable rule as fd_seek.
|
|
690
|
+
fd_tell(fd, _offsetPtr) {
|
|
691
|
+
if (fd === 0 || fd === 1 || fd === 2)
|
|
1479
692
|
return __WASI_ESPIPE;
|
|
1480
|
-
}
|
|
1481
693
|
const entry = fdTable.get(fd);
|
|
1482
694
|
if (entry && (entry.kind === 'socket' || entry.kind === 'listener'))
|
|
1483
695
|
return __WASI_ESPIPE;
|
|
1484
|
-
|
|
1485
|
-
return __WASI_EBADF;
|
|
1486
|
-
writeU64LE(offsetPtr, BigInt(entry.offset));
|
|
1487
|
-
return __WASI_ESUCCESS;
|
|
696
|
+
return __WASI_EBADF;
|
|
1488
697
|
},
|
|
1489
698
|
fd_fdstat_get(fd, statPtr) {
|
|
1490
699
|
const entry = fdTable.get(fd);
|
|
@@ -1495,12 +704,9 @@ export function __wasiMakeImports(opts) {
|
|
|
1495
704
|
if (entry.kind === 'stdin' || entry.kind === 'stdout' || entry.kind === 'stderr') {
|
|
1496
705
|
ftype = __WASI_FT_CHARACTER_DEVICE;
|
|
1497
706
|
}
|
|
1498
|
-
else if (entry.kind === 'preopen'
|
|
707
|
+
else if (entry.kind === 'preopen') {
|
|
1499
708
|
ftype = __WASI_FT_DIRECTORY;
|
|
1500
709
|
}
|
|
1501
|
-
else if (entry.kind === 'file') {
|
|
1502
|
-
ftype = __WASI_FT_REGULAR_FILE;
|
|
1503
|
-
}
|
|
1504
710
|
else if (entry.kind === 'socket' || entry.kind === 'listener') {
|
|
1505
711
|
ftype = __WASI_FT_SOCKET_STREAM; // WASI socket and polling support B7
|
|
1506
712
|
}
|
|
@@ -1524,7 +730,7 @@ export function __wasiMakeImports(opts) {
|
|
|
1524
730
|
return __WASI_EBADF;
|
|
1525
731
|
// Sockets track the mask too: guests set O_NONBLOCK on a socket and
|
|
1526
732
|
// then read it back through fd_fdstat_get.
|
|
1527
|
-
if (entry.kind === '
|
|
733
|
+
if (entry.kind === 'socket' || entry.kind === 'listener')
|
|
1528
734
|
entry.fdflags = flags;
|
|
1529
735
|
return __WASI_ESUCCESS;
|
|
1530
736
|
},
|
|
@@ -1578,9 +784,6 @@ export function __wasiMakeImports(opts) {
|
|
|
1578
784
|
// WASI socket and polling support B7: synthetic /dev/tcp/<host>/<port> path — open a TCP
|
|
1579
785
|
// socket (remote via cloudflare:sockets, loopback via the virtual
|
|
1580
786
|
// socket kernel). Bash-like convention.
|
|
1581
|
-
// Intercept BEFORE resolution because /dev/tcp/* doesn't exist in
|
|
1582
|
-
// the in-memory FS — __wasiResolvePath would canonicalize it but
|
|
1583
|
-
// not find any entry.
|
|
1584
787
|
const guestPath = __wasiGuestPath(baseFd, pathArg);
|
|
1585
788
|
if (guestPath.startsWith(WASI_TCP_PATH_PREFIX)) {
|
|
1586
789
|
return __wasiOpenTcpSocket(guestPath, fdflags, fdOutPtr, writeU32LE);
|
|
@@ -1591,386 +794,32 @@ export function __wasiMakeImports(opts) {
|
|
|
1591
794
|
if (guestPath.startsWith(WASI_LISTEN_PATH_PREFIX)) {
|
|
1592
795
|
return __wasiOpenListener(guestPath, fdflags, fdOutPtr, writeU32LE);
|
|
1593
796
|
}
|
|
1594
|
-
//
|
|
1595
|
-
//
|
|
1596
|
-
|
|
1597
|
-
const follow = (dirflags & __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW) !== 0;
|
|
1598
|
-
const rp = __wasiResolvePathFull(baseFd, pathArg, follow);
|
|
1599
|
-
if (rp.err === __WASI_EBADF)
|
|
1600
|
-
return __WASI_EBADF;
|
|
1601
|
-
if (rp.err === __WASI_ELOOP)
|
|
1602
|
-
return __WASI_ELOOP;
|
|
1603
|
-
const resolved = rp.path;
|
|
1604
|
-
const traversal = __wasiCheckTraversal(baseFd, resolved);
|
|
1605
|
-
if (traversal !== __WASI_ESUCCESS)
|
|
1606
|
-
return traversal;
|
|
1607
|
-
const isCreate = (oflags & __WASI_O_CREAT) !== 0;
|
|
1608
|
-
const isDirectory = (oflags & __WASI_O_DIRECTORY) !== 0;
|
|
1609
|
-
const isExcl = (oflags & __WASI_O_EXCL) !== 0;
|
|
1610
|
-
const isTrunc = (oflags & __WASI_O_TRUNC) !== 0;
|
|
1611
|
-
// O_NOFOLLOW (symlink_follow cleared) on a trailing symlink: POSIX
|
|
1612
|
-
// open() fails with ELOOP rather than opening the link itself.
|
|
1613
|
-
if (!follow && rp.isSymlink) {
|
|
1614
|
-
return __WASI_ELOOP;
|
|
1615
|
-
}
|
|
1616
|
-
const fileExists = __wasiFS.files.has(resolved) || __wasiFS.sizes.has(resolved) || (__wasiFS.modes.has(resolved) &&
|
|
1617
|
-
!__wasiFS.dirs.has(resolved) &&
|
|
1618
|
-
!__wasiFS.symlinks.has(resolved));
|
|
1619
|
-
const dirExists = __wasiFS.dirs.has(resolved);
|
|
1620
|
-
if (isDirectory) {
|
|
1621
|
-
if (!dirExists)
|
|
1622
|
-
return __WASI_ENOENT;
|
|
1623
|
-
const access = __wasiCheckMode(resolved, 1);
|
|
1624
|
-
if (access !== __WASI_ESUCCESS)
|
|
1625
|
-
return access;
|
|
1626
|
-
const fd = __wasiAllocateFd();
|
|
1627
|
-
fdTable.set(fd, { kind: 'dir', vfsPath: resolved, readdirEntries: null, cookie: 0n, oflags, fdflags });
|
|
1628
|
-
writeU32LE(fdOutPtr, fd);
|
|
1629
|
-
return __WASI_ESUCCESS;
|
|
1630
|
-
}
|
|
1631
|
-
if (dirExists) {
|
|
1632
|
-
// Opening a directory without O_DIRECTORY: WASI returns EISDIR.
|
|
1633
|
-
return __WASI_EISDIR;
|
|
1634
|
-
}
|
|
1635
|
-
if (!fileExists) {
|
|
1636
|
-
if (!isCreate)
|
|
1637
|
-
return __WASI_ENOENT;
|
|
1638
|
-
const parentSlash = resolved.lastIndexOf('/');
|
|
1639
|
-
const parent = parentSlash < 0 ? '' : resolved.substring(0, parentSlash);
|
|
1640
|
-
const access = __wasiCheckMode(parent, 3);
|
|
1641
|
-
if (access !== __WASI_ESUCCESS)
|
|
1642
|
-
return access;
|
|
1643
|
-
// create empty file
|
|
1644
|
-
ensureParentDirs(resolved);
|
|
1645
|
-
setFile(resolved, new Uint8Array(0));
|
|
1646
|
-
}
|
|
1647
|
-
else if (isExcl) {
|
|
1648
|
-
return __WASI_EEXIST;
|
|
1649
|
-
}
|
|
1650
|
-
else if (isTrunc) {
|
|
1651
|
-
const access = __wasiCheckMode(resolved, 2);
|
|
1652
|
-
if (access !== __WASI_ESUCCESS)
|
|
1653
|
-
return access;
|
|
1654
|
-
setFile(resolved, new Uint8Array(0));
|
|
1655
|
-
}
|
|
1656
|
-
const requested = typeof rightsBase === 'bigint' ? rightsBase : BigInt(rightsBase >>> 0);
|
|
1657
|
-
let requiredMode = 0;
|
|
1658
|
-
if ((requested & __WASI_RIGHT_FD_READ) !== 0n)
|
|
1659
|
-
requiredMode |= 4;
|
|
1660
|
-
if ((requested & __WASI_RIGHT_FD_WRITE) !== 0n)
|
|
1661
|
-
requiredMode |= 2;
|
|
1662
|
-
if (requiredMode !== 0) {
|
|
1663
|
-
const access = __wasiCheckMode(resolved, requiredMode);
|
|
1664
|
-
if (access !== __WASI_ESUCCESS)
|
|
1665
|
-
return access;
|
|
1666
|
-
}
|
|
1667
|
-
const fd = __wasiAllocateFd();
|
|
1668
|
-
fdTable.set(fd, { kind: 'file', vfsPath: resolved, offset: 0, oflags, fdflags });
|
|
1669
|
-
writeU32LE(fdOutPtr, fd);
|
|
1670
|
-
return __WASI_ESUCCESS;
|
|
797
|
+
// Every other path is the authority codec's; without a supervisor there
|
|
798
|
+
// is no filesystem to open it on.
|
|
799
|
+
return __WASI_EBADF;
|
|
1671
800
|
},
|
|
1672
801
|
// ── path_create_directory ──
|
|
1673
|
-
path_create_directory(
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
if (resolved === null)
|
|
1677
|
-
return __WASI_EBADF;
|
|
1678
|
-
if (__wasiFS.dirs.has(resolved))
|
|
1679
|
-
return __WASI_EEXIST;
|
|
1680
|
-
if (__wasiFS.files.has(resolved))
|
|
1681
|
-
return __WASI_EEXIST;
|
|
1682
|
-
ensureParentDirs(resolved);
|
|
1683
|
-
__wasiFS.dirs.add(resolved);
|
|
1684
|
-
__wasiFS.modes.set(resolved, 7);
|
|
1685
|
-
__wasiNegative.delete(resolved);
|
|
1686
|
-
// WASI socket and polling support B1: seed times for the new dir.
|
|
1687
|
-
const now = __wasiNowNs();
|
|
1688
|
-
__wasiFS.times.set(resolved, { mtime: now, atime: now, ctime: now });
|
|
1689
|
-
__wasiEnqueue('mkdir ' + resolved, (sup) => sup.mkdir(resolved));
|
|
1690
|
-
return __WASI_ESUCCESS;
|
|
1691
|
-
},
|
|
1692
|
-
path_remove_directory(baseFd, pathPtr, pathLen) {
|
|
1693
|
-
const path = readPath(pathPtr, pathLen);
|
|
1694
|
-
const resolved = __wasiResolvePath(baseFd, path);
|
|
1695
|
-
if (resolved === null)
|
|
1696
|
-
return __WASI_EBADF;
|
|
1697
|
-
if (!__wasiFS.dirs.has(resolved))
|
|
1698
|
-
return __WASI_ENOENT;
|
|
1699
|
-
// Must be empty
|
|
1700
|
-
const children = readdirChildren(resolved);
|
|
1701
|
-
if (children.length > 0)
|
|
1702
|
-
return __WASI_ENOTEMPTY;
|
|
1703
|
-
__wasiFS.dirs.delete(resolved);
|
|
1704
|
-
__wasiFS.modes.delete(resolved);
|
|
1705
|
-
__wasiEnqueue('rmdir ' + resolved, (sup) => sup.rmdir(resolved));
|
|
1706
|
-
return __WASI_ESUCCESS;
|
|
1707
|
-
},
|
|
1708
|
-
path_unlink_file(baseFd, pathPtr, pathLen) {
|
|
1709
|
-
const path = readPath(pathPtr, pathLen);
|
|
1710
|
-
const resolved = __wasiResolvePath(baseFd, path);
|
|
1711
|
-
if (resolved === null)
|
|
1712
|
-
return __WASI_EBADF;
|
|
1713
|
-
if (__wasiFS.dirs.has(resolved))
|
|
1714
|
-
return __WASI_EISDIR;
|
|
1715
|
-
// WASI socket and polling support B3: unlink also removes a symlink at this path. We
|
|
1716
|
-
// operate on the unfollowed path (POSIX unlink semantics — removes
|
|
1717
|
-
// the directory entry itself, not the symlink target).
|
|
1718
|
-
if (__wasiFS.symlinks.has(resolved)) {
|
|
1719
|
-
__wasiFS.symlinks.delete(resolved);
|
|
1720
|
-
__wasiFS.times.delete(resolved);
|
|
1721
|
-
__wasiFS.modes.delete(resolved);
|
|
1722
|
-
return __WASI_ESUCCESS;
|
|
1723
|
-
}
|
|
1724
|
-
if (!fileKnown(resolved))
|
|
1725
|
-
return __WASI_ENOENT;
|
|
1726
|
-
unsetFile(resolved);
|
|
1727
|
-
return __WASI_ESUCCESS;
|
|
1728
|
-
},
|
|
802
|
+
path_create_directory(_baseFd, _pathPtr, _pathLen) { return __WASI_EBADF; },
|
|
803
|
+
path_remove_directory(_baseFd, _pathPtr, _pathLen) { return __WASI_EBADF; },
|
|
804
|
+
path_unlink_file(_baseFd, _pathPtr, _pathLen) { return __WASI_EBADF; },
|
|
1729
805
|
// ── path_rename (atomic; overwrites destination) ──
|
|
1730
|
-
path_rename(
|
|
1731
|
-
const srcPath = readPath(srcPathPtr, srcPathLen);
|
|
1732
|
-
const dstPath = readPath(dstPathPtr, dstPathLen);
|
|
1733
|
-
const src = __wasiResolvePath(srcFd, srcPath);
|
|
1734
|
-
const dst = __wasiResolvePath(dstFd, dstPath);
|
|
1735
|
-
if (src === null || dst === null)
|
|
1736
|
-
return __WASI_EBADF;
|
|
1737
|
-
// rename operates on the entry itself (no symlink-follow on the final
|
|
1738
|
-
// component, per POSIX). src may be a file, a directory, or a symlink.
|
|
1739
|
-
const srcFile = __wasiFS.files.get(src);
|
|
1740
|
-
const srcIsDir = __wasiFS.dirs.has(src);
|
|
1741
|
-
const srcIsSymlink = __wasiFS.symlinks.has(src);
|
|
1742
|
-
// A manifest-only file is a file: its bytes just are not resident yet.
|
|
1743
|
-
const srcIsLazy = srcFile === undefined && __wasiFS.sizes.has(src);
|
|
1744
|
-
if (srcFile === undefined && !srcIsLazy && !srcIsDir && !srcIsSymlink)
|
|
1745
|
-
return __WASI_ENOENT;
|
|
1746
|
-
if (src === dst)
|
|
1747
|
-
return __WASI_ESUCCESS; // rename to itself is a no-op
|
|
1748
|
-
// Move the path's timestamps with it, bumping ctime (metadata change).
|
|
1749
|
-
const moveTimes = (from, to) => {
|
|
1750
|
-
const t = __wasiFS.times.get(from);
|
|
1751
|
-
__wasiFS.times.delete(from);
|
|
1752
|
-
const now = __wasiNowNs();
|
|
1753
|
-
__wasiFS.times.set(to, t
|
|
1754
|
-
? { mtime: t.mtime, atime: t.atime, ctime: now }
|
|
1755
|
-
: { mtime: now, atime: now, ctime: now });
|
|
1756
|
-
};
|
|
1757
|
-
const moveMode = (from, to) => {
|
|
1758
|
-
const mode = __wasiFS.modes.get(from);
|
|
1759
|
-
__wasiFS.modes.delete(from);
|
|
1760
|
-
if (mode !== undefined)
|
|
1761
|
-
__wasiFS.modes.set(to, mode);
|
|
1762
|
-
};
|
|
1763
|
-
// Pre-remove the destination of any kind (atomic overwrite).
|
|
1764
|
-
__wasiFS.files.delete(dst);
|
|
1765
|
-
__wasiFS.sizes.delete(dst);
|
|
1766
|
-
__wasiFS.dirs.delete(dst);
|
|
1767
|
-
__wasiFS.symlinks.delete(dst);
|
|
1768
|
-
__wasiFS.times.delete(dst);
|
|
1769
|
-
__wasiFS.modes.delete(dst);
|
|
1770
|
-
if (srcIsLazy) {
|
|
1771
|
-
__wasiFS.sizes.set(dst, __wasiFS.sizes.get(src));
|
|
1772
|
-
__wasiFS.sizes.delete(src);
|
|
1773
|
-
moveTimes(src, dst);
|
|
1774
|
-
moveMode(src, dst);
|
|
1775
|
-
}
|
|
1776
|
-
else if (srcIsSymlink) {
|
|
1777
|
-
__wasiFS.symlinks.set(dst, __wasiFS.symlinks.get(src));
|
|
1778
|
-
__wasiFS.symlinks.delete(src);
|
|
1779
|
-
moveTimes(src, dst);
|
|
1780
|
-
moveMode(src, dst);
|
|
1781
|
-
}
|
|
1782
|
-
else if (srcFile !== undefined) {
|
|
1783
|
-
__wasiFS.files.set(dst, srcFile);
|
|
1784
|
-
__wasiFS.files.delete(src);
|
|
1785
|
-
moveTimes(src, dst);
|
|
1786
|
-
moveMode(src, dst);
|
|
1787
|
-
}
|
|
1788
|
-
else {
|
|
1789
|
-
__wasiFS.dirs.delete(src);
|
|
1790
|
-
__wasiFS.dirs.add(dst);
|
|
1791
|
-
moveTimes(src, dst);
|
|
1792
|
-
moveMode(src, dst);
|
|
1793
|
-
// Rebase every descendant key (files, dirs, symlinks) + its times.
|
|
1794
|
-
const srcPrefix = src + '/';
|
|
1795
|
-
const rebase = (key) => dst + '/' + key.substring(srcPrefix.length);
|
|
1796
|
-
for (const key of [...__wasiFS.files.keys()]) {
|
|
1797
|
-
if (key.startsWith(srcPrefix)) {
|
|
1798
|
-
const nk = rebase(key);
|
|
1799
|
-
__wasiFS.files.set(nk, __wasiFS.files.get(key));
|
|
1800
|
-
__wasiFS.files.delete(key);
|
|
1801
|
-
moveTimes(key, nk);
|
|
1802
|
-
moveMode(key, nk);
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
for (const key of [...__wasiFS.dirs]) {
|
|
1806
|
-
if (key.startsWith(srcPrefix)) {
|
|
1807
|
-
const nk = rebase(key);
|
|
1808
|
-
__wasiFS.dirs.add(nk);
|
|
1809
|
-
__wasiFS.dirs.delete(key);
|
|
1810
|
-
moveTimes(key, nk);
|
|
1811
|
-
moveMode(key, nk);
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
for (const key of [...__wasiFS.symlinks.keys()]) {
|
|
1815
|
-
if (key.startsWith(srcPrefix)) {
|
|
1816
|
-
const nk = rebase(key);
|
|
1817
|
-
__wasiFS.symlinks.set(nk, __wasiFS.symlinks.get(key));
|
|
1818
|
-
__wasiFS.symlinks.delete(key);
|
|
1819
|
-
moveTimes(key, nk);
|
|
1820
|
-
moveMode(key, nk);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
for (const key of [...__wasiFS.sizes.keys()]) {
|
|
1824
|
-
if (key.startsWith(srcPrefix)) {
|
|
1825
|
-
__wasiFS.sizes.set(rebase(key), __wasiFS.sizes.get(key));
|
|
1826
|
-
__wasiFS.sizes.delete(key);
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
// One op: the supervisor renames the whole subtree atomically, which a
|
|
1831
|
-
// per-descendant replay could not.
|
|
1832
|
-
__wasiEnqueue('rename ' + src + ' -> ' + dst, (sup) => sup.rename(src, dst));
|
|
1833
|
-
return __WASI_ESUCCESS;
|
|
1834
|
-
},
|
|
806
|
+
path_rename(_srcFd, _srcPathPtr, _srcPathLen, _dstFd, _dstPathPtr, _dstPathLen) { return __WASI_EBADF; },
|
|
1835
807
|
// ── path_filestat_get ──
|
|
1836
|
-
path_filestat_get(
|
|
1837
|
-
const path = readPath(pathPtr, pathLen);
|
|
1838
|
-
// Follow symlinks only when SYMLINK_FOLLOW is set (stat); flags=0 is lstat.
|
|
1839
|
-
const follow = (lookupflags & __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW) !== 0;
|
|
1840
|
-
const rp = __wasiResolvePathFull(baseFd, path, follow);
|
|
1841
|
-
if (rp.err === __WASI_EBADF)
|
|
1842
|
-
return __WASI_EBADF;
|
|
1843
|
-
if (rp.err === __WASI_ELOOP)
|
|
1844
|
-
return __WASI_ELOOP;
|
|
1845
|
-
const resolved = rp.path;
|
|
1846
|
-
const traversal = __wasiCheckTraversal(baseFd, resolved);
|
|
1847
|
-
if (traversal !== __WASI_ESUCCESS)
|
|
1848
|
-
return traversal;
|
|
1849
|
-
const classify = () => {
|
|
1850
|
-
if (!follow && rp.isSymlink) {
|
|
1851
|
-
return [__WASI_FT_SYMBOLIC_LINK,
|
|
1852
|
-
BigInt(new TextEncoder().encode(__wasiFS.symlinks.get(resolved)).length)];
|
|
1853
|
-
}
|
|
1854
|
-
if (__wasiFS.files.has(resolved) || __wasiFS.sizes.has(resolved)) {
|
|
1855
|
-
return [__WASI_FT_REGULAR_FILE, BigInt(fileSize(resolved))];
|
|
1856
|
-
}
|
|
1857
|
-
if (__wasiFS.dirs.has(resolved))
|
|
1858
|
-
return [__WASI_FT_DIRECTORY, 0n];
|
|
1859
|
-
if (__wasiFS.modes.has(resolved))
|
|
1860
|
-
return [__WASI_FT_REGULAR_FILE, 0n];
|
|
1861
|
-
return null;
|
|
1862
|
-
};
|
|
1863
|
-
let kind = classify();
|
|
1864
|
-
if (!kind) {
|
|
1865
|
-
const live = statLive(resolved);
|
|
1866
|
-
if (live && typeof live.then === 'function') {
|
|
1867
|
-
return live.then((found) => {
|
|
1868
|
-
// classify() is re-run because the live stat is what populated the
|
|
1869
|
-
// maps. It can still answer null: the stat and this callback are
|
|
1870
|
-
// separated by a suspension, and anything that unlinks the path in
|
|
1871
|
-
// between — another thread, another request on a resident server —
|
|
1872
|
-
// leaves nothing to classify. Spreading that null threw "is not
|
|
1873
|
-
// iterable" from inside a Suspending import, which reaches the
|
|
1874
|
-
// guest as a trap carrying no errno. The path is gone, so ENOENT is
|
|
1875
|
-
// the answer the guest can act on.
|
|
1876
|
-
const settled = found ? classify() : null;
|
|
1877
|
-
return settled ? emitStat(...settled) : __WASI_ENOENT;
|
|
1878
|
-
});
|
|
1879
|
-
}
|
|
1880
|
-
return __WASI_ENOENT;
|
|
1881
|
-
}
|
|
1882
|
-
return emitStat(...kind);
|
|
1883
|
-
// Hoisted: reached from both the resident and the post-live-stat path.
|
|
1884
|
-
function emitStat(ftype, size) {
|
|
1885
|
-
// WASI socket and polling support B1: emit real timestamps.
|
|
1886
|
-
const t = __wasiFS.times.get(resolved) || { mtime: 0n, atime: 0n, ctime: 0n };
|
|
1887
|
-
return writeFilestat(statPtr, resolved, ftype, size, t);
|
|
1888
|
-
}
|
|
1889
|
-
},
|
|
808
|
+
path_filestat_get(_baseFd, _lookupflags, _pathPtr, _pathLen, _statPtr) { return __WASI_EBADF; },
|
|
1890
809
|
// WASI socket and polling support B2: real path_filestat_set_times. Honors ATIM/ATIM_NOW/
|
|
1891
810
|
// MTIM/MTIM_NOW flags. Spec: atim_ns and mtim_ns are absolute
|
|
1892
811
|
// nanosecond timestamps; flags select which fields to update + whether
|
|
1893
812
|
// to clamp to "now". ENOENT if path doesn't exist.
|
|
1894
|
-
path_filestat_set_times(
|
|
1895
|
-
const path = readPath(pathPtr, pathLen);
|
|
1896
|
-
const follow = (lookupflags & __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW) !== 0;
|
|
1897
|
-
const rp = __wasiResolvePathFull(baseFd, path, follow);
|
|
1898
|
-
if (rp.err === __WASI_EBADF)
|
|
1899
|
-
return __WASI_EBADF;
|
|
1900
|
-
if (rp.err === __WASI_ELOOP)
|
|
1901
|
-
return __WASI_ELOOP;
|
|
1902
|
-
const resolved = rp.path;
|
|
1903
|
-
const isSymlinkOnly = !follow && rp.isSymlink;
|
|
1904
|
-
if (!isSymlinkOnly && !__wasiFS.files.has(resolved) && !__wasiFS.dirs.has(resolved)) {
|
|
1905
|
-
return __WASI_ENOENT;
|
|
1906
|
-
}
|
|
1907
|
-
// Decode flags. Mutually-exclusive pairs ATIM vs ATIM_NOW (similarly
|
|
1908
|
-
// for MTIM) — caller error if both set; we accept and prefer _NOW.
|
|
1909
|
-
const setAtim = (fstflags & __WASI_FSTFLAGS_ATIM) !== 0;
|
|
1910
|
-
const setAtimNow = (fstflags & __WASI_FSTFLAGS_ATIM_NOW) !== 0;
|
|
1911
|
-
const setMtim = (fstflags & __WASI_FSTFLAGS_MTIM) !== 0;
|
|
1912
|
-
const setMtimNow = (fstflags & __WASI_FSTFLAGS_MTIM_NOW) !== 0;
|
|
1913
|
-
if ((setAtim && setAtimNow) || (setMtim && setMtimNow))
|
|
1914
|
-
return __WASI_EINVAL;
|
|
1915
|
-
const now = __wasiNowNs();
|
|
1916
|
-
const atimNs = setAtimNow ? now : (setAtim ? (typeof atimArg === 'bigint' ? atimArg : BigInt(atimArg)) : null);
|
|
1917
|
-
const mtimNs = setMtimNow ? now : (setMtim ? (typeof mtimArg === 'bigint' ? mtimArg : BigInt(mtimArg)) : null);
|
|
1918
|
-
__wasiTouchTimes(resolved, mtimNs, atimNs, now);
|
|
1919
|
-
__wasiPersistTimes(resolved);
|
|
1920
|
-
return __WASI_ESUCCESS;
|
|
1921
|
-
},
|
|
813
|
+
path_filestat_set_times(_baseFd, _lookupflags, _pathPtr, _pathLen, _atimArg, _mtimArg, _fstflags) { return __WASI_EBADF; },
|
|
1922
814
|
// WASI socket and polling support B3: read the target string of a symlink. Spec:
|
|
1923
815
|
// path_readlink(fd, path, path_len, buf, buf_len, *bufused)
|
|
1924
816
|
// Truncates to buf_len; writes actual bytes to *bufused.
|
|
1925
|
-
path_readlink(
|
|
1926
|
-
const path = readPath(pathPtr, pathLen);
|
|
1927
|
-
// readlink NEVER follows symlinks on the last component (POSIX).
|
|
1928
|
-
const rp = __wasiResolvePathFull(baseFd, path, false);
|
|
1929
|
-
if (rp.err === __WASI_EBADF)
|
|
1930
|
-
return __WASI_EBADF;
|
|
1931
|
-
if (rp.err === __WASI_ELOOP)
|
|
1932
|
-
return __WASI_ELOOP;
|
|
1933
|
-
const resolved = rp.path;
|
|
1934
|
-
if (!__wasiFS.symlinks.has(resolved))
|
|
1935
|
-
return __WASI_EINVAL; // not a symlink
|
|
1936
|
-
const target = __wasiFS.symlinks.get(resolved);
|
|
1937
|
-
const enc = new TextEncoder();
|
|
1938
|
-
const targetBytes = enc.encode(target);
|
|
1939
|
-
const n = Math.min(targetBytes.length, bufLen);
|
|
1940
|
-
u8().set(targetBytes.subarray(0, n), bufPtr);
|
|
1941
|
-
writeU32LE(bufUsedPtr, n);
|
|
1942
|
-
// POSIX: readlink doesn't update atime per most filesystems; but
|
|
1943
|
-
// some (ext4 with strictatime) do. We follow Linux default: no
|
|
1944
|
-
// atime bump on readlink.
|
|
1945
|
-
return __WASI_ESUCCESS;
|
|
1946
|
-
},
|
|
817
|
+
path_readlink(_baseFd, _pathPtr, _pathLen, _bufPtr, _bufLen, _bufUsedPtr) { return __WASI_EBADF; },
|
|
1947
818
|
// WASI socket and polling support B3: create a symlink. Spec:
|
|
1948
819
|
// path_symlink(old_path, fd, new_path)
|
|
1949
820
|
// old_path is the symlink's TARGET (stored verbatim); fd+new_path
|
|
1950
821
|
// is the location where the symlink itself is created.
|
|
1951
|
-
path_symlink(
|
|
1952
|
-
const oldPath = readPath(oldPathPtr, oldPathLen); // target string
|
|
1953
|
-
const newPath = readPath(newPathPtr, newPathLen);
|
|
1954
|
-
const resolved = __wasiResolvePath(newFd, newPath);
|
|
1955
|
-
if (resolved === null)
|
|
1956
|
-
return __WASI_EBADF;
|
|
1957
|
-
if (__wasiFS.files.has(resolved))
|
|
1958
|
-
return __WASI_EEXIST;
|
|
1959
|
-
if (__wasiFS.dirs.has(resolved))
|
|
1960
|
-
return __WASI_EEXIST;
|
|
1961
|
-
if (__wasiFS.symlinks.has(resolved))
|
|
1962
|
-
return __WASI_EEXIST;
|
|
1963
|
-
// Store the target verbatim — symlinks per POSIX are dumb strings;
|
|
1964
|
-
// resolution happens at lookup time.
|
|
1965
|
-
__wasiFS.symlinks.set(resolved, oldPath);
|
|
1966
|
-
__wasiFS.modes.set(resolved, 7);
|
|
1967
|
-
ensureParentDirs(resolved);
|
|
1968
|
-
const now = __wasiNowNs();
|
|
1969
|
-
__wasiFS.times.set(resolved, { mtime: now, atime: now, ctime: now });
|
|
1970
|
-
__wasiNegative.delete(resolved);
|
|
1971
|
-
__wasiPersistSymlink(oldPath, resolved);
|
|
1972
|
-
return __WASI_ESUCCESS;
|
|
1973
|
-
},
|
|
822
|
+
path_symlink(_oldPathPtr, _oldPathLen, _newFd, _newPathPtr, _newPathLen) { return __WASI_EBADF; },
|
|
1974
823
|
// WASI socket and polling support B3: hardlink. Spec:
|
|
1975
824
|
// path_link(old_fd, old_flags, old_path, new_fd, new_path)
|
|
1976
825
|
// Implements as a shared-buffer alias. POSIX semantics: both names
|
|
@@ -1981,54 +830,20 @@ export function __wasiMakeImports(opts) {
|
|
|
1981
830
|
// don't propagate. Matches link(2) at the WASI-layer (link itself
|
|
1982
831
|
// succeeds; concurrent-mutation semantics are filesystem-dependent
|
|
1983
832
|
// and our shim is a single-call sandbox).
|
|
1984
|
-
path_link(
|
|
1985
|
-
const oldPath = readPath(oldPathPtr, oldPathLen);
|
|
1986
|
-
const newPath = readPath(newPathPtr, newPathLen);
|
|
1987
|
-
const follow = (oldFlags & __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW) !== 0;
|
|
1988
|
-
const rpOld = __wasiResolvePathFull(oldFd, oldPath, follow);
|
|
1989
|
-
if (rpOld.err === __WASI_EBADF)
|
|
1990
|
-
return __WASI_EBADF;
|
|
1991
|
-
if (rpOld.err === __WASI_ELOOP)
|
|
1992
|
-
return __WASI_ELOOP;
|
|
1993
|
-
const src = rpOld.path;
|
|
1994
|
-
const dst = __wasiResolvePath(newFd, newPath);
|
|
1995
|
-
if (dst === null)
|
|
1996
|
-
return __WASI_EBADF;
|
|
1997
|
-
if (!__wasiFS.files.has(src))
|
|
1998
|
-
return __WASI_ENOENT;
|
|
1999
|
-
if (__wasiFS.files.has(dst) || __wasiFS.dirs.has(dst) || __wasiFS.symlinks.has(dst)) {
|
|
2000
|
-
return __WASI_EEXIST;
|
|
2001
|
-
}
|
|
2002
|
-
__wasiFS.files.set(dst, __wasiFS.files.get(src)); // shared reference
|
|
2003
|
-
__wasiFS.modes.set(dst, __wasiEffectiveMode(src));
|
|
2004
|
-
ensureParentDirs(dst);
|
|
2005
|
-
const now = __wasiNowNs();
|
|
2006
|
-
__wasiFS.times.set(dst, { mtime: now, atime: now, ctime: now });
|
|
2007
|
-
__wasiNegative.delete(dst);
|
|
2008
|
-
// The session VFS has no hardlinks; the new name is persisted as its own
|
|
2009
|
-
// file with the same bytes, which is what link(2) already degrades to
|
|
2010
|
-
// here (fd_write REPLACES the buffer rather than mutating it in place).
|
|
2011
|
-
__wasiPersistFile(dst);
|
|
2012
|
-
return __WASI_ESUCCESS;
|
|
2013
|
-
},
|
|
833
|
+
path_link(_oldFd, _oldFlags, _oldPathPtr, _oldPathLen, _newFd, _newPathPtr, _newPathLen) { return __WASI_EBADF; },
|
|
2014
834
|
// ── fd_filestat_get / fd_filestat_set_size ──
|
|
2015
835
|
fd_filestat_get(fd, statPtr) {
|
|
2016
836
|
const entry = fdTable.get(fd);
|
|
2017
837
|
if (!entry)
|
|
2018
838
|
return __WASI_EBADF;
|
|
839
|
+
// Files and directories are the authority codec's. A descriptor with no
|
|
840
|
+
// path (stdio, a socket) is still its own object and must not share an
|
|
841
|
+
// inode with every other one, so it is named by its fd — on a device of
|
|
842
|
+
// its own, because (dev, ino) is the identity and the authority's
|
|
843
|
+
// devices already use the small numbers (the kernel namespace is 0,
|
|
844
|
+
// SQLite devices count from 1, and their inodes from 1 too).
|
|
2019
845
|
let ftype = __WASI_FT_UNKNOWN;
|
|
2020
|
-
|
|
2021
|
-
let timesPath = null;
|
|
2022
|
-
if (entry.kind === 'file') {
|
|
2023
|
-
ftype = __WASI_FT_REGULAR_FILE;
|
|
2024
|
-
size = BigInt(fileSize(entry.vfsPath));
|
|
2025
|
-
timesPath = entry.vfsPath;
|
|
2026
|
-
}
|
|
2027
|
-
else if (entry.kind === 'dir' || entry.kind === 'preopen') {
|
|
2028
|
-
ftype = __WASI_FT_DIRECTORY;
|
|
2029
|
-
timesPath = entry.vfsPath;
|
|
2030
|
-
}
|
|
2031
|
-
else if (entry.kind === 'stdin' || entry.kind === 'stdout' || entry.kind === 'stderr') {
|
|
846
|
+
if (entry.kind === 'stdin' || entry.kind === 'stdout' || entry.kind === 'stderr') {
|
|
2032
847
|
ftype = __WASI_FT_CHARACTER_DEVICE;
|
|
2033
848
|
}
|
|
2034
849
|
else if (entry.kind === 'socket' || entry.kind === 'listener') {
|
|
@@ -2036,111 +851,33 @@ export function __wasiMakeImports(opts) {
|
|
|
2036
851
|
// IO layer keys buffering and seekability off exactly this).
|
|
2037
852
|
ftype = __WASI_FT_SOCKET_STREAM;
|
|
2038
853
|
}
|
|
2039
|
-
|
|
2040
|
-
// A path names the inode when there is one. An fd with no path — stdio,
|
|
2041
|
-
// a socket — is still its own object and must not share an inode with
|
|
2042
|
-
// every other one, so it is named by the fd instead.
|
|
2043
|
-
return writeFilestat(statPtr, timesPath === null ? '\0fd/' + fd : timesPath, ftype, size, t);
|
|
2044
|
-
},
|
|
2045
|
-
fd_filestat_set_size(fd, size) {
|
|
2046
|
-
const entry = fdTable.get(fd);
|
|
2047
|
-
if (!entry || entry.kind !== 'file')
|
|
2048
|
-
return __WASI_EBADF;
|
|
2049
|
-
const cur = getFile(entry.vfsPath) || new Uint8Array(0);
|
|
2050
|
-
const newSize = Number(size);
|
|
2051
|
-
const next = new Uint8Array(newSize);
|
|
2052
|
-
next.set(cur.subarray(0, Math.min(cur.length, newSize)), 0);
|
|
2053
|
-
setFile(entry.vfsPath, next);
|
|
2054
|
-
return __WASI_ESUCCESS;
|
|
2055
|
-
},
|
|
2056
|
-
// WASI socket and polling support B2: real fd_filestat_set_times.
|
|
2057
|
-
fd_filestat_set_times(fd, atimArg, mtimArg, fstflags) {
|
|
2058
|
-
const entry = fdTable.get(fd);
|
|
2059
|
-
if (!entry)
|
|
854
|
+
else
|
|
2060
855
|
return __WASI_EBADF;
|
|
2061
|
-
|
|
2062
|
-
|
|
856
|
+
const dv = view();
|
|
857
|
+
dv.setUint8(statPtr + 16, ftype);
|
|
858
|
+
writeU64LE(statPtr, __WASI_STREAM_DEV);
|
|
859
|
+
writeU64LE(statPtr + 8, BigInt(fd) + 1n);
|
|
860
|
+
if (preview0) {
|
|
861
|
+
for (let i = 17; i < 20; i++)
|
|
862
|
+
dv.setUint8(statPtr + i, 0);
|
|
863
|
+
dv.setUint32(statPtr + 20, 1, true); // nlink u32
|
|
864
|
+
for (let off = 24; off < 56; off += 8)
|
|
865
|
+
writeU64LE(statPtr + off, 0n);
|
|
2063
866
|
return __WASI_ESUCCESS;
|
|
2064
867
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
return __WASI_EINVAL;
|
|
2071
|
-
const now = __wasiNowNs();
|
|
2072
|
-
const atimNs = setAtimNow ? now : (setAtim ? (typeof atimArg === 'bigint' ? atimArg : BigInt(atimArg)) : null);
|
|
2073
|
-
const mtimNs = setMtimNow ? now : (setMtim ? (typeof mtimArg === 'bigint' ? mtimArg : BigInt(mtimArg)) : null);
|
|
2074
|
-
__wasiTouchTimes(entry.vfsPath, mtimNs, atimNs, now);
|
|
2075
|
-
__wasiPersistTimes(entry.vfsPath);
|
|
868
|
+
for (let i = 17; i < 24; i++)
|
|
869
|
+
dv.setUint8(statPtr + i, 0);
|
|
870
|
+
writeU64LE(statPtr + 24, 1n); // nlink u64
|
|
871
|
+
for (let off = 32; off < 64; off += 8)
|
|
872
|
+
writeU64LE(statPtr + off, 0n);
|
|
2076
873
|
return __WASI_ESUCCESS;
|
|
2077
874
|
},
|
|
875
|
+
fd_filestat_set_size(_fd, _size) { return __WASI_EBADF; },
|
|
876
|
+
// WASI socket and polling support B2: real fd_filestat_set_times.
|
|
877
|
+
fd_filestat_set_times(_fd, _atimArg, _mtimArg, _fstflags) { return __WASI_EBADF; },
|
|
2078
878
|
// ── fd_pread / fd_pwrite (offset-explicit) ──
|
|
2079
|
-
fd_pread(
|
|
2080
|
-
|
|
2081
|
-
if (!entry || entry.kind !== 'file')
|
|
2082
|
-
return __WASI_EBADF;
|
|
2083
|
-
if (!fileKnown(entry.vfsPath))
|
|
2084
|
-
return __WASI_ENOENT;
|
|
2085
|
-
const offset = typeof offsetArg === 'bigint'
|
|
2086
|
-
? Number(offsetArg)
|
|
2087
|
-
: (offsetArg >>> 0);
|
|
2088
|
-
const want = iovsCapacity(iovsPtr, iovsLen, view());
|
|
2089
|
-
const take = Math.min(want, Math.max(0, fileSize(entry.vfsPath) - offset));
|
|
2090
|
-
if (take === 0) {
|
|
2091
|
-
writeU32LE(nreadPtr, 0);
|
|
2092
|
-
return __WASI_ESUCCESS;
|
|
2093
|
-
}
|
|
2094
|
-
const chunk = readRange(entry.vfsPath, offset, take);
|
|
2095
|
-
const deliver = (bytes) => {
|
|
2096
|
-
if (bytes === null)
|
|
2097
|
-
return __WASI_EIO;
|
|
2098
|
-
writeU32LE(nreadPtr, scatterIovs(bytes, iovsPtr, iovsLen, view(), u8()));
|
|
2099
|
-
return __WASI_ESUCCESS;
|
|
2100
|
-
};
|
|
2101
|
-
return (chunk && typeof chunk.then === 'function') ? chunk.then(deliver) : deliver(chunk);
|
|
2102
|
-
},
|
|
2103
|
-
fd_pwrite(fd, iovsPtr, iovsLen, offsetArg, nwrittenPtr) {
|
|
2104
|
-
const entry = fdTable.get(fd);
|
|
2105
|
-
if (!entry || entry.kind !== 'file')
|
|
2106
|
-
return __WASI_EBADF;
|
|
2107
|
-
let offset = typeof offsetArg === 'bigint'
|
|
2108
|
-
? Number(offsetArg)
|
|
2109
|
-
: (offsetArg >>> 0);
|
|
2110
|
-
const dv = view();
|
|
2111
|
-
const memU8 = u8();
|
|
2112
|
-
let total = 0;
|
|
2113
|
-
const parts = [];
|
|
2114
|
-
for (let i = 0; i < iovsLen; i++) {
|
|
2115
|
-
const iov = iovsPtr + i * 8;
|
|
2116
|
-
const bufPtr = dv.getUint32(iov, true);
|
|
2117
|
-
const bufLen = dv.getUint32(iov + 4, true);
|
|
2118
|
-
if (bufLen > 0)
|
|
2119
|
-
parts.push(memU8.slice(bufPtr, bufPtr + bufLen));
|
|
2120
|
-
total += bufLen;
|
|
2121
|
-
}
|
|
2122
|
-
let combined;
|
|
2123
|
-
if (parts.length === 0)
|
|
2124
|
-
combined = new Uint8Array(0);
|
|
2125
|
-
else if (parts.length === 1)
|
|
2126
|
-
combined = parts[0];
|
|
2127
|
-
else {
|
|
2128
|
-
combined = new Uint8Array(total);
|
|
2129
|
-
let off = 0;
|
|
2130
|
-
for (const p of parts) {
|
|
2131
|
-
combined.set(p, off);
|
|
2132
|
-
off += p.length;
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
const cur = getFile(entry.vfsPath) || new Uint8Array(0);
|
|
2136
|
-
const newLen = Math.max(cur.length, offset + total);
|
|
2137
|
-
const next = new Uint8Array(newLen);
|
|
2138
|
-
next.set(cur, 0);
|
|
2139
|
-
next.set(combined, offset);
|
|
2140
|
-
setFile(entry.vfsPath, next);
|
|
2141
|
-
writeU32LE(nwrittenPtr, total);
|
|
2142
|
-
return __WASI_ESUCCESS;
|
|
2143
|
-
},
|
|
879
|
+
fd_pread(_fd, _iovsPtr, _iovsLen, _offsetArg, _nreadPtr) { return __WASI_EBADF; },
|
|
880
|
+
fd_pwrite(_fd, _iovsPtr, _iovsLen, _offsetArg, _nwrittenPtr) { return __WASI_EBADF; },
|
|
2144
881
|
// ── fd_readdir ──
|
|
2145
882
|
//
|
|
2146
883
|
// WASI dirent layout (24 bytes per entry):
|
|
@@ -2150,72 +887,14 @@ export function __wasiMakeImports(opts) {
|
|
|
2150
887
|
// d_type u8 @ 20
|
|
2151
888
|
// pad 21..23
|
|
2152
889
|
// followed by name bytes (variable).
|
|
2153
|
-
fd_readdir(
|
|
2154
|
-
const entry = fdTable.get(fd);
|
|
2155
|
-
if (!entry || (entry.kind !== 'dir' && entry.kind !== 'preopen'))
|
|
2156
|
-
return __WASI_EBADF;
|
|
2157
|
-
const access = __wasiCheckMode(entry.vfsPath, 4);
|
|
2158
|
-
if (access !== __WASI_ESUCCESS)
|
|
2159
|
-
return access;
|
|
2160
|
-
if (!entry.readdirEntries) {
|
|
2161
|
-
const kids = readdirChildren(entry.vfsPath);
|
|
2162
|
-
entry.readdirEntries = [
|
|
2163
|
-
{ name: '.', type: __WASI_FT_DIRECTORY },
|
|
2164
|
-
{ name: '..', type: __WASI_FT_DIRECTORY },
|
|
2165
|
-
...kids,
|
|
2166
|
-
];
|
|
2167
|
-
}
|
|
2168
|
-
let startCookie = typeof cookieArg === 'bigint'
|
|
2169
|
-
? Number(cookieArg)
|
|
2170
|
-
: (cookieArg >>> 0);
|
|
2171
|
-
let written = 0;
|
|
2172
|
-
const dv = view();
|
|
2173
|
-
const memU8 = u8();
|
|
2174
|
-
for (let i = startCookie; i < entry.readdirEntries.length; i++) {
|
|
2175
|
-
const e = entry.readdirEntries[i];
|
|
2176
|
-
const nameBytes = utf8enc.encode(e.name);
|
|
2177
|
-
const recordSize = 24 + nameBytes.length;
|
|
2178
|
-
if (written + 24 > bufLen)
|
|
2179
|
-
break;
|
|
2180
|
-
// d_next = i+1 (next cookie)
|
|
2181
|
-
writeU64LE(bufPtr + written, BigInt(i + 1));
|
|
2182
|
-
writeU64LE(bufPtr + written + 8, 0n); // d_ino
|
|
2183
|
-
dv.setUint32(bufPtr + written + 16, nameBytes.length, true);
|
|
2184
|
-
dv.setUint8(bufPtr + written + 20, e.type);
|
|
2185
|
-
for (let j = 21; j < 24; j++)
|
|
2186
|
-
dv.setUint8(bufPtr + written + j, 0);
|
|
2187
|
-
written += 24;
|
|
2188
|
-
// Write as much of the name as fits.
|
|
2189
|
-
const nameRoom = Math.min(nameBytes.length, bufLen - written);
|
|
2190
|
-
memU8.set(nameBytes.subarray(0, nameRoom), bufPtr + written);
|
|
2191
|
-
written += nameRoom;
|
|
2192
|
-
if (nameRoom < nameBytes.length)
|
|
2193
|
-
break;
|
|
2194
|
-
}
|
|
2195
|
-
writeU32LE(bufusedPtr, written);
|
|
2196
|
-
return __WASI_ESUCCESS;
|
|
2197
|
-
},
|
|
890
|
+
fd_readdir(_fd, _bufPtr, _bufLen, _cookieArg, _bufusedPtr) { return __WASI_EBADF; },
|
|
2198
891
|
fd_advise(_fd, _offset, _len, _advice) { return __WASI_ESUCCESS; },
|
|
2199
892
|
// WASI socket and polling support B4: real fd_allocate. Extends the file's byte buffer with
|
|
2200
893
|
// zeros so [offset, offset+len) is allocated. POSIX posix_fallocate
|
|
2201
894
|
// semantics. ENOSPC is not reachable in our in-memory FS (the
|
|
2202
895
|
// 32 MiB snapshot cap is enforced at supervisor level; in-call we
|
|
2203
896
|
// just allocate the JS Uint8Array).
|
|
2204
|
-
fd_allocate(
|
|
2205
|
-
const entry = fdTable.get(fd);
|
|
2206
|
-
if (!entry || entry.kind !== 'file')
|
|
2207
|
-
return __WASI_EBADF;
|
|
2208
|
-
const offset = typeof offsetArg === 'bigint' ? Number(offsetArg) : (offsetArg >>> 0);
|
|
2209
|
-
const len = typeof lenArg === 'bigint' ? Number(lenArg) : (lenArg >>> 0);
|
|
2210
|
-
const cur = getFile(entry.vfsPath) || new Uint8Array(0);
|
|
2211
|
-
const needed = offset + len;
|
|
2212
|
-
if (needed <= cur.length)
|
|
2213
|
-
return __WASI_ESUCCESS; // already big enough
|
|
2214
|
-
const next = new Uint8Array(needed);
|
|
2215
|
-
next.set(cur, 0); // [cur.length, needed) is implicitly zero-initialised
|
|
2216
|
-
setFile(entry.vfsPath, next);
|
|
2217
|
-
return __WASI_ESUCCESS;
|
|
2218
|
-
},
|
|
897
|
+
fd_allocate(_fd, _offsetArg, _lenArg) { return __WASI_EBADF; },
|
|
2219
898
|
fd_datasync() { return __WASI_ESUCCESS; },
|
|
2220
899
|
fd_sync() { return __WASI_ESUCCESS; },
|
|
2221
900
|
// dup2(2), and the only way to reach it: preview1 has no dup. Renumbering
|
|
@@ -2427,19 +1106,15 @@ export function __wasiMakeImports(opts) {
|
|
|
2427
1106
|
});
|
|
2428
1107
|
}
|
|
2429
1108
|
// Regular files, dirs, stdio: always ready (POSIX: regular files
|
|
2430
|
-
// never block — read returns immediately even if at EOF).
|
|
2431
|
-
//
|
|
1109
|
+
// never block — read returns immediately even if at EOF). Files and
|
|
1110
|
+
// directories are the authority's descriptors; leaving them out made
|
|
2432
1111
|
// polling one EBADF, which a guest reads as "this fd is gone".
|
|
2433
|
-
if (entry.kind === '
|
|
2434
|
-
entry.kind === 'preopen' || entry.kind === 'authority' ||
|
|
1112
|
+
if (entry.kind === 'preopen' || entry.kind === 'authority' || entry.kind === 'resident' ||
|
|
2435
1113
|
entry.kind === 'stdin' || entry.kind === 'stdout' || entry.kind === 'stderr') {
|
|
2436
1114
|
let nbytes = 0n;
|
|
2437
|
-
if (entry.kind === '
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
const remain = f.length - (entry.offset || 0);
|
|
2441
|
-
nbytes = remain > 0 ? BigInt(remain) : 0n;
|
|
2442
|
-
}
|
|
1115
|
+
if (entry.kind === 'resident' && s.tag === __WASI_EVENTTYPE_FD_READ) {
|
|
1116
|
+
const remain = entry.bytes.length - entry.position;
|
|
1117
|
+
nbytes = remain > 0 ? BigInt(remain) : 0n;
|
|
2443
1118
|
}
|
|
2444
1119
|
else if (s.tag === __WASI_EVENTTYPE_FD_WRITE) {
|
|
2445
1120
|
// Writable: report large available capacity. Most user code
|
|
@@ -3024,6 +1699,4 @@ export async function __wasiRunStartAsync(instance, ctx) {
|
|
|
3024
1699
|
// already follow) because a direct reference to a preamble-only symbol will
|
|
3025
1700
|
// not typecheck in the supervisor bundle the body is authored in.
|
|
3026
1701
|
globalThis.__wasiAdoptSupervisor = __wasiAdoptSupervisor;
|
|
3027
|
-
globalThis.__wasiDrainPersist = __wasiDrainPersist;
|
|
3028
|
-
globalThis.__wasiRevalidateFS = __wasiRevalidateFS;
|
|
3029
1702
|
// ── END: wasi-instance preamble ─────────────────────────────────────────
|