@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.
- package/bin/nish +0 -0
- package/docs/AI.md +45 -4
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/host.d.ts +12 -0
- package/runtime/core/host.mjs +74 -2
- package/runtime/nish.d.ts +148 -0
- package/runtime/nish.h +59 -0
- package/runtime/nish.mjs +25 -0
- package/runtime/node.mjs +136 -6
- package/runtime/runtime-net.c +44 -3
- package/runtime/runtime-os.c +351 -18
- package/scripts/build.sh +13 -3
- package/std/README.md +27 -10
- package/std/cli-args.ts +1216 -0
- package/std/cli-flags.ts +585 -0
- package/std/cli.ts +308 -19
- package/std/compress/checksum.ts +132 -20
- package/std/compress/deflate.ts +638 -248
- package/std/compress/gzip.ts +20 -6
- package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
- package/std/crypto/p256.ts +63 -1
- package/std/crypto/p384.ts +6044 -0
- package/std/crypto/pki-roots.ts +322 -0
- package/std/crypto/pki.ts +1207 -0
- package/std/crypto/rsa.ts +663 -0
- package/std/crypto/x509.ts +1054 -58
- package/std/net/dns-config.ts +734 -0
- package/std/net/dns-message.ts +517 -0
- package/std/net/dns-resolver.ts +1015 -0
- package/std/net/dns.ts +478 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +7 -2
- package/std/net/http1.ts +9 -9
- package/std/net/postgres.ts +259 -26
- package/std/net/tls/client.ts +1094 -0
- package/std/net/tls/codec.ts +585 -3
- package/std/net/tls/record-client.ts +1192 -0
- package/std/net/tls/verify.ts +221 -0
- package/std/net/tls-tcp.ts +642 -11
- package/std/net/tls.ts +2 -2
- package/std/net/url.ts +559 -4
- 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
|
-
|
|
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,
|
package/runtime/runtime-net.c
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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. */
|