@amritk/nish-x86_64-linux 0.24.0 → 0.26.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 (43) hide show
  1. package/bin/nish +0 -0
  2. package/docs/AI.md +45 -4
  3. package/package.json +1 -1
  4. package/runtime/browser.mjs +4 -3
  5. package/runtime/core/host.d.ts +12 -0
  6. package/runtime/core/host.mjs +74 -2
  7. package/runtime/nish.d.ts +148 -0
  8. package/runtime/nish.h +59 -0
  9. package/runtime/nish.mjs +25 -0
  10. package/runtime/node.mjs +136 -6
  11. package/runtime/runtime-net.c +44 -3
  12. package/runtime/runtime-os.c +351 -18
  13. package/scripts/build.sh +13 -3
  14. package/std/README.md +27 -10
  15. package/std/cli-args.ts +1216 -0
  16. package/std/cli-flags.ts +585 -0
  17. package/std/cli.ts +308 -19
  18. package/std/compress/checksum.ts +132 -20
  19. package/std/compress/deflate.ts +638 -248
  20. package/std/compress/gzip.ts +20 -6
  21. package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
  22. package/std/crypto/p256.ts +63 -1
  23. package/std/crypto/p384.ts +6044 -0
  24. package/std/crypto/pki-roots.ts +322 -0
  25. package/std/crypto/pki.ts +1207 -0
  26. package/std/crypto/rsa.ts +663 -0
  27. package/std/crypto/x509.ts +1054 -58
  28. package/std/net/dns-config.ts +734 -0
  29. package/std/net/dns-message.ts +517 -0
  30. package/std/net/dns-resolver.ts +1015 -0
  31. package/std/net/dns.ts +478 -0
  32. package/std/net/http1-client.ts +1227 -0
  33. package/std/net/http1-server.ts +7 -2
  34. package/std/net/http1.ts +9 -9
  35. package/std/net/postgres.ts +259 -26
  36. package/std/net/tls/client.ts +1094 -0
  37. package/std/net/tls/codec.ts +585 -3
  38. package/std/net/tls/record-client.ts +1192 -0
  39. package/std/net/tls/verify.ts +221 -0
  40. package/std/net/tls-tcp.ts +642 -11
  41. package/std/net/tls.ts +2 -2
  42. package/std/net/url.ts +559 -4
  43. package/std/yaml-write.ts +415 -0
package/bin/nish CHANGED
Binary file
package/docs/AI.md CHANGED
@@ -2133,13 +2133,27 @@ a `\u` escape `nish/json` decodes) is one unit per byte, and counts its bytes.
2133
2133
  **Files and the system.** `readFileSync(path)` (exits 1 on failure),
2134
2134
  `readFileSyncOrNull(path)` (`string | null`), `readFileBytesSync(path)`
2135
2135
  (`u8[] | null`, the bytes as they are on disk), `writeFileSync(path, data)`,
2136
- `appendFileSync(path, data)`, `mkdirSync(path)` (one level, `boolean`),
2136
+ `appendFileSync(path, data)`, `writeFileBytesSync(path, bytes)` (a `u8[]` as
2137
+ it is; `i32`: the length, or a negative errno), `renameSync(from, to)` (`0` or
2138
+ a negative errno; atomic on one filesystem, `-18` across two, so write the
2139
+ temporary beside the target), `unlinkSync(path)` (`0` or a negative errno; a
2140
+ directory is refused), `writeFileSyncMode(path, data, mode)` (the file left
2141
+ at exactly `mode` (`0..0o777`), set before the first byte, written in place; a symbolic link refused; the
2142
+ length or a negative errno), `createFileExclusiveSync(path, data, mode)` (the
2143
+ same, but `-17` when the name exists: a lock file), `mkdirSyncMode(path, mode)`
2144
+ (`0`, `-17` when it exists, the umask applies), `writeBytes(bytes, off, len)` (a `u8[]` range to stdout
2145
+ as it is, the length or a negative errno; the range panics like
2146
+ `netWrite`'s), `mkdirSync(path)` (one level, `boolean`),
2137
2147
  `isDirectorySync(path)`, `readdirSync(path)` (`string[] | null`, sorted by
2138
2148
  bytes, no `.`/`..`), `spawnSync(argv)`, `spawnSyncTo(argv, outPath, errPath)`,
2149
+ `spawnPiped(argv, stdin, stdout, stderr)` (stdin from a `u8[]`, both outputs
2150
+ appended to `u8[]`s, the status; `-2` past 16 MiB; the way to hand a child a
2151
+ secret, never argv or the environment),
2139
2152
  `getenv(name)` (`string | null` — unset and empty are different answers),
2140
2153
  `realpathSync(path)` (`string | null`; symbolic links resolved, absolute, and
2141
2154
  `null` when it does not resolve),
2142
- `monotonicNanos()` (`i64`; elapsed time only), and, for checking a path before
2155
+ `monotonicNanos()` (`i64`; elapsed time only), `wallClockNanos()` (`i64`,
2156
+ nanoseconds since the Unix epoch; the wall clock, which can step), and, for checking a path before
2143
2157
  trusting it, `lstatOwnerModeSync(path)` (an `i64`: the owner's uid in the high
2144
2158
  32 bits, `st_mode` in the low 32, `-1` when it does not resolve; a symbolic
2145
2159
  link answers for itself), `geteuid()` (`i64`) and `isExecutableSync(path)`
@@ -2167,6 +2181,12 @@ also watches the signal fd. None of `spawn`, `kill`, `waitPid` may be called in
2167
2181
  a task or a parallel body; the two host facts may. `availableParallelism()` (`i32`, at least 1, no `--threads` needed) and
2168
2182
  `totalMemory()` (`i64` bytes, or `-1`) size a program to the machine. None of
2169
2183
  the five exists on a wasm target, and `spawn` and `waitPid` throw under Node.
2184
+ `setNice(n)` sets the nice value (clamped to -20..19; lowering it unprivileged
2185
+ is `-13`) and `setIdlePriority()` moves the program to Linux's `SCHED_IDLE`
2186
+ (`-95` on Darwin); each answers 0 or a negative errno. Call them at the top of
2187
+ `main`, never in a task. An unprivileged process can never leave `SCHED_IDLE`
2188
+ and an idle one can starve on a saturated machine, so keep a normal-priority
2189
+ fallback. Neither exists on wasm; both throw under Node.
2170
2190
 
2171
2191
  **Sockets** (`nish:net`, or the same names as globals). `tcpListen(host, port,
2172
2192
  backlog)`, `tcpAccept(fd, peer)`, `netRead(fd, buf, off, len)`,
@@ -2179,7 +2199,8 @@ and there is no `async` and no callback: your `main` is the loop. A client calls
2179
2199
  `tcpConnect(addr)`, which answers the descriptor while it is still connecting;
2180
2200
  wait for it to be writable, then `connectResult(fd)` is 0 or the failure. Buffers
2181
2201
  are `u8[]` only; an address is 18 bytes of one (IPv4 as `::ffff:a.b.c.d`, then
2182
- the port big-endian); hosts are numeric literals, because there is no DNS.
2202
+ the port big-endian); hosts are numeric literals; to reach a name, resolve it first with
2203
+ `nish/net/dns` (`DnsClient` on your loop, or the blocking `resolveHost`).
2183
2204
  `netRead` / `netWrite` panic on a range outside the buffer.
2184
2205
  `tcpListenFlags(host, port, backlog, flags)` is `tcpListen` with flag 1
2185
2206
  `SO_REUSEPORT`, so several listeners, in one process or many, share a port (Linux
@@ -2198,7 +2219,13 @@ loop, `pollAdd(loop, fd, events, token)` / `pollModify(...)` watch `fd` for
2198
2219
  for `-1`), fills `ready: i32[]` with pairs — `ready[2k]` a token,
2199
2220
  `ready[2k + 1]` its events, 4 added for a hang-up or error — and answers how
2200
2221
  many, **0 on a timeout or a signal**. It is level-triggered, so what you leave
2201
- unread is reported again. Add `signalFd()` to the loop to wake on SIGTERM. None
2222
+ unread is reported again. Add `signalFd()` to the loop to wake on SIGTERM.
2223
+ `netSetOption(fd, option, value)` sets one socket option from a whitelist:
2224
+ `NET_TCP_NODELAY`, `NET_TCP_QUICKACK` (Linux; `-95` on Darwin),
2225
+ `NET_SO_KEEPALIVE`, `NET_SO_SNDBUF` and `NET_SO_RCVBUF`, `i32` ids exported
2226
+ beside it; any other id is `-22`. Linux drops quick-ACK mode on its own, so set
2227
+ it again after each response. The ids are not module constants: pass them
2228
+ directly. None
2202
2229
  exists on a wasm target, and under Node each throws.
2203
2230
 
2204
2231
  **Arena.** `Arena.mark()`, `Arena.used()` and `using a = arena()` — see below.
@@ -2605,6 +2632,20 @@ the array open: end it and send the rest in the next statement. Every other
2605
2632
  guide's [PostgreSQL page](guide/using-nish/postgres.md#array-parameters) its
2606
2633
  walk-through.)
2607
2634
 
2635
+ **Read PostgreSQL ids, counts and times without parsing them** — there is no
2636
+ driver option for binary results: in `nish/net/postgres`, open the bind with
2637
+ `bindStartBinary(name)` instead of `bindStart(name)`, and every result column
2638
+ of that execution comes in binary. Read an `int2`/`int4`/`int8` with
2639
+ `binaryInt(c)`, a `bool` with `binaryBool(c)`, a `float8` with
2640
+ `binaryFloat(c)`, a `timestamptz` with `conn.binary.timestamp(conn.inputBuffer(),
2641
+ conn.colStart(c), conn.colLength(c), out, at)` (or its microseconds since 2000
2642
+ with `binaryInt(c)`, written out by `conn.binary.iso(micros, out, at)`), and a
2643
+ `text` column with `columnText(c)` as before. Cast anything else (`numeric`,
2644
+ `text[]`) to `text` in the SQL. (No block, as for `nish/text`;
2645
+ `tests/postgres/live`'s `binary` scenario runs it against a server, and the
2646
+ user guide's [PostgreSQL page](guide/using-nish/postgres.md#binary-results)
2647
+ its walk-through.)
2648
+
2608
2649
  **Batch a server's keyed reads into one PostgreSQL query** — there is no
2609
2650
  DataLoader and no `setTimeout` to collect a tick's keys: in `nish/net/postgres`,
2610
2651
  a `PgBatcher` built from a `PgBatchConfig` (`name`, `select` reading the key as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-x86_64-linux",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "The nish native compiler for Linux on x86_64",
5
5
  "license": "MIT",
6
6
  "os": [
@@ -10,9 +10,9 @@
10
10
  * - `exit(code)` throws a `NishExit` carrying the code, since a page cannot
11
11
  * end itself; the caller that started the program catches it, and does
12
12
  * not call the module again (docs/security/runtime-browser.md, BROWSER-2).
13
- * - `monotonicNanos` reads `performance.now()`, `getenv` answers `null`,
14
- * `isTerminal` answers `false` and `terminalSize` `[-25, 0]`, and `argv()`
15
- * is the program alone.
13
+ * - `monotonicNanos` reads `performance.now()`, `wallClockNanos` `Date.now()`,
14
+ * `getenv` answers `null`, `isTerminal` answers `false` and `terminalSize`
15
+ * `[-25, 0]`, and `argv()` is the program alone.
16
16
  * - Every file-system, process and machine call panics, naming itself and
17
17
  * the browser.
18
18
  */
@@ -69,6 +69,7 @@ installHost({
69
69
  call(name) {
70
70
  if (name === "getenv") return null;
71
71
  if (name === "monotonicNanos") return BigInt(Math.round(performance.now() * 1e6));
72
+ if (name === "wallClockNanos") return BigInt(Date.now()) * 1000000n;
72
73
  // A page has no terminal, which `nish:term` answers as a pipe does.
73
74
  if (name === "isTerminal") return false;
74
75
  if (name === "terminalSize") return [-25, 0];
@@ -35,6 +35,7 @@ export declare function log(x: unknown): void;
35
35
  export declare function error(x: unknown): void;
36
36
  export declare function write(s: string): void;
37
37
  export declare function writeError(s: string): void;
38
+ export declare function writeBytes(bytes: readonly u8[], off: i32, len: i32): i32;
38
39
  export declare function panic(message: string): never;
39
40
  export declare function exit(code: i32): never;
40
41
  export declare function argv(): string[];
@@ -43,6 +44,12 @@ export declare function readFileSyncOrNull(path: string): string | null;
43
44
  export declare function readFileBytesSync(path: string): u8[] | null;
44
45
  export declare function writeFileSync(path: string, data: string): void;
45
46
  export declare function appendFileSync(path: string, data: string): void;
47
+ export declare function writeFileBytesSync(path: string, bytes: readonly u8[]): i32;
48
+ export declare function renameSync(from: string, to: string): i32;
49
+ export declare function unlinkSync(path: string): i32;
50
+ export declare function writeFileSyncMode(path: string, data: string, mode: i32): i32;
51
+ export declare function createFileExclusiveSync(path: string, data: string, mode: i32): i32;
52
+ export declare function mkdirSyncMode(path: string, mode: i32): i32;
46
53
  export declare function mkdirSync(path: string): boolean;
47
54
  export declare function isDirectorySync(path: string): boolean;
48
55
  export declare function readdirSync(path: string): string[] | null;
@@ -52,10 +59,12 @@ export declare function arch(): string;
52
59
  export declare function getenv(name: string): string | null;
53
60
  export declare function spawnSync(argv: string[]): number;
54
61
  export declare function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
62
+ export declare function spawnPiped(argv: string[], stdin: readonly u8[], stdout: u8[], stderr: u8[]): i32;
55
63
  export declare function kill(pid: i32, sig: i32): i32;
56
64
  export declare function availableParallelism(): i32;
57
65
  export declare function totalMemory(): i64;
58
66
  export declare function monotonicNanos(): i64;
67
+ export declare function wallClockNanos(): i64;
59
68
  export declare function statMtimeSync(path: string): f64;
60
69
  export declare function lstatOwnerModeSync(path: string): i64;
61
70
  export declare function geteuid(): i64;
@@ -69,6 +78,9 @@ export declare function spawn(argv: string[]): i32;
69
78
  export declare function waitPid(pid: i32, flags: i32): i32[];
70
79
  export declare function signalFd(): i32;
71
80
  export declare function readSignal(fd: i32): i32;
81
+ /** These two have no reading under Node: each throws, naming itself. */
82
+ export declare function setNice(n: i32): i32;
83
+ export declare function setIdlePriority(): i32;
72
84
  /** What each `nish:net` function is under the core: a throw naming `name`. */
73
85
  export declare function noNetReading(name: string): never;
74
86
  /** What `readStdin`, `waitStdin`, `setRawMode` and `setTerminalRestore` are under the core: a throw naming `name`. */
@@ -15,9 +15,11 @@
15
15
  * its own fails loudly rather than writing nowhere.
16
16
  *
17
17
  * The functions that have no synchronous reading under Node (`spawn`,
18
- * `waitPid`, `signalFd`, `readSignal`, the `nish:net` set) throw here without
19
- * asking any host.
18
+ * `waitPid`, `signalFd`, `readSignal`, the `nish:net` set), and `setNice` and
19
+ * `setIdlePriority`, which have none at all, throw here without asking any
20
+ * host.
20
21
  */
22
+ import { panicSlice, toIndex } from "./checks.mjs";
21
23
 
22
24
  /** What every member of the default host throws: no adapter has been imported. */
23
25
  export class NishNoHost extends Error {
@@ -86,6 +88,18 @@ export function writeError(s) {
86
88
  host.writeErr(s);
87
89
  }
88
90
 
91
+ /**
92
+ * `writeBytes(bytes, off, len)`: the range checked as the compiled call checks
93
+ * it, `0 <= off <= off + len <= bytes.length`, through the slice panic, then
94
+ * the host's write of those bytes to stdout.
95
+ */
96
+ export function writeBytes(bytes, off, len) {
97
+ const from = toIndex(off);
98
+ const to = from + toIndex(len);
99
+ if (!(from >= 0 && from <= to && to <= bytes.length)) panicSlice(from, to, bytes.length);
100
+ return host.call("writeBytes", [bytes, from, to]);
101
+ }
102
+
89
103
  /** `panic(message)`: the message on stderr, then exit 1. */
90
104
  export function panic(message) {
91
105
  host.writeErr(`${message}\n`);
@@ -131,6 +145,30 @@ export function appendFileSync(path, data) {
131
145
  return host.call("appendFileSync", [path, data]);
132
146
  }
133
147
 
148
+ export function writeFileBytesSync(path, bytes) {
149
+ return host.call("writeFileBytesSync", [path, bytes]);
150
+ }
151
+
152
+ export function renameSync(from, to) {
153
+ return host.call("renameSync", [from, to]);
154
+ }
155
+
156
+ export function unlinkSync(path) {
157
+ return host.call("unlinkSync", [path]);
158
+ }
159
+
160
+ export function writeFileSyncMode(path, data, mode) {
161
+ return host.call("writeFileSyncMode", [path, data, mode]);
162
+ }
163
+
164
+ export function createFileExclusiveSync(path, data, mode) {
165
+ return host.call("createFileExclusiveSync", [path, data, mode]);
166
+ }
167
+
168
+ export function mkdirSyncMode(path, mode) {
169
+ return host.call("mkdirSyncMode", [path, mode]);
170
+ }
171
+
134
172
  export function mkdirSync(path) {
135
173
  return host.call("mkdirSync", [path]);
136
174
  }
@@ -167,6 +205,18 @@ export function spawnSyncTo(argv, out, err) {
167
205
  return host.call("spawnSyncTo", [argv, out, err]);
168
206
  }
169
207
 
208
+ /**
209
+ * `spawnPiped(argv, stdin, stdout, stderr)`: the host runs the child and hands
210
+ * back its status and both streams' bytes, and they are appended here, as the
211
+ * runtime appends them to the caller's arrays.
212
+ */
213
+ export function spawnPiped(argv, input, out, err) {
214
+ const [status, outBytes, errBytes] = host.call("spawnPiped", [argv, input]);
215
+ for (const b of outBytes) out.push(b);
216
+ for (const b of errBytes) err.push(b);
217
+ return status;
218
+ }
219
+
170
220
  export function kill(pid, sig) {
171
221
  return host.call("kill", [pid, sig]);
172
222
  }
@@ -183,6 +233,10 @@ export function monotonicNanos() {
183
233
  return host.call("monotonicNanos", []);
184
234
  }
185
235
 
236
+ export function wallClockNanos() {
237
+ return host.call("wallClockNanos", []);
238
+ }
239
+
186
240
  export function statMtimeSync(path) {
187
241
  return host.call("statMtimeSync", [path]);
188
242
  }
@@ -259,6 +313,24 @@ export function waitPid() {
259
313
  return noProcessReading("waitPid");
260
314
  }
261
315
 
316
+ /**
317
+ * `setNice(n)` and `setIdlePriority()` have no reading under Node: Node has one
318
+ * of the nice value (`os.setPriority`) but none of `SCHED_IDLE`, and a reading
319
+ * of one without the other would differ from the native build on the same host.
320
+ * Each throws, naming itself, as `runtime/nish.mjs` does.
321
+ */
322
+ function noSchedulingReading(name) {
323
+ throw new Error(`\`${name}\` has no reading under Node: it changes how the kernel schedules the native program`);
324
+ }
325
+
326
+ export function setNice() {
327
+ return noSchedulingReading("setNice");
328
+ }
329
+
330
+ export function setIdlePriority() {
331
+ return noSchedulingReading("setIdlePriority");
332
+ }
333
+
262
334
  /**
263
335
  * `signalFd()` and `readSignal(fd)` have no faithful reading under Node, and
264
336
  * these say so rather than answer something else. Node delivers a signal to
package/runtime/nish.d.ts CHANGED
@@ -227,6 +227,12 @@ declare function readCString(p: CPtr): string;
227
227
  declare function write(s: string): void;
228
228
  /** `s` to stderr, likewise. */
229
229
  declare function writeError(s: string): void;
230
+ /**
231
+ * `bytes[off .. off + len)` to stdout, every byte as it is, partial writes and
232
+ * interruptions retried: the count, or a negative errno. A range outside
233
+ * `bytes` panics, as `dst.set` does. The shape is `netWrite`'s.
234
+ */
235
+ declare function writeBytes(bytes: readonly u8[], off: i32, len: i32): i32;
230
236
  /**
231
237
  * `message` and a newline to stderr, then exit 1. Terminates control flow, so
232
238
  * it is `never`: that is what lets `tsc` agree that a function ending in a
@@ -257,6 +263,46 @@ declare function readFileBytesSync(path: string): u8[] | null;
257
263
  declare function writeFileSync(path: string, data: string): void;
258
264
  /** `data` added at the end of the file, with the same refusal of a symbolic link. */
259
265
  declare function appendFileSync(path: string, data: string): void;
266
+ /**
267
+ * The file's contents replaced by `bytes`, created at 0644 when missing, with
268
+ * the same refusal of a symbolic link: the byte count, or a negative errno.
269
+ * Never an exit.
270
+ */
271
+ declare function writeFileBytesSync(path: string, bytes: readonly u8[]): i32;
272
+ /**
273
+ * `from` moved to `to`, replacing a file there in one step on one file system:
274
+ * 0, or a negative errno (-18 across file systems, -2 for a missing `from`).
275
+ */
276
+ declare function renameSync(from: string, to: string): i32;
277
+ /** The name `path` removed: 0, or a negative errno. A directory is refused, not removed. */
278
+ declare function unlinkSync(path: string): i32;
279
+ /**
280
+ * `writeFileSync` with an explicit `mode`: the file is exactly `mode`
281
+ * (`0o600` for a secret) before a byte of `data` is in it, whatever the umask
282
+ * or a file already there had, and a symbolic link at the path is refused
283
+ * (-40). The byte count, or a negative errno: -22 for a mode outside
284
+ * 0..0o777, the setuid, setgid and sticky bits included. The file is
285
+ * truncated and written in place, so a crash or a failed write leaves it
286
+ * empty or partial, and a file this process may write but does not own is
287
+ * emptied before the mode change answers -1 (`EPERM`). For a file that must
288
+ * be the old one or the new one, write a temporary name with this and move it
289
+ * with `renameSync`. Never an exit.
290
+ */
291
+ declare function writeFileSyncMode(path: string, data: string, mode: i32): i32;
292
+ /**
293
+ * `data` in a new file of exactly `mode`, created only if nothing is at `path`:
294
+ * -17 when something is, a dangling symbolic link included. Of several
295
+ * processes racing for one path exactly one succeeds, so it takes a lock. The
296
+ * byte count, or a negative errno; a write that fails after the create
297
+ * removes the file, so the lock is not left taken. Never an exit.
298
+ */
299
+ declare function createFileExclusiveSync(path: string, data: string, mode: i32): i32;
300
+ /**
301
+ * One directory made with `mode` less the umask (`0o700` is exact under any
302
+ * umask that leaves the owner's bits): 0, or a negative errno. A path already
303
+ * there is -17 and its mode is not changed; -22 for a mode outside 0..0o777.
304
+ */
305
+ declare function mkdirSyncMode(path: string, mode: i32): i32;
260
306
  /** One directory, not recursive; whether a directory is there afterwards. */
261
307
  declare function mkdirSync(path: string): boolean;
262
308
  /** Whether a directory is at `path` right now. One `stat`, and never an exit. */
@@ -280,6 +326,13 @@ declare function spawnSync(argv: string[]): number;
280
326
  * either path is refused, as `writeFileSync` refuses one, and the answer is -1.
281
327
  */
282
328
  declare function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
329
+ /**
330
+ * The same run with all three streams on pipes: `stdin` is written to the
331
+ * child and closed, and what it prints is appended to `stdout` and `stderr`.
332
+ * The status as `spawnSync` answers it, or -2 when a stream passed 16 MiB:
333
+ * the child is then killed and each array keeps at most the first 16 MiB.
334
+ */
335
+ declare function spawnPiped(argv: string[], stdin: readonly u8[], stdout: u8[], stderr: u8[]): i32;
283
336
  /**
284
337
  * Start `argv[0]` through `PATH` without waiting: the child's pid, or a
285
338
  * negative errno. The child is the program's until `waitPid` reaps it. No
@@ -302,6 +355,21 @@ declare function waitPid(pid: i32, flags: i32): i32[];
302
355
  declare function availableParallelism(): i32;
303
356
  /** Bytes of physical memory, or -1 when the host does not say. */
304
357
  declare function totalMemory(): i64;
358
+ /**
359
+ * Set this process's nice value to `n`: 0, or a negative errno. The kernel
360
+ * clamps `n` to -20..19, and lowering it below the current value needs the
361
+ * privilege to (-13 without it on Linux). Children started afterwards inherit
362
+ * it. Throws under Node.
363
+ */
364
+ declare function setNice(n: i32): i32;
365
+ /**
366
+ * Move this process to `SCHED_IDLE`, which runs it only when nothing else wants
367
+ * the CPU: 0, or a negative errno, and -95 where the host has no such policy
368
+ * (Darwin). An unprivileged process can never leave `SCHED_IDLE` again, and an
369
+ * idle process can starve on a saturated machine, so a caller keeps a
370
+ * normal-priority fallback for work that must finish. Throws under Node.
371
+ */
372
+ declare function setIdlePriority(): i32;
305
373
  /** One environment variable, or `null` when it is unset (an empty value is a set variable). */
306
374
  declare function getenv(name: string): string | null;
307
375
  /**
@@ -309,6 +377,11 @@ declare function getenv(name: string): string | null;
309
377
  * origin is arbitrary, so only the difference between two reads is meaningful.
310
378
  */
311
379
  declare function monotonicNanos(): i64;
380
+ /**
381
+ * The wall clock in nanoseconds since the Unix epoch, for dating an event. It
382
+ * steps when the system time is set, so a duration is `monotonicNanos`'s.
383
+ */
384
+ declare function wallClockNanos(): i64;
312
385
  /**
313
386
  * The modification time of `path` in milliseconds since the epoch, with the
314
387
  * sub-millisecond fraction the file system keeps (Node's `mtimeMs`), or NaN
@@ -381,6 +454,26 @@ declare function pollModify(loop: i32, fd: i32, events: i32, token: i32): i32;
381
454
  declare function pollRemove(loop: i32, fd: i32): i32;
382
455
  /** Wait up to `timeoutMs` (negative: forever); pairs `ready[2k]` token, `ready[2k + 1]` events (4: hang-up or error). */
383
456
  declare function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
457
+ /**
458
+ * Set one socket option from a whitelist: `option` is one of the `NET_*` ids
459
+ * below, and any other is -22; there is no raw level and option number. 0, or
460
+ * a negative errno (-9 a closed descriptor, -88 not a socket).
461
+ */
462
+ declare function netSetOption(fd: i32, option: i32, value: i32): i32;
463
+ /** `TCP_NODELAY`: `value` 1 sends a small write at once, 0 lets Nagle batch it. */
464
+ declare const NET_TCP_NODELAY: i32;
465
+ /**
466
+ * `TCP_QUICKACK` (Linux; -95 elsewhere): `value` 1 acknowledges at once, 0
467
+ * delays. Linux does not keep quick-ACK mode: the kernel leaves it on its own,
468
+ * so a server that wants a mode sets it again after every response.
469
+ */
470
+ declare const NET_TCP_QUICKACK: i32;
471
+ /** `SO_KEEPALIVE`: `value` 1 probes an idle connection, 0 does not. */
472
+ declare const NET_SO_KEEPALIVE: i32;
473
+ /** `SO_SNDBUF`: the send buffer, `value` in bytes; Linux doubles the value it stores. */
474
+ declare const NET_SO_SNDBUF: i32;
475
+ /** `SO_RCVBUF`: the receive buffer, `value` in bytes; Linux doubles the value it stores. */
476
+ declare const NET_SO_RCVBUF: i32;
384
477
 
385
478
  // ---- `nish:term` ------------------------------------------------------------------
386
479
  //
@@ -433,6 +526,9 @@ declare var crypto: {
433
526
  //
434
527
  // `docs/LANGUAGE.md` -> Builtins -> Builtin modules is the normative list.
435
528
 
529
+ // Every negative errno a file call answers is the platform's own, untranslated:
530
+ // the numbers shown here are Linux's (-2 ENOENT, -17 EEXIST, -18 EXDEV, -22
531
+ // EINVAL, -40 ELOOP), and Darwin's differ (ELOOP is -62 there).
436
532
  declare module "nish:fs" {
437
533
  export function readFileSync(path: string): string;
438
534
  export function readFileSyncOrNull(path: string): string | null;
@@ -441,6 +537,18 @@ declare module "nish:fs" {
441
537
  export function writeFileSync(path: string, data: string): void;
442
538
  /** Refuses a symbolic link as the last component of the path, as the global does. */
443
539
  export function appendFileSync(path: string, data: string): void;
540
+ /** The byte count, or a negative errno; refuses a symbolic link as `writeFileSync` does. */
541
+ export function writeFileBytesSync(path: string, bytes: readonly u8[]): i32;
542
+ /** 0, or a negative errno: -18 across file systems. */
543
+ export function renameSync(from: string, to: string): i32;
544
+ /** 0, or a negative errno: a directory is refused, not removed. */
545
+ export function unlinkSync(path: string): i32;
546
+ /** The byte count, or a negative errno; the file is exactly `mode` (0..0o777) before it holds a byte, a link refused, written in place. */
547
+ export function writeFileSyncMode(path: string, data: string, mode: i32): i32;
548
+ /** As `writeFileSyncMode`, but -17 when anything is at `path`: one racer wins; a failed write removes the file. */
549
+ export function createFileExclusiveSync(path: string, data: string, mode: i32): i32;
550
+ /** 0, or a negative errno: -17 for a path already there, whose mode is left alone. */
551
+ export function mkdirSyncMode(path: string, mode: i32): i32;
444
552
  /** `true` when the directory was created, `false` when it already existed. */
445
553
  export function mkdirSync(path: string): boolean;
446
554
  export function isDirectorySync(path: string): boolean;
@@ -465,6 +573,11 @@ declare module "nish:process" {
465
573
  * at either path is refused, and the answer is -1.
466
574
  */
467
575
  export function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
576
+ /**
577
+ * The same run with `stdin` written to the child and its stdout and stderr
578
+ * appended to the two arrays: the status, or -2 past 16 MiB on a stream.
579
+ */
580
+ export function spawnPiped(argv: string[], stdin: readonly u8[], stdout: u8[], stderr: u8[]): i32;
468
581
  /** Start a child without waiting: its pid, or a negative errno. */
469
582
  export function spawn(argv: string[]): i32;
470
583
  /** Send 0, 2, 9 or 15 to `pid`: 0, or a negative errno; any other signal is -22. */
@@ -475,11 +588,21 @@ declare module "nish:process" {
475
588
  export function availableParallelism(): i32;
476
589
  /** Bytes of physical memory, or -1. */
477
590
  export function totalMemory(): i64;
591
+ /** Set this process's nice value (clamped to -20..19): 0, or a negative errno. */
592
+ export function setNice(n: i32): i32;
593
+ /**
594
+ * Move this process to `SCHED_IDLE`: 0, a negative errno, or -95 off Linux.
595
+ * An unprivileged process can never leave it, and an idle process can starve
596
+ * on a saturated machine, so a caller keeps a normal-priority fallback.
597
+ */
598
+ export function setIdlePriority(): i32;
478
599
  /**
479
600
  * A monotonic clock in nanoseconds. The origin is arbitrary, so only the
480
601
  * difference between two reads is meaningful.
481
602
  */
482
603
  export function monotonicNanos(): i64;
604
+ /** The wall clock in nanoseconds since the Unix epoch; it steps when the system time is set. */
605
+ export function wallClockNanos(): i64;
483
606
  /** A descriptor readable when SIGTERM or SIGINT arrives; the same one every call, or -1. */
484
607
  export function signalFd(): i32;
485
608
  /** Block until SIGTERM or SIGINT arrives: 15 or 2, or -1 for a descriptor that is not `signalFd()`'s. */
@@ -538,6 +661,26 @@ declare module "nish:net" {
538
661
  export function pollRemove(loop: i32, fd: i32): i32;
539
662
  /** Wait up to `timeoutMs` (negative: forever); pairs `ready[2k]` token, `ready[2k + 1]` events (4: hang-up or error). */
540
663
  export function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
664
+ /**
665
+ * Set one socket option from a whitelist: `option` is one of the `NET_*` ids
666
+ * below, and any other is -22; there is no raw level and option number. 0, or
667
+ * a negative errno (-9 a closed descriptor, -88 not a socket).
668
+ */
669
+ export function netSetOption(fd: i32, option: i32, value: i32): i32;
670
+ /** `TCP_NODELAY`: `value` 1 sends a small write at once, 0 lets Nagle batch it. */
671
+ export const NET_TCP_NODELAY: i32;
672
+ /**
673
+ * `TCP_QUICKACK` (Linux; -95 elsewhere): `value` 1 acknowledges at once, 0
674
+ * delays. Linux does not keep quick-ACK mode: the kernel leaves it on its own,
675
+ * so a server that wants a mode sets it again after every response.
676
+ */
677
+ export const NET_TCP_QUICKACK: i32;
678
+ /** `SO_KEEPALIVE`: `value` 1 probes an idle connection, 0 does not. */
679
+ export const NET_SO_KEEPALIVE: i32;
680
+ /** `SO_SNDBUF`: the send buffer, `value` in bytes; Linux doubles the value it stores. */
681
+ export const NET_SO_SNDBUF: i32;
682
+ /** `SO_RCVBUF`: the receive buffer, `value` in bytes; Linux doubles the value it stores. */
683
+ export const NET_SO_RCVBUF: i32;
541
684
  }
542
685
 
543
686
  declare module "nish:unsafe" {
@@ -560,6 +703,11 @@ declare module "nish:io" {
560
703
  export function write(s: string): void;
561
704
  /** The same, on stderr. */
562
705
  export function writeError(s: string): void;
706
+ /**
707
+ * `bytes[off .. off + len)` to stdout, every byte as it is: the count, or a
708
+ * negative errno. A range outside `bytes` panics, as `dst.set` does.
709
+ */
710
+ export function writeBytes(bytes: readonly u8[], off: i32, len: i32): i32;
563
711
  /** `message` to stderr, then exit 1. A terminator, as `process.exit` is. */
564
712
  export function panic(message: string): never;
565
713
  }
package/runtime/nish.h CHANGED
@@ -396,6 +396,36 @@ nish_array *nish_readdir(const nish_str *path);
396
396
  * and the bytes live in the arena, so copy what you keep before the next reset
397
397
  * or release. */
398
398
  nish_array *nish_read_file_bytes(const nish_str *path);
399
+ /* `renameSync(from, to)`, `unlinkSync(path)` and `writeFileBytesSync(path,
400
+ * bytes)`: rename(2), unlink(2), and every element of a `u8[]` written to a
401
+ * file opened as `nish_write_file` opens it (`O_TRUNC`, 0644, a link refused).
402
+ * None exits: each answers 0 (the write, the length written) or a negative
403
+ * errno — -EXDEV for a rename across file systems, which moves nothing, and
404
+ * -EISDIR or -EPERM for a directory handed to `nish_unlink`, which never calls
405
+ * rmdir. A path holding a NUL answers -EINVAL, not -ENOENT, so a program can
406
+ * tell a malformed path from a missing file. The numbers are the platform's
407
+ * errno values, untranslated: -ELOOP is -40 on Linux and -62 on Darwin. */
408
+ int32_t nish_rename(const nish_str *from, const nish_str *to);
409
+ int32_t nish_unlink(const nish_str *path);
410
+ int32_t nish_write_file_bytes(const nish_str *path, const nish_array *bytes);
411
+ /* `writeFileSyncMode(path, data, mode)`, `createFileExclusiveSync(path, data,
412
+ * mode)` and `mkdirSyncMode(path, mode)`: a file written, or created only if
413
+ * nothing is at the path (`O_EXCL`), then `fchmod`ed to exactly `mode` before a
414
+ * byte is written; a directory made with `mode & ~umask`. A link at a file's
415
+ * path is refused. The file writers answer the byte count and `nish_mkdir_mode`
416
+ * 0, or a negative platform errno: -EEXIST for an exclusive create or a mkdir
417
+ * that finds the path taken (a directory's mode is then left alone), -EINVAL
418
+ * for a mode outside 0..0777 (the setuid, setgid and sticky bits included) or
419
+ * a path holding a NUL, -ENOSYS under WASI. A failed exclusive create leaves
420
+ * no file behind. */
421
+ int32_t nish_write_file_mode(const nish_str *path, const nish_str *data, int32_t mode);
422
+ int32_t nish_create_file_exclusive(const nish_str *path, const nish_str *data, int32_t mode);
423
+ int32_t nish_mkdir_mode(const nish_str *path, int32_t mode);
424
+ /* `writeBytes(bytes, off, len)` (nish:io), in runtime-os.c: the bytes
425
+ * `[off, off + len)` of a `u8[]` on stdout, a short write finished and `EINTR`
426
+ * retried. The caller has range-checked `off` and `len`. Answers `len`, or the
427
+ * negative errno of the write that failed; never exits. */
428
+ int32_t nish_write_bytes(const nish_array *bytes, int64_t off, int64_t len);
399
429
  /* `spawnSync(argv)`: run element 0 of `argv` (searched on `PATH`) with `argv`
400
430
  * as its argument vector, wait for it, and answer its exit status, or
401
431
  * `128 + n` when signal `n` killed it. -1 when `argv` is empty, when the
@@ -422,6 +452,17 @@ int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_st
422
452
  * own error otherwise, and -ENOSYS under WASI. The child is reaped by
423
453
  * `nish_wait_pid`. */
424
454
  int32_t nish_spawn_child(const nish_array *argv);
455
+ /* `spawnPiped(argv, stdin, stdout, stderr)`: start element 0 of `argv` as
456
+ * `nish_spawn` does, with the bytes of `stdin` (a `u8[]`) on its standard input
457
+ * and its standard output and error appended to `stdout` and `stderr`, wait for
458
+ * it, and answer `nish_spawn`'s status. -1 is `nish_spawn`'s -1, a pipe or
459
+ * `poll` that fails included, and always under WASI. -2 is a stream that passed
460
+ * 16 MiB: the child is killed with SIGKILL and reaped, and that array holds the
461
+ * stream's first 16 MiB. The two output arrays grow as `push` grows them, so
462
+ * the compiler treats both as pushed. `stdin` is read in place and never
463
+ * copied; SIGPIPE is blocked on the calling thread for the call, a SIGPIPE the
464
+ * call raised is consumed, and the thread's mask is restored after it. */
465
+ int32_t nish_spawn_piped(const nish_array *argv, const nish_array *in, nish_array *out, nish_array *err);
425
466
  /* `kill(pid, sig)`: send `sig` to `pid` and answer 0, or a negative errno. Only
426
467
  * 0, 2 (SIGINT), 9 (SIGKILL) and 15 (SIGTERM) are sent; any other signal, and
427
468
  * a pid of 0 or below, is -EINVAL. -ESRCH when no process has the pid, -EPERM
@@ -438,6 +479,15 @@ nish_array *nish_wait_pid(int32_t pid, int32_t flags);
438
479
  /* `totalMemory()`: bytes of physical memory (Linux: `_SC_PHYS_PAGES` times the
439
480
  * page size; Darwin: `hw.memsize`), or -1 where the host does not say. */
440
481
  int64_t nish_total_memory(void);
482
+ /* `setNice(n)`: this process's nice value set to `n` (`setpriority`), 0 or a
483
+ * negative errno. The kernel clamps `n` to -20..19; lowering it below the
484
+ * current value without the privilege is -EACCES (or -EPERM). -ENOSYS under
485
+ * WASI. */
486
+ int32_t nish_set_nice(int32_t n);
487
+ /* `setIdlePriority()`: this process moved to `SCHED_IDLE` (Linux), 0 or a
488
+ * negative errno; -95 (Linux's EOPNOTSUPP) elsewhere, -ENOSYS under WASI. An
489
+ * unprivileged process cannot leave the policy again. */
490
+ int32_t nish_set_idle_priority(void);
441
491
 
442
492
  /* ---- The environment (WP19 R1) ------------------------------------------
443
493
  * `getenv(name)`: the value of environment variable `name`, copied into the
@@ -485,6 +535,10 @@ const nish_str *nish_arch(void);
485
535
  * purpose: `readnone` would let LLVM fold the two reads around a measured
486
536
  * region into one and measure zero. */
487
537
  int64_t nish_monotonic_nanos(void);
538
+ /* `wallClockNanos()`: `CLOCK_REALTIME` in nanoseconds since the epoch, the
539
+ * clock `Date.now` reads at a millisecond's grain. It steps when the system
540
+ * time is set, so it dates an event and measures nothing. */
541
+ int64_t nish_wall_clock_nanos(void);
488
542
 
489
543
  /* ---- The host (WP34 N3), runtime/runtime-host.c --------------------------
490
544
  * `Date.now()`: the wall clock, `CLOCK_REALTIME`, in whole milliseconds since
@@ -542,6 +596,10 @@ bool nish_is_executable(const nish_str *path);
542
596
  * len)` of `buf`, which the compiled call has range-checked; a write to a
543
597
  * gone peer is -32, never SIGPIPE. `nish_net_shutdown(fd, how)`: 0 read, 1
544
598
  * write, 2 both. `nish_net_close(fd)`.
599
+ * `nish_net_set_option(fd, option, value)`: one socket option from a
600
+ * whitelist, by the runtime's own id (1 `TCP_NODELAY`, 2 `TCP_QUICKACK`, 3
601
+ * `SO_KEEPALIVE`, 4 `SO_SNDBUF`, 5 `SO_RCVBUF`); any other id is -22, and
602
+ * `TCP_QUICKACK` off Linux is -95.
545
603
  * `nish_tcp_connect(addr)`: a new socket connecting to `addr`, answered at
546
604
  * once; its `EINPROGRESS` is not a failure, and any other closes it.
547
605
  * `nish_connect_result(fd)`: `SO_ERROR` once the socket is writable, 0 when
@@ -576,6 +634,7 @@ int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len);
576
634
  int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t len);
577
635
  int32_t nish_net_shutdown(int32_t fd, int32_t how);
578
636
  int32_t nish_net_close(int32_t fd);
637
+ int32_t nish_net_set_option(int32_t fd, int32_t option, int32_t value);
579
638
  int32_t nish_tcp_connect(const nish_array *addr);
580
639
  int32_t nish_connect_result(int32_t fd);
581
640
  int32_t nish_udp_bind(const nish_str *host, int32_t port, int32_t flags);