@amritk/nish-x86_64-linux 0.23.1 → 0.25.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 +50 -23
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/arrays.d.ts +0 -2
- package/runtime/core/arrays.mjs +0 -2
- package/runtime/core/host.d.ts +8 -0
- package/runtime/core/host.mjs +50 -2
- package/runtime/core/numbers.mjs +2 -3
- package/runtime/nish.d.ts +100 -4
- package/runtime/nish.h +34 -0
- package/runtime/nish.mjs +23 -5
- package/runtime/node.mjs +57 -6
- package/runtime/runtime-net.c +45 -4
- package/runtime/runtime-os.c +115 -12
- package/runtime/runtime-term.c +2 -2
- package/runtime/runtime.c +1 -1
- package/scripts/build.sh +13 -3
- package/std/README.md +44 -7
- 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 +290 -0
- package/std/compress/deflate.ts +2790 -0
- package/std/compress/gzip.ts +761 -0
- package/std/compress/zlib.ts +168 -0
- 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/sha256.ts +2 -3
- package/std/crypto/x25519.ts +3 -4
- 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/http-compress.ts +393 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +101 -1
- package/std/net/http1.ts +9 -9
- 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,12 +84,16 @@ 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);
|
|
93
97
|
provide("mkdirSync", shim.mkdirSync);
|
|
94
98
|
provide("isDirectorySync", shim.isDirectorySync);
|
|
95
99
|
provide("readdirSync", shim.readdirSync);
|
|
@@ -103,6 +107,14 @@ provide("kill", shim.kill);
|
|
|
103
107
|
provide("waitPid", shim.waitPid);
|
|
104
108
|
provide("availableParallelism", shim.availableParallelism);
|
|
105
109
|
provide("totalMemory", shim.totalMemory);
|
|
110
|
+
// `setNice` and `setIdlePriority` throw, naming themselves: Node has a reading
|
|
111
|
+
// of the nice value (`os.setPriority`) but none of `SCHED_IDLE`, and a reading
|
|
112
|
+
// of one without the other would differ from the native build on the same host.
|
|
113
|
+
for (const name of ["setNice", "setIdlePriority"]) {
|
|
114
|
+
provide(name, () => {
|
|
115
|
+
throw new Error(`\`${name}\` has no reading under Node: it changes how the kernel schedules the native program`);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
106
118
|
provide("getenv", shim.getenv);
|
|
107
119
|
provide("statMtimeSync", shim.statMtimeSync);
|
|
108
120
|
provide("signalFd", shim.signalFd);
|
|
@@ -130,9 +142,17 @@ for (const name of [
|
|
|
130
142
|
"pollModify",
|
|
131
143
|
"pollRemove",
|
|
132
144
|
"pollWait",
|
|
145
|
+
"netSetOption",
|
|
133
146
|
]) {
|
|
134
147
|
provide(name, () => shim.noNetReading(name));
|
|
135
148
|
}
|
|
149
|
+
// `netSetOption`'s option ids are plain numbers, so they read the same here
|
|
150
|
+
// as natively: only the call that takes one throws.
|
|
151
|
+
provide("NET_TCP_NODELAY", 1);
|
|
152
|
+
provide("NET_TCP_QUICKACK", 2);
|
|
153
|
+
provide("NET_SO_KEEPALIVE", 3);
|
|
154
|
+
provide("NET_SO_SNDBUF", 4);
|
|
155
|
+
provide("NET_SO_RCVBUF", 5);
|
|
136
156
|
|
|
137
157
|
// `nish:term`. Whether a descriptor is a terminal and how big stdout's is have
|
|
138
158
|
// Node's answers; reading stdin, waiting for it and raw mode throw, each naming
|
|
@@ -180,6 +200,7 @@ if (!("set" in Array.prototype)) {
|
|
|
180
200
|
// describes. A printed reading can never match a native run: the two origins are
|
|
181
201
|
// both arbitrary, and only the difference between two reads means anything.
|
|
182
202
|
provide("monotonicNanos", shim.monotonicNanos);
|
|
203
|
+
provide("wallClockNanos", shim.wallClockNanos);
|
|
183
204
|
|
|
184
205
|
// Conversions. In f64 mode `toF64` is the identity and `toI32` is the one that
|
|
185
206
|
// matters (it saturates, where a JavaScript cast would not).
|
|
@@ -265,15 +286,12 @@ globalThis.parseFloat = shim.parseFloat;
|
|
|
265
286
|
// script under Node, which is one index later than Node's own `argv`.
|
|
266
287
|
process.argv = shim.argv();
|
|
267
288
|
|
|
268
|
-
// There is no arena. `mark
|
|
269
|
-
//
|
|
270
|
-
// zero, which is the honest number for a host that is not bump-allocating.
|
|
289
|
+
// There is no arena. `mark()` answers zero rather than failing so that a
|
|
290
|
+
// program which reads it still runs; `used()` answers zero too, which is the honest number for a host that is not bump-allocating.
|
|
271
291
|
// Delegated like everything else here rather than written inline, so that the
|
|
272
292
|
// answers cannot drift from the ones the rewritten runner gives.
|
|
273
293
|
provide("Arena", {
|
|
274
294
|
mark: shim.arenaMark,
|
|
275
|
-
release: shim.arenaRelease,
|
|
276
|
-
reset: shim.arenaReset,
|
|
277
295
|
used: shim.arenaUsed,
|
|
278
296
|
});
|
|
279
297
|
// `using a = arena()` releases nothing here either: what the block allocated
|
package/runtime/node.mjs
CHANGED
|
@@ -72,6 +72,48 @@ 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
|
+
* `writeBytes`'s write, after the core checked the range `[from, to)`: every
|
|
107
|
+
* byte to stdout, then the count, or a negative errno. `fs.writeSync` may
|
|
108
|
+
* write less than it was given, so it is asked again for the rest.
|
|
109
|
+
*/
|
|
110
|
+
function writeBytes(bytes, from, to) {
|
|
111
|
+
const buf = Uint8Array.from(bytes.slice(from, to));
|
|
112
|
+
return errnoOr(buf.length, () => {
|
|
113
|
+
for (let at = 0; at < buf.length; ) at += fs.writeSync(1, buf, at, buf.length - at);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
75
117
|
/**
|
|
76
118
|
* `mkdirSync(path)` (WP14 D4): one directory, not recursive, answering whether
|
|
77
119
|
* a directory is there afterwards. Node throws where the runtime answers
|
|
@@ -234,12 +276,7 @@ function spawnSyncTo(argv, out, err) {
|
|
|
234
276
|
*/
|
|
235
277
|
function kill(pid, sig) {
|
|
236
278
|
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
|
-
}
|
|
279
|
+
return errnoOr(0, () => process.kill(pid, sig));
|
|
243
280
|
}
|
|
244
281
|
|
|
245
282
|
/**
|
|
@@ -275,6 +312,15 @@ function monotonicNanos() {
|
|
|
275
312
|
return process.hrtime.bigint();
|
|
276
313
|
}
|
|
277
314
|
|
|
315
|
+
/**
|
|
316
|
+
* `wallClockNanos()`: the wall clock in nanoseconds since the epoch, a BigInt
|
|
317
|
+
* like `monotonicNanos`'s. Node's wall clock is `Date.now`, whole
|
|
318
|
+
* milliseconds, so the last six digits are zeros where a native run has them.
|
|
319
|
+
*/
|
|
320
|
+
function wallClockNanos() {
|
|
321
|
+
return BigInt(Date.now()) * 1000000n;
|
|
322
|
+
}
|
|
323
|
+
|
|
278
324
|
/**
|
|
279
325
|
* `statMtimeSync(path)`: Node's `mtimeMs` for the path, or NaN when it cannot
|
|
280
326
|
* be stat'd, which is the native answer too. `mtimeMs` is the same arithmetic
|
|
@@ -344,6 +390,10 @@ const CALLS = {
|
|
|
344
390
|
readFileBytesSync,
|
|
345
391
|
writeFileSync,
|
|
346
392
|
appendFileSync,
|
|
393
|
+
writeFileBytesSync,
|
|
394
|
+
renameSync,
|
|
395
|
+
unlinkSync,
|
|
396
|
+
writeBytes,
|
|
347
397
|
mkdirSync,
|
|
348
398
|
isDirectorySync,
|
|
349
399
|
readdirSync,
|
|
@@ -357,6 +407,7 @@ const CALLS = {
|
|
|
357
407
|
availableParallelism,
|
|
358
408
|
totalMemory,
|
|
359
409
|
monotonicNanos,
|
|
410
|
+
wallClockNanos,
|
|
360
411
|
statMtimeSync,
|
|
361
412
|
lstatOwnerModeSync,
|
|
362
413
|
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
|
}
|
|
@@ -359,7 +362,7 @@ int32_t nish_connect_result(int32_t fd) {
|
|
|
359
362
|
|
|
360
363
|
/* `netRead(fd, buf, off, len)`: at most `len` bytes into `buf` from `off`.
|
|
361
364
|
The compiled call has already checked `0 <= off <= off + len <=
|
|
362
|
-
buf.length
|
|
365
|
+
buf.length`. */
|
|
363
366
|
int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len) {
|
|
364
367
|
ssize_t n = recv(fd, buf->data + off, (size_t)len, 0);
|
|
365
368
|
return n < 0 ? nish_net_fail() : (int32_t)n;
|
|
@@ -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. */
|
package/runtime/runtime-os.c
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
/* WASI has no processes, so `spawn.h` and `wait.h` do not exist there. */
|
|
52
52
|
#include <signal.h>
|
|
53
53
|
#include <spawn.h>
|
|
54
|
+
#include <sys/resource.h>
|
|
54
55
|
#include <sys/wait.h>
|
|
55
56
|
extern char **environ;
|
|
56
57
|
#endif
|
|
@@ -178,23 +179,89 @@ nish_str *nish_read_file(const nish_str *path) {
|
|
|
178
179
|
|
|
179
180
|
`O_CLOEXEC` for the reason the read has it (RT-12), and `EINTR` is retried
|
|
180
181
|
as the read retries it (RT-13): a write cut short is finished, and one that
|
|
181
|
-
fails is `cannot write`, never a silently short file.
|
|
182
|
+
fails is `cannot write`, never a silently short file.
|
|
183
|
+
|
|
184
|
+
The loop is `nish_put_all`, which `writeFileBytesSync` and `writeBytes`
|
|
185
|
+
share: all `n` bytes of `p` to `fd`, answering 0 or the negative errno of
|
|
186
|
+
the write that failed (-EIO for one that wrote nothing). */
|
|
187
|
+
static int32_t nish_put_all(int fd, const char *p, uint64_t n) {
|
|
188
|
+
for (uint64_t done = 0; done < n;) {
|
|
189
|
+
ssize_t k = write(fd, p + done, n - done);
|
|
190
|
+
if (k > 0) done += k;
|
|
191
|
+
else if (k == 0) return -EIO;
|
|
192
|
+
else if (errno != EINTR) return -errno;
|
|
193
|
+
}
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
182
196
|
static void nish_put_file(const nish_str *path, const nish_str *data, int flags) {
|
|
183
197
|
int fd = open(nish_cpath(path), O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC | flags, 0644);
|
|
184
|
-
if (fd < 0) nish_io_fail("write ", path);
|
|
185
|
-
for (uint64_t done = 0; done < data->len;) {
|
|
186
|
-
ssize_t n = write(fd, data->data + done, data->len - done);
|
|
187
|
-
if (n > 0) {
|
|
188
|
-
done += n;
|
|
189
|
-
} else if (n == 0 || errno != EINTR) {
|
|
190
|
-
nish_io_fail("write ", path);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
198
|
+
if (fd < 0 || nish_put_all(fd, data->data, data->len) < 0) nish_io_fail("write ", path);
|
|
193
199
|
close(fd);
|
|
194
200
|
}
|
|
195
201
|
void nish_write_file(const nish_str *path, const nish_str *data) { nish_put_file(path, data, O_TRUNC); }
|
|
196
202
|
void nish_append_file(const nish_str *path, const nish_str *data) { nish_put_file(path, data, O_APPEND); }
|
|
197
203
|
|
|
204
|
+
/* ---- Replacing and removing files: `renameSync`, `unlinkSync` and
|
|
205
|
+
`writeFileBytesSync`. Unlike the writers above, none exits: each answers 0
|
|
206
|
+
(or the bytes written) on success and a negative errno otherwise, so a
|
|
207
|
+
program that writes a file beside its target and renames it over the
|
|
208
|
+
target owns its own diagnostic. A path holding a NUL answers -EINVAL before
|
|
209
|
+
any system call, rather than reaching the file its prefix names
|
|
210
|
+
(docs/security/runtime.md, RT-3). */
|
|
211
|
+
|
|
212
|
+
/* `renameSync(from, to)`: rename(2). On one file system it replaces `to`
|
|
213
|
+
atomically, so a reader sees the old file or the new one and never a part;
|
|
214
|
+
across two it answers -EXDEV and moves nothing, since a copy and a delete
|
|
215
|
+
would not be atomic. A link at `from` is moved as a link, not followed. */
|
|
216
|
+
int32_t nish_rename(const nish_str *from, const nish_str *to) {
|
|
217
|
+
const char *f = nish_cpath(from), *t = nish_cpath(to);
|
|
218
|
+
if (f != from->data || t != to->data) return -EINVAL;
|
|
219
|
+
return rename(f, t) == 0 ? 0 : -errno;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* `unlinkSync(path)`: unlink(2). A directory is refused with whatever the
|
|
223
|
+
kernel answers (-EISDIR on Linux, -EPERM elsewhere) and is never handed to
|
|
224
|
+
rmdir. A link is removed, not the file it names. */
|
|
225
|
+
int32_t nish_unlink(const nish_str *path) {
|
|
226
|
+
const char *p = nish_cpath(path);
|
|
227
|
+
if (p != path->data) return -EINVAL;
|
|
228
|
+
return unlink(p) == 0 ? 0 : -errno;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* `writeFileBytesSync(path, bytes)`: every element of a `u8[]` to the file,
|
|
232
|
+
opened as `writeFileSync` opens it (`O_NOFOLLOW`, `O_CLOEXEC`, `O_TRUNC`,
|
|
233
|
+
0644 before the umask) and written through `nish_put_all`, a short write
|
|
234
|
+
finished and `EINTR` retried. It answers the length written or the negative
|
|
235
|
+
errno of the `open` or `write` that failed (-EIO for a write that wrote
|
|
236
|
+
nothing); a failed write leaves the file truncated to what got out. */
|
|
237
|
+
int32_t nish_write_file_bytes(const nish_str *path, const nish_array *bytes) {
|
|
238
|
+
const char *p = nish_cpath(path);
|
|
239
|
+
if (p != path->data) return -EINVAL;
|
|
240
|
+
if (bytes->len > NISH_LENGTH_MAX) return -EFBIG;
|
|
241
|
+
int fd = open(p, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 0644);
|
|
242
|
+
if (fd < 0) return -errno;
|
|
243
|
+
int32_t r = nish_put_all(fd, bytes->data, bytes->len);
|
|
244
|
+
/* A write NFS or a quota refuses can surface only at `close`, and the file
|
|
245
|
+
is then short: an answer of the full length would let the caller rename
|
|
246
|
+
it over the one it replaces. */
|
|
247
|
+
if (close(fd) != 0 && r == 0 && errno != EINTR) r = -errno;
|
|
248
|
+
return r < 0 ? r : (int32_t)bytes->len;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* `writeBytes(bytes, off, len)` (nish:io): the bytes `[off, off + len)` of a
|
|
252
|
+
`u8[]` on stdout, through `nish_put_all`, so a short write is finished and `EINTR` retried. The
|
|
253
|
+
emitter has range-checked `off` and `len` against `bytes->len`
|
|
254
|
+
(`emitNetCall`). It answers `len`, every byte out, or the negative errno of
|
|
255
|
+
the write that failed (`-EIO` for one that wrote nothing), and never exits:
|
|
256
|
+
how much of a failed call reached the descriptor is not said. A pipe whose
|
|
257
|
+
reader has gone raises SIGPIPE, as it does for `write`. */
|
|
258
|
+
int32_t nish_write_bytes(const nish_array *bytes, int64_t off, int64_t len) {
|
|
259
|
+
/* An empty array's data may be NULL, and NULL + 0 is undefined. */
|
|
260
|
+
if (len == 0) return 0;
|
|
261
|
+
int32_t r = nish_put_all(1, bytes->data + off, (uint64_t)len);
|
|
262
|
+
return r < 0 ? r : (int32_t)len;
|
|
263
|
+
}
|
|
264
|
+
|
|
198
265
|
/* ---- Directories and subprocesses (WP14 D4): what a driver needs to create
|
|
199
266
|
its own output directory and shell out for `--link`. Both answer a value
|
|
200
267
|
instead of exiting, as `nish_read_file_or_null` does — there are no
|
|
@@ -477,6 +544,36 @@ int64_t nish_total_memory(void) {
|
|
|
477
544
|
#endif
|
|
478
545
|
}
|
|
479
546
|
|
|
547
|
+
/* `setNice(n)`: this process's nice value set to `n`, 0 or a negative errno.
|
|
548
|
+
The kernel clamps `n` to its range (-20 to 19), so only lowering the value
|
|
549
|
+
below what the process has can fail: -EACCES (Linux, Darwin) or -EPERM
|
|
550
|
+
without the privilege to raise a priority. Linux keeps the value per thread,
|
|
551
|
+
so it is the calling thread's and every thread started after it. */
|
|
552
|
+
int32_t nish_set_nice(int32_t n) {
|
|
553
|
+
#ifdef __wasi__
|
|
554
|
+
(void)n;
|
|
555
|
+
return -ENOSYS;
|
|
556
|
+
#else
|
|
557
|
+
return setpriority(PRIO_PROCESS, 0, n) == 0 ? 0 : -errno;
|
|
558
|
+
#endif
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/* `setIdlePriority()`: this process moved to `SCHED_IDLE`, which runs it only
|
|
562
|
+
when nothing else wants the CPU. Linux alone has the policy; elsewhere the
|
|
563
|
+
answer is -EOPNOTSUPP's Linux number, -95, as runtime-net.c answers it. An
|
|
564
|
+
unprivileged process cannot leave the policy again. Like the nice value, the
|
|
565
|
+
policy is the calling thread's. */
|
|
566
|
+
int32_t nish_set_idle_priority(void) {
|
|
567
|
+
#if defined(__linux__)
|
|
568
|
+
struct sched_param param = {0};
|
|
569
|
+
return sched_setscheduler(0, SCHED_IDLE, ¶m) == 0 ? 0 : -errno;
|
|
570
|
+
#elif defined(__wasi__)
|
|
571
|
+
return -ENOSYS;
|
|
572
|
+
#else
|
|
573
|
+
return -95;
|
|
574
|
+
#endif
|
|
575
|
+
}
|
|
576
|
+
|
|
480
577
|
/* `readdirSync(path)`: the listing a driver needs to discover its own inputs.
|
|
481
578
|
|
|
482
579
|
**Sorted by bytes**, which `readdir(3)` and Node are not, for two reasons
|
|
@@ -545,11 +642,17 @@ nish_array *nish_readdir(const nish_str *path) {
|
|
|
545
642
|
backwards and make an elapsed time negative. Only the difference between two
|
|
546
643
|
reads means anything — the origin is arbitrary and is not comparable across
|
|
547
644
|
processes or machines — and an i64 of nanoseconds is 292 years of it. */
|
|
548
|
-
int64_t
|
|
645
|
+
static int64_t nish_clock_nanos(clockid_t clock) {
|
|
549
646
|
struct timespec ts;
|
|
550
|
-
clock_gettime(
|
|
647
|
+
clock_gettime(clock, &ts);
|
|
551
648
|
return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
|
|
552
649
|
}
|
|
650
|
+
int64_t nish_monotonic_nanos(void) { return nish_clock_nanos(CLOCK_MONOTONIC); }
|
|
651
|
+
|
|
652
|
+
/* `wallClockNanos()`: `CLOCK_REALTIME`, nanoseconds since the Unix epoch, for
|
|
653
|
+
dating an event. It steps when the system time is set, so a duration is
|
|
654
|
+
`monotonicNanos`'s to measure. */
|
|
655
|
+
int64_t nish_wall_clock_nanos(void) { return nish_clock_nanos(CLOCK_REALTIME); }
|
|
553
656
|
|
|
554
657
|
/* A C string copied into the arena, or NULL for NULL: the shape of every
|
|
555
658
|
answer libc hands back by pointer, `getenv`'s here and `realpath`'s below. */
|
package/runtime/runtime-term.c
CHANGED
|
@@ -145,8 +145,8 @@ static int nish_stdin_ready(int32_t timeout_ms) {
|
|
|
145
145
|
|
|
146
146
|
/* `readStdin(buf, off, len)`: at most `len` bytes into `buf` from `off`, the
|
|
147
147
|
count, 0 at the end of the stream and -11 when nothing is waiting. The
|
|
148
|
-
compiled call has already checked `0 <= off <= off + len <= buf.length
|
|
149
|
-
|
|
148
|
+
compiled call has already checked `0 <= off <= off + len <= buf.length`.
|
|
149
|
+
A `read` that a
|
|
150
150
|
signal interrupts is asked again; one that would block (a descriptor some
|
|
151
151
|
other program made non-blocking) is -11 too, and so is a readiness check a
|
|
152
152
|
signal interrupts, which would otherwise fall through to a `read` that
|
package/runtime/runtime.c
CHANGED
|
@@ -1354,7 +1354,7 @@ nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len) {
|
|
|
1354
1354
|
return a;
|
|
1355
1355
|
}
|
|
1356
1356
|
|
|
1357
|
-
/* process.argv: malloc, not the arena, so
|
|
1357
|
+
/* process.argv: malloc, not the arena, so no arena scope can free it. Built once by @main.
|
|
1358
1358
|
Deliberately *not* NISH_TLS (WP20 T0 §3.2): `nish_argv_init` writes it once
|
|
1359
1359
|
before the program's `main` body runs and nothing writes it afterwards, so
|
|
1360
1360
|
every thread may read the one copy. That holds only while no thread is
|
package/scripts/build.sh
CHANGED
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
# put in the .ll (line table, variables) reaches the binary. `nish
|
|
63
63
|
# --link -g` passes it through automatically.
|
|
64
64
|
#
|
|
65
|
+
# Build id: every native ELF binary carries an NT_GNU_BUILD_ID note
|
|
66
|
+
# (--build-id=sha1). A -g link hashes other bytes than a plain one, so strip
|
|
67
|
+
# the -g build to pair a binary with its debug file (docs/guide/using-nish/cli.md).
|
|
68
|
+
#
|
|
65
69
|
# CPU level (WP38 S2): `--cpu x86-64-v3` compiles and links every input --
|
|
66
70
|
# the module, the runtime and any other C -- with -march=x86-64-v3
|
|
67
71
|
# -ffp-contract=off, so the code generator may use AVX2, BMI2 and the other
|
|
@@ -281,8 +285,8 @@ case "$(uname -s)" in
|
|
|
281
285
|
# followed by SIGABRT, so the stage1 that linked went on to abort the
|
|
282
286
|
# moment the bootstrap ran it. Measured too, on the run after the one that
|
|
283
287
|
# named the UUID. An unloadable compiler is worse than any reproducibility,
|
|
284
|
-
# so the UUID stays.
|
|
285
|
-
#
|
|
288
|
+
# so the UUID stays. ELF's twin is the build-id note below, which ELF's
|
|
289
|
+
# loader never asks for and which is a hash of the output's bytes anyway.
|
|
286
290
|
#
|
|
287
291
|
# It costs nothing, because the UUID was measured stable across two links
|
|
288
292
|
# of one input to one output path, and differing on a link to another. That
|
|
@@ -294,8 +298,14 @@ case "$(uname -s)" in
|
|
|
294
298
|
strip_flag=(-Wl,-x) ;;
|
|
295
299
|
*)
|
|
296
300
|
# shellcheck disable=SC2054 # -Wl,<flag> is one argument: the comma is the linker's
|
|
297
|
-
gc=(-Wl,--gc-sections -Wl,--as-needed -Wl,-O2
|
|
301
|
+
gc=(-Wl,--gc-sections -Wl,--as-needed -Wl,-O2); strip_flag=(-s)
|
|
298
302
|
elf=(-fno-plt)
|
|
303
|
+
# A GNU build-id note on every native link, debug included, so perf, gdb
|
|
304
|
+
# and debuginfod can match a stripped binary to its debug file. sha1 is a
|
|
305
|
+
# hash of the output's own bytes in lld, GNU ld and gold, so one input
|
|
306
|
+
# links to one id and `stage3 == stage2` holds; uuid would be random.
|
|
307
|
+
# shellcheck disable=SC2054
|
|
308
|
+
common+=(-Wl,--build-id=sha1)
|
|
299
309
|
# GNU ld needs the gold plugin for LTO; prefer lld when clang can find it.
|
|
300
310
|
if command -v ld.lld >/dev/null 2>&1; then common+=(-fuse-ld=lld); lld=1; fi ;;
|
|
301
311
|
esac
|