@amritk/nish-x86_64-linux 0.15.0 → 0.17.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/INSTALL.md +58 -40
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +67 -10
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +72 -26
- package/runtime/runtime-net.c +72 -19
- package/runtime/runtime-os.c +127 -45
- package/runtime/runtime-parallel.c +5 -1
- package/runtime/runtime-wasm.c +26 -2
- package/runtime/runtime.c +86 -14
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +192 -36
- package/std/crypto/aes.ts +104 -21
- package/std/crypto/base64url.ts +43 -17
- package/std/crypto/chacha20poly1305.ts +52 -31
- package/std/crypto/ct.ts +14 -3
- package/std/crypto/hkdf.ts +385 -8
- package/std/crypto/hmac.ts +325 -9
- package/std/crypto/p256.ts +153 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +15 -3
- package/std/crypto/sha512.ts +16 -2
- package/std/crypto/x25519.ts +90 -37
- package/std/crypto/x509.ts +265 -65
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +905 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +392 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +653 -0
- package/std/net/quic-packet.ts +1474 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1603 -0
- package/std/net/quic.ts +3214 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +410 -0
- package/std/net/tls.ts +1368 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
package/INSTALL.md
CHANGED
|
@@ -61,8 +61,9 @@ The `wasi` profile links the runtime against wasi-libc so that whole programs
|
|
|
61
61
|
needs a WASI sysroot and compiler-rt's wasm32 builtins next to your clang:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
# Ubuntu / Debian: the packaged sysroot lands in /usr/lib/wasi
|
|
64
|
+
# Ubuntu / Debian: the packaged sysroot lands in /usr (/usr/include/wasm32-wasi, /usr/lib/wasm32-wasi)
|
|
65
65
|
sudo apt-get install -y wasi-libc libclang-rt-18-dev-wasm32
|
|
66
|
+
export WASI_SYSROOT=/usr # add to your shell profile
|
|
66
67
|
|
|
67
68
|
# Any platform: wasi-sdk's sysroot and builtins tarballs (versions that match your clang)
|
|
68
69
|
curl -L -o - https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sysroot-24.0.tar.gz | tar -xz -C /opt
|
|
@@ -74,7 +75,11 @@ export WASI_SYSROOT=/opt/wasi-sysroot-24.0 # add to your shell profile
|
|
|
74
75
|
`/usr/lib/wasi-sysroot`, `/opt/wasi-sdk/share/wasi-sysroot` and
|
|
75
76
|
`/usr/share/wasi-sysroot`, and for `libclang_rt.builtins-wasm32.a` in clang's
|
|
76
77
|
resource directory, next to the sysroot (as the tarball above unpacks it),
|
|
77
|
-
in `<sysroot>/lib/wasm32-wasi/`, or at `WASI_BUILTINS=<file>`.
|
|
78
|
+
in `<sysroot>/lib/wasm32-wasi/`, or at `WASI_BUILTINS=<file>`.
|
|
79
|
+
|
|
80
|
+
The packaged wasi-libc predates wasi-libc's `realpath`, so a module linked
|
|
81
|
+
against it answers `realpathSync` with `null` for every path. Use wasi-sdk's
|
|
82
|
+
sysroot (22 or later) when a program needs `realpathSync` to resolve. Then:
|
|
78
83
|
|
|
79
84
|
```bash
|
|
80
85
|
nish examples/argv.ts --link build/argv.wasm --profile wasi
|
|
@@ -218,7 +223,8 @@ routes are
|
|
|
218
223
|
# in the glibc container, where build/nish runs
|
|
219
224
|
build/nish app.ts -o app.ll
|
|
220
225
|
# on the musl host, with its own clang and the runtime from this repository
|
|
221
|
-
clang app.ll runtime/runtime.c runtime/runtime-os.c -
|
|
226
|
+
clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
|
|
227
|
+
runtime/runtime-host.c runtime/runtime-net.c -lm -o app
|
|
222
228
|
```
|
|
223
229
|
|
|
224
230
|
`--link`ing inside the container is the mistake to avoid: it shells out to the
|
|
@@ -285,21 +291,20 @@ to the machine that built it.
|
|
|
285
291
|
|
|
286
292
|
The last column is there because a release is a past event and a workflow is
|
|
287
293
|
not. `release.yml` builds all four, but a release already published cannot grow
|
|
288
|
-
an asset: **v0.
|
|
294
|
+
an asset: **the releases before v0.4.0 attach `x86_64-linux` only**. Those
|
|
289
295
|
versions are not prose — they are `attachedSince` in
|
|
290
296
|
[`.github/seed-targets.json`](https://github.com/amritk/nish/blob/main/.github/seed-targets.json),
|
|
291
297
|
the same file the release workflow builds its matrix from, so this table and
|
|
292
298
|
the assets cannot drift apart without a test failing.
|
|
293
299
|
|
|
294
|
-
**The three
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
than a green tick. `scripts/verify-binaries.sh`'s header is the long version.
|
|
300
|
+
**The three newer rows say v0.4.0 rather than v0.3.0 on purpose.** Nothing in
|
|
301
|
+
this repository had run on macOS or on ARM Linux before, and a row that has
|
|
302
|
+
never run is not something to put in front of a release: `release` needs the
|
|
303
|
+
whole matrix, so one red row blocks the publish. So they waited until a run on
|
|
304
|
+
their own hardware had exercised them. The two macOS rows waited on a second
|
|
305
|
+
thing as well: the bootstrap's `stage3 == stage2` check did not hold as a raw
|
|
306
|
+
byte comparison under `ld64` until both stages were linked at one output path.
|
|
307
|
+
`scripts/verify-binaries.sh`'s header is the long version.
|
|
303
308
|
|
|
304
309
|
Check [the releases page](https://github.com/amritk/nish/releases/latest) for
|
|
305
310
|
what a given version actually carries; on a platform whose row has not shipped
|
|
@@ -307,15 +312,15 @@ yet, take the npm package above, or build from source below.
|
|
|
307
312
|
|
|
308
313
|
```bash
|
|
309
314
|
# pick the row above that matches `uname -s` and `uname -m`
|
|
310
|
-
curl -LO https://github.com/amritk/nish/releases/download/v0.
|
|
311
|
-
tar -xzf nish-0.
|
|
312
|
-
nish-0.
|
|
315
|
+
curl -LO https://github.com/amritk/nish/releases/download/v0.16.0/nish-0.16.0-x86_64-linux.tar.gz
|
|
316
|
+
tar -xzf nish-0.16.0-x86_64-linux.tar.gz
|
|
317
|
+
nish-0.16.0-x86_64-linux/bin/nish --version
|
|
313
318
|
```
|
|
314
319
|
|
|
315
320
|
Both URLs name the version rather than using GitHub's version-neutral
|
|
316
321
|
`/releases/latest/download/` form, because that form needs the asset's exact
|
|
317
|
-
file name and every asset name carries the version in it.
|
|
318
|
-
|
|
322
|
+
file name and every asset name carries the version in it. The example names
|
|
323
|
+
v0.16.0; v0.1.1 was the first release with a binary attached.
|
|
319
324
|
The `v0.1.0` tag exists but has no release behind it and no assets, so every
|
|
320
325
|
`v0.1.0` download URL is a 404 — the tag was pushed by a workflow, and GitHub
|
|
321
326
|
raises no event for that, so nothing ever built it
|
|
@@ -333,8 +338,7 @@ find `scripts/build.sh`.
|
|
|
333
338
|
|
|
334
339
|
It still needs `clang` and `lld` on `PATH` for `--link` (§1), because linking
|
|
335
340
|
is the C toolchain's job, not the compiler's; what it does not need is Node.
|
|
336
|
-
|
|
337
|
-
`.tgz` above or build from a checkout.
|
|
341
|
+
On a platform outside the table, build from a checkout.
|
|
338
342
|
|
|
339
343
|
From a checkout:
|
|
340
344
|
|
|
@@ -354,7 +358,7 @@ it into `build/seed/`; `NISH_BOOTSTRAP=<path>` names one you already have and
|
|
|
354
358
|
skips the download. §2a has what the build does with it.
|
|
355
359
|
|
|
356
360
|
The published package ships `bin/` (the `nish` command, which is a launcher),
|
|
357
|
-
`runtime/` (the C runtime —
|
|
361
|
+
`runtime/` (the C runtime — five translation units and their header),
|
|
358
362
|
`scripts/build.sh` (the link pipeline) and `std/` (the standard library). The
|
|
359
363
|
compiler itself is not in it: it arrives as one prebuilt
|
|
360
364
|
`@amritk/nish-<asset>` package per platform, and `bin/nish` hands over to
|
|
@@ -364,22 +368,27 @@ working directory — and the binary runs from inside its own platform package
|
|
|
364
368
|
for exactly that reason.
|
|
365
369
|
|
|
366
370
|
Building the IR yourself rather than through `--link` means naming the runtime
|
|
367
|
-
on the `clang` line, and it is
|
|
371
|
+
on the `clang` line, and it is five files:
|
|
368
372
|
|
|
369
373
|
```bash
|
|
370
|
-
clang app.ll runtime/runtime.c runtime/runtime-os.c -
|
|
374
|
+
clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
|
|
375
|
+
runtime/runtime-host.c runtime/runtime-net.c -lm -o app
|
|
371
376
|
```
|
|
372
377
|
|
|
373
|
-
`runtime.c` is the
|
|
374
|
-
number formatting, the panics — and `runtime-os.c`
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
378
|
+
`runtime.c` is the part every program touches — the arena, strings, arrays,
|
|
379
|
+
number formatting, the panics — and `runtime-os.c` wraps the system calls:
|
|
380
|
+
files, directories, subprocesses, `getenv`, the monotonic clock.
|
|
381
|
+
`runtime-parallel.c` divides work across threads for `nish/threads` (a
|
|
382
|
+
program built with `--threads`, as every importer of `nish/threads` is, also
|
|
383
|
+
needs `-DNISH_THREADS=1 -pthread` on the line), `runtime-host.c` holds the
|
|
384
|
+
wall clock, entropy, file times and signals, and `runtime-net.c` the sockets
|
|
385
|
+
of `nish:net`. They are separate so that each
|
|
386
|
+
carries its own measured size ceiling ([docs/wp7-runtime.md](wp7-runtime.md));
|
|
387
|
+
nothing in the core calls into the others, so a line that names only
|
|
388
|
+
`runtime.c` and `runtime-os.c` still links a program that uses none of the
|
|
389
|
+
other three. `scripts/build.sh` compiles the other four beside any
|
|
390
|
+
`runtime.c` it is handed, so a build that goes through it — every `--link`,
|
|
391
|
+
and every `--profile` recipe in these documents — needs to name only the one.
|
|
383
392
|
|
|
384
393
|
## 2a. What `npm run build` does
|
|
385
394
|
|
|
@@ -402,9 +411,16 @@ release is expected to move it. `--stages 1` stops one link sooner.
|
|
|
402
411
|
`build/nish` is then the compiler you run: it takes the same `-o`, `--link`
|
|
403
412
|
and `--profile` spellings as an installed `nish`, because it is the same
|
|
404
413
|
program, and makes every directory in the way of the IR, a sidecar or the
|
|
405
|
-
binary itself. It looks for `scripts/build.sh` and the
|
|
406
|
-
one level up from wherever it was invoked
|
|
407
|
-
it wants a checkout or an installed package
|
|
414
|
+
binary itself. It looks for `scripts/build.sh` and the `runtime/*.c` files
|
|
415
|
+
one level up from wherever it was invoked (and one level up from the file a
|
|
416
|
+
symbolic link to it names), so it wants a checkout or an installed package
|
|
417
|
+
around it the way `nish` does. It falls back to the working directory only
|
|
418
|
+
when the compiler itself lies inside that directory, so a checkout you merely
|
|
419
|
+
`cd` into cannot hand it a `scripts/build.sh` to run. Keep `bin/nish` beside
|
|
420
|
+
`scripts/`, `runtime/` and `std/`, as every install above does, and keep that
|
|
421
|
+
directory's parent writable only by you: the package root is the parent of
|
|
422
|
+
the compiler's directory, so a compiler at `/tmp/x/nish` would trust
|
|
423
|
+
`/tmp/scripts/build.sh` (CLI-9 in [docs/security/cli.md](security/cli.md)).
|
|
408
424
|
|
|
409
425
|
Because the seed is the last release, `src/` may only *use* in its own
|
|
410
426
|
source the constructs that release compiles. A new construct is implemented
|
|
@@ -457,9 +473,11 @@ nish run hello.ts
|
|
|
457
473
|
`$XDG_CACHE_HOME/nish/run` (or `~/.cache/nish/run`) when that program has not
|
|
458
474
|
been linked before, and starts it with the arguments after the file. The first
|
|
459
475
|
run of a new edit pays for one link, and later runs start the cached binary.
|
|
460
|
-
It needs clang only for that link. It needs `HOME` or `XDG_CACHE_HOME` set
|
|
461
|
-
|
|
462
|
-
directory.
|
|
476
|
+
It needs clang only for that link. It needs `HOME` or `XDG_CACHE_HOME` set
|
|
477
|
+
to an absolute path (a relative one is ignored, as if it were unset), and
|
|
478
|
+
refuses the run without one rather than keep a binary in a shared directory.
|
|
479
|
+
The first run that builds into the cache makes its root `0700`, so no other
|
|
480
|
+
user can plant a binary in it ([docs/security/cli.md](security/cli.md)).
|
|
463
481
|
|
|
464
482
|
## 4. Exit codes
|
|
465
483
|
|
|
@@ -469,7 +487,7 @@ directory.
|
|
|
469
487
|
| 1 | the program was rejected: compile error (`file:line:col: error: ...`), missing input file, or an `-o` layout that does not fit the module count |
|
|
470
488
|
| 2 | usage error: unknown flag, missing argument, no input files |
|
|
471
489
|
| 3 | toolchain error: `--link` found no `clang` (`CC` overrides), or `scripts/build.sh` failed (its output is shown; the `.ll` files are still written) — or the `nish` command found no prebuilt compiler for this platform, or one that would not start. Under `--json` all of these are one `NL0002` object |
|
|
472
|
-
| | `nish run` answers these codes until the program starts, and the program's own exit status (`128 + n` for a signal) after that. With neither `HOME` nor `XDG_CACHE_HOME` set it answers 3 |
|
|
490
|
+
| | `nish run` answers these codes until the program starts, and the program's own exit status (`128 + n` for a signal) after that. With neither `HOME` nor `XDG_CACHE_HOME` set to an absolute path it answers 3 |
|
|
473
491
|
| 70 | internal compiler error: an unexpected exception. Please report it at <https://github.com/amritk/nish/issues> with the input and command line; `NISH_DEBUG=1` prints the stack trace |
|
|
474
492
|
|
|
475
493
|
## Troubleshooting
|
package/bin/nish
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/runtime/nish.d.ts
CHANGED
|
@@ -112,9 +112,10 @@ declare function Err<T, E>(error: E): Result<T, E>;
|
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* The disposable protocol `using` reads (WP29 P2, docs/wp29-thread-surface.md
|
|
115
|
-
* §5): declared here so that a program using `nish/threads`'s scope
|
|
116
|
-
* `"ESNext.Disposable"` in its `lib`. `nish` itself takes
|
|
117
|
-
* `scope()`, and `[Symbol.dispose]` only in
|
|
115
|
+
* §5): declared here so that a program using `nish/threads`'s scope or
|
|
116
|
+
* `arena()` needs no `"ESNext.Disposable"` in its `lib`. `nish` itself takes
|
|
117
|
+
* `using` only for a `scope()` or an `arena()`, and `[Symbol.dispose]` only in
|
|
118
|
+
* `nish/threads`.
|
|
118
119
|
*/
|
|
119
120
|
interface SymbolConstructor {
|
|
120
121
|
readonly dispose: unique symbol;
|
|
@@ -172,6 +173,39 @@ declare function bitsToF64(bits: i64): f64;
|
|
|
172
173
|
declare function ctSelect<T extends u32 | u64>(mask: T, a: T, b: T): T;
|
|
173
174
|
/** All-ones of the operands' type when `a === b`, zero otherwise, without a branch. */
|
|
174
175
|
declare function ctEq<T extends u32 | u64>(a: T, b: T): T;
|
|
176
|
+
/**
|
|
177
|
+
* Every byte of `bytes` set to zero, in stores neither `-O2` nor `-flto` may
|
|
178
|
+
* drop as dead (#385): how a key, a secret scalar or another secret is cleared
|
|
179
|
+
* once it is used. A statement.
|
|
180
|
+
*/
|
|
181
|
+
declare function secureZero(bytes: u8[]): void;
|
|
182
|
+
/**
|
|
183
|
+
* One `lstat` of `path`, so a symbolic link answers for itself: the owner's uid
|
|
184
|
+
* in the high 32 bits and `st_mode` in the low 32, or -1 when the path does not
|
|
185
|
+
* resolve (#386).
|
|
186
|
+
*/
|
|
187
|
+
declare function lstatOwnerModeSync(path: string): i64;
|
|
188
|
+
/** The effective user id, the one to compare an owner from `lstatOwnerModeSync` with. */
|
|
189
|
+
declare function geteuid(): i64;
|
|
190
|
+
/** Whether the real user may run `path`: `access(path, X_OK)`. */
|
|
191
|
+
declare function isExecutableSync(path: string): boolean;
|
|
192
|
+
|
|
193
|
+
// ---- Unsafe (`nish:unsafe`) --------------------------------------------------
|
|
194
|
+
//
|
|
195
|
+
// Declared as globals so `tsc` reads a program that imports them, as every
|
|
196
|
+
// `nish:` export is, but `nish` refuses a call to any of the five that is not
|
|
197
|
+
// imported from `nish:unsafe`: the import is the module's visible opt-in.
|
|
198
|
+
|
|
199
|
+
/** `xs[i]` with no bounds check: undefined behaviour when `i` is out of range. */
|
|
200
|
+
declare function uncheckedGet<T extends number>(xs: readonly T[], i: i32): T;
|
|
201
|
+
/** `xs[i] = v` with no bounds check: undefined behaviour when `i` is out of range. */
|
|
202
|
+
declare function uncheckedSet<T extends number>(xs: T[], i: i32, v: T): void;
|
|
203
|
+
/** `a + b` wrapped to the operands' width, `i32` or `i64`: defined for every pair. */
|
|
204
|
+
declare function wrappingAdd<T extends i32 | i64>(a: T, b: T): T;
|
|
205
|
+
/** `a - b` wrapped to the operands' width. */
|
|
206
|
+
declare function wrappingSub<T extends i32 | i64>(a: T, b: T): T;
|
|
207
|
+
/** `a * b` wrapped to the operands' width. */
|
|
208
|
+
declare function wrappingMul<T extends i32 | i64>(a: T, b: T): T;
|
|
175
209
|
|
|
176
210
|
// ---- Streams and files (globals: Nish has no package resolution) ---------
|
|
177
211
|
|
|
@@ -200,7 +234,14 @@ declare function readFileSyncOrNull(path: string): string | null;
|
|
|
200
234
|
* `readFileSyncOrNull` answers `null` for.
|
|
201
235
|
*/
|
|
202
236
|
declare function readFileBytesSync(path: string): u8[] | null;
|
|
237
|
+
/**
|
|
238
|
+
* The file's contents replaced by `data`, created at 0644 when missing. A
|
|
239
|
+
* symbolic link as the last component of the path is refused rather than
|
|
240
|
+
* followed, natively and under Node alike, and a path that cannot be written
|
|
241
|
+
* prints `nish: cannot write <path>` and exits 1.
|
|
242
|
+
*/
|
|
203
243
|
declare function writeFileSync(path: string, data: string): void;
|
|
244
|
+
/** `data` added at the end of the file, with the same refusal of a symbolic link. */
|
|
204
245
|
declare function appendFileSync(path: string, data: string): void;
|
|
205
246
|
/** One directory, not recursive; whether a directory is there afterwards. */
|
|
206
247
|
declare function mkdirSync(path: string): boolean;
|
|
@@ -221,7 +262,8 @@ declare function realpathSync(path: string): string | null;
|
|
|
221
262
|
declare function spawnSync(argv: string[]): number;
|
|
222
263
|
/**
|
|
223
264
|
* The same run with each non-empty path receiving that stream, created or
|
|
224
|
-
* truncated; an empty string leaves that stream inherited.
|
|
265
|
+
* truncated; an empty string leaves that stream inherited. A symbolic link at
|
|
266
|
+
* either path is refused, as `writeFileSync` refuses one, and the answer is -1.
|
|
225
267
|
*/
|
|
226
268
|
declare function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
|
|
227
269
|
/** One environment variable, or `null` when it is unset (an empty value is a set variable). */
|
|
@@ -252,9 +294,10 @@ declare function readSignal(fd: i32): i32;
|
|
|
252
294
|
//
|
|
253
295
|
// Every call answers an `i32`: `>= 0` on success, a negative errno otherwise, in
|
|
254
296
|
// Linux's numbering on every platform for -11 (would block), -95, -32, -104,
|
|
255
|
-
// -98 and -22. An address is 18 bytes of a
|
|
256
|
-
// `::ffff:a.b.c.d`), then the port,
|
|
257
|
-
// close-on-exec. No reading under
|
|
297
|
+
// -98, -111 (refused), -110 (timed out) and -22. An address is 18 bytes of a
|
|
298
|
+
// `u8[]`: 16 of IPv6 address (IPv4 as `::ffff:a.b.c.d`), then the port,
|
|
299
|
+
// big-endian. Every socket is non-blocking and close-on-exec. No reading under
|
|
300
|
+
// Node, where each throws.
|
|
258
301
|
|
|
259
302
|
/** Write the address form of a numeric `host` and `port` into `out`: 0, or -22. */
|
|
260
303
|
declare function netAddress(out: u8[], host: string, port: i32): i32;
|
|
@@ -272,6 +315,10 @@ declare function netWrite(fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
|
|
|
272
315
|
declare function netShutdown(fd: i32, how: i32): i32;
|
|
273
316
|
/** Close the descriptor. */
|
|
274
317
|
declare function netClose(fd: i32): i32;
|
|
318
|
+
/** A TCP socket connecting to `addr`, answered while it connects: writable once it has, or has failed. */
|
|
319
|
+
declare function tcpConnect(addr: readonly u8[]): i32;
|
|
320
|
+
/** Once `fd` is writable: 0 when the connection is made, else its failure (-111 refused). */
|
|
321
|
+
declare function connectResult(fd: i32): i32;
|
|
275
322
|
/** A UDP socket: flag 1 `SO_REUSEPORT`, flag 2 `UDP_GRO` (-95 on Darwin); ECN is always read. */
|
|
276
323
|
declare function udpBind(host: string, port: i32, flags: i32): i32;
|
|
277
324
|
/** `buf[off, off + len)` to `to`, cut into `segment`-byte datagrams when `segment > 0`, with ECN bits `ecn`. */
|
|
@@ -325,7 +372,9 @@ declare module "nish:fs" {
|
|
|
325
372
|
export function readFileSync(path: string): string;
|
|
326
373
|
export function readFileSyncOrNull(path: string): string | null;
|
|
327
374
|
export function readFileBytesSync(path: string): u8[] | null;
|
|
375
|
+
/** Refuses a symbolic link as the last component of the path, as the global does. */
|
|
328
376
|
export function writeFileSync(path: string, data: string): void;
|
|
377
|
+
/** Refuses a symbolic link as the last component of the path, as the global does. */
|
|
329
378
|
export function appendFileSync(path: string, data: string): void;
|
|
330
379
|
/** `true` when the directory was created, `false` when it already existed. */
|
|
331
380
|
export function mkdirSync(path: string): boolean;
|
|
@@ -347,7 +396,8 @@ declare module "nish:process" {
|
|
|
347
396
|
export function spawnSync(argv: string[]): number;
|
|
348
397
|
/**
|
|
349
398
|
* The same run with each non-empty path receiving that stream, created or
|
|
350
|
-
* truncated; an empty string leaves that stream inherited.
|
|
399
|
+
* truncated; an empty string leaves that stream inherited. A symbolic link
|
|
400
|
+
* at either path is refused, and the answer is -1.
|
|
351
401
|
*/
|
|
352
402
|
export function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
|
|
353
403
|
/**
|
|
@@ -383,6 +433,10 @@ declare module "nish:net" {
|
|
|
383
433
|
/** Shut the read side (0), the write side (1) or both (2). */
|
|
384
434
|
export function netShutdown(fd: i32, how: i32): i32;
|
|
385
435
|
export function netClose(fd: i32): i32;
|
|
436
|
+
/** A TCP socket connecting to `addr`, answered while it connects: writable once it has, or has failed. */
|
|
437
|
+
export function tcpConnect(addr: readonly u8[]): i32;
|
|
438
|
+
/** Once `fd` is writable: 0 when the connection is made, else its failure (-111 refused). */
|
|
439
|
+
export function connectResult(fd: i32): i32;
|
|
386
440
|
/** A UDP socket: flag 1 `SO_REUSEPORT`, flag 2 `UDP_GRO` (-95 on Darwin); ECN is always read. */
|
|
387
441
|
export function udpBind(host: string, port: i32, flags: i32): i32;
|
|
388
442
|
/** `buf[off, off + len)` to `to`, cut into `segment`-byte datagrams when `segment > 0`, with ECN bits `ecn`. */
|
|
@@ -409,6 +463,19 @@ declare module "nish:net" {
|
|
|
409
463
|
export function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
|
|
410
464
|
}
|
|
411
465
|
|
|
466
|
+
declare module "nish:unsafe" {
|
|
467
|
+
/** `xs[i]` with no bounds check: undefined behaviour when `i` is out of range. */
|
|
468
|
+
export function uncheckedGet<T extends number>(xs: readonly T[], i: i32): T;
|
|
469
|
+
/** `xs[i] = v` with no bounds check: undefined behaviour when `i` is out of range. */
|
|
470
|
+
export function uncheckedSet<T extends number>(xs: T[], i: i32, v: T): void;
|
|
471
|
+
/** `a + b` wrapped to the operands' width, `i32` or `i64`. */
|
|
472
|
+
export function wrappingAdd<T extends i32 | i64>(a: T, b: T): T;
|
|
473
|
+
/** `a - b` wrapped to the operands' width. */
|
|
474
|
+
export function wrappingSub<T extends i32 | i64>(a: T, b: T): T;
|
|
475
|
+
/** `a * b` wrapped to the operands' width. */
|
|
476
|
+
export function wrappingMul<T extends i32 | i64>(a: T, b: T): T;
|
|
477
|
+
}
|
|
478
|
+
|
|
412
479
|
declare module "nish:io" {
|
|
413
480
|
/** `s` to stdout with no trailing newline and no conversion. */
|
|
414
481
|
export function write(s: string): void;
|
|
@@ -418,6 +485,39 @@ declare module "nish:io" {
|
|
|
418
485
|
export function panic(message: string): never;
|
|
419
486
|
}
|
|
420
487
|
|
|
488
|
+
// `nish:secret` has source behind it, `std/secret.ts`, but a program imports it
|
|
489
|
+
// by this name, so `tsc` reads these declarations rather than that file. The
|
|
490
|
+
// class is declared with a private constructor and a private field: `tsc` then
|
|
491
|
+
// refuses `new Secret(...)`, `s.value` and a structural look-alike, as `nish`
|
|
492
|
+
// does. Everything else `nish` refuses about a `Secret` — printing it,
|
|
493
|
+
// comparing it, branching on it, dropping it unwiped, an `expose` whose
|
|
494
|
+
// function does I/O — is flow, which `tsc` cannot see
|
|
495
|
+
// (docs/LANGUAGE.md -> Secrets).
|
|
496
|
+
declare module "nish:secret" {
|
|
497
|
+
/**
|
|
498
|
+
* Key material: an array of integers or a record of integer fields, held
|
|
499
|
+
* opaque. Made by `secret`, read only through `expose`, and wiped or
|
|
500
|
+
* returned by the function that made it.
|
|
501
|
+
*/
|
|
502
|
+
export class Secret<T> {
|
|
503
|
+
private constructor();
|
|
504
|
+
private readonly value: T;
|
|
505
|
+
}
|
|
506
|
+
/** `value`, wrapped. A local handed in is moved: it may not be read again. */
|
|
507
|
+
export function secret<T>(value: T): Secret<T>;
|
|
508
|
+
/**
|
|
509
|
+
* `f(value)`. `f` reaches no I/O and no C, keeps nothing of `value`, and
|
|
510
|
+
* declares a return type that holds no `Secret`: that type is what leaves.
|
|
511
|
+
*/
|
|
512
|
+
export function expose<T, R>(s: Secret<T>, f: (value: T) => R): R;
|
|
513
|
+
/** `f(value, arg)`, under `expose`'s rules; `f` may not write `arg` either. */
|
|
514
|
+
export function exposeWith<T, A, R>(s: Secret<T>, arg: A, f: (value: T, arg: A) => R): R;
|
|
515
|
+
/** Zero the value's whole storage, with a store the optimiser may not remove. */
|
|
516
|
+
export function wipe<T>(target: Secret<T>): void;
|
|
517
|
+
/** Zero an array or record of integers that `expose`'s function holds, likewise. */
|
|
518
|
+
export function wipe(target: object): void;
|
|
519
|
+
}
|
|
520
|
+
|
|
421
521
|
// ---- Arena (docs/LANGUAGE.md -> Arena) ---------------------------------------
|
|
422
522
|
|
|
423
523
|
declare const Arena: {
|
|
@@ -431,6 +531,15 @@ declare const Arena: {
|
|
|
431
531
|
used(): i64;
|
|
432
532
|
};
|
|
433
533
|
|
|
534
|
+
/**
|
|
535
|
+
* `using a = arena()` (docs/LANGUAGE.md -> Arena): everything the block
|
|
536
|
+
* allocates after this declaration is released when the block ends, on every
|
|
537
|
+
* exit, and the compiler refuses the block if anything allocated in it could
|
|
538
|
+
* outlive it. Only a `using` initialiser, and the binding is never read; under
|
|
539
|
+
* Node there is no arena and the disposal does nothing.
|
|
540
|
+
*/
|
|
541
|
+
declare function arena(): Disposable;
|
|
542
|
+
|
|
434
543
|
/**
|
|
435
544
|
* `CPtr` (WP27 S2): the address a `declare function` hands back, opaque and
|
|
436
545
|
* eight bytes wide. `docs/LANGUAGE.md` has the rules — it may be written in a
|
package/runtime/nish.h
CHANGED
|
@@ -63,6 +63,17 @@ typedef struct nish_str {
|
|
|
63
63
|
char data[];
|
|
64
64
|
} nish_str;
|
|
65
65
|
|
|
66
|
+
/* The bytes of `s` as the C string a system call takes, or "" when `s` holds
|
|
67
|
+
* a NUL. The kernel reads a path to its first NUL, so `"secret\0.txt"` would
|
|
68
|
+
* pass a `.txt` check and open `secret`; "" names nothing, so each caller
|
|
69
|
+
* answers it as it answers a missing path. The one test every path, name and
|
|
70
|
+
* argument the runtime hands the OS goes through (docs/security/runtime.md,
|
|
71
|
+
* RT-3). `__builtin_strlen`, so this header needs no libc header of its own,
|
|
72
|
+
* and `unused` because a host that includes it may never call it. */
|
|
73
|
+
static inline __attribute__((unused)) const char *nish_cpath(const nish_str *s) {
|
|
74
|
+
return __builtin_strlen(s->data) == s->len ? s->data : "";
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
/* ---- Arena --------------------------------------------------------------
|
|
67
78
|
* One bump allocator per process, or one per thread under `-DNISH_THREADS`
|
|
68
79
|
* (WP20 T0; the layout and every function below are the same either way).
|
|
@@ -139,7 +150,16 @@ nish_str *nish_str_from_i64(int64_t v);
|
|
|
139
150
|
nish_str *nish_str_from_u64(uint64_t v);
|
|
140
151
|
|
|
141
152
|
/* Process and file I/O (WP7). `nish_exit` never returns; the file functions
|
|
142
|
-
* print a message to stderr and exit(1) on a fatal error.
|
|
153
|
+
* print a message to stderr and exit(1) on a fatal error.
|
|
154
|
+
*
|
|
155
|
+
* Every function here that takes a path, a name or an argument vector refuses
|
|
156
|
+
* one holding a NUL, answering as it answers a path that does not exist: the
|
|
157
|
+
* kernel would read only the bytes before the NUL, which is a different path
|
|
158
|
+
* from the one the program checked (docs/security/runtime.md, RT-3). A file
|
|
159
|
+
* longer than 2^31 - 1 bytes is unreadable, as it is to `nish_read_file_or_null`
|
|
160
|
+
* and `nish_read_file_bytes` too, because no string or array may be longer
|
|
161
|
+
* (RT-1). The two writers refuse a symbolic link as the last component of the
|
|
162
|
+
* path rather than follow it (RT-4). Everything from here
|
|
143
163
|
* to the end of the clock section below is implemented in runtime-os.c, with
|
|
144
164
|
* `nish_random` and `nish_argv_init` the exceptions: neither asks the operating
|
|
145
165
|
* system anything (the clock only seeds the first, and the entry point hands the
|
|
@@ -194,12 +214,22 @@ typedef struct nish_array { uint64_t len; uint64_t cap; char *data; } nish_array
|
|
|
194
214
|
* `elem_size` is `sizeof` one element, so for a record element type it is
|
|
195
215
|
* `sizeof(struct Point)` and the block is a C array of them. */
|
|
196
216
|
nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len);
|
|
217
|
+
/* No array grows past 2^31 - 1 elements and no string past 2^31 - 1 bytes:
|
|
218
|
+
* `nish_array_grow`, `nish_alloc_array` and `nish_str_concat` fail as an
|
|
219
|
+
* allocation does (`nish: out of memory`, exit 1; a trap on wasm) rather than
|
|
220
|
+
* make one, because under --number-mode i32 its `length` would read back
|
|
221
|
+
* negative (docs/security/codegen.md K1-6, docs/security/runtime.md RT-2). */
|
|
197
222
|
/* The cold paths compiled code calls: `push` when len == cap, a failed bounds
|
|
198
223
|
* check. `nish_array_grow` moves `cap * elem_size` bytes into a fresh arena
|
|
199
224
|
* block and writes `data`/`cap`, so for a record element type it relocates the
|
|
200
225
|
* elements themselves — which is why the checker refuses to let a program hold
|
|
201
226
|
* a pointer to one across a `push` (WP15 section 2a). */
|
|
202
227
|
void nish_array_grow(nish_array *a, uint64_t elem_size);
|
|
228
|
+
/* `secureZero(bytes)` (#385): sets every byte of `bytes` to zero through
|
|
229
|
+
* `volatile` stores, out of line, so that neither `-O2` nor `-flto` can drop
|
|
230
|
+
* them as dead when the buffer's life ends straight after the call. For
|
|
231
|
+
* secret material -- a private key, a secret scalar -- once it is used. */
|
|
232
|
+
void nish_wipe(nish_array *bytes);
|
|
203
233
|
void nish_panic_index(uint64_t idx, uint64_t len);
|
|
204
234
|
/* The failed range check of `s.slice(start, end)` (WP15 section 4): prints the
|
|
205
235
|
half-open interval that was asked for and the byte length it left, then
|
|
@@ -254,12 +284,12 @@ nish_array *nish_read_file_bytes(const nish_str *path);
|
|
|
254
284
|
* `128 + n` when signal `n` killed it. -1 when `argv` is empty, when the
|
|
255
285
|
* child cannot be started or waited for, and always under WASI, which has no
|
|
256
286
|
* processes. The elements are `nish_str *`; the child receives their bytes,
|
|
257
|
-
*
|
|
287
|
+
* and an argument containing a NUL is refused (-1) rather than truncated. */
|
|
258
288
|
int32_t nish_spawn(const nish_array *argv);
|
|
259
289
|
/* `spawnSyncTo(argv, stdoutPath, stderrPath)`: exactly `nish_spawn` — the same
|
|
260
290
|
* `PATH` search, the same wait, the same status, `128 + n` and -1 — except that
|
|
261
291
|
* each non-empty path receives that stream, created or truncated at 0644 as
|
|
262
|
-
* `nish_write_file` would leave it. An **empty** string leaves that stream
|
|
292
|
+
* `nish_write_file` would leave it, a symbolic link at the path refused. An **empty** string leaves that stream
|
|
263
293
|
* inherited, so one call can capture stdout and let stderr through to the
|
|
264
294
|
* terminal. The child opens the files, so a failed open is a child that could
|
|
265
295
|
* not start and answers -1 rather than leaving this process redirected. The two
|
|
@@ -275,8 +305,8 @@ int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_st
|
|
|
275
305
|
* arena (so a later `setenv` cannot change a string the program still holds),
|
|
276
306
|
* or NULL when it is unset — the language's `string | null`. An empty value
|
|
277
307
|
* is a set variable and answers a zero-length string, not NULL. `name` is
|
|
278
|
-
* read and never retained; a
|
|
279
|
-
*
|
|
308
|
+
* read and never retained; a name holding a NUL is unset, as it is for every
|
|
309
|
+
* other path-like argument here. */
|
|
280
310
|
nish_str *nish_getenv(const nish_str *name);
|
|
281
311
|
|
|
282
312
|
/* ---- Symlinks (WP19 §5a item 4) -----------------------------------------
|
|
@@ -288,7 +318,12 @@ nish_str *nish_getenv(const nish_str *name);
|
|
|
288
318
|
* but the last must exist for POSIX `realpath` to answer at all.
|
|
289
319
|
*
|
|
290
320
|
* `path` is read and never retained. The answer is a fresh arena string, so
|
|
291
|
-
* it outlives the call the way every other string here does.
|
|
321
|
+
* it outlives the call the way every other string here does.
|
|
322
|
+
*
|
|
323
|
+
* Under the wasi profile the answer is the sysroot's `realpath`, which
|
|
324
|
+
* wasi-sdk 22 and later have. A wasi-libc without one (the one Ubuntu and
|
|
325
|
+
* Debian package) answers NULL for every path, which a caller already
|
|
326
|
+
* handles. */
|
|
292
327
|
nish_str *nish_realpath(const nish_str *path);
|
|
293
328
|
|
|
294
329
|
/* ---- What machine this is (WP14 §7a) ------------------------------------
|
|
@@ -336,15 +371,26 @@ double nish_stat_mtime(const nish_str *path);
|
|
|
336
371
|
* or a read that fails. */
|
|
337
372
|
int32_t nish_signal_fd(void);
|
|
338
373
|
int32_t nish_read_signal(int32_t fd);
|
|
374
|
+
/* Who owns a path, and whether it runs (docs/security/runtime.md, RT-9; the
|
|
375
|
+
* builtins `lstatOwnerModeSync`, `geteuid` and `isExecutableSync`, #386).
|
|
376
|
+
* `nish_lstat_owner_mode(path)`: `lstat`, so a
|
|
377
|
+
* symbolic link answers for itself, as the owner's uid in the high 32 bits
|
|
378
|
+
* and `st_mode` in the low 32, or exactly -1 when the path does not resolve.
|
|
379
|
+
* `nish_euid()`: the effective uid. `nish_is_executable(path)`:
|
|
380
|
+
* `access(path, X_OK)`, for the real uid. */
|
|
381
|
+
int64_t nish_lstat_owner_mode(const nish_str *path);
|
|
382
|
+
int64_t nish_euid(void);
|
|
383
|
+
bool nish_is_executable(const nish_str *path);
|
|
339
384
|
|
|
340
385
|
/* ---- The network (WP34 N5), runtime/runtime-net.c, `nish:net` ------------
|
|
341
386
|
* Every call answers an `int32_t`: `>= 0` on success (a descriptor, a byte
|
|
342
387
|
* count, 0) and a negative errno otherwise, in Linux's numbering on every
|
|
343
388
|
* platform for -11 (would block), -95 (unsupported), -32 (the peer is gone),
|
|
344
|
-
* -104 (reset), -98 (address in use)
|
|
345
|
-
* is non-blocking and close-on-exec. An
|
|
346
|
-
* (element type `uint8_t`): 16 of IPv6
|
|
347
|
-
* then the port, big-endian; a shorter
|
|
389
|
+
* -104 (reset), -98 (address in use), -111 (refused), -110 (timed out) and
|
|
390
|
+
* -22 (a bad argument). Every socket is non-blocking and close-on-exec. An
|
|
391
|
+
* address is 18 bytes of a `u8[]` (element type `uint8_t`): 16 of IPv6
|
|
392
|
+
* address, IPv4 as `::ffff:a.b.c.d`, then the port, big-endian; a shorter
|
|
393
|
+
* array is -22. Nothing allocates.
|
|
348
394
|
*
|
|
349
395
|
* `nish_net_address(out, host, port)`: the form of a numeric `host`.
|
|
350
396
|
* `nish_net_local_port(fd)`: the port a socket is bound to.
|
|
@@ -355,6 +401,10 @@ int32_t nish_read_signal(int32_t fd);
|
|
|
355
401
|
* len)` of `buf`, which the compiled call has range-checked; a write to a
|
|
356
402
|
* gone peer is -32, never SIGPIPE. `nish_net_shutdown(fd, how)`: 0 read, 1
|
|
357
403
|
* write, 2 both. `nish_net_close(fd)`.
|
|
404
|
+
* `nish_tcp_connect(addr)`: a new socket connecting to `addr`, answered at
|
|
405
|
+
* once; its `EINPROGRESS` is not a failure, and any other closes it.
|
|
406
|
+
* `nish_connect_result(fd)`: `SO_ERROR` once the socket is writable, 0 when
|
|
407
|
+
* the connection is made.
|
|
358
408
|
*
|
|
359
409
|
* UDP: `nish_udp_bind(host, port, flags)` binds a datagram socket, flag 1
|
|
360
410
|
* `SO_REUSEPORT` and flag 2 `UDP_GRO` (-95 on Darwin). `nish_udp_send_to(fd,
|
|
@@ -384,6 +434,8 @@ int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len);
|
|
|
384
434
|
int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t len);
|
|
385
435
|
int32_t nish_net_shutdown(int32_t fd, int32_t how);
|
|
386
436
|
int32_t nish_net_close(int32_t fd);
|
|
437
|
+
int32_t nish_tcp_connect(const nish_array *addr);
|
|
438
|
+
int32_t nish_connect_result(int32_t fd);
|
|
387
439
|
int32_t nish_udp_bind(const nish_str *host, int32_t port, int32_t flags);
|
|
388
440
|
int32_t nish_udp_send_to(int32_t fd, const nish_array *buf, int64_t off, int64_t len, const nish_array *to,
|
|
389
441
|
int32_t segment, int32_t ecn);
|
|
@@ -405,6 +457,11 @@ double nish_parse_number(const nish_str *s, int32_t mode);
|
|
|
405
457
|
/* Checked integer division (Rust semantics): the failed-check path. */
|
|
406
458
|
void nish_panic_div(bool by_zero);
|
|
407
459
|
|
|
460
|
+
/* Checked signed arithmetic: the failed-check path. `op` is 0 add, 1
|
|
461
|
+
* subtract, 2 multiply, 3 negate; prints "attempt to <op> with overflow" and
|
|
462
|
+
* exits 1. */
|
|
463
|
+
void nish_panic_overflow(uint32_t op);
|
|
464
|
+
|
|
408
465
|
/* ---- Parallel work (WP20 T1 / wp29 stage P1), runtime/runtime-parallel.c ----
|
|
409
466
|
*
|
|
410
467
|
* One region of work, divided. `nish_parallel_range` calls `body(lo, hi, ctx)`
|