@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/runtime/nish.mjs CHANGED
@@ -84,18 +84,26 @@ globalThis.console = {
84
84
  // `Array#sort`'s UTF-16 order — the two agree on ASCII names only.
85
85
  provide("write", shim.write);
86
86
  provide("writeError", shim.writeError);
87
+ provide("writeBytes", shim.writeBytes);
87
88
  provide("panic", shim.panic);
88
89
  provide("readFileSync", shim.readFileSync);
89
90
  provide("readFileSyncOrNull", shim.readFileSyncOrNull);
90
91
  provide("readFileBytesSync", shim.readFileBytesSync);
91
92
  provide("writeFileSync", shim.writeFileSync);
92
93
  provide("appendFileSync", shim.appendFileSync);
94
+ provide("writeFileBytesSync", shim.writeFileBytesSync);
95
+ provide("renameSync", shim.renameSync);
96
+ provide("unlinkSync", shim.unlinkSync);
97
+ provide("writeFileSyncMode", shim.writeFileSyncMode);
98
+ provide("createFileExclusiveSync", shim.createFileExclusiveSync);
99
+ provide("mkdirSyncMode", shim.mkdirSyncMode);
93
100
  provide("mkdirSync", shim.mkdirSync);
94
101
  provide("isDirectorySync", shim.isDirectorySync);
95
102
  provide("readdirSync", shim.readdirSync);
96
103
  provide("realpathSync", shim.realpathSync);
97
104
  provide("spawnSync", shim.spawnSync);
98
105
  provide("spawnSyncTo", shim.spawnSyncTo);
106
+ provide("spawnPiped", shim.spawnPiped);
99
107
  // WP34 N12. `spawn` and `waitPid` throw, naming themselves: a child is reaped
100
108
  // by Node's event loop. `kill`, `availableParallelism` and `totalMemory` answer.
101
109
  provide("spawn", shim.spawn);
@@ -103,6 +111,14 @@ provide("kill", shim.kill);
103
111
  provide("waitPid", shim.waitPid);
104
112
  provide("availableParallelism", shim.availableParallelism);
105
113
  provide("totalMemory", shim.totalMemory);
114
+ // `setNice` and `setIdlePriority` throw, naming themselves: Node has a reading
115
+ // of the nice value (`os.setPriority`) but none of `SCHED_IDLE`, and a reading
116
+ // of one without the other would differ from the native build on the same host.
117
+ for (const name of ["setNice", "setIdlePriority"]) {
118
+ provide(name, () => {
119
+ throw new Error(`\`${name}\` has no reading under Node: it changes how the kernel schedules the native program`);
120
+ });
121
+ }
106
122
  provide("getenv", shim.getenv);
107
123
  provide("statMtimeSync", shim.statMtimeSync);
108
124
  provide("signalFd", shim.signalFd);
@@ -130,9 +146,17 @@ for (const name of [
130
146
  "pollModify",
131
147
  "pollRemove",
132
148
  "pollWait",
149
+ "netSetOption",
133
150
  ]) {
134
151
  provide(name, () => shim.noNetReading(name));
135
152
  }
153
+ // `netSetOption`'s option ids are plain numbers, so they read the same here
154
+ // as natively: only the call that takes one throws.
155
+ provide("NET_TCP_NODELAY", 1);
156
+ provide("NET_TCP_QUICKACK", 2);
157
+ provide("NET_SO_KEEPALIVE", 3);
158
+ provide("NET_SO_SNDBUF", 4);
159
+ provide("NET_SO_RCVBUF", 5);
136
160
 
137
161
  // `nish:term`. Whether a descriptor is a terminal and how big stdout's is have
138
162
  // Node's answers; reading stdin, waiting for it and raw mode throw, each naming
@@ -180,6 +204,7 @@ if (!("set" in Array.prototype)) {
180
204
  // describes. A printed reading can never match a native run: the two origins are
181
205
  // both arbitrary, and only the difference between two reads means anything.
182
206
  provide("monotonicNanos", shim.monotonicNanos);
207
+ provide("wallClockNanos", shim.wallClockNanos);
183
208
 
184
209
  // Conversions. In f64 mode `toF64` is the identity and `toI32` is the one that
185
210
  // matters (it saturates, where a JavaScript cast would not).
package/runtime/node.mjs CHANGED
@@ -72,6 +72,88 @@ function appendFileSync(path, data) {
72
72
  }
73
73
  }
74
74
 
75
+ /**
76
+ * The negative errno a failed `fs` call names, as `runtime-os.c` answers it.
77
+ * Node names the errno by its code; one it gives no number for, such as the
78
+ * `ERR_INVALID_ARG_VALUE` a path holding a NUL throws, is -22 (`EINVAL`), the
79
+ * runtime's answer to that path.
80
+ */
81
+ function negErrno(e) {
82
+ return -(os.constants.errno[e.code] ?? 22);
83
+ }
84
+
85
+ /** `ok` when `f` returns, or the negative errno of what it threw. */
86
+ function errnoOr(ok, f) {
87
+ try {
88
+ f();
89
+ return ok;
90
+ } catch (e) {
91
+ return negErrno(e);
92
+ }
93
+ }
94
+
95
+ /** `renameSync(from, to)`: 0, or a negative errno (-18 across file systems). */
96
+ const renameSync = (from, to) => errnoOr(0, () => fs.renameSync(from, to));
97
+
98
+ /** `unlinkSync(path)`: 0, or a negative errno; a directory is refused, not removed. */
99
+ const unlinkSync = (path) => errnoOr(0, () => fs.unlinkSync(path));
100
+
101
+ /** `writeFileBytesSync(path, bytes)`: the byte count, or a negative errno; mode 0644 and `WRITE_FLAGS`. */
102
+ const writeFileBytesSync = (path, bytes) =>
103
+ errnoOr(bytes.length, () => fs.writeFileSync(path, Uint8Array.from(bytes), { flag: WRITE_FLAGS, mode: 0o644 }));
104
+
105
+ /**
106
+ * `writeFileSyncMode` and `createFileExclusiveSync`'s shared body, as
107
+ * `runtime-os.c`'s: the file opened with `flag`, `fchmod`ed to exactly `mode`
108
+ * before a byte is written, then every byte of `data`, then the count, or a
109
+ * negative errno. A mode outside 0..0o777 is -22 before the file is touched.
110
+ * After an exclusive create (`"wx"`), any failure unlinks the file it made.
111
+ */
112
+ function putFileMode(path, data, flag, mode) {
113
+ if (!(mode >= 0 && mode <= 0o777)) return -22;
114
+ let fd;
115
+ try {
116
+ fd = fs.openSync(path, flag, mode);
117
+ } catch (e) {
118
+ return negErrno(e);
119
+ }
120
+ let r;
121
+ try {
122
+ fs.fchmodSync(fd, mode);
123
+ const buf = Buffer.from(data, "utf8");
124
+ for (let done = 0; done < buf.length; ) done += fs.writeSync(fd, buf, done);
125
+ r = buf.length;
126
+ } catch (e) {
127
+ r = negErrno(e);
128
+ }
129
+ const closed = errnoOr(0, () => fs.closeSync(fd));
130
+ const answer = r >= 0 && closed < 0 ? closed : r;
131
+ if (answer < 0 && flag === "wx") errnoOr(0, () => fs.unlinkSync(path));
132
+ return answer;
133
+ }
134
+
135
+ /** `writeFileSyncMode(path, data, mode)`: `WRITE_FLAGS`, so a link at the path is refused. */
136
+ const writeFileSyncMode = (path, data, mode) => putFileMode(path, data, WRITE_FLAGS, mode);
137
+
138
+ /** `createFileExclusiveSync(path, data, mode)`: `"wx"`, -17 when anything is at the path. */
139
+ const createFileExclusiveSync = (path, data, mode) => putFileMode(path, data, "wx", mode);
140
+
141
+ /** `mkdirSyncMode(path, mode)`: 0, or a negative errno; -17 for a path already there. */
142
+ const mkdirSyncMode = (path, mode) =>
143
+ mode >= 0 && mode <= 0o777 ? errnoOr(0, () => fs.mkdirSync(path, { mode })) : -22;
144
+
145
+ /**
146
+ * `writeBytes`'s write, after the core checked the range `[from, to)`: every
147
+ * byte to stdout, then the count, or a negative errno. `fs.writeSync` may
148
+ * write less than it was given, so it is asked again for the rest.
149
+ */
150
+ function writeBytes(bytes, from, to) {
151
+ const buf = Uint8Array.from(bytes.slice(from, to));
152
+ return errnoOr(buf.length, () => {
153
+ for (let at = 0; at < buf.length; ) at += fs.writeSync(1, buf, at, buf.length - at);
154
+ });
155
+ }
156
+
75
157
  /**
76
158
  * `mkdirSync(path)` (WP14 D4): one directory, not recursive, answering whether
77
159
  * a directory is there afterwards. Node throws where the runtime answers
@@ -226,6 +308,41 @@ function spawnSyncTo(argv, out, err) {
226
308
  return spawnImpl(argv, out, err);
227
309
  }
228
310
 
311
+ /** The bytes `spawnPiped` keeps of one stream, as `runtime-os.c`'s `NISH_PIPED_CAP`. */
312
+ const PIPED_CAP = 16 * 1024 * 1024;
313
+
314
+ /**
315
+ * `spawnPiped(argv, stdin)`'s run, after the core took it apart: the status
316
+ * and the two streams' bytes, which the core appends to the caller's arrays.
317
+ * The status is `spawnImpl`'s, and -2 when a stream passed `PIPED_CAP`: Node
318
+ * then kills the child with `SIGKILL`, as the runtime does, and reports
319
+ * `ENOBUFS`, and what it read is cut to the cap here as the runtime keeps it.
320
+ * A child that exits without reading its stdin is not an error on either side.
321
+ */
322
+ function spawnPiped(argv, input) {
323
+ if (argv.length === 0) return [-1, [], []];
324
+ let r;
325
+ // Node throws, rather than reporting, for an empty command or a NUL in any
326
+ // argument, which the runtime answers -1 for without starting a child.
327
+ try {
328
+ r = child_process.spawnSync(argv[0], argv.slice(1), {
329
+ input: Uint8Array.from(input),
330
+ maxBuffer: PIPED_CAP,
331
+ killSignal: "SIGKILL",
332
+ });
333
+ } catch {
334
+ return [-1, [], []];
335
+ }
336
+ const bytes = (b) => (b === null || b === undefined ? [] : Array.from(b.subarray(0, PIPED_CAP)));
337
+ const out = bytes(r.stdout);
338
+ const err = bytes(r.stderr);
339
+ // `EPIPE` is the child closing its stdin unread, which Node reports with
340
+ // the status it still waited for; the runtime ignores it the same way.
341
+ if (r.error !== undefined && r.error.code !== "EPIPE") return [r.error.code === "ENOBUFS" ? -2 : -1, out, err];
342
+ if (r.signal !== null && r.signal !== undefined) return [128 + (os.constants.signals[r.signal] ?? 0), out, err];
343
+ return [r.status === null ? -1 : r.status, out, err];
344
+ }
345
+
229
346
  /**
230
347
  * `kill(pid, sig)`: 0, or a negative errno, as `runtime-os.c`'s `nish_kill`
231
348
  * answers. Only 0, 2, 9 and 15 are sent and a pid of 0 or below is refused,
@@ -234,12 +351,7 @@ function spawnSyncTo(argv, out, err) {
234
351
  */
235
352
  function kill(pid, sig) {
236
353
  if (pid <= 0 || !(sig === 0 || sig === 2 || sig === 9 || sig === 15)) return -22;
237
- try {
238
- process.kill(pid, sig);
239
- return 0;
240
- } catch (e) {
241
- return -(os.constants.errno[e.code] ?? 22);
242
- }
354
+ return errnoOr(0, () => process.kill(pid, sig));
243
355
  }
244
356
 
245
357
  /**
@@ -275,6 +387,15 @@ function monotonicNanos() {
275
387
  return process.hrtime.bigint();
276
388
  }
277
389
 
390
+ /**
391
+ * `wallClockNanos()`: the wall clock in nanoseconds since the epoch, a BigInt
392
+ * like `monotonicNanos`'s. Node's wall clock is `Date.now`, whole
393
+ * milliseconds, so the last six digits are zeros where a native run has them.
394
+ */
395
+ function wallClockNanos() {
396
+ return BigInt(Date.now()) * 1000000n;
397
+ }
398
+
278
399
  /**
279
400
  * `statMtimeSync(path)`: Node's `mtimeMs` for the path, or NaN when it cannot
280
401
  * be stat'd, which is the native answer too. `mtimeMs` is the same arithmetic
@@ -344,6 +465,13 @@ const CALLS = {
344
465
  readFileBytesSync,
345
466
  writeFileSync,
346
467
  appendFileSync,
468
+ writeFileBytesSync,
469
+ renameSync,
470
+ unlinkSync,
471
+ writeFileSyncMode,
472
+ createFileExclusiveSync,
473
+ mkdirSyncMode,
474
+ writeBytes,
347
475
  mkdirSync,
348
476
  isDirectorySync,
349
477
  readdirSync,
@@ -353,10 +481,12 @@ const CALLS = {
353
481
  getenv,
354
482
  spawnSync,
355
483
  spawnSyncTo,
484
+ spawnPiped,
356
485
  kill,
357
486
  availableParallelism,
358
487
  totalMemory,
359
488
  monotonicNanos,
489
+ wallClockNanos,
360
490
  statMtimeSync,
361
491
  lstatOwnerModeSync,
362
492
  geteuid,
@@ -17,7 +17,8 @@
17
17
  * errno to it: where `signalFd` and `readSignal` answer a plain -1, a call
18
18
  * here answers the failure's errno, negated. The codes a loop branches on are Linux's numbers on every platform (-11
19
19
  * would block, -95 unsupported, -32 the peer is gone, -104 reset, -98 the
20
- * address is in use, -111 refused, -110 timed out, -22 a bad argument), so
20
+ * address is in use, -111 refused, -110 timed out, -22 a bad argument, -88
21
+ * not a socket), so
21
22
  * `nish_net_err` translates Darwin's; any other failure is the host's own
22
23
  * `-errno`. Nothing here allocates: the addresses a call reads or writes are
23
24
  * the caller's `u8[]`.
@@ -29,13 +30,14 @@
29
30
  * (`EAFNOSUPPORT`, as in many containers) `::` falls back to IPv4's
30
31
  * `0.0.0.0`.
31
32
  *
32
- * Linux and Darwin differ in five places: `SOCK_NONBLOCK | SOCK_CLOEXEC` and
33
+ * Linux and Darwin differ in six places: `SOCK_NONBLOCK | SOCK_CLOEXEC` and
33
34
  * `accept4` against a `socket` or `accept` followed by `fcntl`, `MSG_NOSIGNAL`
34
35
  * against the `SO_NOSIGPIPE` socket option (a write to a gone peer is -32 on
35
36
  * both, never SIGPIPE), the errno numbers, UDP's segmentation offload
36
37
  * (`UDP_SEGMENT`, `UDP_GRO`) and ECN marks, which Linux has and for which
37
38
  * Darwin answers -95, and the readiness loop, which is epoll on Linux and
38
- * kqueue on Darwin. Only the sending side and the GRO bind say -95 there: a
39
+ * kqueue on Darwin, and `TCP_QUICKACK`, which only Linux has (-95 on
40
+ * Darwin). Only the sending side and the GRO bind say -95 there: a
39
41
  * receive leaves `meta` at 0, 0, and a `meta[1]` of 0 is also what a real
40
42
  * Not-ECT datagram reads as, so a program learns that Darwin has no ECN from
41
43
  * the send, never from the receive. The Darwin branch is compiled by CI's Darwin bootstrap
@@ -100,6 +102,7 @@ static int32_t nish_net_err(int e) {
100
102
  if (e == EADDRINUSE) return -98;
101
103
  if (e == ECONNREFUSED) return -111;
102
104
  if (e == ETIMEDOUT) return -110;
105
+ if (e == ENOTSOCK) return -88;
103
106
  #endif
104
107
  return -e;
105
108
  }
@@ -383,6 +386,44 @@ int32_t nish_net_shutdown(int32_t fd, int32_t how) {
383
386
  promises on Linux, so a failure is information rather than a retry. */
384
387
  int32_t nish_net_close(int32_t fd) { return close(fd) != 0 ? nish_net_fail() : 0; }
385
388
 
389
+ /* `netSetOption(fd, option, value)`: one option from a whitelist, named by
390
+ the runtime's own id rather than a level and an option number, so a
391
+ program can set these five and nothing else. `value` is 0 or 1 for the
392
+ flags and a byte count for the buffers, passed to the kernel as it is
393
+ (Linux stores twice a buffer size, to cover its bookkeeping). Linux does
394
+ not keep quick-ACK mode: the kernel leaves it of its own accord, so a
395
+ server that wants it on or off sets it again after each response. */
396
+ int32_t nish_net_set_option(int32_t fd, int32_t option, int32_t value) {
397
+ int level = SOL_SOCKET;
398
+ int name;
399
+ switch (option) {
400
+ case 1:
401
+ level = IPPROTO_TCP;
402
+ name = TCP_NODELAY;
403
+ break;
404
+ case 2:
405
+ #if defined(__linux__)
406
+ level = IPPROTO_TCP;
407
+ name = TCP_QUICKACK;
408
+ break;
409
+ #else
410
+ return -95;
411
+ #endif
412
+ case 3:
413
+ name = SO_KEEPALIVE;
414
+ break;
415
+ case 4:
416
+ name = SO_SNDBUF;
417
+ break;
418
+ case 5:
419
+ name = SO_RCVBUF;
420
+ break;
421
+ default:
422
+ return -22;
423
+ }
424
+ return setsockopt(fd, level, name, &value, sizeof value) != 0 ? nish_net_fail() : 0;
425
+ }
426
+
386
427
  /* `udpBind(host, port, flags)`: a datagram socket, non-blocking and
387
428
  close-on-exec, bound like `tcpListen`'s, with `SO_REUSEPORT` for flag 1
388
429
  and `UDP_GRO` for flag 2; any other bit is -22. */