@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/runtime-os.c
CHANGED
|
@@ -49,8 +49,10 @@
|
|
|
49
49
|
#include <unistd.h>
|
|
50
50
|
#ifndef __wasi__
|
|
51
51
|
/* WASI has no processes, so `spawn.h` and `wait.h` do not exist there. */
|
|
52
|
+
#include <poll.h>
|
|
52
53
|
#include <signal.h>
|
|
53
54
|
#include <spawn.h>
|
|
55
|
+
#include <sys/resource.h>
|
|
54
56
|
#include <sys/wait.h>
|
|
55
57
|
extern char **environ;
|
|
56
58
|
#endif
|
|
@@ -169,6 +171,19 @@ nish_str *nish_read_file(const nish_str *path) {
|
|
|
169
171
|
return s;
|
|
170
172
|
}
|
|
171
173
|
|
|
174
|
+
/* All `n` bytes of `p` to `fd`, answering 0 or the negative errno of the
|
|
175
|
+
write that failed (-EIO for one that wrote nothing). A write cut short is
|
|
176
|
+
finished and `EINTR` retried (RT-13). Every writer below shares it. */
|
|
177
|
+
static int32_t nish_put_all(int fd, const char *p, uint64_t n) {
|
|
178
|
+
for (uint64_t done = 0; done < n;) {
|
|
179
|
+
ssize_t k = write(fd, p + done, n - done);
|
|
180
|
+
if (k > 0) done += k;
|
|
181
|
+
else if (k == 0) return -EIO;
|
|
182
|
+
else if (errno != EINTR) return -errno;
|
|
183
|
+
}
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
172
187
|
/* `O_NOFOLLOW` (docs/security/runtime.md, RT-4): a symbolic link as the last
|
|
173
188
|
component of the path is refused, as an unwritable file is, rather than
|
|
174
189
|
followed to whatever file it names. `nish file.ts` writes `file.ll` beside
|
|
@@ -176,25 +191,145 @@ nish_str *nish_read_file(const nish_str *path) {
|
|
|
176
191
|
truncate any file the user can write. A link among the directories on the
|
|
177
192
|
way is still followed.
|
|
178
193
|
|
|
179
|
-
`O_CLOEXEC` for the reason the read has it (RT-12), and
|
|
180
|
-
|
|
181
|
-
|
|
194
|
+
`O_CLOEXEC` for the reason the read has it (RT-12), and the bytes go out
|
|
195
|
+
through `nish_put_all`, so a write that fails is `cannot write`, never a
|
|
196
|
+
silently short file. */
|
|
182
197
|
static void nish_put_file(const nish_str *path, const nish_str *data, int flags) {
|
|
183
198
|
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
|
-
}
|
|
199
|
+
if (fd < 0 || nish_put_all(fd, data->data, data->len) < 0) nish_io_fail("write ", path);
|
|
193
200
|
close(fd);
|
|
194
201
|
}
|
|
195
202
|
void nish_write_file(const nish_str *path, const nish_str *data) { nish_put_file(path, data, O_TRUNC); }
|
|
196
203
|
void nish_append_file(const nish_str *path, const nish_str *data) { nish_put_file(path, data, O_APPEND); }
|
|
197
204
|
|
|
205
|
+
/* ---- Replacing and removing files: `renameSync`, `unlinkSync` and
|
|
206
|
+
`writeFileBytesSync`. Unlike the writers above, none exits: each answers 0
|
|
207
|
+
(or the bytes written) on success and a negative errno otherwise, so a
|
|
208
|
+
program that writes a file beside its target and renames it over the
|
|
209
|
+
target owns its own diagnostic. A path holding a NUL answers -EINVAL before
|
|
210
|
+
any system call, rather than reaching the file its prefix names
|
|
211
|
+
(docs/security/runtime.md, RT-3). */
|
|
212
|
+
|
|
213
|
+
/* `renameSync(from, to)`: rename(2). On one file system it replaces `to`
|
|
214
|
+
atomically, so a reader sees the old file or the new one and never a part;
|
|
215
|
+
across two it answers -EXDEV and moves nothing, since a copy and a delete
|
|
216
|
+
would not be atomic. A link at `from` is moved as a link, not followed. */
|
|
217
|
+
int32_t nish_rename(const nish_str *from, const nish_str *to) {
|
|
218
|
+
const char *f = nish_cpath(from), *t = nish_cpath(to);
|
|
219
|
+
if (f != from->data || t != to->data) return -EINVAL;
|
|
220
|
+
return rename(f, t) == 0 ? 0 : -errno;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* `unlinkSync(path)`: unlink(2). A directory is refused with whatever the
|
|
224
|
+
kernel answers (-EISDIR on Linux, -EPERM elsewhere) and is never handed to
|
|
225
|
+
rmdir. A link is removed, not the file it names. */
|
|
226
|
+
int32_t nish_unlink(const nish_str *path) {
|
|
227
|
+
const char *p = nish_cpath(path);
|
|
228
|
+
if (p != path->data) return -EINVAL;
|
|
229
|
+
return unlink(p) == 0 ? 0 : -errno;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* `writeFileBytesSync(path, bytes)`: every element of a `u8[]` to the file,
|
|
233
|
+
opened as `writeFileSync` opens it (`O_NOFOLLOW`, `O_CLOEXEC`, `O_TRUNC`,
|
|
234
|
+
0644 before the umask) and written through `nish_put_all`, a short write
|
|
235
|
+
finished and `EINTR` retried. It answers the length written or the negative
|
|
236
|
+
errno of the `open` or `write` that failed (-EIO for a write that wrote
|
|
237
|
+
nothing); a failed write leaves the file truncated to what got out. */
|
|
238
|
+
int32_t nish_write_file_bytes(const nish_str *path, const nish_array *bytes) {
|
|
239
|
+
const char *p = nish_cpath(path);
|
|
240
|
+
if (p != path->data) return -EINVAL;
|
|
241
|
+
if (bytes->len > NISH_LENGTH_MAX) return -EFBIG;
|
|
242
|
+
int fd = open(p, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 0644);
|
|
243
|
+
if (fd < 0) return -errno;
|
|
244
|
+
int32_t r = nish_put_all(fd, bytes->data, bytes->len);
|
|
245
|
+
/* A write NFS or a quota refuses can surface only at `close`, and the file
|
|
246
|
+
is then short: an answer of the full length would let the caller rename
|
|
247
|
+
it over the one it replaces. */
|
|
248
|
+
if (close(fd) != 0 && r == 0 && errno != EINTR) r = -errno;
|
|
249
|
+
return r < 0 ? r : (int32_t)bytes->len;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* ---- Files and directories with an explicit mode (docs/security/runtime.md,
|
|
253
|
+
RT-24 to RT-26): `writeFileSyncMode`, `createFileExclusiveSync` and
|
|
254
|
+
`mkdirSyncMode`. Each answers a value and never exits, as the writers just
|
|
255
|
+
above do. A mode outside 0..0777 is -EINVAL before any system call: the
|
|
256
|
+
setuid, setgid and sticky bits are refused, not applied, so the mode asked
|
|
257
|
+
for is the mode the file has. WASI
|
|
258
|
+
has no file modes to apply, so there each answers -ENOSYS and creates
|
|
259
|
+
nothing rather than a file whose mode was not set. */
|
|
260
|
+
|
|
261
|
+
/* The two file writers' shared body: `open(p, O_WRONLY | O_CREAT |
|
|
262
|
+
O_NOFOLLOW | O_CLOEXEC | flags, mode)`, then `fchmod(fd, mode)` before a
|
|
263
|
+
byte is written, so the file is exactly `mode` whatever the umask and
|
|
264
|
+
whatever mode a file already there had: no reader that opens it while it is
|
|
265
|
+
written finds it wider (RT-24). Then the data through `nish_put_all` and a
|
|
266
|
+
checked `close`, as `nish_write_file_bytes` does. The open truncates
|
|
267
|
+
before the `fchmod`, so a file there that this process may write but does
|
|
268
|
+
not own is emptied and then answers -EPERM. When `flags` holds `O_EXCL`,
|
|
269
|
+
any failure after the create unlinks the file this call made, so a lock
|
|
270
|
+
whose write failed does not stay taken. */
|
|
271
|
+
static int32_t nish_put_file_mode(const nish_str *path, const nish_str *data, int flags, int32_t mode) {
|
|
272
|
+
const char *p = nish_cpath(path);
|
|
273
|
+
if (p != path->data || mode < 0 || mode > 0777) return -EINVAL;
|
|
274
|
+
if (data->len > NISH_LENGTH_MAX) return -EFBIG;
|
|
275
|
+
#ifdef __wasi__
|
|
276
|
+
(void)flags;
|
|
277
|
+
return -ENOSYS;
|
|
278
|
+
#else
|
|
279
|
+
int fd = open(p, O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC | flags, (mode_t)mode);
|
|
280
|
+
if (fd < 0) return -errno;
|
|
281
|
+
int32_t r = fchmod(fd, (mode_t)mode) == 0 ? 0 : -errno;
|
|
282
|
+
if (r == 0) r = nish_put_all(fd, data->data, data->len);
|
|
283
|
+
if (close(fd) != 0 && r == 0 && errno != EINTR) r = -errno;
|
|
284
|
+
if (r < 0 && (flags & O_EXCL)) unlink(p);
|
|
285
|
+
return r < 0 ? r : (int32_t)data->len;
|
|
286
|
+
#endif
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* `writeFileSyncMode(path, data, mode)`: `writeFileSync` with the mode given
|
|
290
|
+
and an answer instead of an exit. A link at the path is refused (-ELOOP on
|
|
291
|
+
Linux) and the file it names is untouched (RT-25). */
|
|
292
|
+
int32_t nish_write_file_mode(const nish_str *path, const nish_str *data, int32_t mode) {
|
|
293
|
+
return nish_put_file_mode(path, data, O_TRUNC, mode);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* `createFileExclusiveSync(path, data, mode)`: `O_EXCL`, so the path is
|
|
297
|
+
created by this call or the call answers -EEXIST, a link there included,
|
|
298
|
+
dangling or not. Of N processes racing for one path exactly one wins, which
|
|
299
|
+
is what a lock file needs; a winner whose write then fails removes the file
|
|
300
|
+
and answers the error. */
|
|
301
|
+
int32_t nish_create_file_exclusive(const nish_str *path, const nish_str *data, int32_t mode) {
|
|
302
|
+
return nish_put_file_mode(path, data, O_EXCL, mode);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* `mkdirSyncMode(path, mode)`: mkdir(2) with `mode`, one directory, not
|
|
306
|
+
recursive. A path already there is -EEXIST and its mode is left as it was.
|
|
307
|
+
The kernel applies the umask, so the directory is `mode & ~umask`: 0700 is
|
|
308
|
+
exact under any umask that leaves the owner's bits (RT-26). */
|
|
309
|
+
int32_t nish_mkdir_mode(const nish_str *path, int32_t mode) {
|
|
310
|
+
const char *p = nish_cpath(path);
|
|
311
|
+
if (p != path->data || mode < 0 || mode > 0777) return -EINVAL;
|
|
312
|
+
#ifdef __wasi__
|
|
313
|
+
return -ENOSYS;
|
|
314
|
+
#else
|
|
315
|
+
return mkdir(p, (mode_t)mode) == 0 ? 0 : -errno;
|
|
316
|
+
#endif
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* `writeBytes(bytes, off, len)` (nish:io): the bytes `[off, off + len)` of a
|
|
320
|
+
`u8[]` on stdout, through `nish_put_all`, so a short write is finished and `EINTR` retried. The
|
|
321
|
+
emitter has range-checked `off` and `len` against `bytes->len`
|
|
322
|
+
(`emitNetCall`). It answers `len`, every byte out, or the negative errno of
|
|
323
|
+
the write that failed (`-EIO` for one that wrote nothing), and never exits:
|
|
324
|
+
how much of a failed call reached the descriptor is not said. A pipe whose
|
|
325
|
+
reader has gone raises SIGPIPE, as it does for `write`. */
|
|
326
|
+
int32_t nish_write_bytes(const nish_array *bytes, int64_t off, int64_t len) {
|
|
327
|
+
/* An empty array's data may be NULL, and NULL + 0 is undefined. */
|
|
328
|
+
if (len == 0) return 0;
|
|
329
|
+
int32_t r = nish_put_all(1, bytes->data + off, (uint64_t)len);
|
|
330
|
+
return r < 0 ? r : (int32_t)len;
|
|
331
|
+
}
|
|
332
|
+
|
|
198
333
|
/* ---- Directories and subprocesses (WP14 D4): what a driver needs to create
|
|
199
334
|
its own output directory and shell out for `--link`. Both answer a value
|
|
200
335
|
instead of exiting, as `nish_read_file_or_null` does — there are no
|
|
@@ -223,17 +358,19 @@ _Bool nish_mkdir(const nish_str *path) {
|
|
|
223
358
|
value rather than through a child that has already run.
|
|
224
359
|
|
|
225
360
|
`out` and `err` are paths for the child's stdout and stderr, or NULL to let
|
|
226
|
-
it inherit this process's
|
|
361
|
+
it inherit this process's; `dup`, when not NULL, is three descriptors the
|
|
362
|
+
child takes as its stdin, stdout and stderr instead (`spawnPiped`'s pipes). Every spawn builtin starts its child here, so the
|
|
227
363
|
argv vector and the redirects are written once; it is `static`, so a program
|
|
228
364
|
that spawns nothing loses it to `--gc-sections`. The answer is the child's
|
|
229
365
|
pid, or a negative errno: -EINVAL for an empty argv or an argument holding a
|
|
230
366
|
NUL, and `posix_spawnp`'s own error otherwise (-ENOENT for a command that is
|
|
231
367
|
not on PATH, on glibc). */
|
|
232
|
-
static int32_t nish_spawn_start(const nish_array *argv, const nish_str *out, const nish_str *err) {
|
|
368
|
+
static int32_t nish_spawn_start(const nish_array *argv, const nish_str *out, const nish_str *err, const int *dup) {
|
|
233
369
|
#ifdef __wasi__
|
|
234
370
|
(void)argv;
|
|
235
371
|
(void)out;
|
|
236
372
|
(void)err;
|
|
373
|
+
(void)dup;
|
|
237
374
|
return -ENOSYS;
|
|
238
375
|
#else
|
|
239
376
|
if (!argv->len) return -EINVAL; /* argv[0] would read past an empty array */
|
|
@@ -256,6 +393,13 @@ static int32_t nish_spawn_start(const nish_array *argv, const nish_str *out, con
|
|
|
256
393
|
posix_spawn_file_actions_t fa;
|
|
257
394
|
int failed = posix_spawn_file_actions_init(&fa);
|
|
258
395
|
if (failed) return -failed;
|
|
396
|
+
/* A `dup2` action that could not be recorded would start the child on this
|
|
397
|
+
process's own stream in its place, so it fails the spawn. */
|
|
398
|
+
for (int k = 0; dup && k < 3 && !failed; k++) failed = posix_spawn_file_actions_adddup2(&fa, dup[k], k);
|
|
399
|
+
if (failed) {
|
|
400
|
+
posix_spawn_file_actions_destroy(&fa);
|
|
401
|
+
return -failed;
|
|
402
|
+
}
|
|
259
403
|
const nish_str *to[2] = { out, err };
|
|
260
404
|
for (int k = 0; k < 2; k++) {
|
|
261
405
|
/* The child opens the file, not this process: a redirect that this process
|
|
@@ -281,7 +425,7 @@ static int32_t nish_exit_code(int status) {
|
|
|
281
425
|
/* The two blocking builtins: start, then wait for that one child. A failed
|
|
282
426
|
start is -1, the answer they have always given. */
|
|
283
427
|
static int32_t nish_spawn_impl(const nish_array *argv, const nish_str *out, const nish_str *err) {
|
|
284
|
-
int32_t pid = nish_spawn_start(argv, out, err);
|
|
428
|
+
int32_t pid = nish_spawn_start(argv, out, err, 0);
|
|
285
429
|
if (pid < 0) return -1;
|
|
286
430
|
#ifdef __wasi__
|
|
287
431
|
return -1;
|
|
@@ -312,7 +456,160 @@ int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_st
|
|
|
312
456
|
/* `spawn(argv)`: the child's pid, or a negative errno, without waiting. The
|
|
313
457
|
child is this process's until a `waitPid` reaps it; one never reaped stays
|
|
314
458
|
a zombie until the program exits, as it would in C. */
|
|
315
|
-
int32_t nish_spawn_child(const nish_array *argv) { return nish_spawn_start(argv, 0, 0); }
|
|
459
|
+
int32_t nish_spawn_child(const nish_array *argv) { return nish_spawn_start(argv, 0, 0, 0); }
|
|
460
|
+
|
|
461
|
+
/* `spawnPiped(argv, stdin, stdout, stderr)`: run the child as `nish_spawn`
|
|
462
|
+
does, with `stdin`'s bytes on its standard input and its standard output
|
|
463
|
+
and error appended to `stdout` and `stderr`, and answer its status. -1 is
|
|
464
|
+
`nish_spawn`'s failure (an empty argv, a NUL, a child that cannot start or
|
|
465
|
+
be waited for, a pipe or `poll` that fails), and -2 is a stream that passed
|
|
466
|
+
NISH_PIPED_CAP bytes: the child is killed with SIGKILL and reaped, and the
|
|
467
|
+
array holds the first NISH_PIPED_CAP bytes of that stream.
|
|
468
|
+
|
|
469
|
+
The three pipes are close-on-exec, so no other child this process starts
|
|
470
|
+
inherits an end, and each end is moved off descriptors 0 to 2, where a
|
|
471
|
+
`dup2` onto itself would leave close-on-exec set and the child without
|
|
472
|
+
that stream. Darwin has no `pipe2`, so there an end is close-on-exec only
|
|
473
|
+
once `F_DUPFD_CLOEXEC` has moved it: a child another thread starts in
|
|
474
|
+
that window inherits it (docs/security/runtime.md, RT-30).
|
|
475
|
+
|
|
476
|
+
One `poll` loop over non-blocking ends writes stdin and reads both outputs
|
|
477
|
+
as each is ready, so a child that fills its stdout before it reads all its
|
|
478
|
+
stdin, or the reverse, cannot deadlock against this process. A child that
|
|
479
|
+
exits, or closes its stdin, before reading everything ends the writing
|
|
480
|
+
with EPIPE rather than SIGPIPE: the signal is blocked on this thread once
|
|
481
|
+
the child has started, the one a write raised is taken off the pending
|
|
482
|
+
set, and the mask is put back before the answer (docs/security/runtime.md,
|
|
483
|
+
RT-29). Ignoring it instead would be process-wide, and every child
|
|
484
|
+
started meanwhile, this one included, would inherit SIGPIPE ignored and
|
|
485
|
+
never die of a reader that has gone.
|
|
486
|
+
|
|
487
|
+
The bytes are written from the caller's array as they stand, and nothing
|
|
488
|
+
copies them: the only copies are the kernel's pipe buffer and the child.
|
|
489
|
+
The length is read once, so `stdout` being the same array as `stdin`
|
|
490
|
+
appends to it without feeding the output back in, and the pointer at each
|
|
491
|
+
write, because that array moves when it grows and its stdin bytes stay
|
|
492
|
+
its first `n`. */
|
|
493
|
+
#define NISH_PIPED_CAP (16u << 20)
|
|
494
|
+
#ifndef __wasi__
|
|
495
|
+
static int nish_pipe(int *p) {
|
|
496
|
+
#ifdef __linux__
|
|
497
|
+
if (pipe2(p, O_CLOEXEC)) return -1;
|
|
498
|
+
#else
|
|
499
|
+
if (pipe(p)) return -1;
|
|
500
|
+
#endif
|
|
501
|
+
for (int k = 0; k < 2; k++) {
|
|
502
|
+
#ifdef __linux__
|
|
503
|
+
if (p[k] > 2) continue;
|
|
504
|
+
#endif
|
|
505
|
+
int moved = fcntl(p[k], F_DUPFD_CLOEXEC, 3);
|
|
506
|
+
close(p[k]);
|
|
507
|
+
p[k] = moved;
|
|
508
|
+
if (moved < 0) return -1;
|
|
509
|
+
}
|
|
510
|
+
return 0;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
#endif
|
|
514
|
+
|
|
515
|
+
int32_t nish_spawn_piped(const nish_array *argv, const nish_array *in, nish_array *out, nish_array *err) {
|
|
516
|
+
#ifdef __wasi__
|
|
517
|
+
(void)argv;
|
|
518
|
+
(void)in;
|
|
519
|
+
(void)out;
|
|
520
|
+
(void)err;
|
|
521
|
+
return -1;
|
|
522
|
+
#else
|
|
523
|
+
uint64_t n = in->len, sent = 0, got[3] = { 0, 0, 0 };
|
|
524
|
+
int p[6] = { -1, -1, -1, -1, -1, -1 };
|
|
525
|
+
int32_t rc = -1;
|
|
526
|
+
if (nish_pipe(p) || nish_pipe(p + 2) || nish_pipe(p + 4)) goto done;
|
|
527
|
+
int child[3] = { p[0], p[3], p[5] };
|
|
528
|
+
int32_t pid = nish_spawn_start(argv, 0, 0, child);
|
|
529
|
+
/* The child holds its ends now; this process keeps only its own. */
|
|
530
|
+
for (int k = 0; k < 3; k++) {
|
|
531
|
+
close(child[k]);
|
|
532
|
+
p[k ? 2 * k + 1 : 0] = -1;
|
|
533
|
+
}
|
|
534
|
+
if (pid < 0) goto done;
|
|
535
|
+
sigset_t pipe_sig, mask, pending;
|
|
536
|
+
sigemptyset(&pipe_sig);
|
|
537
|
+
sigaddset(&pipe_sig, SIGPIPE);
|
|
538
|
+
pthread_sigmask(SIG_BLOCK, &pipe_sig, &mask);
|
|
539
|
+
sigpending(&pending);
|
|
540
|
+
int queued = sigismember(&pending, SIGPIPE);
|
|
541
|
+
struct pollfd f[3] = { { p[1], POLLOUT, 0 }, { p[2], POLLIN, 0 }, { p[4], POLLIN, 0 } };
|
|
542
|
+
p[1] = p[2] = p[4] = -1;
|
|
543
|
+
/* A blocking end could stall the loop on one stream while the child fills
|
|
544
|
+
another, so an end that stays blocking fails the run: the loop is skipped
|
|
545
|
+
and the child killed. */
|
|
546
|
+
rc = 0;
|
|
547
|
+
for (int k = 0; k < 3; k++)
|
|
548
|
+
if (fcntl(f[k].fd, F_SETFL, O_NONBLOCK) < 0) rc = -1;
|
|
549
|
+
if (!n) {
|
|
550
|
+
close(f[0].fd);
|
|
551
|
+
f[0].fd = -1;
|
|
552
|
+
}
|
|
553
|
+
nish_array *to[3] = { 0, out, err };
|
|
554
|
+
while (!rc && (f[0].fd >= 0 || f[1].fd >= 0 || f[2].fd >= 0)) {
|
|
555
|
+
if (poll(f, 3, -1) < 0) {
|
|
556
|
+
if (errno != EINTR) rc = -1;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (f[0].revents) {
|
|
560
|
+
ssize_t k = write(f[0].fd, in->data + sent, n - sent);
|
|
561
|
+
if (k > 0) sent += k;
|
|
562
|
+
if (sent == n || (k < 0 && errno != EINTR && errno != EAGAIN)) {
|
|
563
|
+
close(f[0].fd);
|
|
564
|
+
f[0].fd = -1;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
for (int s = 1; s < 3 && !rc; s++) {
|
|
568
|
+
if (!f[s].revents) continue;
|
|
569
|
+
nish_array *a = to[s];
|
|
570
|
+
/* Grown as `push` grows it, so the array keeps the shape every other
|
|
571
|
+
array has, and an ASan build tracks the block the same way. */
|
|
572
|
+
if (a->len == a->cap) nish_array_grow(a, 1);
|
|
573
|
+
/* One byte past the cap at most, which is how a stream that ends at
|
|
574
|
+
exactly the cap is told from one that goes on. */
|
|
575
|
+
uint64_t room = a->cap - a->len, left = NISH_PIPED_CAP + 1 - got[s];
|
|
576
|
+
ssize_t k = read(f[s].fd, a->data + a->len, room < left ? room : left);
|
|
577
|
+
if (k > 0) {
|
|
578
|
+
a->len += k;
|
|
579
|
+
got[s] += k;
|
|
580
|
+
if (got[s] > NISH_PIPED_CAP) {
|
|
581
|
+
a->len--;
|
|
582
|
+
rc = -2;
|
|
583
|
+
}
|
|
584
|
+
} else if (!k || (errno != EINTR && errno != EAGAIN)) {
|
|
585
|
+
close(f[s].fd);
|
|
586
|
+
f[s].fd = -1;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
sigpending(&pending);
|
|
591
|
+
if (!queued && sigismember(&pending, SIGPIPE)) {
|
|
592
|
+
int sig;
|
|
593
|
+
sigwait(&pipe_sig, &sig);
|
|
594
|
+
}
|
|
595
|
+
pthread_sigmask(SIG_SETMASK, &mask, 0);
|
|
596
|
+
if (rc) kill(pid, SIGKILL);
|
|
597
|
+
for (int k = 0; k < 3; k++)
|
|
598
|
+
if (f[k].fd >= 0) close(f[k].fd);
|
|
599
|
+
int status;
|
|
600
|
+
while (waitpid(pid, &status, 0) < 0) {
|
|
601
|
+
if (errno != EINTR) {
|
|
602
|
+
rc = -1;
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (!rc) rc = nish_exit_code(status);
|
|
607
|
+
done:
|
|
608
|
+
for (int k = 0; k < 6; k++)
|
|
609
|
+
if (p[k] >= 0) close(p[k]);
|
|
610
|
+
return rc;
|
|
611
|
+
#endif
|
|
612
|
+
}
|
|
316
613
|
|
|
317
614
|
/* `kill(pid, sig)`: 0, or a negative errno. Only the four signals a program
|
|
318
615
|
asks a child to stop or report with are sent -- 0 (is it there), SIGINT,
|
|
@@ -477,6 +774,36 @@ int64_t nish_total_memory(void) {
|
|
|
477
774
|
#endif
|
|
478
775
|
}
|
|
479
776
|
|
|
777
|
+
/* `setNice(n)`: this process's nice value set to `n`, 0 or a negative errno.
|
|
778
|
+
The kernel clamps `n` to its range (-20 to 19), so only lowering the value
|
|
779
|
+
below what the process has can fail: -EACCES (Linux, Darwin) or -EPERM
|
|
780
|
+
without the privilege to raise a priority. Linux keeps the value per thread,
|
|
781
|
+
so it is the calling thread's and every thread started after it. */
|
|
782
|
+
int32_t nish_set_nice(int32_t n) {
|
|
783
|
+
#ifdef __wasi__
|
|
784
|
+
(void)n;
|
|
785
|
+
return -ENOSYS;
|
|
786
|
+
#else
|
|
787
|
+
return setpriority(PRIO_PROCESS, 0, n) == 0 ? 0 : -errno;
|
|
788
|
+
#endif
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/* `setIdlePriority()`: this process moved to `SCHED_IDLE`, which runs it only
|
|
792
|
+
when nothing else wants the CPU. Linux alone has the policy; elsewhere the
|
|
793
|
+
answer is -EOPNOTSUPP's Linux number, -95, as runtime-net.c answers it. An
|
|
794
|
+
unprivileged process cannot leave the policy again. Like the nice value, the
|
|
795
|
+
policy is the calling thread's. */
|
|
796
|
+
int32_t nish_set_idle_priority(void) {
|
|
797
|
+
#if defined(__linux__)
|
|
798
|
+
struct sched_param param = {0};
|
|
799
|
+
return sched_setscheduler(0, SCHED_IDLE, ¶m) == 0 ? 0 : -errno;
|
|
800
|
+
#elif defined(__wasi__)
|
|
801
|
+
return -ENOSYS;
|
|
802
|
+
#else
|
|
803
|
+
return -95;
|
|
804
|
+
#endif
|
|
805
|
+
}
|
|
806
|
+
|
|
480
807
|
/* `readdirSync(path)`: the listing a driver needs to discover its own inputs.
|
|
481
808
|
|
|
482
809
|
**Sorted by bytes**, which `readdir(3)` and Node are not, for two reasons
|
|
@@ -545,11 +872,17 @@ nish_array *nish_readdir(const nish_str *path) {
|
|
|
545
872
|
backwards and make an elapsed time negative. Only the difference between two
|
|
546
873
|
reads means anything — the origin is arbitrary and is not comparable across
|
|
547
874
|
processes or machines — and an i64 of nanoseconds is 292 years of it. */
|
|
548
|
-
int64_t
|
|
875
|
+
static int64_t nish_clock_nanos(clockid_t clock) {
|
|
549
876
|
struct timespec ts;
|
|
550
|
-
clock_gettime(
|
|
877
|
+
clock_gettime(clock, &ts);
|
|
551
878
|
return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
|
|
552
879
|
}
|
|
880
|
+
int64_t nish_monotonic_nanos(void) { return nish_clock_nanos(CLOCK_MONOTONIC); }
|
|
881
|
+
|
|
882
|
+
/* `wallClockNanos()`: `CLOCK_REALTIME`, nanoseconds since the Unix epoch, for
|
|
883
|
+
dating an event. It steps when the system time is set, so a duration is
|
|
884
|
+
`monotonicNanos`'s to measure. */
|
|
885
|
+
int64_t nish_wall_clock_nanos(void) { return nish_clock_nanos(CLOCK_REALTIME); }
|
|
553
886
|
|
|
554
887
|
/* A C string copied into the arena, or NULL for NULL: the shape of every
|
|
555
888
|
answer libc hands back by pointer, `getenv`'s here and `realpath`'s below. */
|
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
|