@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.
Files changed (286) hide show
  1. package/NOTICE.md +5 -0
  2. package/README.md +10 -0
  3. package/dist/_shared/session-router.d.ts +7 -0
  4. package/dist/_shared/session-router.d.ts.map +1 -1
  5. package/dist/_shared/session-router.js +7 -0
  6. package/dist/bindings/body.d.ts +6 -0
  7. package/dist/bindings/body.d.ts.map +1 -0
  8. package/dist/bindings/body.js +37 -0
  9. package/dist/bindings/kv.d.ts +0 -2
  10. package/dist/bindings/kv.d.ts.map +1 -1
  11. package/dist/bindings/kv.js +3 -42
  12. package/dist/bindings/r2.d.ts +0 -2
  13. package/dist/bindings/r2.d.ts.map +1 -1
  14. package/dist/bindings/r2.js +4 -44
  15. package/dist/esbuild-cli-artifact.generated.d.ts +18 -0
  16. package/dist/esbuild-cli-artifact.generated.d.ts.map +1 -0
  17. package/dist/esbuild-cli-artifact.generated.js +17 -0
  18. package/dist/esbuild-wasm-bundle.generated.d.ts +24 -24
  19. package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -1
  20. package/dist/esbuild-wasm-bundle.generated.js +24 -24
  21. package/dist/facets/cirrus-real.d.ts.map +1 -1
  22. package/dist/facets/cirrus-real.js +11 -6
  23. package/dist/facets/compose.d.ts +11 -7
  24. package/dist/facets/compose.d.ts.map +1 -1
  25. package/dist/facets/compose.js +8 -31
  26. package/dist/facets/data-plan.d.ts +79 -0
  27. package/dist/facets/data-plan.d.ts.map +1 -0
  28. package/dist/facets/data-plan.js +477 -0
  29. package/dist/facets/esbuild-bundle-pool.d.ts +1 -1
  30. package/dist/facets/esbuild-bundle-pool.js +3 -3
  31. package/dist/facets/esbuild-transform.d.ts +38 -8
  32. package/dist/facets/esbuild-transform.d.ts.map +1 -1
  33. package/dist/facets/esbuild-transform.js +219 -19
  34. package/dist/facets/exec-telemetry.d.ts +7 -3
  35. package/dist/facets/exec-telemetry.d.ts.map +1 -1
  36. package/dist/facets/manager.d.ts +198 -69
  37. package/dist/facets/manager.d.ts.map +1 -1
  38. package/dist/facets/manager.js +1276 -652
  39. package/dist/facets/opencode-staging.d.ts +0 -2
  40. package/dist/facets/opencode-staging.d.ts.map +1 -1
  41. package/dist/facets/opencode-staging.js +5 -11
  42. package/dist/facets/process.d.ts +4 -0
  43. package/dist/facets/process.d.ts.map +1 -1
  44. package/dist/facets/process.js +3 -0
  45. package/dist/facets/read-profile.d.ts +149 -0
  46. package/dist/facets/read-profile.d.ts.map +1 -0
  47. package/dist/facets/read-profile.js +483 -0
  48. package/dist/facets/real-vite-hmr.d.ts +2 -0
  49. package/dist/facets/real-vite-hmr.d.ts.map +1 -1
  50. package/dist/facets/real-vite-hmr.js +3 -2
  51. package/dist/facets/resident-identity.d.ts +0 -1
  52. package/dist/facets/resident-identity.d.ts.map +1 -1
  53. package/dist/facets/resident-identity.js +0 -3
  54. package/dist/facets/wasm-swap-registry.d.ts +34 -25
  55. package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
  56. package/dist/facets/wasm-swap-registry.js +62 -56
  57. package/dist/facets/ws-terminal.d.ts +4 -4
  58. package/dist/facets/ws-terminal.d.ts.map +1 -1
  59. package/dist/facets/ws-terminal.js +4 -5
  60. package/dist/git/commands.d.ts +33 -10
  61. package/dist/git/commands.d.ts.map +1 -1
  62. package/dist/git/commands.js +1891 -197
  63. package/dist/git/network-facet.d.ts +12 -2
  64. package/dist/git/network-facet.d.ts.map +1 -1
  65. package/dist/git/network-facet.js +69 -18
  66. package/dist/git/unified-diff.d.ts +74 -0
  67. package/dist/git/unified-diff.d.ts.map +1 -0
  68. package/dist/git/unified-diff.js +676 -0
  69. package/dist/git-bundle.generated.d.ts +7 -11
  70. package/dist/git-bundle.generated.d.ts.map +1 -1
  71. package/dist/git-bundle.generated.js +7 -11
  72. package/dist/hosted/commands.d.ts.map +1 -1
  73. package/dist/hosted/commands.js +64 -245
  74. package/dist/hosted/runtime.d.ts +22 -15
  75. package/dist/hosted/runtime.d.ts.map +1 -1
  76. package/dist/hosted/runtime.js +72 -14
  77. package/dist/hosted/services.d.ts +8 -1
  78. package/dist/hosted/services.d.ts.map +1 -1
  79. package/dist/hosted/services.js +12 -13
  80. package/dist/hosted/session.d.ts +165 -0
  81. package/dist/hosted/session.d.ts.map +1 -0
  82. package/dist/hosted/session.js +132 -0
  83. package/dist/loaders/generated-workers.d.ts +6 -3
  84. package/dist/loaders/generated-workers.d.ts.map +1 -1
  85. package/dist/loaders/generated-workers.js +8 -5
  86. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
  87. package/dist/loaders/npm-resolve-preamble.js +0 -27
  88. package/dist/loaders/pre-bundle-preamble.d.ts +6 -3
  89. package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -1
  90. package/dist/loaders/pre-bundle-preamble.js +13 -8
  91. package/dist/node-shims-artifact.generated.d.ts +13 -4
  92. package/dist/node-shims-artifact.generated.d.ts.map +1 -1
  93. package/dist/node-shims-artifact.generated.js +16 -7
  94. package/dist/npm/bin-links.d.ts +3 -1
  95. package/dist/npm/bin-links.d.ts.map +1 -1
  96. package/dist/npm/bin-links.js +32 -8
  97. package/dist/npm/cache.d.ts +11 -1
  98. package/dist/npm/cache.d.ts.map +1 -1
  99. package/dist/npm/cache.js +21 -7
  100. package/dist/npm/install-batch-facet.d.ts +4 -2
  101. package/dist/npm/install-batch-facet.d.ts.map +1 -1
  102. package/dist/npm/install-batch-facet.js +32 -18
  103. package/dist/npm/installer.d.ts +23 -3
  104. package/dist/npm/installer.d.ts.map +1 -1
  105. package/dist/npm/installer.js +330 -131
  106. package/dist/npm/npx-install.d.ts +3 -1
  107. package/dist/npm/npx-install.d.ts.map +1 -1
  108. package/dist/npm/npx-install.js +4 -1
  109. package/dist/npm/package-lock.d.ts +32 -0
  110. package/dist/npm/package-lock.d.ts.map +1 -0
  111. package/dist/npm/package-lock.js +65 -0
  112. package/dist/npm/placement.d.ts +16 -0
  113. package/dist/npm/placement.d.ts.map +1 -0
  114. package/dist/npm/placement.js +33 -0
  115. package/dist/npm/pre-bundle-facet.d.ts +1 -30
  116. package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
  117. package/dist/npm/pre-bundle-facet.js +3 -5
  118. package/dist/npm/r2-cache.d.ts +12 -6
  119. package/dist/npm/r2-cache.d.ts.map +1 -1
  120. package/dist/npm/r2-cache.js +34 -16
  121. package/dist/npm/resolve-one-facet.d.ts +3 -0
  122. package/dist/npm/resolve-one-facet.d.ts.map +1 -1
  123. package/dist/npm/resolve-one-facet.js +15 -3
  124. package/dist/npm/resolver.d.ts +11 -13
  125. package/dist/npm/resolver.d.ts.map +1 -1
  126. package/dist/npm/resolver.js +12 -25
  127. package/dist/opencode-artifact.generated.js +2 -2
  128. package/dist/replica/routing.d.ts +2 -38
  129. package/dist/replica/routing.d.ts.map +1 -1
  130. package/dist/replica/routing.js +3 -55
  131. package/dist/router/index.d.ts.map +1 -1
  132. package/dist/router/index.js +15 -2
  133. package/dist/router/public-directory.d.ts +18 -0
  134. package/dist/router/public-directory.d.ts.map +1 -1
  135. package/dist/router/public-directory.js +1 -1
  136. package/dist/router/remote-api.d.ts +3 -1
  137. package/dist/router/remote-api.d.ts.map +1 -1
  138. package/dist/router/remote-api.js +42 -79
  139. package/dist/runtime/bash-repl.js +2 -2
  140. package/dist/runtime/bun-repl.d.ts.map +1 -1
  141. package/dist/runtime/bun-repl.js +2 -13
  142. package/dist/runtime/cpython-resident.d.ts.map +1 -1
  143. package/dist/runtime/cpython-resident.js +2 -10
  144. package/dist/runtime/esbuild-wasm-bytes.d.ts +37 -18
  145. package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
  146. package/dist/runtime/esbuild-wasm-bytes.js +66 -92
  147. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  148. package/dist/runtime/facet-loader-host.js +0 -7
  149. package/dist/runtime/git-bundle-artifact.d.ts +17 -0
  150. package/dist/runtime/git-bundle-artifact.d.ts.map +1 -0
  151. package/dist/runtime/git-bundle-artifact.js +25 -0
  152. package/dist/runtime/node-repl.d.ts.map +1 -1
  153. package/dist/runtime/node-repl.js +2 -13
  154. package/dist/runtime/node-shims-artifact.d.ts +22 -23
  155. package/dist/runtime/node-shims-artifact.d.ts.map +1 -1
  156. package/dist/runtime/node-shims-artifact.js +48 -78
  157. package/dist/runtime/node-shims.d.ts.map +1 -1
  158. package/dist/runtime/node-shims.js +2358 -996
  159. package/dist/runtime/opencode-artifact.d.ts.map +1 -1
  160. package/dist/runtime/opencode-artifact.js +17 -53
  161. package/dist/runtime/opencode-facet-runner.d.ts +3 -6
  162. package/dist/runtime/opencode-facet-runner.d.ts.map +1 -1
  163. package/dist/runtime/opencode-facet-runner.js +45 -10
  164. package/dist/runtime/opentui-wasm-bytes.d.ts.map +1 -1
  165. package/dist/runtime/opentui-wasm-bytes.js +16 -49
  166. package/dist/runtime/package-manager.d.ts +0 -2
  167. package/dist/runtime/package-manager.d.ts.map +1 -1
  168. package/dist/runtime/package-manager.js +0 -3
  169. package/dist/runtime/python-repl.d.ts.map +1 -1
  170. package/dist/runtime/python-repl.js +4 -35
  171. package/dist/runtime/repl-session.d.ts +10 -0
  172. package/dist/runtime/repl-session.d.ts.map +1 -1
  173. package/dist/runtime/repl-session.js +7 -0
  174. package/dist/runtime/ruby-repl.d.ts.map +1 -1
  175. package/dist/runtime/ruby-repl.js +2 -2
  176. package/dist/runtime/ruby-resident.d.ts.map +1 -1
  177. package/dist/runtime/ruby-resident.js +41 -51
  178. package/dist/runtime/runtime-catalog.d.ts +10 -3
  179. package/dist/runtime/runtime-catalog.d.ts.map +1 -1
  180. package/dist/runtime/runtime-catalog.js +125 -15
  181. package/dist/runtime/sqlite-wasm-bytes.d.ts +4 -4
  182. package/dist/runtime/sqlite-wasm-bytes.d.ts.map +1 -1
  183. package/dist/runtime/sqlite-wasm-bytes.js +21 -57
  184. package/dist/runtime/staged-source.d.ts +67 -0
  185. package/dist/runtime/staged-source.d.ts.map +1 -0
  186. package/dist/runtime/staged-source.js +113 -0
  187. package/dist/runtime/wasi/preamble.d.ts +0 -22
  188. package/dist/runtime/wasi/preamble.d.ts.map +1 -1
  189. package/dist/runtime/wasi/preamble.js +98 -1425
  190. package/dist/runtime-catalog.generated.js +1 -1
  191. package/dist/session/agent.d.ts +14 -2
  192. package/dist/session/agent.d.ts.map +1 -1
  193. package/dist/session/agent.js +57 -19
  194. package/dist/session/ai.d.ts +0 -11
  195. package/dist/session/ai.d.ts.map +1 -1
  196. package/dist/session/ai.js +0 -4
  197. package/dist/session/fs-watch.d.ts +0 -11
  198. package/dist/session/fs-watch.d.ts.map +1 -1
  199. package/dist/session/fs-watch.js +0 -21
  200. package/dist/session/hibernation.d.ts +68 -2
  201. package/dist/session/hibernation.d.ts.map +1 -1
  202. package/dist/session/hibernation.js +95 -1
  203. package/dist/session/init-phases.d.ts +1 -2
  204. package/dist/session/init-phases.d.ts.map +1 -1
  205. package/dist/session/init-phases.js +1 -2
  206. package/dist/session/init.d.ts.map +1 -1
  207. package/dist/session/init.js +7 -20
  208. package/dist/session/keys.d.ts +0 -3
  209. package/dist/session/keys.d.ts.map +1 -1
  210. package/dist/session/keys.js +0 -3
  211. package/dist/session/legacy-reset.d.ts +11 -0
  212. package/dist/session/legacy-reset.d.ts.map +1 -0
  213. package/dist/session/legacy-reset.js +16 -0
  214. package/dist/session/nimbus-session.d.ts +50 -16
  215. package/dist/session/nimbus-session.d.ts.map +1 -1
  216. package/dist/session/nimbus-session.js +77 -71
  217. package/dist/session/npm-install-port.d.ts.map +1 -1
  218. package/dist/session/npm-install-port.js +2 -0
  219. package/dist/session/programmatic.d.ts +13 -18
  220. package/dist/session/programmatic.d.ts.map +1 -1
  221. package/dist/session/programmatic.js +53 -39
  222. package/dist/session/replica-routes.d.ts +2 -3
  223. package/dist/session/replica-routes.d.ts.map +1 -1
  224. package/dist/session/replica-routes.js +3 -10
  225. package/dist/session/routes.d.ts.map +1 -1
  226. package/dist/session/routes.js +11 -12
  227. package/dist/session/rpc.d.ts +108 -21
  228. package/dist/session/rpc.d.ts.map +1 -1
  229. package/dist/session/rpc.js +194 -39
  230. package/dist/session/start-real-vite.js +2 -2
  231. package/dist/session/state-store.d.ts +1 -17
  232. package/dist/session/state-store.d.ts.map +1 -1
  233. package/dist/session/state-store.js +3 -53
  234. package/dist/session/supervisor-op.d.ts +7 -0
  235. package/dist/session/supervisor-op.d.ts.map +1 -1
  236. package/dist/session/supervisor-op.js +6 -1
  237. package/dist/session/supervisor-rpc.d.ts +116 -19
  238. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  239. package/dist/session/supervisor-rpc.js +194 -84
  240. package/dist/session/vite-command.d.ts.map +1 -1
  241. package/dist/session/vite-command.js +17 -14
  242. package/dist/session/ws.d.ts.map +1 -1
  243. package/dist/session/ws.js +20 -8
  244. package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
  245. package/dist/shell/npm-bin-entrypoints.js +30 -5
  246. package/dist/vfs/facet-resident-limits.d.ts +33 -0
  247. package/dist/vfs/facet-resident-limits.d.ts.map +1 -0
  248. package/dist/vfs/facet-resident-limits.js +33 -0
  249. package/dist/vfs/facet-resident-store.d.ts +2 -10
  250. package/dist/vfs/facet-resident-store.d.ts.map +1 -1
  251. package/dist/vfs/facet-resident-store.js +1594 -219
  252. package/dist/workspace-host.d.ts +3 -1
  253. package/dist/workspace-host.d.ts.map +1 -1
  254. package/dist/workspace-host.js +2 -0
  255. package/dist/wrangler/nimbus-wrangler.d.ts +0 -2
  256. package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -1
  257. package/dist/wrangler/nimbus-wrangler.js +8 -22
  258. package/package.json +34 -7
  259. package/public/_assets/esbuild-0.24.2.js +2380 -0
  260. package/public/_assets/opencode/1.16.2/index-attach.js +2 -2
  261. package/public/_assets/runtime/esbuild-cli-c4fda380d52befc1.js +6892 -0
  262. package/public/_assets/runtime/git-de3e60c9f3ff006a.js +103 -0
  263. package/public/_assets/runtime/{node-shims-acaa037e6d26ec0d.js → node-shims-06565b121555c194.js} +2879 -992
  264. package/public/_assets/runtime/resident-store-6ffb1939c53d982f.js +2264 -0
  265. package/public/_assets/runtime/vfs-write-ledger-e501e8fc793bc2c1.js +889 -0
  266. package/scripts/build-opencode-attach-entry.mjs +51 -1
  267. package/scripts/bundle-esbuild-wasm.mjs +55 -39
  268. package/scripts/bundle-facet-workers.mjs +159 -10
  269. package/scripts/bundle-git.mjs +88 -27
  270. package/scripts/bundle-node-shims.mjs +72 -46
  271. package/scripts/bundle-runtime.mjs +18 -421
  272. package/scripts/cf-git-patch.mjs +137 -0
  273. package/scripts/patch-install-deps.mjs +49 -96
  274. package/scripts/runtime-specs.mjs +435 -0
  275. package/vendor/git-http.generated.d.ts +82 -0
  276. package/vendor/git-upstream.generated.d.ts +4378 -0
  277. package/vendor/git.LICENSE +7 -0
  278. package/vendor/git.generated.d.mts +21 -0
  279. package/vendor/git.generated.mjs +103 -0
  280. package/dist/replica/suspension.d.ts +0 -36
  281. package/dist/replica/suspension.d.ts.map +0 -1
  282. package/dist/replica/suspension.js +0 -49
  283. package/dist/runtime/static-server.d.ts +0 -3
  284. package/dist/runtime/static-server.d.ts.map +0 -1
  285. package/dist/runtime/static-server.js +0 -133
  286. 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
- // ─── Virtual filesystem state ───────────────────────────────────────────
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, files, dirs, modes, times?, symlinks? }) —
94
- // install a per-call FS:
95
- // root string — canonical session root, e.g. 'home/user/wasi-files'.
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
- // Mutations go back to the session VFS as they happen, through the supervisor
111
- // stub __wasiAdoptSupervisor installs. A sealed instance (no stub) keeps them
112
- // in memory and its caller names what it wants back via __wasiReadFilesB64.
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
- // An empty filesystem from the first instruction, replaced wholesale by
115
- // __wasiInitFS. It used to start null, which 113 syscall bodies then
116
- // dereferenced without checking — a fact only visible once a type checker read
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, when present, makes __wasiFS a CACHE of the session
149
- // VFS rather than a closed world. Absent (unit tests, pools that legitimately
150
- // run sealed) the seed is authoritative and every path below degrades to
151
- // pure in-memory behavior.
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 holding a cache it can no longer write back.
135
+ // strand the process.
158
136
  export function __wasiAdoptSupervisor(sup) {
159
137
  if (sup)
160
138
  __wasiSup = sup;
161
139
  }
162
- /**
163
- * True when the seed deliberately shipped no content and is relying on a
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, and none of
375
- // the previous tenant's queued writes, dirty paths or negative lookups may
376
- // survive into it. Pools reuse an isolate across calls, so leaking any of
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
- files, dirs, times, symlinks, modes, sizes,
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: 'file', vfsPath, offset, oflags, fdflags, rights? }
761
- // { kind: 'dir', vfsPath, readdirEntries: null | Array, cookie, rights? }
762
- // { kind: 'socket', socket, reader, writer, readBuf, ... }
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 in this shim (we don't enforce per-fn
767
- // rights checks in v1 — single-tenant facet, no untrusted callers).
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 === 'dir' || entry.kind === 'preopen')
637
+ if (entry.kind === 'preopen')
1356
638
  return __WASI_EISDIR;
1357
- if (entry.kind !== 'file')
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
- if (fd === 1 || fd === 2) {
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, offsetArg, whence, newOffsetPtr) {
1438
- // WASI/V8 passes i64 args as BigInt. core WASI mistakenly assumed
1439
- // (lo, hi) i32 pairs; that worked only because hello-world never
1440
- // exercised seek. filesystem WASI: accept BigInt directly.
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
- if (!entry || entry.kind !== 'file')
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, offsetPtr) {
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
- if (!entry || entry.kind !== 'file')
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' || entry.kind === 'dir') {
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 === 'file' || entry.kind === 'socket' || entry.kind === 'listener')
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
- // Honor LOOKUPFLAGS_SYMLINK_FOLLOW strictly: bit 0 set → follow.
1595
- // wasi-libc clears this bit for O_NOFOLLOW, so dirflags === 0 IS
1596
- // O_NOFOLLOW and must NOT be treated as follow.
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(baseFd, pathPtr, pathLen) {
1674
- const path = readPath(pathPtr, pathLen);
1675
- const resolved = __wasiResolvePath(baseFd, path);
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(srcFd, srcPathPtr, srcPathLen, dstFd, dstPathPtr, dstPathLen) {
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(baseFd, lookupflags, pathPtr, pathLen, statPtr) {
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(baseFd, lookupflags, pathPtr, pathLen, atimArg, mtimArg, fstflags) {
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(baseFd, pathPtr, pathLen, bufPtr, bufLen, bufUsedPtr) {
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(oldPathPtr, oldPathLen, newFd, newPathPtr, newPathLen) {
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(oldFd, oldFlags, oldPathPtr, oldPathLen, newFd, newPathPtr, newPathLen) {
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
- let size = 0n;
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
- const t = (timesPath && __wasiFS.times.get(timesPath)) || { mtime: 0n, atime: 0n, ctime: 0n };
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
- if (entry.kind !== 'file' && entry.kind !== 'dir' && entry.kind !== 'preopen') {
2062
- // stdio / socket fds: no-op success (POSIX touches /dev/stdin etc. silently).
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
- const setAtim = (fstflags & __WASI_FSTFLAGS_ATIM) !== 0;
2066
- const setAtimNow = (fstflags & __WASI_FSTFLAGS_ATIM_NOW) !== 0;
2067
- const setMtim = (fstflags & __WASI_FSTFLAGS_MTIM) !== 0;
2068
- const setMtimNow = (fstflags & __WASI_FSTFLAGS_MTIM_NOW) !== 0;
2069
- if ((setAtim && setAtimNow) || (setMtim && setMtimNow))
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(fd, iovsPtr, iovsLen, offsetArg, nreadPtr) {
2080
- const entry = fdTable.get(fd);
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(fd, bufPtr, bufLen, cookieArg, bufusedPtr) {
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(fd, offsetArg, lenArg) {
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). A file the
2431
- // authority owns is a regular file too; leaving 'authority' out made
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 === 'file' || entry.kind === 'dir' ||
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 === 'file' && s.tag === __WASI_EVENTTYPE_FD_READ) {
2438
- const f = getFile(entry.vfsPath);
2439
- if (f) {
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 ─────────────────────────────────────────