flatsql 3.11.1 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -795
- package/bin/flatsql.mjs +76 -0
- package/cpp/README.md +18 -0
- package/cpp/include/flatsql/flatsql.h +162 -0
- package/cpp/include/flatsql/flatsql_io.h +159 -0
- package/docs/API-3.12.md +114 -0
- package/package.json +28 -123
- package/wasm/flatsql.wasm +0 -0
- package/wasm/index.d.ts +61 -548
- package/wasm/index.js +280 -1281
- package/wasm/integrity.json +6 -26
- package/wasm/runtime/LICENSE.sdk.txt +190 -0
- package/wasm/runtime/NOTICE.txt +2 -0
- package/wasm/runtime/browser-guest.mjs +1 -0
- package/wasm/runtime/browser-host.mjs +1 -0
- package/wasm/runtime/node-guest.mjs +2 -0
- package/wasm/runtime/node-host.mjs +5 -0
- package/wasm/runtime/opfsIoWorker.mjs +1 -0
- package/wasm/runtime/wasiThreadBrowserWorker.mjs +1 -0
- package/dist/artifacts/index.d.ts +0 -3
- package/dist/artifacts/index.d.ts.map +0 -1
- package/dist/artifacts/index.js +0 -3
- package/dist/artifacts/index.js.map +0 -1
- package/dist/artifacts/transport.d.ts +0 -5
- package/dist/artifacts/transport.d.ts.map +0 -1
- package/dist/artifacts/transport.js +0 -45
- package/dist/artifacts/transport.js.map +0 -1
- package/dist/artifacts/types.d.ts +0 -34
- package/dist/artifacts/types.d.ts.map +0 -1
- package/dist/artifacts/types.js +0 -2
- package/dist/artifacts/types.js.map +0 -1
- package/dist/btree/btree.d.ts +0 -34
- package/dist/btree/btree.d.ts.map +0 -1
- package/dist/btree/btree.js +0 -234
- package/dist/btree/btree.js.map +0 -1
- package/dist/btree/index.d.ts +0 -3
- package/dist/btree/index.d.ts.map +0 -1
- package/dist/btree/index.js +0 -3
- package/dist/btree/index.js.map +0 -1
- package/dist/btree/types.d.ts +0 -36
- package/dist/btree/types.d.ts.map +0 -1
- package/dist/btree/types.js +0 -55
- package/dist/btree/types.js.map +0 -1
- package/dist/cluster/index.d.ts +0 -11
- package/dist/cluster/index.d.ts.map +0 -1
- package/dist/cluster/index.js +0 -59
- package/dist/cluster/index.js.map +0 -1
- package/dist/response/artifact.d.ts +0 -4
- package/dist/response/artifact.d.ts.map +0 -1
- package/dist/response/artifact.js +0 -102
- package/dist/response/artifact.js.map +0 -1
- package/dist/response/cache-key.d.ts +0 -6
- package/dist/response/cache-key.d.ts.map +0 -1
- package/dist/response/cache-key.js +0 -114
- package/dist/response/cache-key.js.map +0 -1
- package/dist/response/index.d.ts +0 -5
- package/dist/response/index.d.ts.map +0 -1
- package/dist/response/index.js +0 -5
- package/dist/response/index.js.map +0 -1
- package/dist/response/memory-cache.d.ts +0 -17
- package/dist/response/memory-cache.d.ts.map +0 -1
- package/dist/response/memory-cache.js +0 -76
- package/dist/response/memory-cache.js.map +0 -1
- package/dist/response/types.d.ts +0 -57
- package/dist/response/types.d.ts.map +0 -1
- package/dist/response/types.js +0 -2
- package/dist/response/types.js.map +0 -1
- package/dist/schema/index.d.ts +0 -3
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js +0 -3
- package/dist/schema/index.js.map +0 -1
- package/dist/schema/parser.d.ts +0 -25
- package/dist/schema/parser.d.ts.map +0 -1
- package/dist/schema/parser.js +0 -474
- package/dist/schema/parser.js.map +0 -1
- package/dist/schema/types.d.ts +0 -33
- package/dist/schema/types.d.ts.map +0 -1
- package/dist/schema/types.js +0 -29
- package/dist/schema/types.js.map +0 -1
- package/dist/standalone/index.d.ts +0 -3
- package/dist/standalone/index.d.ts.map +0 -1
- package/dist/standalone/index.js +0 -3
- package/dist/standalone/index.js.map +0 -1
- package/dist/standalone/process-client.d.ts +0 -85
- package/dist/standalone/process-client.d.ts.map +0 -1
- package/dist/standalone/process-client.js +0 -537
- package/dist/standalone/process-client.js.map +0 -1
- package/dist/standalone/wasmedge-runner.d.ts +0 -19
- package/dist/standalone/wasmedge-runner.d.ts.map +0 -1
- package/dist/standalone/wasmedge-runner.js +0 -108
- package/dist/standalone/wasmedge-runner.js.map +0 -1
- package/dist/storage/index.d.ts +0 -3
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js +0 -3
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/stacked-flatbuffers.d.ts +0 -38
- package/dist/storage/stacked-flatbuffers.d.ts.map +0 -1
- package/dist/storage/stacked-flatbuffers.js +0 -253
- package/dist/storage/stacked-flatbuffers.js.map +0 -1
- package/dist/storage/types.d.ts +0 -23
- package/dist/storage/types.d.ts.map +0 -1
- package/dist/storage/types.js +0 -27
- package/dist/storage/types.js.map +0 -1
- package/dist/stress/aggregate.d.ts +0 -3
- package/dist/stress/aggregate.d.ts.map +0 -1
- package/dist/stress/aggregate.js +0 -61
- package/dist/stress/aggregate.js.map +0 -1
- package/dist/stress/docker-compose.d.ts +0 -17
- package/dist/stress/docker-compose.d.ts.map +0 -1
- package/dist/stress/docker-compose.js +0 -156
- package/dist/stress/docker-compose.js.map +0 -1
- package/dist/stress/index.d.ts +0 -10
- package/dist/stress/index.d.ts.map +0 -1
- package/dist/stress/index.js +0 -10
- package/dist/stress/index.js.map +0 -1
- package/dist/stress/metrics.d.ts +0 -3
- package/dist/stress/metrics.d.ts.map +0 -1
- package/dist/stress/metrics.js +0 -83
- package/dist/stress/metrics.js.map +0 -1
- package/dist/stress/native-publish-event-workload.d.ts +0 -3
- package/dist/stress/native-publish-event-workload.d.ts.map +0 -1
- package/dist/stress/native-publish-event-workload.js +0 -465
- package/dist/stress/native-publish-event-workload.js.map +0 -1
- package/dist/stress/production-gate.d.ts +0 -4
- package/dist/stress/production-gate.d.ts.map +0 -1
- package/dist/stress/production-gate.js +0 -12
- package/dist/stress/production-gate.js.map +0 -1
- package/dist/stress/report.d.ts +0 -3
- package/dist/stress/report.d.ts.map +0 -1
- package/dist/stress/report.js +0 -174
- package/dist/stress/report.js.map +0 -1
- package/dist/stress/sds-discovery.d.ts +0 -4
- package/dist/stress/sds-discovery.d.ts.map +0 -1
- package/dist/stress/sds-discovery.js +0 -40
- package/dist/stress/sds-discovery.js.map +0 -1
- package/dist/stress/smoke-runner.d.ts +0 -3
- package/dist/stress/smoke-runner.d.ts.map +0 -1
- package/dist/stress/smoke-runner.js +0 -64
- package/dist/stress/smoke-runner.js.map +0 -1
- package/dist/stress/types.d.ts +0 -219
- package/dist/stress/types.d.ts.map +0 -1
- package/dist/stress/types.js +0 -2
- package/dist/stress/types.js.map +0 -1
- package/dist/stress/workload.d.ts +0 -6
- package/dist/stress/workload.d.ts.map +0 -1
- package/dist/stress/workload.js +0 -144
- package/dist/stress/workload.js.map +0 -1
- package/docs/PARTITION-STORE-WASM.md +0 -338
- package/docs/PARTITION-STORE.md +0 -2380
- package/docs/agents/response-artifacts.md +0 -65
- package/docs/response-artifacts.md +0 -86
- package/scripts/stress-docker.sh +0 -279
- package/sdm/flatsql-spatial.wasm +0 -0
- package/sdm/index.d.ts +0 -88
- package/sdm/index.js +0 -253
- package/sdm/manifest.json +0 -402
- package/stress/docker/Dockerfile +0 -11
- package/stress/docker/docker-compose.yml +0 -51
- package/stress/sds/aggregate.mjs +0 -97
- package/stress/sds/run.mjs +0 -105
- package/stress/sds/write-compose.mjs +0 -96
- package/wasm/flatsql-client.js +0 -280
- package/wasm/flatsql-io.d.ts +0 -97
- package/wasm/flatsql-io.js +0 -625
- package/wasm/flatsql-p4-threads.wasm +0 -0
- package/wasm/flatsql-ps-threads.wasm +0 -0
- package/wasm/flatsql-wasi-noeh.wasm +0 -0
- package/wasm/flatsql-wasi.wasm +0 -0
- package/wasm/flatsql.js +0 -2
- package/wasm/flatsql.worker.js +0 -209
- package/wasm/native/flatsql_wasmedge_runner.cpp +0 -858
- package/wasm/ps-node-fault.d.mts +0 -23
- package/wasm/ps-node-fault.mjs +0 -381
- package/wasm/ps-node-guest.mjs +0 -173
- package/wasm/ps-node-host.d.mts +0 -95
- package/wasm/ps-node-host.mjs +0 -476
- package/wasm/ps-node-imports.mjs +0 -847
- package/wasm/ps.d.ts +0 -21
- package/wasm/ps.js +0 -75
- package/wasm/wasi.d.ts +0 -11
- package/wasm/wasi.js +0 -51
package/wasm/ps-node-imports.mjs
DELETED
|
@@ -1,847 +0,0 @@
|
|
|
1
|
-
// FlatSQL partition store: the per-thread imports of the Node wasi-threads
|
|
2
|
-
// host (design §5.6, docs/PARTITION-STORE-WASM.md).
|
|
3
|
-
//
|
|
4
|
-
// Every guest thread (the thread running `_start` or the reactor's exec
|
|
5
|
-
// thread, and every thread the SDK's Node pool spawns for wasi.thread-spawn)
|
|
6
|
-
// instantiates the module with the imports built here:
|
|
7
|
-
//
|
|
8
|
-
// wasi_snapshot_preview1 WASI preview1 over synchronous `fs`. Descriptors
|
|
9
|
-
// live in a SharedArrayBuffer table every thread
|
|
10
|
-
// reads, so a descriptor opened on one thread works
|
|
11
|
-
// on any other (Node fds are process-wide). Clocks
|
|
12
|
-
// are process-wide (a worker's performance.now()
|
|
13
|
-
// has its own origin and would break the engine's
|
|
14
|
-
// cross-thread time comparisons).
|
|
15
|
-
// env.flatsql_io_* the seven host I/O imports: the SDK's Node
|
|
16
|
-
// sync-fs provider over a shared virtual-handle
|
|
17
|
-
// table, with the fault overlay (ps-node-host.mjs)
|
|
18
|
-
// interposed on every syscall when configured.
|
|
19
|
-
//
|
|
20
|
-
// wasi.thread-spawn is a pool of pre-started workers (createThreadPool,
|
|
21
|
-
// poolThreadSpawn): any guest thread claims an idle worker through the pool's
|
|
22
|
-
// SharedArrayBuffer, so spawning never needs an event loop, workers are
|
|
23
|
-
// reused, and a guest thread may spawn threads of its own.
|
|
24
|
-
|
|
25
|
-
import fs from "node:fs";
|
|
26
|
-
import path from "node:path";
|
|
27
|
-
import { randomFillSync } from "node:crypto";
|
|
28
|
-
import { threadId } from "node:worker_threads";
|
|
29
|
-
|
|
30
|
-
import { createFlatsqlIoImports } from "space-data-module-sdk/host/flatsql-io";
|
|
31
|
-
import { createNodeSyncFsIo } from "space-data-module-sdk/host/flatsql-io/node";
|
|
32
|
-
|
|
33
|
-
import { createFaultInterposer } from "./ps-node-fault.mjs";
|
|
34
|
-
|
|
35
|
-
// ---- WASI constants ------------------------------------------------------------
|
|
36
|
-
const E = Object.freeze({
|
|
37
|
-
SUCCESS: 0, TOOBIG: 1, ACCES: 2, AGAIN: 6, BADF: 8, EXIST: 20, INVAL: 28, IO: 29, ISDIR: 31,
|
|
38
|
-
LOOP: 32, MFILE: 33, NAMETOOLONG: 37, NOENT: 44, NOSPC: 51, NOSYS: 52, NOTDIR: 54,
|
|
39
|
-
NOTEMPTY: 55, NOTSUP: 58, PERM: 63, SPIPE: 70, XDEV: 75, NOTCAPABLE: 76,
|
|
40
|
-
});
|
|
41
|
-
const NODE_ERRNO = {
|
|
42
|
-
ENOENT: E.NOENT, EEXIST: E.EXIST, ENOTDIR: E.NOTDIR, EISDIR: E.ISDIR, ENOTEMPTY: E.NOTEMPTY,
|
|
43
|
-
EACCES: E.ACCES, EPERM: E.PERM, EBADF: E.BADF, EINVAL: E.INVAL, ENOSPC: E.NOSPC,
|
|
44
|
-
EMFILE: E.MFILE, ELOOP: E.LOOP, ENAMETOOLONG: E.NAMETOOLONG, EXDEV: E.XDEV, EIO: E.IO,
|
|
45
|
-
EAGAIN: E.AGAIN, ENOTSUP: E.NOTSUP, E2BIG: E.TOOBIG,
|
|
46
|
-
};
|
|
47
|
-
const errnoOf = (error) => NODE_ERRNO[error?.code] ?? E.IO;
|
|
48
|
-
|
|
49
|
-
const FT = Object.freeze({ UNKNOWN: 0, CHAR: 2, DIR: 3, FILE: 4, SYMLINK: 7 });
|
|
50
|
-
const OFLAG_CREAT = 1;
|
|
51
|
-
const OFLAG_DIRECTORY = 2;
|
|
52
|
-
const OFLAG_EXCL = 4;
|
|
53
|
-
const OFLAG_TRUNC = 8;
|
|
54
|
-
const FDFLAG_APPEND = 1;
|
|
55
|
-
const RIGHT_FD_READ = 1n << 1n;
|
|
56
|
-
const RIGHT_FD_WRITE = 1n << 6n;
|
|
57
|
-
const ALL_RIGHTS = (1n << 30n) - 1n;
|
|
58
|
-
const LOOKUP_SYMLINK_FOLLOW = 1;
|
|
59
|
-
const WHENCE_SET = 0;
|
|
60
|
-
const WHENCE_CUR = 1;
|
|
61
|
-
const WHENCE_END = 2;
|
|
62
|
-
const CLOCK_REALTIME = 0;
|
|
63
|
-
const CLOCK_MONOTONIC = 1;
|
|
64
|
-
const CLOCK_PROCESS = 2;
|
|
65
|
-
const CLOCK_THREAD = 3;
|
|
66
|
-
|
|
67
|
-
// ---- shared descriptor table ---------------------------------------------------
|
|
68
|
-
// Int32 words per descriptor: state, kind, osFd, pathLen, appendFlag, pad x3.
|
|
69
|
-
// A parallel Float64Array holds each descriptor's file position. Paths are
|
|
70
|
-
// host paths, MAX_PATH bytes each.
|
|
71
|
-
const FD_WORDS = 8;
|
|
72
|
-
const FD_STATE = 0;
|
|
73
|
-
const FD_KIND = 1;
|
|
74
|
-
const FD_OSFD = 2;
|
|
75
|
-
const FD_PATHLEN = 3;
|
|
76
|
-
const FD_APPEND = 4;
|
|
77
|
-
const FD_FREE = 0;
|
|
78
|
-
const FD_CLAIMING = 1;
|
|
79
|
-
const FD_LIVE = 2;
|
|
80
|
-
const KIND_STDIO = 1;
|
|
81
|
-
const KIND_FILE = 2;
|
|
82
|
-
const KIND_DIR = 3;
|
|
83
|
-
const KIND_PREOPEN = 4;
|
|
84
|
-
const MAX_PATH = 1024;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* The WASI descriptor table shared by every thread of one guest process.
|
|
88
|
-
* @param {{ preopens: Array<{ guest: string, host: string }>, maxFds?: number }} options
|
|
89
|
-
*/
|
|
90
|
-
export function createWasiFdTable({ preopens = [], maxFds = 1024 } = {}) {
|
|
91
|
-
const words = maxFds * FD_WORDS;
|
|
92
|
-
const buffer = new SharedArrayBuffer(16 + words * 4 + maxFds * 8 + maxFds * MAX_PATH);
|
|
93
|
-
const header = new Int32Array(buffer, 0, 4);
|
|
94
|
-
header[0] = maxFds;
|
|
95
|
-
header[1] = preopens.length;
|
|
96
|
-
const t = fdTableViews(buffer);
|
|
97
|
-
for (let fd = 0; fd < 3; fd += 1) {
|
|
98
|
-
t.words[fd * FD_WORDS + FD_KIND] = KIND_STDIO;
|
|
99
|
-
t.words[fd * FD_WORDS + FD_OSFD] = fd;
|
|
100
|
-
t.words[fd * FD_WORDS + FD_STATE] = FD_LIVE;
|
|
101
|
-
}
|
|
102
|
-
preopens.forEach((p, i) => {
|
|
103
|
-
const fd = 3 + i;
|
|
104
|
-
const host = Buffer.from(path.resolve(p.host));
|
|
105
|
-
const guest = Buffer.from(p.guest);
|
|
106
|
-
if (host.length + guest.length + 1 > MAX_PATH) throw new RangeError("preopen path too long");
|
|
107
|
-
// Preopen entries store "<guest>\0<host>".
|
|
108
|
-
t.paths.set(guest, fd * MAX_PATH);
|
|
109
|
-
t.paths[fd * MAX_PATH + guest.length] = 0;
|
|
110
|
-
t.paths.set(host, fd * MAX_PATH + guest.length + 1);
|
|
111
|
-
t.words[fd * FD_WORDS + FD_KIND] = KIND_PREOPEN;
|
|
112
|
-
t.words[fd * FD_WORDS + FD_OSFD] = -1;
|
|
113
|
-
t.words[fd * FD_WORDS + FD_PATHLEN] = guest.length + 1 + host.length;
|
|
114
|
-
t.words[fd * FD_WORDS + FD_STATE] = FD_LIVE;
|
|
115
|
-
});
|
|
116
|
-
return buffer;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function fdTableViews(buffer) {
|
|
120
|
-
const maxFds = new Int32Array(buffer, 0, 4)[0];
|
|
121
|
-
let off = 16;
|
|
122
|
-
const words = new Int32Array(buffer, off, maxFds * FD_WORDS);
|
|
123
|
-
off += maxFds * FD_WORDS * 4;
|
|
124
|
-
const positions = new Float64Array(buffer, off, maxFds);
|
|
125
|
-
off += maxFds * 8;
|
|
126
|
-
const paths = new Uint8Array(buffer, off, maxFds * MAX_PATH);
|
|
127
|
-
return { maxFds, words, positions, paths };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ---- process-wide clocks -------------------------------------------------------
|
|
131
|
-
// hrtime is the system monotonic clock in every thread of the process.
|
|
132
|
-
function monotonicNs() {
|
|
133
|
-
return process.hrtime.bigint();
|
|
134
|
-
}
|
|
135
|
-
function realtimeNs() {
|
|
136
|
-
return BigInt(Math.round((performance.timeOrigin + performance.now()) * 1e6));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ---- control block (supervisor words) --------------------------------------------
|
|
140
|
-
// Int32 words: 0 exited, 1 exit code, 2 fault (a guest thread died), 3 live
|
|
141
|
-
// guest threads, 4 max live, 5 threads started, 6 next thread-id slot,
|
|
142
|
-
// 7 fault tid; then CONTROL_TIDS slots of Node worker threadIds.
|
|
143
|
-
export const CONTROL_EXITED = 0;
|
|
144
|
-
export const CONTROL_EXIT_CODE = 1;
|
|
145
|
-
export const CONTROL_FAULT = 2;
|
|
146
|
-
export const CONTROL_LIVE = 3;
|
|
147
|
-
export const CONTROL_MAX_LIVE = 4;
|
|
148
|
-
export const CONTROL_STARTED = 5;
|
|
149
|
-
export const CONTROL_SLOT = 6;
|
|
150
|
-
export const CONTROL_FAULT_TID = 7;
|
|
151
|
-
export const CONTROL_HEADER = 16;
|
|
152
|
-
export const CONTROL_TIDS = 65536;
|
|
153
|
-
|
|
154
|
-
export function createControlBlock() {
|
|
155
|
-
return new SharedArrayBuffer((CONTROL_HEADER + CONTROL_TIDS) * 4);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function registerThread(control) {
|
|
159
|
-
const c = new Int32Array(control);
|
|
160
|
-
const live = Atomics.add(c, CONTROL_LIVE, 1) + 1;
|
|
161
|
-
for (;;) {
|
|
162
|
-
const max = Atomics.load(c, CONTROL_MAX_LIVE);
|
|
163
|
-
if (live <= max || Atomics.compareExchange(c, CONTROL_MAX_LIVE, max, live) === max) break;
|
|
164
|
-
}
|
|
165
|
-
Atomics.add(c, CONTROL_STARTED, 1);
|
|
166
|
-
const slot = Atomics.add(c, CONTROL_SLOT, 1);
|
|
167
|
-
if (slot < CONTROL_TIDS) Atomics.store(c, CONTROL_HEADER + slot, threadId);
|
|
168
|
-
return c;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export class WasiExitError extends Error {
|
|
172
|
-
constructor(code) {
|
|
173
|
-
super(`WASI exit with code ${code}`);
|
|
174
|
-
// The SDK pool worker treats this name as a clean thread exit.
|
|
175
|
-
this.name = "WasiExitError";
|
|
176
|
-
this.code = code;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ---- WASI preview1 ---------------------------------------------------------------
|
|
181
|
-
/**
|
|
182
|
-
* @param {object} cfg the shared host config (see ps-node-host.mjs)
|
|
183
|
-
* @param {() => WebAssembly.Memory} getMemory
|
|
184
|
-
*/
|
|
185
|
-
export function createWasi(cfg, getMemory) {
|
|
186
|
-
const table = fdTableViews(cfg.fdTable);
|
|
187
|
-
const control = new Int32Array(cfg.control);
|
|
188
|
-
const encoder = new TextEncoder();
|
|
189
|
-
const args = (cfg.args ?? []).map((a) => encoder.encode(a));
|
|
190
|
-
const env = Object.entries(cfg.env ?? {}).map(([k, v]) => encoder.encode(`${k}=${v}`));
|
|
191
|
-
const stdoutFd = Number.isInteger(cfg.stdoutFd) ? cfg.stdoutFd : 1;
|
|
192
|
-
const stderrFd = Number.isInteger(cfg.stderrFd) ? cfg.stderrFd : 2;
|
|
193
|
-
const sleeper = new Int32Array(new SharedArrayBuffer(4));
|
|
194
|
-
let rngState = null;
|
|
195
|
-
if (cfg.deterministic?.randomSeed !== undefined) {
|
|
196
|
-
rngState = new BigUint64Array(cfg.deterministic.randomState);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const u8 = () => new Uint8Array(getMemory().buffer);
|
|
200
|
-
const dv = () => new DataView(getMemory().buffer);
|
|
201
|
-
const readString = (ptr, len) => Buffer.from(u8().slice(ptr, ptr + len)).toString("utf8");
|
|
202
|
-
|
|
203
|
-
function entry(fd) {
|
|
204
|
-
if (!Number.isInteger(fd) || fd < 0 || fd >= table.maxFds) return null;
|
|
205
|
-
const base = fd * FD_WORDS;
|
|
206
|
-
if (Atomics.load(table.words, base + FD_STATE) !== FD_LIVE) return null;
|
|
207
|
-
return {
|
|
208
|
-
fd,
|
|
209
|
-
base,
|
|
210
|
-
kind: table.words[base + FD_KIND],
|
|
211
|
-
osFd: table.words[base + FD_OSFD],
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
function hostPathOf(e) {
|
|
215
|
-
const len = table.words[e.base + FD_PATHLEN];
|
|
216
|
-
const bytes = table.paths.slice(e.fd * MAX_PATH, e.fd * MAX_PATH + len);
|
|
217
|
-
if (e.kind === KIND_PREOPEN) {
|
|
218
|
-
const nul = bytes.indexOf(0);
|
|
219
|
-
return Buffer.from(bytes.subarray(nul + 1)).toString("utf8");
|
|
220
|
-
}
|
|
221
|
-
return Buffer.from(bytes).toString("utf8");
|
|
222
|
-
}
|
|
223
|
-
function preopenGuestName(e) {
|
|
224
|
-
const len = table.words[e.base + FD_PATHLEN];
|
|
225
|
-
const bytes = table.paths.subarray(e.fd * MAX_PATH, e.fd * MAX_PATH + len);
|
|
226
|
-
return Buffer.from(bytes.slice(0, bytes.indexOf(0)));
|
|
227
|
-
}
|
|
228
|
-
function allocFd(kind, osFd, hostPath, append) {
|
|
229
|
-
const bytes = Buffer.from(hostPath);
|
|
230
|
-
if (bytes.length > MAX_PATH) return -E.NAMETOOLONG;
|
|
231
|
-
const first = 3 + new Int32Array(cfg.fdTable, 0, 4)[1];
|
|
232
|
-
for (let fd = first; fd < table.maxFds; fd += 1) {
|
|
233
|
-
const base = fd * FD_WORDS;
|
|
234
|
-
if (Atomics.compareExchange(table.words, base + FD_STATE, FD_FREE, FD_CLAIMING) === FD_FREE) {
|
|
235
|
-
table.paths.set(bytes, fd * MAX_PATH);
|
|
236
|
-
table.words[base + FD_PATHLEN] = bytes.length;
|
|
237
|
-
table.words[base + FD_KIND] = kind;
|
|
238
|
-
table.words[base + FD_OSFD] = osFd;
|
|
239
|
-
table.words[base + FD_APPEND] = append ? 1 : 0;
|
|
240
|
-
table.positions[fd] = 0;
|
|
241
|
-
Atomics.store(table.words, base + FD_STATE, FD_LIVE);
|
|
242
|
-
return fd;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return -E.MFILE;
|
|
246
|
-
}
|
|
247
|
-
// Resolve a guest path relative to a directory descriptor to a host path,
|
|
248
|
-
// confined below the preopen the directory belongs to.
|
|
249
|
-
function resolveAt(dirfd, pathPtr, pathLen) {
|
|
250
|
-
const e = entry(dirfd);
|
|
251
|
-
if (!e) return { errno: E.BADF };
|
|
252
|
-
if (e.kind !== KIND_DIR && e.kind !== KIND_PREOPEN) return { errno: E.NOTDIR };
|
|
253
|
-
const rel = readString(pathPtr, pathLen);
|
|
254
|
-
if (rel.includes("\0")) return { errno: E.INVAL };
|
|
255
|
-
const base = hostPathOf(e);
|
|
256
|
-
const full = path.resolve(base, rel);
|
|
257
|
-
const root = rootOf(base);
|
|
258
|
-
if (root !== "/" && full !== root && !full.startsWith(root + path.sep)) return { errno: E.NOTCAPABLE };
|
|
259
|
-
return { full };
|
|
260
|
-
}
|
|
261
|
-
function rootOf(hostPath) {
|
|
262
|
-
let best = null;
|
|
263
|
-
const n = new Int32Array(cfg.fdTable, 0, 4)[1];
|
|
264
|
-
for (let i = 0; i < n; i += 1) {
|
|
265
|
-
const root = hostPathOf(entry(3 + i));
|
|
266
|
-
if (root === "/" || hostPath === root || hostPath.startsWith(root + path.sep)) {
|
|
267
|
-
if (!best || root.length > best.length) best = root;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return best ?? "/";
|
|
271
|
-
}
|
|
272
|
-
function iovecs(iovsPtr, iovsLen) {
|
|
273
|
-
const view = dv();
|
|
274
|
-
const out = [];
|
|
275
|
-
for (let i = 0; i < iovsLen; i += 1) {
|
|
276
|
-
out.push([view.getUint32(iovsPtr + i * 8, true), view.getUint32(iovsPtr + i * 8 + 4, true)]);
|
|
277
|
-
}
|
|
278
|
-
return out;
|
|
279
|
-
}
|
|
280
|
-
function writeStat(ptr, st) {
|
|
281
|
-
const view = dv();
|
|
282
|
-
const type = st.isDirectory() ? FT.DIR : st.isFile() ? FT.FILE : st.isSymbolicLink() ? FT.SYMLINK
|
|
283
|
-
: st.isCharacterDevice() ? FT.CHAR : FT.UNKNOWN;
|
|
284
|
-
view.setBigUint64(ptr, BigInt(st.dev), true);
|
|
285
|
-
view.setBigUint64(ptr + 8, BigInt(st.ino), true);
|
|
286
|
-
view.setUint8(ptr + 16, type);
|
|
287
|
-
view.setBigUint64(ptr + 24, BigInt(st.nlink), true);
|
|
288
|
-
view.setBigUint64(ptr + 32, BigInt(st.size), true);
|
|
289
|
-
view.setBigUint64(ptr + 40, st.atimeNs ?? BigInt(Math.round(st.atimeMs * 1e6)), true);
|
|
290
|
-
view.setBigUint64(ptr + 48, st.mtimeNs ?? BigInt(Math.round(st.mtimeMs * 1e6)), true);
|
|
291
|
-
view.setBigUint64(ptr + 56, st.ctimeNs ?? BigInt(Math.round(st.ctimeMs * 1e6)), true);
|
|
292
|
-
}
|
|
293
|
-
function outputFd(e) {
|
|
294
|
-
if (e.kind === KIND_STDIO) return e.fd === 1 ? stdoutFd : e.fd === 2 ? stderrFd : -1;
|
|
295
|
-
return e.osFd;
|
|
296
|
-
}
|
|
297
|
-
const guarded = (fn) => (...a) => {
|
|
298
|
-
try {
|
|
299
|
-
return fn(...a);
|
|
300
|
-
} catch (error) {
|
|
301
|
-
if (error instanceof WasiExitError) throw error;
|
|
302
|
-
return errnoOf(error);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const wasi = {
|
|
307
|
-
args_sizes_get(argcPtr, bufPtr) {
|
|
308
|
-
const view = dv();
|
|
309
|
-
view.setUint32(argcPtr, args.length, true);
|
|
310
|
-
view.setUint32(bufPtr, args.reduce((n, a) => n + a.length + 1, 0), true);
|
|
311
|
-
return E.SUCCESS;
|
|
312
|
-
},
|
|
313
|
-
args_get(argvPtr, bufPtr) {
|
|
314
|
-
const view = dv();
|
|
315
|
-
const mem = u8();
|
|
316
|
-
let at = bufPtr;
|
|
317
|
-
args.forEach((a, i) => {
|
|
318
|
-
view.setUint32(argvPtr + i * 4, at, true);
|
|
319
|
-
mem.set(a, at);
|
|
320
|
-
mem[at + a.length] = 0;
|
|
321
|
-
at += a.length + 1;
|
|
322
|
-
});
|
|
323
|
-
return E.SUCCESS;
|
|
324
|
-
},
|
|
325
|
-
environ_sizes_get(countPtr, bufPtr) {
|
|
326
|
-
const view = dv();
|
|
327
|
-
view.setUint32(countPtr, env.length, true);
|
|
328
|
-
view.setUint32(bufPtr, env.reduce((n, a) => n + a.length + 1, 0), true);
|
|
329
|
-
return E.SUCCESS;
|
|
330
|
-
},
|
|
331
|
-
environ_get(envPtr, bufPtr) {
|
|
332
|
-
const view = dv();
|
|
333
|
-
const mem = u8();
|
|
334
|
-
let at = bufPtr;
|
|
335
|
-
env.forEach((a, i) => {
|
|
336
|
-
view.setUint32(envPtr + i * 4, at, true);
|
|
337
|
-
mem.set(a, at);
|
|
338
|
-
mem[at + a.length] = 0;
|
|
339
|
-
at += a.length + 1;
|
|
340
|
-
});
|
|
341
|
-
return E.SUCCESS;
|
|
342
|
-
},
|
|
343
|
-
clock_res_get(id, resPtr) {
|
|
344
|
-
dv().setBigUint64(resPtr, 1000n, true);
|
|
345
|
-
return E.SUCCESS;
|
|
346
|
-
},
|
|
347
|
-
clock_time_get(id, _precision, timePtr) {
|
|
348
|
-
let ns;
|
|
349
|
-
if (id === CLOCK_REALTIME) ns = realtimeNs();
|
|
350
|
-
else if (id === CLOCK_MONOTONIC || id === CLOCK_PROCESS || id === CLOCK_THREAD) ns = monotonicNs();
|
|
351
|
-
else return E.INVAL;
|
|
352
|
-
dv().setBigUint64(timePtr, ns, true);
|
|
353
|
-
return E.SUCCESS;
|
|
354
|
-
},
|
|
355
|
-
fd_advise: () => E.SUCCESS,
|
|
356
|
-
fd_allocate: guarded((fd, offset, len) => {
|
|
357
|
-
const e = entry(fd);
|
|
358
|
-
if (!e || e.kind !== KIND_FILE) return E.BADF;
|
|
359
|
-
const want = Number(offset + len);
|
|
360
|
-
if (fs.fstatSync(e.osFd).size < want) fs.ftruncateSync(e.osFd, want);
|
|
361
|
-
return E.SUCCESS;
|
|
362
|
-
}),
|
|
363
|
-
fd_close: guarded((fd) => {
|
|
364
|
-
const e = entry(fd);
|
|
365
|
-
if (!e) return E.BADF;
|
|
366
|
-
if (e.kind === KIND_STDIO || e.kind === KIND_PREOPEN) return E.SUCCESS;
|
|
367
|
-
if (Atomics.compareExchange(table.words, e.base + FD_STATE, FD_LIVE, FD_CLAIMING) !== FD_LIVE) {
|
|
368
|
-
return E.BADF;
|
|
369
|
-
}
|
|
370
|
-
try {
|
|
371
|
-
if (e.osFd >= 0) fs.closeSync(e.osFd);
|
|
372
|
-
} finally {
|
|
373
|
-
Atomics.store(table.words, e.base + FD_STATE, FD_FREE);
|
|
374
|
-
}
|
|
375
|
-
return E.SUCCESS;
|
|
376
|
-
}),
|
|
377
|
-
fd_datasync: guarded((fd) => {
|
|
378
|
-
const e = entry(fd);
|
|
379
|
-
if (!e) return E.BADF;
|
|
380
|
-
if (e.osFd >= 0 && e.kind === KIND_FILE) fs.fdatasyncSync(e.osFd);
|
|
381
|
-
return E.SUCCESS;
|
|
382
|
-
}),
|
|
383
|
-
fd_sync: guarded((fd) => {
|
|
384
|
-
const e = entry(fd);
|
|
385
|
-
if (!e) return E.BADF;
|
|
386
|
-
if (e.osFd >= 0 && e.kind === KIND_FILE) fs.fsyncSync(e.osFd);
|
|
387
|
-
return E.SUCCESS;
|
|
388
|
-
}),
|
|
389
|
-
fd_fdstat_get: guarded((fd, ptr) => {
|
|
390
|
-
const e = entry(fd);
|
|
391
|
-
if (!e) return E.BADF;
|
|
392
|
-
const view = dv();
|
|
393
|
-
const type = e.kind === KIND_STDIO ? FT.CHAR : e.kind === KIND_FILE ? FT.FILE : FT.DIR;
|
|
394
|
-
view.setUint8(ptr, type);
|
|
395
|
-
view.setUint16(ptr + 2, table.words[e.base + FD_APPEND] ? FDFLAG_APPEND : 0, true);
|
|
396
|
-
view.setBigUint64(ptr + 8, ALL_RIGHTS, true);
|
|
397
|
-
view.setBigUint64(ptr + 16, ALL_RIGHTS, true);
|
|
398
|
-
return E.SUCCESS;
|
|
399
|
-
}),
|
|
400
|
-
fd_fdstat_set_flags: guarded((fd, flags) => {
|
|
401
|
-
const e = entry(fd);
|
|
402
|
-
if (!e) return E.BADF;
|
|
403
|
-
table.words[e.base + FD_APPEND] = flags & FDFLAG_APPEND ? 1 : 0;
|
|
404
|
-
return E.SUCCESS;
|
|
405
|
-
}),
|
|
406
|
-
fd_filestat_get: guarded((fd, ptr) => {
|
|
407
|
-
const e = entry(fd);
|
|
408
|
-
if (!e) return E.BADF;
|
|
409
|
-
if (e.kind === KIND_STDIO) {
|
|
410
|
-
dv().setUint8(ptr + 16, FT.CHAR);
|
|
411
|
-
return E.SUCCESS;
|
|
412
|
-
}
|
|
413
|
-
const st = e.osFd >= 0 ? fs.fstatSync(e.osFd, { bigint: true }) : fs.statSync(hostPathOf(e), { bigint: true });
|
|
414
|
-
writeStat(ptr, st);
|
|
415
|
-
return E.SUCCESS;
|
|
416
|
-
}),
|
|
417
|
-
fd_filestat_set_size: guarded((fd, size) => {
|
|
418
|
-
const e = entry(fd);
|
|
419
|
-
if (!e || e.kind !== KIND_FILE) return E.BADF;
|
|
420
|
-
fs.ftruncateSync(e.osFd, Number(size));
|
|
421
|
-
return E.SUCCESS;
|
|
422
|
-
}),
|
|
423
|
-
fd_filestat_set_times: () => E.SUCCESS,
|
|
424
|
-
fd_pread: guarded((fd, iovsPtr, iovsLen, offset, nreadPtr) => {
|
|
425
|
-
const e = entry(fd);
|
|
426
|
-
if (!e || e.kind !== KIND_FILE) return E.BADF;
|
|
427
|
-
let pos = Number(offset);
|
|
428
|
-
let total = 0;
|
|
429
|
-
for (const [ptr, len] of iovecs(iovsPtr, iovsLen)) {
|
|
430
|
-
const tmp = Buffer.alloc(len);
|
|
431
|
-
const n = fs.readSync(e.osFd, tmp, 0, len, pos);
|
|
432
|
-
u8().set(tmp.subarray(0, n), ptr);
|
|
433
|
-
total += n;
|
|
434
|
-
pos += n;
|
|
435
|
-
if (n < len) break;
|
|
436
|
-
}
|
|
437
|
-
dv().setUint32(nreadPtr, total, true);
|
|
438
|
-
return E.SUCCESS;
|
|
439
|
-
}),
|
|
440
|
-
fd_pwrite: guarded((fd, iovsPtr, iovsLen, offset, nwrittenPtr) => {
|
|
441
|
-
const e = entry(fd);
|
|
442
|
-
if (!e || e.kind !== KIND_FILE) return E.BADF;
|
|
443
|
-
let pos = Number(offset);
|
|
444
|
-
let total = 0;
|
|
445
|
-
for (const [ptr, len] of iovecs(iovsPtr, iovsLen)) {
|
|
446
|
-
const n = fs.writeSync(e.osFd, Buffer.from(u8().slice(ptr, ptr + len)), 0, len, pos);
|
|
447
|
-
total += n;
|
|
448
|
-
pos += n;
|
|
449
|
-
}
|
|
450
|
-
dv().setUint32(nwrittenPtr, total, true);
|
|
451
|
-
return E.SUCCESS;
|
|
452
|
-
}),
|
|
453
|
-
fd_read: guarded((fd, iovsPtr, iovsLen, nreadPtr) => {
|
|
454
|
-
const e = entry(fd);
|
|
455
|
-
if (!e) return E.BADF;
|
|
456
|
-
if (e.kind === KIND_STDIO) {
|
|
457
|
-
dv().setUint32(nreadPtr, 0, true); // no stdin
|
|
458
|
-
return fd === 0 ? E.SUCCESS : E.BADF;
|
|
459
|
-
}
|
|
460
|
-
if (e.kind !== KIND_FILE) return E.ISDIR;
|
|
461
|
-
let total = 0;
|
|
462
|
-
for (const [ptr, len] of iovecs(iovsPtr, iovsLen)) {
|
|
463
|
-
const tmp = Buffer.alloc(len);
|
|
464
|
-
const pos = table.positions[fd];
|
|
465
|
-
const n = fs.readSync(e.osFd, tmp, 0, len, pos);
|
|
466
|
-
table.positions[fd] = pos + n;
|
|
467
|
-
u8().set(tmp.subarray(0, n), ptr);
|
|
468
|
-
total += n;
|
|
469
|
-
if (n < len) break;
|
|
470
|
-
}
|
|
471
|
-
dv().setUint32(nreadPtr, total, true);
|
|
472
|
-
return E.SUCCESS;
|
|
473
|
-
}),
|
|
474
|
-
fd_write: guarded((fd, iovsPtr, iovsLen, nwrittenPtr) => {
|
|
475
|
-
const e = entry(fd);
|
|
476
|
-
if (!e) return E.BADF;
|
|
477
|
-
const out = outputFd(e);
|
|
478
|
-
if (out < 0) return E.BADF;
|
|
479
|
-
let total = 0;
|
|
480
|
-
for (const [ptr, len] of iovecs(iovsPtr, iovsLen)) {
|
|
481
|
-
if (len === 0) continue;
|
|
482
|
-
const bytes = Buffer.from(u8().slice(ptr, ptr + len));
|
|
483
|
-
if (e.kind === KIND_STDIO) {
|
|
484
|
-
let done = 0;
|
|
485
|
-
while (done < len) done += fs.writeSync(out, bytes, done, len - done);
|
|
486
|
-
total += len;
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
let pos = table.words[e.base + FD_APPEND] ? fs.fstatSync(out).size : table.positions[fd];
|
|
490
|
-
const n = fs.writeSync(out, bytes, 0, len, pos);
|
|
491
|
-
table.positions[fd] = pos + n;
|
|
492
|
-
total += n;
|
|
493
|
-
}
|
|
494
|
-
dv().setUint32(nwrittenPtr, total, true);
|
|
495
|
-
return E.SUCCESS;
|
|
496
|
-
}),
|
|
497
|
-
fd_prestat_get(fd, ptr) {
|
|
498
|
-
const e = entry(fd);
|
|
499
|
-
if (!e || e.kind !== KIND_PREOPEN) return E.BADF;
|
|
500
|
-
const view = dv();
|
|
501
|
-
view.setUint8(ptr, 0);
|
|
502
|
-
view.setUint32(ptr + 4, preopenGuestName(e).length, true);
|
|
503
|
-
return E.SUCCESS;
|
|
504
|
-
},
|
|
505
|
-
fd_prestat_dir_name(fd, ptr, len) {
|
|
506
|
-
const e = entry(fd);
|
|
507
|
-
if (!e || e.kind !== KIND_PREOPEN) return E.BADF;
|
|
508
|
-
const name = preopenGuestName(e);
|
|
509
|
-
u8().set(name.subarray(0, len), ptr);
|
|
510
|
-
return E.SUCCESS;
|
|
511
|
-
},
|
|
512
|
-
fd_readdir: guarded((fd, bufPtr, bufLen, cookie, usedPtr) => {
|
|
513
|
-
const e = entry(fd);
|
|
514
|
-
if (!e || (e.kind !== KIND_DIR && e.kind !== KIND_PREOPEN)) return E.BADF;
|
|
515
|
-
const dir = hostPathOf(e);
|
|
516
|
-
const names = [".", "..", ...fs.readdirSync(dir)];
|
|
517
|
-
const view = dv();
|
|
518
|
-
const mem = u8();
|
|
519
|
-
let at = 0;
|
|
520
|
-
for (let i = Number(cookie); i < names.length; i += 1) {
|
|
521
|
-
const name = Buffer.from(names[i]);
|
|
522
|
-
let type = FT.UNKNOWN;
|
|
523
|
-
let ino = 0n;
|
|
524
|
-
try {
|
|
525
|
-
const st = fs.lstatSync(path.join(dir, names[i]), { bigint: true });
|
|
526
|
-
type = st.isDirectory() ? FT.DIR : st.isFile() ? FT.FILE : st.isSymbolicLink() ? FT.SYMLINK : FT.UNKNOWN;
|
|
527
|
-
ino = st.ino;
|
|
528
|
-
} catch {
|
|
529
|
-
// raced with an unlink: report it with an unknown type
|
|
530
|
-
}
|
|
531
|
-
const head = new Uint8Array(24);
|
|
532
|
-
const hv = new DataView(head.buffer);
|
|
533
|
-
hv.setBigUint64(0, BigInt(i + 1), true);
|
|
534
|
-
hv.setBigUint64(8, ino, true);
|
|
535
|
-
hv.setUint32(16, name.length, true);
|
|
536
|
-
hv.setUint8(20, type);
|
|
537
|
-
const record = Buffer.concat([Buffer.from(head), name]);
|
|
538
|
-
const room = bufLen - at;
|
|
539
|
-
if (room <= 0) break;
|
|
540
|
-
mem.set(record.subarray(0, Math.min(room, record.length)), bufPtr + at);
|
|
541
|
-
at += Math.min(room, record.length);
|
|
542
|
-
if (record.length > room) break;
|
|
543
|
-
}
|
|
544
|
-
view.setUint32(usedPtr, at, true);
|
|
545
|
-
return E.SUCCESS;
|
|
546
|
-
}),
|
|
547
|
-
fd_renumber: () => E.NOTSUP,
|
|
548
|
-
fd_seek: guarded((fd, offset, whence, newPtr) => {
|
|
549
|
-
const e = entry(fd);
|
|
550
|
-
if (!e) return E.BADF;
|
|
551
|
-
if (e.kind === KIND_STDIO) return E.SPIPE;
|
|
552
|
-
if (e.kind !== KIND_FILE) return E.ISDIR;
|
|
553
|
-
const off = Number(offset);
|
|
554
|
-
let pos;
|
|
555
|
-
if (whence === WHENCE_SET) pos = off;
|
|
556
|
-
else if (whence === WHENCE_CUR) pos = table.positions[fd] + off;
|
|
557
|
-
else if (whence === WHENCE_END) pos = fs.fstatSync(e.osFd).size + off;
|
|
558
|
-
else return E.INVAL;
|
|
559
|
-
if (pos < 0) return E.INVAL;
|
|
560
|
-
table.positions[fd] = pos;
|
|
561
|
-
dv().setBigUint64(newPtr, BigInt(pos), true);
|
|
562
|
-
return E.SUCCESS;
|
|
563
|
-
}),
|
|
564
|
-
fd_tell: guarded((fd, ptr) => {
|
|
565
|
-
const e = entry(fd);
|
|
566
|
-
if (!e || e.kind !== KIND_FILE) return E.BADF;
|
|
567
|
-
dv().setBigUint64(ptr, BigInt(table.positions[fd]), true);
|
|
568
|
-
return E.SUCCESS;
|
|
569
|
-
}),
|
|
570
|
-
path_create_directory: guarded((fd, p, n) => {
|
|
571
|
-
const r = resolveAt(fd, p, n);
|
|
572
|
-
if (r.errno) return r.errno;
|
|
573
|
-
fs.mkdirSync(r.full);
|
|
574
|
-
return E.SUCCESS;
|
|
575
|
-
}),
|
|
576
|
-
path_filestat_get: guarded((fd, flags, p, n, ptr) => {
|
|
577
|
-
const r = resolveAt(fd, p, n);
|
|
578
|
-
if (r.errno) return r.errno;
|
|
579
|
-
const st = flags & LOOKUP_SYMLINK_FOLLOW
|
|
580
|
-
? fs.statSync(r.full, { bigint: true })
|
|
581
|
-
: fs.lstatSync(r.full, { bigint: true });
|
|
582
|
-
writeStat(ptr, st);
|
|
583
|
-
return E.SUCCESS;
|
|
584
|
-
}),
|
|
585
|
-
path_filestat_set_times: () => E.SUCCESS,
|
|
586
|
-
path_link: () => E.NOTSUP,
|
|
587
|
-
path_open: guarded((dirfd, dirflags, p, n, oflags, rightsBase, _rightsInh, fdflags, fdPtr) => {
|
|
588
|
-
const r = resolveAt(dirfd, p, n);
|
|
589
|
-
if (r.errno) return r.errno;
|
|
590
|
-
let st = null;
|
|
591
|
-
try {
|
|
592
|
-
st = fs.statSync(r.full);
|
|
593
|
-
} catch (error) {
|
|
594
|
-
if (error?.code !== "ENOENT" || !(oflags & OFLAG_CREAT)) throw error;
|
|
595
|
-
}
|
|
596
|
-
if (st && oflags & OFLAG_CREAT && oflags & OFLAG_EXCL) return E.EXIST;
|
|
597
|
-
if (oflags & OFLAG_DIRECTORY || (st && st.isDirectory())) {
|
|
598
|
-
if (st && !st.isDirectory()) return E.NOTDIR;
|
|
599
|
-
if (!st) return E.NOENT;
|
|
600
|
-
const fd = allocFd(KIND_DIR, -1, r.full, false);
|
|
601
|
-
if (fd < 0) return -fd;
|
|
602
|
-
dv().setUint32(fdPtr, fd, true);
|
|
603
|
-
return E.SUCCESS;
|
|
604
|
-
}
|
|
605
|
-
const rights = BigInt.asUintN(64, BigInt(rightsBase));
|
|
606
|
-
const read = (rights & RIGHT_FD_READ) !== 0n;
|
|
607
|
-
const write = (rights & RIGHT_FD_WRITE) !== 0n || (oflags & OFLAG_TRUNC) !== 0;
|
|
608
|
-
const c = fs.constants;
|
|
609
|
-
let mode = write ? (read ? c.O_RDWR : c.O_WRONLY) : c.O_RDONLY;
|
|
610
|
-
if (oflags & OFLAG_CREAT) mode |= c.O_CREAT;
|
|
611
|
-
if (oflags & OFLAG_EXCL) mode |= c.O_EXCL;
|
|
612
|
-
if (oflags & OFLAG_TRUNC) mode |= c.O_TRUNC;
|
|
613
|
-
const osFd = fs.openSync(r.full, mode, 0o644);
|
|
614
|
-
const fd = allocFd(KIND_FILE, osFd, r.full, (fdflags & FDFLAG_APPEND) !== 0);
|
|
615
|
-
if (fd < 0) {
|
|
616
|
-
fs.closeSync(osFd);
|
|
617
|
-
return -fd;
|
|
618
|
-
}
|
|
619
|
-
dv().setUint32(fdPtr, fd, true);
|
|
620
|
-
return E.SUCCESS;
|
|
621
|
-
}),
|
|
622
|
-
path_readlink: guarded((fd, p, n, buf, bufLen, usedPtr) => {
|
|
623
|
-
const r = resolveAt(fd, p, n);
|
|
624
|
-
if (r.errno) return r.errno;
|
|
625
|
-
const target = Buffer.from(fs.readlinkSync(r.full));
|
|
626
|
-
const count = Math.min(bufLen, target.length);
|
|
627
|
-
u8().set(target.subarray(0, count), buf);
|
|
628
|
-
dv().setUint32(usedPtr, count, true);
|
|
629
|
-
return E.SUCCESS;
|
|
630
|
-
}),
|
|
631
|
-
path_remove_directory: guarded((fd, p, n) => {
|
|
632
|
-
const r = resolveAt(fd, p, n);
|
|
633
|
-
if (r.errno) return r.errno;
|
|
634
|
-
fs.rmdirSync(r.full);
|
|
635
|
-
return E.SUCCESS;
|
|
636
|
-
}),
|
|
637
|
-
path_rename: guarded((fd, p, n, fd2, p2, n2) => {
|
|
638
|
-
const a = resolveAt(fd, p, n);
|
|
639
|
-
if (a.errno) return a.errno;
|
|
640
|
-
const b = resolveAt(fd2, p2, n2);
|
|
641
|
-
if (b.errno) return b.errno;
|
|
642
|
-
fs.renameSync(a.full, b.full);
|
|
643
|
-
return E.SUCCESS;
|
|
644
|
-
}),
|
|
645
|
-
path_symlink: () => E.NOTSUP,
|
|
646
|
-
path_unlink_file: guarded((fd, p, n) => {
|
|
647
|
-
const r = resolveAt(fd, p, n);
|
|
648
|
-
if (r.errno) return r.errno;
|
|
649
|
-
if (fs.lstatSync(r.full).isDirectory()) return E.ISDIR;
|
|
650
|
-
fs.unlinkSync(r.full);
|
|
651
|
-
return E.SUCCESS;
|
|
652
|
-
}),
|
|
653
|
-
poll_oneoff: guarded((inPtr, outPtr, nsubs, neventsPtr) => {
|
|
654
|
-
// Clock subscriptions sleep until the earliest deadline; descriptor
|
|
655
|
-
// subscriptions are always ready (the guest never polls real I/O).
|
|
656
|
-
const view = dv();
|
|
657
|
-
const now = monotonicNs();
|
|
658
|
-
let earliest = null;
|
|
659
|
-
const subs = [];
|
|
660
|
-
for (let i = 0; i < nsubs; i += 1) {
|
|
661
|
-
const s = inPtr + i * 48;
|
|
662
|
-
const userdata = view.getBigUint64(s, true);
|
|
663
|
-
const tag = view.getUint8(s + 8);
|
|
664
|
-
if (tag === 0) {
|
|
665
|
-
const clock = view.getUint32(s + 16, true);
|
|
666
|
-
const timeout = view.getBigUint64(s + 24, true);
|
|
667
|
-
const absolute = view.getUint16(s + 40, true) & 1;
|
|
668
|
-
let deadline;
|
|
669
|
-
if (absolute) deadline = clock === CLOCK_REALTIME ? now + (timeout - realtimeNs()) : timeout;
|
|
670
|
-
else deadline = now + timeout;
|
|
671
|
-
subs.push({ userdata, tag, deadline });
|
|
672
|
-
if (earliest === null || deadline < earliest) earliest = deadline;
|
|
673
|
-
} else {
|
|
674
|
-
subs.push({ userdata, tag, deadline: now });
|
|
675
|
-
earliest = now;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
if (earliest !== null && earliest > now) {
|
|
679
|
-
const ms = Number(earliest - now) / 1e6;
|
|
680
|
-
Atomics.wait(sleeper, 0, 0, ms);
|
|
681
|
-
}
|
|
682
|
-
const after = monotonicNs();
|
|
683
|
-
let n = 0;
|
|
684
|
-
for (const sub of subs) {
|
|
685
|
-
if (sub.deadline > after) continue;
|
|
686
|
-
const o = outPtr + n * 32;
|
|
687
|
-
view.setBigUint64(o, sub.userdata, true);
|
|
688
|
-
view.setUint16(o + 8, 0, true);
|
|
689
|
-
view.setUint8(o + 10, sub.tag);
|
|
690
|
-
view.setBigUint64(o + 16, 0n, true);
|
|
691
|
-
view.setUint16(o + 24, 0, true);
|
|
692
|
-
n += 1;
|
|
693
|
-
}
|
|
694
|
-
view.setUint32(neventsPtr, n, true);
|
|
695
|
-
return E.SUCCESS;
|
|
696
|
-
}),
|
|
697
|
-
proc_exit(code) {
|
|
698
|
-
// Any thread's exit ends the whole guest: the supervisor sees the word.
|
|
699
|
-
if (Atomics.compareExchange(control, CONTROL_EXITED, 0, 1) === 0) {
|
|
700
|
-
Atomics.store(control, CONTROL_EXIT_CODE, code);
|
|
701
|
-
}
|
|
702
|
-
Atomics.notify(control, CONTROL_EXITED);
|
|
703
|
-
throw new WasiExitError(code);
|
|
704
|
-
},
|
|
705
|
-
proc_raise: () => E.NOSYS,
|
|
706
|
-
random_get(ptr, len) {
|
|
707
|
-
const out = new Uint8Array(len);
|
|
708
|
-
if (rngState) {
|
|
709
|
-
// Deterministic mode: one process-wide xorshift64* stream.
|
|
710
|
-
for (let i = 0; i < len; i += 8) {
|
|
711
|
-
let x;
|
|
712
|
-
for (;;) {
|
|
713
|
-
const old = Atomics.load(rngState, 0);
|
|
714
|
-
x = old;
|
|
715
|
-
x ^= x >> 12n;
|
|
716
|
-
x ^= (x << 25n) & 0xffffffffffffffffn;
|
|
717
|
-
x ^= x >> 27n;
|
|
718
|
-
if (Atomics.compareExchange(rngState, 0, old, x) === old) break;
|
|
719
|
-
}
|
|
720
|
-
let v = (x * 0x2545f4914f6cdd1dn) & 0xffffffffffffffffn;
|
|
721
|
-
for (let k = 0; k < 8 && i + k < len; k += 1) {
|
|
722
|
-
out[i + k] = Number(v & 0xffn);
|
|
723
|
-
v >>= 8n;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
} else {
|
|
727
|
-
randomFillSync(out);
|
|
728
|
-
}
|
|
729
|
-
u8().set(out, ptr);
|
|
730
|
-
return E.SUCCESS;
|
|
731
|
-
},
|
|
732
|
-
sched_yield: () => E.SUCCESS,
|
|
733
|
-
sock_accept: () => E.NOSYS,
|
|
734
|
-
sock_recv: () => E.NOSYS,
|
|
735
|
-
sock_send: () => E.NOSYS,
|
|
736
|
-
sock_shutdown: () => E.NOSYS,
|
|
737
|
-
};
|
|
738
|
-
return wasi;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// ---- flatsql_io -------------------------------------------------------------------
|
|
742
|
-
export function createFlatsqlIo(cfg, getMemory) {
|
|
743
|
-
const interpose = cfg.fault ? createFaultInterposer(cfg.fault, cfg.ioRoot) : undefined;
|
|
744
|
-
const provider = createNodeSyncFsIo({
|
|
745
|
-
root: cfg.ioRoot,
|
|
746
|
-
table: cfg.ioTable,
|
|
747
|
-
instanceId: cfg.instanceId,
|
|
748
|
-
getMemory,
|
|
749
|
-
interpose,
|
|
750
|
-
});
|
|
751
|
-
return {
|
|
752
|
-
imports: createFlatsqlIoImports({ getMemory, provider }),
|
|
753
|
-
close: () => provider.closeLocalFds(),
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
/**
|
|
758
|
-
* The complete import fragment for one guest thread (without wasi.thread-spawn
|
|
759
|
-
* and env.memory, which the caller adds).
|
|
760
|
-
*/
|
|
761
|
-
export function createThreadImports(cfg, getMemory) {
|
|
762
|
-
const wasi = createWasi(cfg, getMemory);
|
|
763
|
-
const io = createFlatsqlIo(cfg, getMemory);
|
|
764
|
-
return {
|
|
765
|
-
imports: { wasi_snapshot_preview1: wasi, env: { ...io.imports.env } },
|
|
766
|
-
close: io.close,
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
export { registerThread };
|
|
771
|
-
|
|
772
|
-
// ---- thread pool ------------------------------------------------------------------
|
|
773
|
-
// Int32 words: 0 cap, 1 workers created, 2 workers wanted, 3 next tid,
|
|
774
|
-
// 4 running guest threads, 5 max running, 6 spawned, 7 declined,
|
|
775
|
-
// 8 pool generation (bumped whenever a worker becomes idle); then per slot
|
|
776
|
-
// POOL_SLOT_WORDS: state, tid, start argument, worker threadId.
|
|
777
|
-
export const POOL_CAP = 0;
|
|
778
|
-
export const POOL_CREATED = 1;
|
|
779
|
-
export const POOL_WANT = 2;
|
|
780
|
-
export const POOL_NEXT_TID = 3;
|
|
781
|
-
export const POOL_RUNNING = 4;
|
|
782
|
-
export const POOL_MAX_RUNNING = 5;
|
|
783
|
-
export const POOL_SPAWNED = 6;
|
|
784
|
-
export const POOL_DECLINED = 7;
|
|
785
|
-
export const POOL_GEN = 8;
|
|
786
|
-
export const POOL_HEADER = 16;
|
|
787
|
-
export const POOL_SLOT_WORDS = 4;
|
|
788
|
-
export const SLOT_EMPTY = 0;
|
|
789
|
-
export const SLOT_STARTING = 1;
|
|
790
|
-
export const SLOT_IDLE = 2;
|
|
791
|
-
export const SLOT_CLAIMED = 3;
|
|
792
|
-
export const SLOT_ASSIGNED = 4;
|
|
793
|
-
export const SLOT_RUNNING = 5;
|
|
794
|
-
export const SLOT_DEAD = 6;
|
|
795
|
-
// wasi-libc keeps a thread id in the low 30 bits of a mutex word.
|
|
796
|
-
const MAX_TID = (1 << 29) - 1;
|
|
797
|
-
|
|
798
|
-
export function createThreadPool(cap) {
|
|
799
|
-
const buffer = new SharedArrayBuffer((POOL_HEADER + cap * POOL_SLOT_WORDS) * 4);
|
|
800
|
-
const w = new Int32Array(buffer);
|
|
801
|
-
w[POOL_CAP] = cap;
|
|
802
|
-
w[POOL_NEXT_TID] = 1;
|
|
803
|
-
return buffer;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
/**
|
|
807
|
-
* wasi.thread-spawn over the pool: returns the new thread's tid, or -6
|
|
808
|
-
* (EAGAIN: pthread_create fails) when no worker frees up within waitMs and
|
|
809
|
-
* the pool is at its cap.
|
|
810
|
-
*/
|
|
811
|
-
export function poolThreadSpawn(poolBuffer, waitMs = 60000) {
|
|
812
|
-
const w = new Int32Array(poolBuffer);
|
|
813
|
-
const cap = w[POOL_CAP];
|
|
814
|
-
return (startArg) => {
|
|
815
|
-
const tid = Atomics.add(w, POOL_NEXT_TID, 1);
|
|
816
|
-
if (tid > MAX_TID) return -6;
|
|
817
|
-
const deadline = performance.now() + waitMs;
|
|
818
|
-
let askedAt = -Infinity;
|
|
819
|
-
for (;;) {
|
|
820
|
-
const created = Math.min(Atomics.load(w, POOL_CREATED), cap);
|
|
821
|
-
for (let i = 0; i < created; i += 1) {
|
|
822
|
-
const at = POOL_HEADER + i * POOL_SLOT_WORDS;
|
|
823
|
-
if (Atomics.compareExchange(w, at, SLOT_IDLE, SLOT_CLAIMED) !== SLOT_IDLE) continue;
|
|
824
|
-
Atomics.store(w, at + 1, tid);
|
|
825
|
-
Atomics.store(w, at + 2, startArg);
|
|
826
|
-
Atomics.store(w, at, SLOT_ASSIGNED);
|
|
827
|
-
Atomics.notify(w, at);
|
|
828
|
-
Atomics.add(w, POOL_SPAWNED, 1);
|
|
829
|
-
return tid;
|
|
830
|
-
}
|
|
831
|
-
const now = performance.now();
|
|
832
|
-
if (created < cap && now - askedAt > 50) {
|
|
833
|
-
// Ask the supervisor (whose event loop is free) for one more worker.
|
|
834
|
-
Atomics.add(w, POOL_WANT, 1);
|
|
835
|
-
Atomics.notify(w, POOL_WANT);
|
|
836
|
-
askedAt = now;
|
|
837
|
-
}
|
|
838
|
-
if (now > deadline) {
|
|
839
|
-
Atomics.add(w, POOL_DECLINED, 1);
|
|
840
|
-
return -6;
|
|
841
|
-
}
|
|
842
|
-
const gen = Atomics.load(w, POOL_GEN);
|
|
843
|
-
Atomics.wait(w, POOL_GEN, gen, 5);
|
|
844
|
-
}
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
|