@amritk/nish-x86_64-linux 0.14.0 → 0.16.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/INSTALL.md +15 -6
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +91 -0
- package/runtime/nish.h +97 -5
- package/runtime/nish.mjs +23 -0
- package/runtime/runtime-host.c +29 -2
- package/runtime/runtime-net.c +534 -0
- package/runtime/runtime-os.c +77 -37
- package/runtime/runtime-parallel.c +5 -1
- package/runtime/runtime-wasm.c +25 -2
- package/runtime/runtime.c +40 -6
- package/runtime/shim.mjs +15 -0
- package/scripts/build.sh +8 -7
- package/std/README.md +88 -23
- package/std/crypto/LICENSE-bearssl +21 -0
- package/std/crypto/LICENSE-fiat-crypto +21 -0
- package/std/crypto/aes.ts +939 -0
- package/std/crypto/base64url.ts +18 -5
- package/std/crypto/chacha20poly1305.ts +705 -0
- package/std/crypto/ct.ts +8 -2
- package/std/crypto/hkdf.ts +21 -5
- package/std/crypto/hmac.ts +18 -2
- package/std/crypto/p256.ts +4342 -0
- package/std/crypto/sha256.ts +12 -1
- package/std/crypto/sha512.ts +16 -2
- package/std/crypto/x25519.ts +26 -10
- package/std/crypto/x509.ts +1331 -0
package/INSTALL.md
CHANGED
|
@@ -403,8 +403,15 @@ release is expected to move it. `--stages 1` stops one link sooner.
|
|
|
403
403
|
and `--profile` spellings as an installed `nish`, because it is the same
|
|
404
404
|
program, and makes every directory in the way of the IR, a sidecar or the
|
|
405
405
|
binary itself. It looks for `scripts/build.sh` and the two `runtime/*.c` files
|
|
406
|
-
one level up from wherever it was invoked
|
|
407
|
-
it wants a checkout or an installed package
|
|
406
|
+
one level up from wherever it was invoked (and one level up from the file a
|
|
407
|
+
symbolic link to it names), so it wants a checkout or an installed package
|
|
408
|
+
around it the way `nish` does. It falls back to the working directory only
|
|
409
|
+
when the compiler itself lies inside that directory, so a checkout you merely
|
|
410
|
+
`cd` into cannot hand it a `scripts/build.sh` to run. Keep `bin/nish` beside
|
|
411
|
+
`scripts/`, `runtime/` and `std/`, as every install above does, and keep that
|
|
412
|
+
directory's parent writable only by you: the package root is the parent of
|
|
413
|
+
the compiler's directory, so a compiler at `/tmp/x/nish` would trust
|
|
414
|
+
`/tmp/scripts/build.sh` (CLI-9 in [docs/security/cli.md](security/cli.md)).
|
|
408
415
|
|
|
409
416
|
Because the seed is the last release, `src/` may only *use* in its own
|
|
410
417
|
source the constructs that release compiles. A new construct is implemented
|
|
@@ -457,9 +464,11 @@ nish run hello.ts
|
|
|
457
464
|
`$XDG_CACHE_HOME/nish/run` (or `~/.cache/nish/run`) when that program has not
|
|
458
465
|
been linked before, and starts it with the arguments after the file. The first
|
|
459
466
|
run of a new edit pays for one link, and later runs start the cached binary.
|
|
460
|
-
It needs clang only for that link. It needs `HOME` or `XDG_CACHE_HOME` set
|
|
461
|
-
|
|
462
|
-
directory.
|
|
467
|
+
It needs clang only for that link. It needs `HOME` or `XDG_CACHE_HOME` set
|
|
468
|
+
to an absolute path (a relative one is ignored, as if it were unset), and
|
|
469
|
+
refuses the run without one rather than keep a binary in a shared directory.
|
|
470
|
+
The first run that builds into the cache makes its root `0700`, so no other
|
|
471
|
+
user can plant a binary in it ([docs/security/cli.md](security/cli.md)).
|
|
463
472
|
|
|
464
473
|
## 4. Exit codes
|
|
465
474
|
|
|
@@ -469,7 +478,7 @@ directory.
|
|
|
469
478
|
| 1 | the program was rejected: compile error (`file:line:col: error: ...`), missing input file, or an `-o` layout that does not fit the module count |
|
|
470
479
|
| 2 | usage error: unknown flag, missing argument, no input files |
|
|
471
480
|
| 3 | toolchain error: `--link` found no `clang` (`CC` overrides), or `scripts/build.sh` failed (its output is shown; the `.ll` files are still written) — or the `nish` command found no prebuilt compiler for this platform, or one that would not start. Under `--json` all of these are one `NL0002` object |
|
|
472
|
-
| | `nish run` answers these codes until the program starts, and the program's own exit status (`128 + n` for a signal) after that. With neither `HOME` nor `XDG_CACHE_HOME` set it answers 3 |
|
|
481
|
+
| | `nish run` answers these codes until the program starts, and the program's own exit status (`128 + n` for a signal) after that. With neither `HOME` nor `XDG_CACHE_HOME` set to an absolute path it answers 3 |
|
|
473
482
|
| 70 | internal compiler error: an unexpected exception. Please report it at <https://github.com/amritk/nish/issues> with the input and command line; `NISH_DEBUG=1` prints the stack trace |
|
|
474
483
|
|
|
475
484
|
## Troubleshooting
|
package/bin/nish
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/runtime/nish.d.ts
CHANGED
|
@@ -248,6 +248,55 @@ declare function signalFd(): i32;
|
|
|
248
248
|
*/
|
|
249
249
|
declare function readSignal(fd: i32): i32;
|
|
250
250
|
|
|
251
|
+
// ---- `nish:net` (WP34 N5) --------------------------------------------------------
|
|
252
|
+
//
|
|
253
|
+
// Every call answers an `i32`: `>= 0` on success, a negative errno otherwise, in
|
|
254
|
+
// Linux's numbering on every platform for -11 (would block), -95, -32, -104,
|
|
255
|
+
// -98 and -22. An address is 18 bytes of a `u8[]`: 16 of IPv6 address (IPv4 as
|
|
256
|
+
// `::ffff:a.b.c.d`), then the port, big-endian. Every socket is non-blocking and
|
|
257
|
+
// close-on-exec. No reading under Node, where each throws.
|
|
258
|
+
|
|
259
|
+
/** Write the address form of a numeric `host` and `port` into `out`: 0, or -22. */
|
|
260
|
+
declare function netAddress(out: u8[], host: string, port: i32): i32;
|
|
261
|
+
/** The port the socket `fd` is bound to. */
|
|
262
|
+
declare function netLocalPort(fd: i32): i32;
|
|
263
|
+
/** A listening TCP socket with `SO_REUSEADDR`; `"::"` hears IPv4 and IPv6. */
|
|
264
|
+
declare function tcpListen(host: string, port: i32, backlog: i32): i32;
|
|
265
|
+
/** The next connection's descriptor, its address in `peer`; -11 when none waits. */
|
|
266
|
+
declare function tcpAccept(fd: i32, peer: u8[]): i32;
|
|
267
|
+
/** Up to `len` bytes into `buf` from `off`: the count, 0 at end of stream, or -11. */
|
|
268
|
+
declare function netRead(fd: i32, buf: u8[], off: i32, len: i32): i32;
|
|
269
|
+
/** Up to `len` bytes of `buf` from `off`: the count; a gone peer is -32, never SIGPIPE. */
|
|
270
|
+
declare function netWrite(fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
|
|
271
|
+
/** Shut the read side (0), the write side (1) or both (2). */
|
|
272
|
+
declare function netShutdown(fd: i32, how: i32): i32;
|
|
273
|
+
/** Close the descriptor. */
|
|
274
|
+
declare function netClose(fd: i32): i32;
|
|
275
|
+
/** A UDP socket: flag 1 `SO_REUSEPORT`, flag 2 `UDP_GRO` (-95 on Darwin); ECN is always read. */
|
|
276
|
+
declare function udpBind(host: string, port: i32, flags: i32): i32;
|
|
277
|
+
/** `buf[off, off + len)` to `to`, cut into `segment`-byte datagrams when `segment > 0`, with ECN bits `ecn`. */
|
|
278
|
+
declare function udpSendTo(
|
|
279
|
+
fd: i32,
|
|
280
|
+
buf: readonly u8[],
|
|
281
|
+
off: i32,
|
|
282
|
+
len: i32,
|
|
283
|
+
to: readonly u8[],
|
|
284
|
+
segment: i32,
|
|
285
|
+
ecn: i32
|
|
286
|
+
): i32;
|
|
287
|
+
/** One datagram, or several GRO coalesced, into `buf[off, off + len)`; the sender in `from`, `meta` [segment size, ECN]. */
|
|
288
|
+
declare function udpRecvFrom(fd: i32, buf: u8[], off: i32, len: i32, from: u8[], meta: i32[]): i32;
|
|
289
|
+
/** A readiness loop, level-triggered: epoll on Linux, kqueue on Darwin. */
|
|
290
|
+
declare function pollCreate(): i32;
|
|
291
|
+
/** Watch `fd` for `events` (1 readable, 2 writable) under `token`. */
|
|
292
|
+
declare function pollAdd(loop: i32, fd: i32, events: i32, token: i32): i32;
|
|
293
|
+
/** New events or a new token for a watched `fd`. */
|
|
294
|
+
declare function pollModify(loop: i32, fd: i32, events: i32, token: i32): i32;
|
|
295
|
+
/** Stop watching `fd`. */
|
|
296
|
+
declare function pollRemove(loop: i32, fd: i32): i32;
|
|
297
|
+
/** Wait up to `timeoutMs` (negative: forever); pairs `ready[2k]` token, `ready[2k + 1]` events (4: hang-up or error). */
|
|
298
|
+
declare function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
|
|
299
|
+
|
|
251
300
|
// ---- `Date` and `crypto` (WP34 N3) -----------------------------------------------
|
|
252
301
|
//
|
|
253
302
|
// `lib.es2022` already declares `Date`, whose `now()` is Nish's one member of it,
|
|
@@ -318,6 +367,48 @@ declare module "nish:process" {
|
|
|
318
367
|
export const arch: string;
|
|
319
368
|
}
|
|
320
369
|
|
|
370
|
+
declare module "nish:net" {
|
|
371
|
+
/** The address form of a numeric `host` and `port` in `out`'s first 18 bytes: 0, or -22. */
|
|
372
|
+
export function netAddress(out: u8[], host: string, port: i32): i32;
|
|
373
|
+
/** The port the socket is bound to. */
|
|
374
|
+
export function netLocalPort(fd: i32): i32;
|
|
375
|
+
/** A listening TCP socket with `SO_REUSEADDR`; `"::"` hears IPv4 and IPv6. */
|
|
376
|
+
export function tcpListen(host: string, port: i32, backlog: i32): i32;
|
|
377
|
+
/** The next connection's descriptor, its address in `peer`; -11 when none waits. */
|
|
378
|
+
export function tcpAccept(fd: i32, peer: u8[]): i32;
|
|
379
|
+
/** Up to `len` bytes into `buf` from `off`: the count, 0 at end of stream, or -11. */
|
|
380
|
+
export function netRead(fd: i32, buf: u8[], off: i32, len: i32): i32;
|
|
381
|
+
/** Up to `len` bytes of `buf` from `off`: the count; a gone peer is -32, never SIGPIPE. */
|
|
382
|
+
export function netWrite(fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
|
|
383
|
+
/** Shut the read side (0), the write side (1) or both (2). */
|
|
384
|
+
export function netShutdown(fd: i32, how: i32): i32;
|
|
385
|
+
export function netClose(fd: i32): i32;
|
|
386
|
+
/** A UDP socket: flag 1 `SO_REUSEPORT`, flag 2 `UDP_GRO` (-95 on Darwin); ECN is always read. */
|
|
387
|
+
export function udpBind(host: string, port: i32, flags: i32): i32;
|
|
388
|
+
/** `buf[off, off + len)` to `to`, cut into `segment`-byte datagrams when `segment > 0`, with ECN bits `ecn`. */
|
|
389
|
+
export function udpSendTo(
|
|
390
|
+
fd: i32,
|
|
391
|
+
buf: readonly u8[],
|
|
392
|
+
off: i32,
|
|
393
|
+
len: i32,
|
|
394
|
+
to: readonly u8[],
|
|
395
|
+
segment: i32,
|
|
396
|
+
ecn: i32
|
|
397
|
+
): i32;
|
|
398
|
+
/** One datagram, or several GRO coalesced, into `buf[off, off + len)`; the sender in `from`, `meta` [segment size, ECN]. */
|
|
399
|
+
export function udpRecvFrom(fd: i32, buf: u8[], off: i32, len: i32, from: u8[], meta: i32[]): i32;
|
|
400
|
+
/** A readiness loop, level-triggered: epoll on Linux, kqueue on Darwin. */
|
|
401
|
+
export function pollCreate(): i32;
|
|
402
|
+
/** Watch `fd` for `events` (1 readable, 2 writable) under `token`. */
|
|
403
|
+
export function pollAdd(loop: i32, fd: i32, events: i32, token: i32): i32;
|
|
404
|
+
/** New events or a new token for a watched `fd`. */
|
|
405
|
+
export function pollModify(loop: i32, fd: i32, events: i32, token: i32): i32;
|
|
406
|
+
/** Stop watching `fd`. */
|
|
407
|
+
export function pollRemove(loop: i32, fd: i32): i32;
|
|
408
|
+
/** Wait up to `timeoutMs` (negative: forever); pairs `ready[2k]` token, `ready[2k + 1]` events (4: hang-up or error). */
|
|
409
|
+
export function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
|
|
410
|
+
}
|
|
411
|
+
|
|
321
412
|
declare module "nish:io" {
|
|
322
413
|
/** `s` to stdout with no trailing newline and no conversion. */
|
|
323
414
|
export function write(s: string): void;
|
package/runtime/nish.h
CHANGED
|
@@ -63,6 +63,17 @@ typedef struct nish_str {
|
|
|
63
63
|
char data[];
|
|
64
64
|
} nish_str;
|
|
65
65
|
|
|
66
|
+
/* The bytes of `s` as the C string a system call takes, or "" when `s` holds
|
|
67
|
+
* a NUL. The kernel reads a path to its first NUL, so `"secret\0.txt"` would
|
|
68
|
+
* pass a `.txt` check and open `secret`; "" names nothing, so each caller
|
|
69
|
+
* answers it as it answers a missing path. The one test every path, name and
|
|
70
|
+
* argument the runtime hands the OS goes through (docs/security/runtime.md,
|
|
71
|
+
* RT-3). `__builtin_strlen`, so this header needs no libc header of its own,
|
|
72
|
+
* and `unused` because a host that includes it may never call it. */
|
|
73
|
+
static inline __attribute__((unused)) const char *nish_cpath(const nish_str *s) {
|
|
74
|
+
return __builtin_strlen(s->data) == s->len ? s->data : "";
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
/* ---- Arena --------------------------------------------------------------
|
|
67
78
|
* One bump allocator per process, or one per thread under `-DNISH_THREADS`
|
|
68
79
|
* (WP20 T0; the layout and every function below are the same either way).
|
|
@@ -139,7 +150,16 @@ nish_str *nish_str_from_i64(int64_t v);
|
|
|
139
150
|
nish_str *nish_str_from_u64(uint64_t v);
|
|
140
151
|
|
|
141
152
|
/* Process and file I/O (WP7). `nish_exit` never returns; the file functions
|
|
142
|
-
* print a message to stderr and exit(1) on a fatal error.
|
|
153
|
+
* print a message to stderr and exit(1) on a fatal error.
|
|
154
|
+
*
|
|
155
|
+
* Every function here that takes a path, a name or an argument vector refuses
|
|
156
|
+
* one holding a NUL, answering as it answers a path that does not exist: the
|
|
157
|
+
* kernel would read only the bytes before the NUL, which is a different path
|
|
158
|
+
* from the one the program checked (docs/security/runtime.md, RT-3). A file
|
|
159
|
+
* longer than 2^31 - 1 bytes is unreadable, as it is to `nish_read_file_or_null`
|
|
160
|
+
* and `nish_read_file_bytes` too, because no string or array may be longer
|
|
161
|
+
* (RT-1). The two writers refuse a symbolic link as the last component of the
|
|
162
|
+
* path rather than follow it (RT-4). Everything from here
|
|
143
163
|
* to the end of the clock section below is implemented in runtime-os.c, with
|
|
144
164
|
* `nish_random` and `nish_argv_init` the exceptions: neither asks the operating
|
|
145
165
|
* system anything (the clock only seeds the first, and the entry point hands the
|
|
@@ -194,6 +214,11 @@ typedef struct nish_array { uint64_t len; uint64_t cap; char *data; } nish_array
|
|
|
194
214
|
* `elem_size` is `sizeof` one element, so for a record element type it is
|
|
195
215
|
* `sizeof(struct Point)` and the block is a C array of them. */
|
|
196
216
|
nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len);
|
|
217
|
+
/* No array grows past 2^31 - 1 elements and no string past 2^31 - 1 bytes:
|
|
218
|
+
* `nish_array_grow`, `nish_alloc_array` and `nish_str_concat` fail as an
|
|
219
|
+
* allocation does (`nish: out of memory`, exit 1; a trap on wasm) rather than
|
|
220
|
+
* make one, because under --number-mode i32 its `length` would read back
|
|
221
|
+
* negative (docs/security/codegen.md K1-6, docs/security/runtime.md RT-2). */
|
|
197
222
|
/* The cold paths compiled code calls: `push` when len == cap, a failed bounds
|
|
198
223
|
* check. `nish_array_grow` moves `cap * elem_size` bytes into a fresh arena
|
|
199
224
|
* block and writes `data`/`cap`, so for a record element type it relocates the
|
|
@@ -254,12 +279,12 @@ nish_array *nish_read_file_bytes(const nish_str *path);
|
|
|
254
279
|
* `128 + n` when signal `n` killed it. -1 when `argv` is empty, when the
|
|
255
280
|
* child cannot be started or waited for, and always under WASI, which has no
|
|
256
281
|
* processes. The elements are `nish_str *`; the child receives their bytes,
|
|
257
|
-
*
|
|
282
|
+
* and an argument containing a NUL is refused (-1) rather than truncated. */
|
|
258
283
|
int32_t nish_spawn(const nish_array *argv);
|
|
259
284
|
/* `spawnSyncTo(argv, stdoutPath, stderrPath)`: exactly `nish_spawn` — the same
|
|
260
285
|
* `PATH` search, the same wait, the same status, `128 + n` and -1 — except that
|
|
261
286
|
* each non-empty path receives that stream, created or truncated at 0644 as
|
|
262
|
-
* `nish_write_file` would leave it. An **empty** string leaves that stream
|
|
287
|
+
* `nish_write_file` would leave it, a symbolic link at the path refused. An **empty** string leaves that stream
|
|
263
288
|
* inherited, so one call can capture stdout and let stderr through to the
|
|
264
289
|
* terminal. The child opens the files, so a failed open is a child that could
|
|
265
290
|
* not start and answers -1 rather than leaving this process redirected. The two
|
|
@@ -275,8 +300,8 @@ int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_st
|
|
|
275
300
|
* arena (so a later `setenv` cannot change a string the program still holds),
|
|
276
301
|
* or NULL when it is unset — the language's `string | null`. An empty value
|
|
277
302
|
* is a set variable and answers a zero-length string, not NULL. `name` is
|
|
278
|
-
* read and never retained; a
|
|
279
|
-
*
|
|
303
|
+
* read and never retained; a name holding a NUL is unset, as it is for every
|
|
304
|
+
* other path-like argument here. */
|
|
280
305
|
nish_str *nish_getenv(const nish_str *name);
|
|
281
306
|
|
|
282
307
|
/* ---- Symlinks (WP19 §5a item 4) -----------------------------------------
|
|
@@ -336,6 +361,73 @@ double nish_stat_mtime(const nish_str *path);
|
|
|
336
361
|
* or a read that fails. */
|
|
337
362
|
int32_t nish_signal_fd(void);
|
|
338
363
|
int32_t nish_read_signal(int32_t fd);
|
|
364
|
+
/* Who owns a path, and whether it runs (docs/security/runtime.md, RT-9; no
|
|
365
|
+
* builtin reaches these yet). `nish_lstat_owner_mode(path)`: `lstat`, so a
|
|
366
|
+
* symbolic link answers for itself, as the owner's uid in the high 32 bits
|
|
367
|
+
* and `st_mode` in the low 32, or exactly -1 when the path does not resolve.
|
|
368
|
+
* `nish_euid()`: the effective uid. `nish_is_executable(path)`:
|
|
369
|
+
* `access(path, X_OK)`, for the real uid. */
|
|
370
|
+
int64_t nish_lstat_owner_mode(const nish_str *path);
|
|
371
|
+
int64_t nish_euid(void);
|
|
372
|
+
bool nish_is_executable(const nish_str *path);
|
|
373
|
+
|
|
374
|
+
/* ---- The network (WP34 N5), runtime/runtime-net.c, `nish:net` ------------
|
|
375
|
+
* Every call answers an `int32_t`: `>= 0` on success (a descriptor, a byte
|
|
376
|
+
* count, 0) and a negative errno otherwise, in Linux's numbering on every
|
|
377
|
+
* platform for -11 (would block), -95 (unsupported), -32 (the peer is gone),
|
|
378
|
+
* -104 (reset), -98 (address in use) and -22 (a bad argument). Every socket
|
|
379
|
+
* is non-blocking and close-on-exec. An address is 18 bytes of a `u8[]`
|
|
380
|
+
* (element type `uint8_t`): 16 of IPv6 address, IPv4 as `::ffff:a.b.c.d`,
|
|
381
|
+
* then the port, big-endian; a shorter array is -22. Nothing allocates.
|
|
382
|
+
*
|
|
383
|
+
* `nish_net_address(out, host, port)`: the form of a numeric `host`.
|
|
384
|
+
* `nish_net_local_port(fd)`: the port a socket is bound to.
|
|
385
|
+
* `nish_tcp_listen(host, port, backlog)`: `SO_REUSEADDR`, and `::` hears
|
|
386
|
+
* both families, or IPv4 alone on a kernel without IPv6.
|
|
387
|
+
* `nish_tcp_accept(fd, peer)`: a new descriptor and the peer's address.
|
|
388
|
+
* `nish_net_read` / `nish_net_write(fd, buf, off, len)`: bytes `[off, off +
|
|
389
|
+
* len)` of `buf`, which the compiled call has range-checked; a write to a
|
|
390
|
+
* gone peer is -32, never SIGPIPE. `nish_net_shutdown(fd, how)`: 0 read, 1
|
|
391
|
+
* write, 2 both. `nish_net_close(fd)`.
|
|
392
|
+
*
|
|
393
|
+
* UDP: `nish_udp_bind(host, port, flags)` binds a datagram socket, flag 1
|
|
394
|
+
* `SO_REUSEPORT` and flag 2 `UDP_GRO` (-95 on Darwin). `nish_udp_send_to(fd,
|
|
395
|
+
* buf, off, len, to, segment, ecn)` sends `buf[off, off + len)` to the
|
|
396
|
+
* address `to` in one `sendmsg`, cut into `segment`-byte datagrams by
|
|
397
|
+
* `UDP_SEGMENT` when `segment > 0`, with the ECN bits `ecn` (0..3); either on
|
|
398
|
+
* Darwin is -95. `nish_udp_recv_from(fd, buf, off, len, from, meta)` takes
|
|
399
|
+
* one datagram, or several `UDP_GRO` coalesced, writes the sender into `from`
|
|
400
|
+
* and into `meta` (element type `int32_t`, at least 2) the segment size, 0
|
|
401
|
+
* when none was coalesced, and the ECN bits.
|
|
402
|
+
*
|
|
403
|
+
* The readiness loop, level-triggered: epoll on Linux, kqueue on Darwin.
|
|
404
|
+
* `nish_poll_create()` is a close-on-exec loop descriptor.
|
|
405
|
+
* `nish_poll_add(loop, fd, events, token)` and `nish_poll_modify` watch `fd`
|
|
406
|
+
* for `events` (1 readable, 2 writable; any other bit is -22) under `token`,
|
|
407
|
+
* and `nish_poll_remove(loop, fd)` stops. `nish_poll_wait(loop, ready,
|
|
408
|
+
* timeout_ms)` waits at most `timeout_ms` (a negative one forever) and writes
|
|
409
|
+
* into `ready` (element type `int32_t`) a token and its events, 4 added for a
|
|
410
|
+
* hang-up or an error, per ready descriptor: at most `ready->len / 2` pairs
|
|
411
|
+
* and at most 64 in one call. It answers the count, 0 on a timeout or an
|
|
412
|
+
* interrupting signal, and -22 for a `ready` shorter than 2. */
|
|
413
|
+
int32_t nish_net_address(nish_array *out, const nish_str *host, int32_t port);
|
|
414
|
+
int32_t nish_net_local_port(int32_t fd);
|
|
415
|
+
int32_t nish_tcp_listen(const nish_str *host, int32_t port, int32_t backlog);
|
|
416
|
+
int32_t nish_tcp_accept(int32_t fd, nish_array *peer);
|
|
417
|
+
int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len);
|
|
418
|
+
int32_t nish_net_write(int32_t fd, const nish_array *buf, int64_t off, int64_t len);
|
|
419
|
+
int32_t nish_net_shutdown(int32_t fd, int32_t how);
|
|
420
|
+
int32_t nish_net_close(int32_t fd);
|
|
421
|
+
int32_t nish_udp_bind(const nish_str *host, int32_t port, int32_t flags);
|
|
422
|
+
int32_t nish_udp_send_to(int32_t fd, const nish_array *buf, int64_t off, int64_t len, const nish_array *to,
|
|
423
|
+
int32_t segment, int32_t ecn);
|
|
424
|
+
int32_t nish_udp_recv_from(int32_t fd, nish_array *buf, int64_t off, int64_t len, nish_array *from,
|
|
425
|
+
nish_array *meta);
|
|
426
|
+
int32_t nish_poll_create(void);
|
|
427
|
+
int32_t nish_poll_add(int32_t loop, int32_t fd, int32_t events, int32_t token);
|
|
428
|
+
int32_t nish_poll_modify(int32_t loop, int32_t fd, int32_t events, int32_t token);
|
|
429
|
+
int32_t nish_poll_remove(int32_t loop, int32_t fd);
|
|
430
|
+
int32_t nish_poll_wait(int32_t loop, nish_array *ready, int32_t timeout_ms);
|
|
339
431
|
|
|
340
432
|
/* String to number (WP7), ASCII whitespace only. mode 0 is `parseFloat`
|
|
341
433
|
* (longest JS decimal literal or `Infinity`, else NaN), mode 1 is `Number`
|
package/runtime/nish.mjs
CHANGED
|
@@ -98,6 +98,29 @@ provide("statMtimeSync", shim.statMtimeSync);
|
|
|
98
98
|
provide("signalFd", shim.signalFd);
|
|
99
99
|
provide("readSignal", shim.readSignal);
|
|
100
100
|
|
|
101
|
+
// `nish:net` (WP34 N5). Each throws, naming itself: a socket is ready only to
|
|
102
|
+
// Node's event loop, which a program that owns its loop never returns to.
|
|
103
|
+
for (const name of [
|
|
104
|
+
"netAddress",
|
|
105
|
+
"netLocalPort",
|
|
106
|
+
"tcpListen",
|
|
107
|
+
"tcpAccept",
|
|
108
|
+
"netRead",
|
|
109
|
+
"netWrite",
|
|
110
|
+
"netShutdown",
|
|
111
|
+
"netClose",
|
|
112
|
+
"udpBind",
|
|
113
|
+
"udpSendTo",
|
|
114
|
+
"udpRecvFrom",
|
|
115
|
+
"pollCreate",
|
|
116
|
+
"pollAdd",
|
|
117
|
+
"pollModify",
|
|
118
|
+
"pollRemove",
|
|
119
|
+
"pollWait",
|
|
120
|
+
]) {
|
|
121
|
+
provide(name, () => shim.noNetReading(name));
|
|
122
|
+
}
|
|
123
|
+
|
|
101
124
|
// `crypto.getRandomValues(bytes)` (WP34 N3). Node has the global, but it
|
|
102
125
|
// takes only a typed array and a `u8[]` is a plain `Array` here, so the one
|
|
103
126
|
// method is replaced on Node's own `crypto` object by one that fills a plain
|
package/runtime/runtime-host.c
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* Nish runtime, the host half: the wall clock, entropy, a file's modification
|
|
2
2
|
* time and a descriptor that becomes readable when SIGTERM or SIGINT arrives
|
|
3
3
|
* (WP34 N3) — the four facts a program that runs for days asks of the machine
|
|
4
|
-
* it runs on
|
|
4
|
+
* it runs on — and who owns a path and whether it runs, which a driver asks
|
|
5
|
+
* before it trusts one.
|
|
5
6
|
*
|
|
6
7
|
* A translation unit of its own for the reason runtime-os.c is one: each file
|
|
7
8
|
* carries its own measured `.text*` ceiling in tests/run.js. runtime-os.c had
|
|
@@ -33,6 +34,7 @@ typedef int nish_host_unused;
|
|
|
33
34
|
#include <signal.h>
|
|
34
35
|
#include <stdint.h>
|
|
35
36
|
#include <stdio.h>
|
|
37
|
+
#include <string.h>
|
|
36
38
|
#include <sys/stat.h>
|
|
37
39
|
#include <time.h>
|
|
38
40
|
#include <unistd.h>
|
|
@@ -98,7 +100,7 @@ void nish_random_fill(nish_array *bytes) {
|
|
|
98
100
|
digits. `stat` follows a symbolic link, as `fs.statSync` does. */
|
|
99
101
|
double nish_stat_mtime(const nish_str *path) {
|
|
100
102
|
struct stat st;
|
|
101
|
-
if (stat(path
|
|
103
|
+
if (stat(nish_cpath(path), &st) != 0) return __builtin_nan("");
|
|
102
104
|
#if defined(__APPLE__)
|
|
103
105
|
struct timespec t = st.st_mtimespec;
|
|
104
106
|
#else
|
|
@@ -107,6 +109,31 @@ double nish_stat_mtime(const nish_str *path) {
|
|
|
107
109
|
return (double)t.tv_sec * 1e3 + (double)t.tv_nsec / 1e6;
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
/* ---- Who owns a path, and whether it runs (docs/security/runtime.md, RT-9)
|
|
113
|
+
*
|
|
114
|
+
* The primitives a driver needs before it trusts a directory or a program it
|
|
115
|
+
* found on its own. `nish_lstat_owner_mode` answers for the path itself, a
|
|
116
|
+
* symbolic link included, rather than for what it names: the owner's uid in
|
|
117
|
+
* the high 32 bits and `st_mode` in the low 32, or exactly -1 when the path
|
|
118
|
+
* does not resolve or holds a NUL. `nish_euid` is the user to compare the
|
|
119
|
+
* owner with, and `nish_is_executable` is `access(X_OK)`: whether the real
|
|
120
|
+
* user may run the file, which is what tells an executable `PATH` entry from
|
|
121
|
+
* one that is only readable. Nothing in the language reaches them yet: `src/` may call
|
|
122
|
+
* a runtime function only once the last release declares it, so they are here
|
|
123
|
+
* for the change after the next release (docs/security/cli.md, CLI-7 and
|
|
124
|
+
* CLI-9). */
|
|
125
|
+
int64_t nish_lstat_owner_mode(const nish_str *path) {
|
|
126
|
+
struct stat st;
|
|
127
|
+
if (lstat(nish_cpath(path), &st) != 0) return -1;
|
|
128
|
+
return (int64_t)((uint64_t)(uint32_t)st.st_uid << 32 | (uint32_t)st.st_mode);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
int64_t nish_euid(void) { return (int64_t)geteuid(); }
|
|
132
|
+
|
|
133
|
+
_Bool nish_is_executable(const nish_str *path) {
|
|
134
|
+
return access(nish_cpath(path), X_OK) == 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
110
137
|
/* ---- Signals: `signalFd()` and `readSignal(fd)`
|
|
111
138
|
*
|
|
112
139
|
* A descriptor rather than a handler in the language, because a handler would
|