@amritk/nish-aarch64-darwin 0.25.0 → 0.27.2

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 CHANGED
Binary file
package/docs/AI.md CHANGED
@@ -2137,11 +2137,18 @@ a `\u` escape `nish/json` decodes) is one unit per byte, and counts its bytes.
2137
2137
  it is; `i32`: the length, or a negative errno), `renameSync(from, to)` (`0` or
2138
2138
  a negative errno; atomic on one filesystem, `-18` across two, so write the
2139
2139
  temporary beside the target), `unlinkSync(path)` (`0` or a negative errno; a
2140
- directory is refused), `writeBytes(bytes, off, len)` (a `u8[]` range to stdout
2140
+ directory is refused), `writeFileSyncMode(path, data, mode)` (the file left
2141
+ at exactly `mode` (`0..0o777`), set before the first byte, written in place; a symbolic link refused; the
2142
+ length or a negative errno), `createFileExclusiveSync(path, data, mode)` (the
2143
+ same, but `-17` when the name exists: a lock file), `mkdirSyncMode(path, mode)`
2144
+ (`0`, `-17` when it exists, the umask applies), `writeBytes(bytes, off, len)` (a `u8[]` range to stdout
2141
2145
  as it is, the length or a negative errno; the range panics like
2142
2146
  `netWrite`'s), `mkdirSync(path)` (one level, `boolean`),
2143
2147
  `isDirectorySync(path)`, `readdirSync(path)` (`string[] | null`, sorted by
2144
2148
  bytes, no `.`/`..`), `spawnSync(argv)`, `spawnSyncTo(argv, outPath, errPath)`,
2149
+ `spawnPiped(argv, stdin, stdout, stderr)` (stdin from a `u8[]`, both outputs
2150
+ appended to `u8[]`s, the status; `-2` past 16 MiB; the way to hand a child a
2151
+ secret, never argv or the environment),
2145
2152
  `getenv(name)` (`string | null` — unset and empty are different answers),
2146
2153
  `realpathSync(path)` (`string | null`; symbolic links resolved, absolute, and
2147
2154
  `null` when it does not resolve),
@@ -2174,8 +2181,9 @@ also watches the signal fd. None of `spawn`, `kill`, `waitPid` may be called in
2174
2181
  a task or a parallel body; the two host facts may. `availableParallelism()` (`i32`, at least 1, no `--threads` needed) and
2175
2182
  `totalMemory()` (`i64` bytes, or `-1`) size a program to the machine. None of
2176
2183
  the five exists on a wasm target, and `spawn` and `waitPid` throw under Node.
2177
- `setNice(n)` sets the nice value (clamped to -20..19; lowering it unprivileged
2178
- is `-13`) and `setIdlePriority()` moves the program to Linux's `SCHED_IDLE`
2184
+ `setNice(n)` sets the nice value, and it is lower-only even as root: `n` takes
2185
+ 0..19 (`-22` outside it) and a value below the current one is `-13`, with no
2186
+ system call either way. `setIdlePriority()` moves the program to Linux's `SCHED_IDLE`
2179
2187
  (`-95` on Darwin); each answers 0 or a negative errno. Call them at the top of
2180
2188
  `main`, never in a task. An unprivileged process can never leave `SCHED_IDLE`
2181
2189
  and an idle one can starve on a saturated machine, so keep a normal-priority
@@ -2194,6 +2202,10 @@ wait for it to be writable, then `connectResult(fd)` is 0 or the failure. Buffer
2194
2202
  are `u8[]` only; an address is 18 bytes of one (IPv4 as `::ffff:a.b.c.d`, then
2195
2203
  the port big-endian); hosts are numeric literals; to reach a name, resolve it first with
2196
2204
  `nish/net/dns` (`DnsClient` on your loop, or the blocking `resolveHost`).
2205
+ To fetch a URL from a CLI, skip all of this: `nish/net/http-client`'s
2206
+ `httpFetch(new HttpRequest("GET", url), new HttpOptions())` blocks until the
2207
+ response's head is in and answers it with `body.readAll(limit)`, or null on a
2208
+ failure (an `HttpClient` keeps the connection and says why in `error`).
2197
2209
  `netRead` / `netWrite` panic on a range outside the buffer.
2198
2210
  `tcpListenFlags(host, port, backlog, flags)` is `tcpListen` with flag 1
2199
2211
  `SO_REUSEPORT`, so several listeners, in one process or many, share a port (Linux
@@ -2625,6 +2637,20 @@ the array open: end it and send the rest in the next statement. Every other
2625
2637
  guide's [PostgreSQL page](guide/using-nish/postgres.md#array-parameters) its
2626
2638
  walk-through.)
2627
2639
 
2640
+ **Read PostgreSQL ids, counts and times without parsing them** — there is no
2641
+ driver option for binary results: in `nish/net/postgres`, open the bind with
2642
+ `bindStartBinary(name)` instead of `bindStart(name)`, and every result column
2643
+ of that execution comes in binary. Read an `int2`/`int4`/`int8` with
2644
+ `binaryInt(c)`, a `bool` with `binaryBool(c)`, a `float8` with
2645
+ `binaryFloat(c)`, a `timestamptz` with `conn.binary.timestamp(conn.inputBuffer(),
2646
+ conn.colStart(c), conn.colLength(c), out, at)` (or its microseconds since 2000
2647
+ with `binaryInt(c)`, written out by `conn.binary.iso(micros, out, at)`), and a
2648
+ `text` column with `columnText(c)` as before. Cast anything else (`numeric`,
2649
+ `text[]`) to `text` in the SQL. (No block, as for `nish/text`;
2650
+ `tests/postgres/live`'s `binary` scenario runs it against a server, and the
2651
+ user guide's [PostgreSQL page](guide/using-nish/postgres.md#binary-results)
2652
+ its walk-through.)
2653
+
2628
2654
  **Batch a server's keyed reads into one PostgreSQL query** — there is no
2629
2655
  DataLoader and no `setTimeout` to collect a tick's keys: in `nish/net/postgres`,
2630
2656
  a `PgBatcher` built from a `PgBatchConfig` (`name`, `select` reading the key as
@@ -2633,7 +2659,7 @@ a `PgBatcher` built from a `PgBatchConfig` (`name`, `select` reading the key as
2633
2659
  queue is full, or for a text key too long for `maxBytes`, with no room left in
2634
2660
  `queueBytes`, with a NUL or not UTF-8 — and `take(conn)`
2635
2661
  binds the waiting ones, each key once, as one execution of its `sql` over
2636
- `unnest($1) with ordinality`, answering a run handle or -1. Hand that
2662
+ `unnest($1) with ordinality`, answering a run handle or -1. Set `binary` in the `PgBatchConfig` and each batch comes in binary: `take` opens its bind with `bindStartBinary`, and `row` reads the routing ordinal with `binaryInt` (`binary.failed` is saved and restored, so the caller's own decoding is untouched). Hand that
2637
2663
  execution's `PG_ROW` to `row(run, conn)`, `PG_COMPLETE` to `end(run)`, an error
2638
2664
  or `PG_LOST` to `fail(run)` (on a spent handle these do nothing and count in
2639
2665
  `stray`), and after each call `next(run)` until
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-aarch64-darwin",
3
- "version": "0.25.0",
3
+ "version": "0.27.2",
4
4
  "description": "The nish native compiler for macOS on ARM64",
5
5
  "license": "MIT",
6
6
  "os": [
@@ -47,6 +47,9 @@ export declare function appendFileSync(path: string, data: string): void;
47
47
  export declare function writeFileBytesSync(path: string, bytes: readonly u8[]): i32;
48
48
  export declare function renameSync(from: string, to: string): i32;
49
49
  export declare function unlinkSync(path: string): i32;
50
+ export declare function writeFileSyncMode(path: string, data: string, mode: i32): i32;
51
+ export declare function createFileExclusiveSync(path: string, data: string, mode: i32): i32;
52
+ export declare function mkdirSyncMode(path: string, mode: i32): i32;
50
53
  export declare function mkdirSync(path: string): boolean;
51
54
  export declare function isDirectorySync(path: string): boolean;
52
55
  export declare function readdirSync(path: string): string[] | null;
@@ -56,6 +59,7 @@ export declare function arch(): string;
56
59
  export declare function getenv(name: string): string | null;
57
60
  export declare function spawnSync(argv: string[]): number;
58
61
  export declare function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
62
+ export declare function spawnPiped(argv: string[], stdin: readonly u8[], stdout: u8[], stderr: u8[]): i32;
59
63
  export declare function kill(pid: i32, sig: i32): i32;
60
64
  export declare function availableParallelism(): i32;
61
65
  export declare function totalMemory(): i64;
@@ -157,6 +157,18 @@ export function unlinkSync(path) {
157
157
  return host.call("unlinkSync", [path]);
158
158
  }
159
159
 
160
+ export function writeFileSyncMode(path, data, mode) {
161
+ return host.call("writeFileSyncMode", [path, data, mode]);
162
+ }
163
+
164
+ export function createFileExclusiveSync(path, data, mode) {
165
+ return host.call("createFileExclusiveSync", [path, data, mode]);
166
+ }
167
+
168
+ export function mkdirSyncMode(path, mode) {
169
+ return host.call("mkdirSyncMode", [path, mode]);
170
+ }
171
+
160
172
  export function mkdirSync(path) {
161
173
  return host.call("mkdirSync", [path]);
162
174
  }
@@ -193,6 +205,18 @@ export function spawnSyncTo(argv, out, err) {
193
205
  return host.call("spawnSyncTo", [argv, out, err]);
194
206
  }
195
207
 
208
+ /**
209
+ * `spawnPiped(argv, stdin, stdout, stderr)`: the host runs the child and hands
210
+ * back its status and both streams' bytes, and they are appended here, as the
211
+ * runtime appends them to the caller's arrays.
212
+ */
213
+ export function spawnPiped(argv, input, out, err) {
214
+ const [status, outBytes, errBytes] = host.call("spawnPiped", [argv, input]);
215
+ for (const b of outBytes) out.push(b);
216
+ for (const b of errBytes) err.push(b);
217
+ return status;
218
+ }
219
+
196
220
  export function kill(pid, sig) {
197
221
  return host.call("kill", [pid, sig]);
198
222
  }
package/runtime/nish.d.ts CHANGED
@@ -276,6 +276,33 @@ declare function writeFileBytesSync(path: string, bytes: readonly u8[]): i32;
276
276
  declare function renameSync(from: string, to: string): i32;
277
277
  /** The name `path` removed: 0, or a negative errno. A directory is refused, not removed. */
278
278
  declare function unlinkSync(path: string): i32;
279
+ /**
280
+ * `writeFileSync` with an explicit `mode`: the file is exactly `mode`
281
+ * (`0o600` for a secret) before a byte of `data` is in it, whatever the umask
282
+ * or a file already there had, and a symbolic link at the path is refused
283
+ * (-40). The byte count, or a negative errno: -22 for a mode outside
284
+ * 0..0o777, the setuid, setgid and sticky bits included. The file is
285
+ * truncated and written in place, so a crash or a failed write leaves it
286
+ * empty or partial, and a file this process may write but does not own is
287
+ * emptied before the mode change answers -1 (`EPERM`). For a file that must
288
+ * be the old one or the new one, write a temporary name with this and move it
289
+ * with `renameSync`. Never an exit.
290
+ */
291
+ declare function writeFileSyncMode(path: string, data: string, mode: i32): i32;
292
+ /**
293
+ * `data` in a new file of exactly `mode`, created only if nothing is at `path`:
294
+ * -17 when something is, a dangling symbolic link included. Of several
295
+ * processes racing for one path exactly one succeeds, so it takes a lock. The
296
+ * byte count, or a negative errno; a write that fails after the create
297
+ * removes the file, so the lock is not left taken. Never an exit.
298
+ */
299
+ declare function createFileExclusiveSync(path: string, data: string, mode: i32): i32;
300
+ /**
301
+ * One directory made with `mode` less the umask (`0o700` is exact under any
302
+ * umask that leaves the owner's bits): 0, or a negative errno. A path already
303
+ * there is -17 and its mode is not changed; -22 for a mode outside 0..0o777.
304
+ */
305
+ declare function mkdirSyncMode(path: string, mode: i32): i32;
279
306
  /** One directory, not recursive; whether a directory is there afterwards. */
280
307
  declare function mkdirSync(path: string): boolean;
281
308
  /** Whether a directory is at `path` right now. One `stat`, and never an exit. */
@@ -299,6 +326,13 @@ declare function spawnSync(argv: string[]): number;
299
326
  * either path is refused, as `writeFileSync` refuses one, and the answer is -1.
300
327
  */
301
328
  declare function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
329
+ /**
330
+ * The same run with all three streams on pipes: `stdin` is written to the
331
+ * child and closed, and what it prints is appended to `stdout` and `stderr`.
332
+ * The status as `spawnSync` answers it, or -2 when a stream passed 16 MiB:
333
+ * the child is then killed and each array keeps at most the first 16 MiB.
334
+ */
335
+ declare function spawnPiped(argv: string[], stdin: readonly u8[], stdout: u8[], stderr: u8[]): i32;
302
336
  /**
303
337
  * Start `argv[0]` through `PATH` without waiting: the child's pid, or a
304
338
  * negative errno. The child is the program's until `waitPid` reaps it. No
@@ -322,10 +356,10 @@ declare function availableParallelism(): i32;
322
356
  /** Bytes of physical memory, or -1 when the host does not say. */
323
357
  declare function totalMemory(): i64;
324
358
  /**
325
- * Set this process's nice value to `n`: 0, or a negative errno. The kernel
326
- * clamps `n` to -20..19, and lowering it below the current value needs the
327
- * privilege to (-13 without it on Linux). Children started afterwards inherit
328
- * it. Throws under Node.
359
+ * Set this process's nice value to `n`, which must be 0..19: 0, or a negative
360
+ * errno. Lower-only, even as root: `n` outside 0..19 is -22 (EINVAL), and `n`
361
+ * below the current value is -13 (EACCES), both with no system call. Children
362
+ * started afterwards inherit it. Throws under Node.
329
363
  */
330
364
  declare function setNice(n: i32): i32;
331
365
  /**
@@ -492,6 +526,9 @@ declare var crypto: {
492
526
  //
493
527
  // `docs/LANGUAGE.md` -> Builtins -> Builtin modules is the normative list.
494
528
 
529
+ // Every negative errno a file call answers is the platform's own, untranslated:
530
+ // the numbers shown here are Linux's (-2 ENOENT, -17 EEXIST, -18 EXDEV, -22
531
+ // EINVAL, -40 ELOOP), and Darwin's differ (ELOOP is -62 there).
495
532
  declare module "nish:fs" {
496
533
  export function readFileSync(path: string): string;
497
534
  export function readFileSyncOrNull(path: string): string | null;
@@ -506,6 +543,12 @@ declare module "nish:fs" {
506
543
  export function renameSync(from: string, to: string): i32;
507
544
  /** 0, or a negative errno: a directory is refused, not removed. */
508
545
  export function unlinkSync(path: string): i32;
546
+ /** The byte count, or a negative errno; the file is exactly `mode` (0..0o777) before it holds a byte, a link refused, written in place. */
547
+ export function writeFileSyncMode(path: string, data: string, mode: i32): i32;
548
+ /** As `writeFileSyncMode`, but -17 when anything is at `path`: one racer wins; a failed write removes the file. */
549
+ export function createFileExclusiveSync(path: string, data: string, mode: i32): i32;
550
+ /** 0, or a negative errno: -17 for a path already there, whose mode is left alone. */
551
+ export function mkdirSyncMode(path: string, mode: i32): i32;
509
552
  /** `true` when the directory was created, `false` when it already existed. */
510
553
  export function mkdirSync(path: string): boolean;
511
554
  export function isDirectorySync(path: string): boolean;
@@ -530,6 +573,11 @@ declare module "nish:process" {
530
573
  * at either path is refused, and the answer is -1.
531
574
  */
532
575
  export function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
576
+ /**
577
+ * The same run with `stdin` written to the child and its stdout and stderr
578
+ * appended to the two arrays: the status, or -2 past 16 MiB on a stream.
579
+ */
580
+ export function spawnPiped(argv: string[], stdin: readonly u8[], stdout: u8[], stderr: u8[]): i32;
533
581
  /** Start a child without waiting: its pid, or a negative errno. */
534
582
  export function spawn(argv: string[]): i32;
535
583
  /** Send 0, 2, 9 or 15 to `pid`: 0, or a negative errno; any other signal is -22. */
@@ -540,7 +588,7 @@ declare module "nish:process" {
540
588
  export function availableParallelism(): i32;
541
589
  /** Bytes of physical memory, or -1. */
542
590
  export function totalMemory(): i64;
543
- /** Set this process's nice value (clamped to -20..19): 0, or a negative errno. */
591
+ /** Set this process's nice value, 0..19 only: 0, -22 outside it, or -13 below the current value (lower-only, even as root). */
544
592
  export function setNice(n: i32): i32;
545
593
  /**
546
594
  * Move this process to `SCHED_IDLE`: 0, a negative errno, or -95 off Linux.
package/runtime/nish.h CHANGED
@@ -282,6 +282,20 @@ nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len);
282
282
  Variable-time: never call it on a secret. */
283
283
  int64_t nish_regex_scan(const nish_str *s, const nish_array *masks, int64_t depth, int64_t off, int64_t width,
284
284
  int64_t from);
285
+ /* `Math.floor`, `Math.ceil` and `Math.trunc`, and the floor `Math.round`
286
+ takes (#943): each answers as C's `floor`, `ceil` and `trunc` do, -0, the
287
+ infinities and NaN included. A module for x86-64 calls them and registers a
288
+ constructor in `@llvm.global_ctors` that passes `nish_round_init` libm's
289
+ `floor`, `ceil` and `trunc`; it asks the CPU for SSE4.1 once before `main`
290
+ and keeps them on a machine without it, or under `NISH_SIMD=scalar`. Each
291
+ is otherwise SSE4.1's `roundsd`, so the constructor must run first. The
292
+ runtime references no libm symbol: the module that rounds does. They live
293
+ in runtime/runtime-simd.c; runtime-wasm.c has the wasm32 ones for host IR
294
+ that `scripts/build.sh --profile wasm` links. */
295
+ void nish_round_init(double (*floor_fn)(double), double (*ceil_fn)(double), double (*trunc_fn)(double));
296
+ double nish_floor(double x);
297
+ double nish_ceil(double x);
298
+ double nish_trunc(double x);
285
299
  /* No array grows past 2^31 - 1 elements and no string past 2^31 - 1 bytes:
286
300
  * `nish_array_grow`, `nish_alloc_array` and `nish_str_concat` fail as an
287
301
  * allocation does (`nish: out of memory`, exit 1; a trap on wasm) rather than
@@ -403,10 +417,24 @@ nish_array *nish_read_file_bytes(const nish_str *path);
403
417
  * errno — -EXDEV for a rename across file systems, which moves nothing, and
404
418
  * -EISDIR or -EPERM for a directory handed to `nish_unlink`, which never calls
405
419
  * rmdir. A path holding a NUL answers -EINVAL, not -ENOENT, so a program can
406
- * tell a malformed path from a missing file. */
420
+ * tell a malformed path from a missing file. The numbers are the platform's
421
+ * errno values, untranslated: -ELOOP is -40 on Linux and -62 on Darwin. */
407
422
  int32_t nish_rename(const nish_str *from, const nish_str *to);
408
423
  int32_t nish_unlink(const nish_str *path);
409
424
  int32_t nish_write_file_bytes(const nish_str *path, const nish_array *bytes);
425
+ /* `writeFileSyncMode(path, data, mode)`, `createFileExclusiveSync(path, data,
426
+ * mode)` and `mkdirSyncMode(path, mode)`: a file written, or created only if
427
+ * nothing is at the path (`O_EXCL`), then `fchmod`ed to exactly `mode` before a
428
+ * byte is written; a directory made with `mode & ~umask`. A link at a file's
429
+ * path is refused. The file writers answer the byte count and `nish_mkdir_mode`
430
+ * 0, or a negative platform errno: -EEXIST for an exclusive create or a mkdir
431
+ * that finds the path taken (a directory's mode is then left alone), -EINVAL
432
+ * for a mode outside 0..0777 (the setuid, setgid and sticky bits included) or
433
+ * a path holding a NUL, -ENOSYS under WASI. A failed exclusive create leaves
434
+ * no file behind. */
435
+ int32_t nish_write_file_mode(const nish_str *path, const nish_str *data, int32_t mode);
436
+ int32_t nish_create_file_exclusive(const nish_str *path, const nish_str *data, int32_t mode);
437
+ int32_t nish_mkdir_mode(const nish_str *path, int32_t mode);
410
438
  /* `writeBytes(bytes, off, len)` (nish:io), in runtime-os.c: the bytes
411
439
  * `[off, off + len)` of a `u8[]` on stdout, a short write finished and `EINTR`
412
440
  * retried. The caller has range-checked `off` and `len`. Answers `len`, or the
@@ -438,6 +466,17 @@ int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_st
438
466
  * own error otherwise, and -ENOSYS under WASI. The child is reaped by
439
467
  * `nish_wait_pid`. */
440
468
  int32_t nish_spawn_child(const nish_array *argv);
469
+ /* `spawnPiped(argv, stdin, stdout, stderr)`: start element 0 of `argv` as
470
+ * `nish_spawn` does, with the bytes of `stdin` (a `u8[]`) on its standard input
471
+ * and its standard output and error appended to `stdout` and `stderr`, wait for
472
+ * it, and answer `nish_spawn`'s status. -1 is `nish_spawn`'s -1, a pipe or
473
+ * `poll` that fails included, and always under WASI. -2 is a stream that passed
474
+ * 16 MiB: the child is killed with SIGKILL and reaped, and that array holds the
475
+ * stream's first 16 MiB. The two output arrays grow as `push` grows them, so
476
+ * the compiler treats both as pushed. `stdin` is read in place and never
477
+ * copied; SIGPIPE is blocked on the calling thread for the call, a SIGPIPE the
478
+ * call raised is consumed, and the thread's mask is restored after it. */
479
+ int32_t nish_spawn_piped(const nish_array *argv, const nish_array *in, nish_array *out, nish_array *err);
441
480
  /* `kill(pid, sig)`: send `sig` to `pid` and answer 0, or a negative errno. Only
442
481
  * 0, 2 (SIGINT), 9 (SIGKILL) and 15 (SIGTERM) are sent; any other signal, and
443
482
  * a pid of 0 or below, is -EINVAL. -ESRCH when no process has the pid, -EPERM
@@ -455,9 +494,9 @@ nish_array *nish_wait_pid(int32_t pid, int32_t flags);
455
494
  * page size; Darwin: `hw.memsize`), or -1 where the host does not say. */
456
495
  int64_t nish_total_memory(void);
457
496
  /* `setNice(n)`: this process's nice value set to `n` (`setpriority`), 0 or a
458
- * negative errno. The kernel clamps `n` to -20..19; lowering it below the
459
- * current value without the privilege is -EACCES (or -EPERM). -ENOSYS under
460
- * WASI. */
497
+ * negative errno. Lower-only, as root too: `n` takes 0..19, and anything else
498
+ * is -22 (-EINVAL) with no system call; `n` below the current value is -13
499
+ * (-EACCES) with none either. -ENOSYS under WASI. */
461
500
  int32_t nish_set_nice(int32_t n);
462
501
  /* `setIdlePriority()`: this process moved to `SCHED_IDLE` (Linux), 0 or a
463
502
  * negative errno; -95 (Linux's EOPNOTSUPP) elsewhere, -ENOSYS under WASI. An
package/runtime/nish.mjs CHANGED
@@ -94,12 +94,16 @@ provide("appendFileSync", shim.appendFileSync);
94
94
  provide("writeFileBytesSync", shim.writeFileBytesSync);
95
95
  provide("renameSync", shim.renameSync);
96
96
  provide("unlinkSync", shim.unlinkSync);
97
+ provide("writeFileSyncMode", shim.writeFileSyncMode);
98
+ provide("createFileExclusiveSync", shim.createFileExclusiveSync);
99
+ provide("mkdirSyncMode", shim.mkdirSyncMode);
97
100
  provide("mkdirSync", shim.mkdirSync);
98
101
  provide("isDirectorySync", shim.isDirectorySync);
99
102
  provide("readdirSync", shim.readdirSync);
100
103
  provide("realpathSync", shim.realpathSync);
101
104
  provide("spawnSync", shim.spawnSync);
102
105
  provide("spawnSyncTo", shim.spawnSyncTo);
106
+ provide("spawnPiped", shim.spawnPiped);
103
107
  // WP34 N12. `spawn` and `waitPid` throw, naming themselves: a child is reaped
104
108
  // by Node's event loop. `kill`, `availableParallelism` and `totalMemory` answer.
105
109
  provide("spawn", shim.spawn);
package/runtime/node.mjs CHANGED
@@ -102,6 +102,46 @@ const unlinkSync = (path) => errnoOr(0, () => fs.unlinkSync(path));
102
102
  const writeFileBytesSync = (path, bytes) =>
103
103
  errnoOr(bytes.length, () => fs.writeFileSync(path, Uint8Array.from(bytes), { flag: WRITE_FLAGS, mode: 0o644 }));
104
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
+
105
145
  /**
106
146
  * `writeBytes`'s write, after the core checked the range `[from, to)`: every
107
147
  * byte to stdout, then the count, or a negative errno. `fs.writeSync` may
@@ -268,6 +308,41 @@ function spawnSyncTo(argv, out, err) {
268
308
  return spawnImpl(argv, out, err);
269
309
  }
270
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
+
271
346
  /**
272
347
  * `kill(pid, sig)`: 0, or a negative errno, as `runtime-os.c`'s `nish_kill`
273
348
  * answers. Only 0, 2, 9 and 15 are sent and a pid of 0 or below is refused,
@@ -393,6 +468,9 @@ const CALLS = {
393
468
  writeFileBytesSync,
394
469
  renameSync,
395
470
  unlinkSync,
471
+ writeFileSyncMode,
472
+ createFileExclusiveSync,
473
+ mkdirSyncMode,
396
474
  writeBytes,
397
475
  mkdirSync,
398
476
  isDirectorySync,
@@ -403,6 +481,7 @@ const CALLS = {
403
481
  getenv,
404
482
  spawnSync,
405
483
  spawnSyncTo,
484
+ spawnPiped,
406
485
  kill,
407
486
  availableParallelism,
408
487
  totalMemory,