@amritk/nish-x86_64-linux 0.28.0 → 0.30.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 +8 -5
- package/bin/nish +0 -0
- package/docs/AI.md +114 -24
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -1
- package/runtime/core/host.mjs +4 -0
- package/runtime/nish.d.ts +127 -0
- package/runtime/nish.h +94 -5
- package/runtime/nish.mjs +18 -4
- package/runtime/runtime-net.c +386 -5
- package/runtime/runtime-os.c +59 -8
- package/runtime/runtime-parallel.c +60 -5
- package/runtime/runtime-region.c +231 -0
- package/runtime/runtime-sandbox.c +43 -34
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +838 -792
- package/scripts/build.sh +55 -12
- package/std/README.md +12 -3
- package/std/collections.ts +5 -5
- package/std/crypto/hmac.ts +305 -2
- package/std/crypto/jwt.ts +1661 -0
- package/std/json-typed.ts +34 -0
- package/std/json.ts +35 -6
- package/std/net/handler.ts +1348 -0
- package/std/net/http-client.ts +97 -16
- package/std/net/http-compress.ts +54 -12
- package/std/net/http1-client.ts +270 -41
- package/std/net/http1-server.ts +145 -32
- package/std/net/http1.ts +274 -51
- package/std/net/outbound.ts +409 -0
- package/std/net/postgres-carrier.ts +985 -0
- package/std/net/postgres.ts +297 -4
- package/std/net/prefork.ts +344 -20
- package/std/net/upstream.ts +759 -0
- package/std/secret.ts +1 -1
- package/std/sync/ring.ts +587 -0
- package/std/sync/shm.ts +284 -0
- package/std/term-rate.ts +13 -1
- package/std/threads.ts +35 -3
- /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
package/runtime/nish.mjs
CHANGED
|
@@ -104,9 +104,11 @@ provide("realpathSync", shim.realpathSync);
|
|
|
104
104
|
provide("spawnSync", shim.spawnSync);
|
|
105
105
|
provide("spawnSyncTo", shim.spawnSyncTo);
|
|
106
106
|
provide("spawnPiped", shim.spawnPiped);
|
|
107
|
-
// WP34 N12. `spawn` and `waitPid` throw, naming themselves: a
|
|
108
|
-
// by Node's event loop. `kill`, `availableParallelism` and
|
|
107
|
+
// WP34 N12. `spawn`, `spawnWithFds` and `waitPid` throw, naming themselves: a
|
|
108
|
+
// child is reaped by Node's event loop. `kill`, `availableParallelism` and
|
|
109
|
+
// `totalMemory` answer.
|
|
109
110
|
provide("spawn", shim.spawn);
|
|
111
|
+
provide("spawnWithFds", shim.spawnWithFds);
|
|
110
112
|
provide("kill", shim.kill);
|
|
111
113
|
provide("waitPid", shim.waitPid);
|
|
112
114
|
provide("availableParallelism", shim.availableParallelism);
|
|
@@ -147,16 +149,28 @@ for (const name of [
|
|
|
147
149
|
"pollRemove",
|
|
148
150
|
"pollWait",
|
|
149
151
|
"netSetOption",
|
|
152
|
+
"netMss",
|
|
153
|
+
"unixListen",
|
|
154
|
+
"unixListenMode",
|
|
155
|
+
"unixConnect",
|
|
156
|
+
"unixAccept",
|
|
157
|
+
"unixSocketPair",
|
|
158
|
+
"sendFd",
|
|
159
|
+
"recvFd",
|
|
160
|
+
"peerCredentials",
|
|
150
161
|
]) {
|
|
151
162
|
provide(name, () => shim.noNetReading(name));
|
|
152
163
|
}
|
|
153
|
-
// `netSetOption`'s option ids
|
|
154
|
-
// as natively: only
|
|
164
|
+
// `netSetOption`'s option ids and the `NET_UNIX_` flag and kinds are plain
|
|
165
|
+
// numbers, so they read the same here as natively: only a call throws.
|
|
155
166
|
provide("NET_TCP_NODELAY", 1);
|
|
156
167
|
provide("NET_TCP_QUICKACK", 2);
|
|
157
168
|
provide("NET_SO_KEEPALIVE", 3);
|
|
158
169
|
provide("NET_SO_SNDBUF", 4);
|
|
159
170
|
provide("NET_SO_RCVBUF", 5);
|
|
171
|
+
provide("NET_UNIX_UNLINK_STALE", 1);
|
|
172
|
+
provide("NET_UNIX_STREAM", 1);
|
|
173
|
+
provide("NET_UNIX_SEQPACKET", 2);
|
|
160
174
|
|
|
161
175
|
// `nish:term`. Whether a descriptor is a terminal and how big stdout's is have
|
|
162
176
|
// Node's answers; reading stdin, waiting for it and raw mode throw, each naming
|
package/runtime/runtime-net.c
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
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
20
|
* address is in use, -111 refused, -110 timed out, -22 a bad argument, -88
|
|
21
|
-
* not a socket), so
|
|
21
|
+
* not a socket, -107 not connected), so
|
|
22
22
|
* `nish_net_err` translates Darwin's; any other failure is the host's own
|
|
23
23
|
* `-errno`. Nothing here allocates: the addresses a call reads or writes are
|
|
24
24
|
* the caller's `u8[]`.
|
|
@@ -30,20 +30,26 @@
|
|
|
30
30
|
* (`EAFNOSUPPORT`, as in many containers) `::` falls back to IPv4's
|
|
31
31
|
* `0.0.0.0`.
|
|
32
32
|
*
|
|
33
|
-
* Linux and Darwin differ in
|
|
33
|
+
* Linux and Darwin differ in nine places: `SOCK_NONBLOCK | SOCK_CLOEXEC` and
|
|
34
34
|
* `accept4` against a `socket` or `accept` followed by `fcntl`, `MSG_NOSIGNAL`
|
|
35
35
|
* against the `SO_NOSIGPIPE` socket option (a write to a gone peer is -32 on
|
|
36
36
|
* both, never SIGPIPE), the errno numbers, UDP's segmentation offload
|
|
37
37
|
* (`UDP_SEGMENT`, `UDP_GRO`) and ECN marks, which Linux has and for which
|
|
38
38
|
* Darwin answers -95, and the readiness loop, which is epoll on Linux and
|
|
39
|
-
* kqueue on Darwin,
|
|
40
|
-
* Darwin)
|
|
39
|
+
* kqueue on Darwin, `TCP_QUICKACK`, which only Linux has (-95 on
|
|
40
|
+
* Darwin), and the MSS, which `netMss` reads from Linux's `TCP_INFO` and from
|
|
41
|
+
* `TCP_MAXSEG` on Darwin, a descriptor `recvFd` keeps, which Linux's
|
|
42
|
+
* `MSG_CMSG_CLOEXEC` makes close-on-exec as it arrives and Darwin marks with
|
|
43
|
+
* `fcntl` after `recvmsg` returns (the window RT-38 records), and
|
|
44
|
+
* `peerCredentials`, which reads Linux's `SO_PEERCRED` and Darwin's
|
|
45
|
+
* `getpeereid` with `LOCAL_PEERPID` (pid -1 where that refuses). Of the UDP calls, only the sending side and
|
|
46
|
+
* the GRO bind say -95 on Darwin: a
|
|
41
47
|
* receive leaves `meta` at 0, 0, and a `meta[1]` of 0 is also what a real
|
|
42
48
|
* Not-ECT datagram reads as, so a program learns that Darwin has no ECN from
|
|
43
49
|
* the send, never from the receive. The Darwin branch is compiled by CI's Darwin bootstrap
|
|
44
50
|
* rows and run by its `runtime-net (macos-latest)` job: tests/runtime-test.c and
|
|
45
51
|
* the `net_` sections of tests/run.js, on a mac. A WASI build has none of this (the checker refuses
|
|
46
|
-
* every `nish:net` call
|
|
52
|
+
* every `nish:net` call on wasm but `netMss`, folded to -1), so the file is empty.
|
|
47
53
|
*/
|
|
48
54
|
#if defined(__wasi__) || defined(__wasm__)
|
|
49
55
|
/* ISO C wants at least one declaration in a translation unit. */
|
|
@@ -58,9 +64,13 @@ typedef int nish_net_unused;
|
|
|
58
64
|
#include <fcntl.h>
|
|
59
65
|
#include <netinet/in.h>
|
|
60
66
|
#include <netinet/tcp.h>
|
|
67
|
+
#include <stddef.h>
|
|
61
68
|
#include <stdint.h>
|
|
69
|
+
#include <stddef.h>
|
|
62
70
|
#include <string.h>
|
|
63
71
|
#include <sys/socket.h>
|
|
72
|
+
#include <sys/stat.h>
|
|
73
|
+
#include <sys/un.h>
|
|
64
74
|
#include <unistd.h>
|
|
65
75
|
#if defined(__linux__)
|
|
66
76
|
#include <netinet/udp.h>
|
|
@@ -103,6 +113,9 @@ static int32_t nish_net_err(int e) {
|
|
|
103
113
|
if (e == ECONNREFUSED) return -111;
|
|
104
114
|
if (e == ETIMEDOUT) return -110;
|
|
105
115
|
if (e == ENOTSOCK) return -88;
|
|
116
|
+
if (e == ENAMETOOLONG) return -36;
|
|
117
|
+
if (e == ELOOP) return -40;
|
|
118
|
+
if (e == ENOTCONN) return -107;
|
|
106
119
|
#endif
|
|
107
120
|
return -e;
|
|
108
121
|
}
|
|
@@ -424,6 +437,35 @@ int32_t nish_net_set_option(int32_t fd, int32_t option, int32_t value) {
|
|
|
424
437
|
return setsockopt(fd, level, name, &value, sizeof value) != 0 ? nish_net_fail() : 0;
|
|
425
438
|
}
|
|
426
439
|
|
|
440
|
+
/* `netMss(fd)`: the segment size a connected TCP socket sends, the bytes of
|
|
441
|
+
payload one packet carries, or a plain -1 when there is none to read: not
|
|
442
|
+
an errno, because "no MSS" is one answer whatever the reason. A socket
|
|
443
|
+
with no peer is -1 first, as a fresh one answers the default 536 to both
|
|
444
|
+
options below. Linux's `TCP_INFO` answers the current `tcpi_snd_mss`; the
|
|
445
|
+
kernel copies at most the struct it has and says how much, so an answer
|
|
446
|
+
too short to reach the field is not read. `TCP_MAXSEG` is the fallback,
|
|
447
|
+
and Darwin's only reading. */
|
|
448
|
+
int32_t nish_net_mss(int32_t fd) {
|
|
449
|
+
struct sockaddr_storage peer;
|
|
450
|
+
socklen_t len = sizeof peer;
|
|
451
|
+
if (getpeername(fd, (struct sockaddr *)&peer, &len) != 0) return -1;
|
|
452
|
+
#if defined(TCP_INFO) && defined(__linux__)
|
|
453
|
+
struct tcp_info info;
|
|
454
|
+
memset(&info, 0, sizeof info);
|
|
455
|
+
len = sizeof info;
|
|
456
|
+
if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &len) == 0 &&
|
|
457
|
+
len >= offsetof(struct tcp_info, tcpi_snd_mss) + sizeof info.tcpi_snd_mss &&
|
|
458
|
+
info.tcpi_snd_mss > 0 && info.tcpi_snd_mss <= INT32_MAX)
|
|
459
|
+
return (int32_t)info.tcpi_snd_mss;
|
|
460
|
+
#endif
|
|
461
|
+
#if defined(TCP_MAXSEG)
|
|
462
|
+
int mss = 0;
|
|
463
|
+
len = sizeof mss;
|
|
464
|
+
if (getsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, &mss, &len) == 0 && len == sizeof mss && mss > 0) return mss;
|
|
465
|
+
#endif
|
|
466
|
+
return -1;
|
|
467
|
+
}
|
|
468
|
+
|
|
427
469
|
/* `udpBind(host, port, flags)`: a datagram socket, non-blocking and
|
|
428
470
|
close-on-exec, bound like `tcpListen`'s, with `SO_REUSEPORT` for flag 1
|
|
429
471
|
and `UDP_GRO` for flag 2; any other bit is -22. */
|
|
@@ -689,4 +731,343 @@ int32_t nish_poll_wait(int32_t loop, nish_array *ready, int32_t timeout_ms) {
|
|
|
689
731
|
return n;
|
|
690
732
|
#endif
|
|
691
733
|
}
|
|
734
|
+
|
|
735
|
+
/* The Unix-domain address `path` names into `s`, its length into `n`, and 1
|
|
736
|
+
or 0 for whether it is abstract; or the failure, before any socket exists.
|
|
737
|
+
An empty path or one with a NUL inside is -22, and so is a bare `@`. A path
|
|
738
|
+
too long for `sun_path` with its NUL is -36: the kernel would cut it short
|
|
739
|
+
or refuse it, and a cut one binds a file the program never named
|
|
740
|
+
(docs/security/runtime.md, RT-33). A leading `@` is Linux's abstract
|
|
741
|
+
namespace: the rest, at most `sizeof sun_path - 1` bytes, follows a NUL,
|
|
742
|
+
and the length is exact, because the name is every byte the length covers.
|
|
743
|
+
Darwin has no abstract namespace, so there `@` is -95. */
|
|
744
|
+
static int32_t nish_unix_addr(const nish_str *path, struct sockaddr_un *s, socklen_t *n) {
|
|
745
|
+
const char *p = path->data;
|
|
746
|
+
uint64_t len = path->len;
|
|
747
|
+
memset(s, 0, sizeof *s);
|
|
748
|
+
s->sun_family = AF_UNIX;
|
|
749
|
+
if (len == 0 || strlen(p) != len || (len == 1 && p[0] == '@')) return -22;
|
|
750
|
+
int abstract = p[0] == '@';
|
|
751
|
+
#if !defined(__linux__)
|
|
752
|
+
if (abstract) return -95;
|
|
753
|
+
#endif
|
|
754
|
+
if (len + !abstract > sizeof s->sun_path) return -36;
|
|
755
|
+
memcpy(s->sun_path, p, len);
|
|
756
|
+
if (abstract) s->sun_path[0] = 0;
|
|
757
|
+
*n = (socklen_t)(offsetof(struct sockaddr_un, sun_path) + len + !abstract);
|
|
758
|
+
#if !defined(__linux__)
|
|
759
|
+
s->sun_len = (unsigned char)*n;
|
|
760
|
+
#endif
|
|
761
|
+
return abstract;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/* Whether the socket file at `s` (`n` bytes) is stale, for `unixListen` flag
|
|
765
|
+
1 after a bind found the path taken: 0 once it is unlinked, else the
|
|
766
|
+
failure. Anything but a socket is -17 and left alone. A probe connects to
|
|
767
|
+
it: a refusal means nothing listens, and only then is the file unlinked; a
|
|
768
|
+
connect that succeeds or would wait (a full backlog) is a live listener,
|
|
769
|
+
-98. The probe is a connection the live listener sees and that closes at
|
|
770
|
+
once. Between the probe and the unlink another process may bind the path,
|
|
771
|
+
and that file is the one unlinked: the socket's directory is the trust
|
|
772
|
+
boundary (RT-34). Darwin refuses a connect to a listener whose backlog is
|
|
773
|
+
full with the same `ECONNREFUSED` as one to a dead file, so the probe
|
|
774
|
+
cannot tell them apart there, and a socket file in the way is -95 and left
|
|
775
|
+
alone: the flag never removes a live listener's file. */
|
|
776
|
+
static int32_t nish_unix_stale(const struct sockaddr_un *s, socklen_t n) {
|
|
777
|
+
struct stat st;
|
|
778
|
+
if (lstat(s->sun_path, &st) != 0) return errno == ENOENT ? 0 : nish_net_fail();
|
|
779
|
+
if (!S_ISSOCK(st.st_mode)) return -17;
|
|
780
|
+
#if !defined(__linux__)
|
|
781
|
+
(void)n;
|
|
782
|
+
return -95;
|
|
783
|
+
#else
|
|
784
|
+
int probe = nish_net_socket(AF_UNIX, SOCK_STREAM);
|
|
785
|
+
if (probe < 0) return nish_net_fail();
|
|
786
|
+
int e = connect(probe, (const struct sockaddr *)s, n) == 0 ? 0 : errno;
|
|
787
|
+
close(probe);
|
|
788
|
+
if (e != ECONNREFUSED) return e == 0 || e == EAGAIN || e == EINPROGRESS ? -98 : nish_net_err(e);
|
|
789
|
+
return unlink(s->sun_path) != 0 && errno != ENOENT ? nish_net_fail() : 0;
|
|
790
|
+
#endif
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/* A listening Unix stream socket at `path`, or the failure. A path that
|
|
794
|
+
exists is -98, unless flag 1 asks `nish_unix_stale` to clear a socket file
|
|
795
|
+
nothing listens on, after which the bind is tried once more; any other bit
|
|
796
|
+
is -22, and an abstract name ignores the flag, since no file is left
|
|
797
|
+
behind. A pathname socket's file gets `mode` between the bind and the
|
|
798
|
+
listen: a connect needs write permission on it, and until the listen every
|
|
799
|
+
connect is refused, so the umask's mode the bind gave it never admits
|
|
800
|
+
anyone (RT-35; `tests/cases/net_unix_paths` checks the order under umask
|
|
801
|
+
0). A failure after the bind closes the socket and unlinks the
|
|
802
|
+
file it made, so no failure leaves a descriptor or a file behind
|
|
803
|
+
(RT-36). */
|
|
804
|
+
static int32_t nish_unix_bound(const nish_str *path, int32_t backlog, int32_t flags, int32_t mode) {
|
|
805
|
+
if ((uint32_t)flags > 1) return -22;
|
|
806
|
+
struct sockaddr_un s;
|
|
807
|
+
socklen_t n;
|
|
808
|
+
int32_t abstract = nish_unix_addr(path, &s, &n);
|
|
809
|
+
if (abstract < 0) return abstract;
|
|
810
|
+
int fd = nish_net_socket(AF_UNIX, SOCK_STREAM);
|
|
811
|
+
if (fd < 0) return nish_net_fail();
|
|
812
|
+
int bound = bind(fd, (struct sockaddr *)&s, n);
|
|
813
|
+
if (bound != 0 && errno == EADDRINUSE && (flags & 1) && !abstract) {
|
|
814
|
+
int32_t stale = nish_unix_stale(&s, n);
|
|
815
|
+
if (stale != 0) {
|
|
816
|
+
close(fd);
|
|
817
|
+
return stale;
|
|
818
|
+
}
|
|
819
|
+
bound = bind(fd, (struct sockaddr *)&s, n);
|
|
820
|
+
}
|
|
821
|
+
if (bound != 0) {
|
|
822
|
+
int e = errno;
|
|
823
|
+
close(fd);
|
|
824
|
+
return nish_net_err(e);
|
|
825
|
+
}
|
|
826
|
+
if ((!abstract && chmod(s.sun_path, (mode_t)mode) != 0) || listen(fd, backlog) != 0) {
|
|
827
|
+
int e = errno;
|
|
828
|
+
close(fd);
|
|
829
|
+
if (!abstract) unlink(s.sun_path);
|
|
830
|
+
return nish_net_err(e);
|
|
831
|
+
}
|
|
832
|
+
return fd;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/* `unixListen(path, backlog, flags)`: a listening Unix stream socket whose
|
|
836
|
+
file only its owner may connect to (0600). */
|
|
837
|
+
int32_t nish_unix_listen(const nish_str *path, int32_t backlog, int32_t flags) {
|
|
838
|
+
return nish_unix_bound(path, backlog, flags, 0600);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/* `unixListenMode(path, backlog, flags, mode)`: the same with the file's mode
|
|
842
|
+
chosen, 0 to 0o777 and no other bits; an abstract name has no file to give
|
|
843
|
+
one, so it is -22, as a mode outside the range is. */
|
|
844
|
+
int32_t nish_unix_listen_mode(const nish_str *path, int32_t backlog, int32_t flags, int32_t mode) {
|
|
845
|
+
if ((uint32_t)mode > 0777) return -22;
|
|
846
|
+
if (path->len > 0 && path->data[0] == '@') return -22;
|
|
847
|
+
return nish_unix_bound(path, backlog, flags, mode);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/* `unixConnect(path)`: a new socket, non-blocking and close-on-exec,
|
|
851
|
+
connecting to the listener at `path`. A Unix connect finishes at once or
|
|
852
|
+
fails; a full backlog is -11 on Linux (`EAGAIN`) and -111 on Darwin, which
|
|
853
|
+
answers it as it answers a dead file, and either closes the socket, so the
|
|
854
|
+
caller tries again later. `EINPROGRESS` is answered as `tcpConnect`
|
|
855
|
+
answers it: the descriptor, with `connectResult` to say how it went once it
|
|
856
|
+
is writable. Any other failure closes the socket and is answered. */
|
|
857
|
+
int32_t nish_unix_connect(const nish_str *path) {
|
|
858
|
+
struct sockaddr_un s;
|
|
859
|
+
socklen_t n;
|
|
860
|
+
int32_t abstract = nish_unix_addr(path, &s, &n);
|
|
861
|
+
if (abstract < 0) return abstract;
|
|
862
|
+
int fd = nish_net_socket(AF_UNIX, SOCK_STREAM);
|
|
863
|
+
if (fd < 0) return nish_net_fail();
|
|
864
|
+
if (connect(fd, (struct sockaddr *)&s, n) != 0 && errno != EINPROGRESS) {
|
|
865
|
+
int e = errno;
|
|
866
|
+
close(fd);
|
|
867
|
+
return nish_net_err(e);
|
|
868
|
+
}
|
|
869
|
+
return fd;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/* `unixAccept(fd)`: the next connection, non-blocking and close-on-exec; -11
|
|
873
|
+
when none is waiting. A Unix peer has no address worth the name (a client
|
|
874
|
+
is seldom bound), so none is written. */
|
|
875
|
+
int32_t nish_unix_accept(int32_t fd) {
|
|
876
|
+
#if defined(__linux__)
|
|
877
|
+
int c = accept4(fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
|
|
878
|
+
#else
|
|
879
|
+
int c = nish_net_setup(accept(fd, NULL, NULL));
|
|
880
|
+
#endif
|
|
881
|
+
return c < 0 ? nish_net_fail() : c;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/* `unixSocketPair(kind, fds)`: two connected sockets into `fds[0]` and
|
|
885
|
+
`fds[1]`, non-blocking and close-on-exec, and 0. Kind 1 is a byte stream,
|
|
886
|
+
kind 2 keeps each write one message (`SOCK_SEQPACKET`), and any other kind,
|
|
887
|
+
or an `fds` shorter than two, is -22 before any socket exists. Darwin has
|
|
888
|
+
no Unix `SOCK_SEQPACKET`, so kind 2 there is -95. */
|
|
889
|
+
int32_t nish_unix_socket_pair(int32_t kind, nish_array *fds) {
|
|
890
|
+
if ((kind != 1 && kind != 2) || fds->len < 2) return -22;
|
|
891
|
+
int sv[2];
|
|
892
|
+
#if defined(__linux__)
|
|
893
|
+
if (socketpair(AF_UNIX, (kind == 1 ? SOCK_STREAM : SOCK_SEQPACKET) | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, sv) != 0)
|
|
894
|
+
return nish_net_fail();
|
|
895
|
+
#else
|
|
896
|
+
if (socketpair(AF_UNIX, kind == 1 ? SOCK_STREAM : SOCK_SEQPACKET, 0, sv) != 0)
|
|
897
|
+
return errno == EPROTONOSUPPORT || errno == EPROTOTYPE ? -95 : nish_net_fail();
|
|
898
|
+
nish_net_setup(sv[0]);
|
|
899
|
+
nish_net_setup(sv[1]);
|
|
900
|
+
#endif
|
|
901
|
+
int32_t *out = (int32_t *)fds->data;
|
|
902
|
+
out[0] = sv[0];
|
|
903
|
+
out[1] = sv[1];
|
|
904
|
+
return 0;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/* 0 when `fd` is a Unix-domain socket, the one kind a descriptor or a
|
|
908
|
+
credential passes over; else the refusal: -88 for a descriptor that is not
|
|
909
|
+
a socket, -95 for a socket of another family, and the host's failure for a
|
|
910
|
+
descriptor that is not open (-9). Asked before the call rather than read
|
|
911
|
+
from it, because a TCP socket does not refuse `SCM_RIGHTS` as such: Linux
|
|
912
|
+
answers -22 for the control message, and `SO_PEERCRED` on one succeeds with
|
|
913
|
+
no peer behind it. */
|
|
914
|
+
static int32_t nish_unix_only(int32_t fd) {
|
|
915
|
+
struct sockaddr_un s;
|
|
916
|
+
socklen_t n = sizeof s;
|
|
917
|
+
if (getsockname(fd, (struct sockaddr *)&s, &n) != 0) return nish_net_fail();
|
|
918
|
+
return s.sun_family == AF_UNIX ? 0 : -95;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/* The most descriptors one `recvFd` makes room for: one is kept and every
|
|
922
|
+
other is closed, so the room only decides how many the kernel installs to
|
|
923
|
+
be closed rather than drops itself (docs/security/runtime.md, RT-37). */
|
|
924
|
+
#define NISH_RECV_FDS 8
|
|
925
|
+
|
|
926
|
+
/* Linux marks a received descriptor close-on-exec as it arrives; Darwin has
|
|
927
|
+
no such flag, and `nish_recv_fd` marks it after (RT-38). */
|
|
928
|
+
#if defined(__linux__)
|
|
929
|
+
#define NISH_RECV_CLOEXEC MSG_CMSG_CLOEXEC
|
|
930
|
+
#else
|
|
931
|
+
#define NISH_RECV_CLOEXEC 0
|
|
932
|
+
#endif
|
|
933
|
+
|
|
934
|
+
/* `sendFd(sock, fd, buf, off, len)`: `len` bytes of `buf` from `off`, with
|
|
935
|
+
one `SCM_RIGHTS` copy of `fd` riding on them, and the bytes sent or the
|
|
936
|
+
failure. A descriptor travels with data, so `len` 0 is -22, and so is a
|
|
937
|
+
negative `fd`; one that is not open is the kernel's -9. Only a Unix socket
|
|
938
|
+
carries one (`nish_unix_only`). A gone peer is -32, as `netWrite`'s is.
|
|
939
|
+
The descriptor stays open here: the receiver gets its own. */
|
|
940
|
+
int32_t nish_send_fd(int32_t sock, int32_t fd, const nish_array *buf, int64_t off, int64_t len) {
|
|
941
|
+
if (len == 0 || fd < 0) return -22;
|
|
942
|
+
int32_t kind = nish_unix_only(sock);
|
|
943
|
+
if (kind != 0) return kind;
|
|
944
|
+
union {
|
|
945
|
+
struct cmsghdr h;
|
|
946
|
+
char room[CMSG_SPACE(sizeof(int))];
|
|
947
|
+
} c;
|
|
948
|
+
memset(&c, 0, sizeof c);
|
|
949
|
+
struct iovec v = {buf->data + off, (size_t)len};
|
|
950
|
+
struct msghdr m;
|
|
951
|
+
memset(&m, 0, sizeof m);
|
|
952
|
+
m.msg_iov = &v;
|
|
953
|
+
m.msg_iovlen = 1;
|
|
954
|
+
m.msg_control = c.room;
|
|
955
|
+
m.msg_controllen = sizeof c.room;
|
|
956
|
+
struct cmsghdr *h = CMSG_FIRSTHDR(&m);
|
|
957
|
+
h->cmsg_level = SOL_SOCKET;
|
|
958
|
+
h->cmsg_type = SCM_RIGHTS;
|
|
959
|
+
h->cmsg_len = CMSG_LEN(sizeof(int));
|
|
960
|
+
int one = fd;
|
|
961
|
+
memcpy(CMSG_DATA(h), &one, sizeof one);
|
|
962
|
+
ssize_t n = sendmsg(sock, &m, NISH_NOSIGNAL);
|
|
963
|
+
return n < 0 ? nish_net_fail() : (int32_t)n;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/* `recvFd(sock, buf, off, len, fd)`: at most `len` bytes into `buf` from
|
|
967
|
+
`off`, and the bytes read (0 at end of stream) or the failure. `fd[0]` is
|
|
968
|
+
the first descriptor the message carried, or -1 when it carried none; every
|
|
969
|
+
further one is closed before the answer, so a peer cannot make this process
|
|
970
|
+
hold descriptors it never asked for (RT-37). A message carrying more than
|
|
971
|
+
NISH_RECV_FDS has the rest dropped by the kernel (`MSG_CTRUNC`), and the
|
|
972
|
+
bytes and the first descriptor are answered under the same rule. `len` 0 is
|
|
973
|
+
-22, as `sendFd`'s is, and so is an empty `fd`, both before anything is
|
|
974
|
+
read. The descriptor kept is close-on-exec: from the receive on Linux
|
|
975
|
+
(`MSG_CMSG_CLOEXEC`), and from the `fcntl` after it on Darwin, where a child
|
|
976
|
+
another thread spawns in between inherits it (RT-38); a Darwin `fcntl`
|
|
977
|
+
that fails closes it and answers the failure, so no descriptor is handed
|
|
978
|
+
out without the flag. It is not made
|
|
979
|
+
non-blocking: its status flags are the open file's, which the sender
|
|
980
|
+
shares. */
|
|
981
|
+
int32_t nish_recv_fd(int32_t sock, nish_array *buf, int64_t off, int64_t len, nish_array *fd) {
|
|
982
|
+
if (len == 0 || fd->len < 1) return -22;
|
|
983
|
+
int32_t *out = (int32_t *)fd->data;
|
|
984
|
+
out[0] = -1;
|
|
985
|
+
int32_t kind = nish_unix_only(sock);
|
|
986
|
+
if (kind != 0) return kind;
|
|
987
|
+
union {
|
|
988
|
+
struct cmsghdr h;
|
|
989
|
+
char room[CMSG_SPACE(NISH_RECV_FDS * sizeof(int))];
|
|
990
|
+
} c;
|
|
991
|
+
struct iovec v = {buf->data + off, (size_t)len};
|
|
992
|
+
struct msghdr m;
|
|
993
|
+
memset(&m, 0, sizeof m);
|
|
994
|
+
m.msg_iov = &v;
|
|
995
|
+
m.msg_iovlen = 1;
|
|
996
|
+
m.msg_control = c.room;
|
|
997
|
+
m.msg_controllen = sizeof c.room;
|
|
998
|
+
ssize_t n = recvmsg(sock, &m, NISH_RECV_CLOEXEC);
|
|
999
|
+
if (n < 0) return nish_net_fail();
|
|
1000
|
+
int32_t err = 0;
|
|
1001
|
+
int kept = 0;
|
|
1002
|
+
for (struct cmsghdr *h = CMSG_FIRSTHDR(&m); h; h = CMSG_NXTHDR(&m, h)) {
|
|
1003
|
+
if (h->cmsg_level != SOL_SOCKET || h->cmsg_type != SCM_RIGHTS) continue;
|
|
1004
|
+
/* Counted from the header, but never past the room the kernel filled:
|
|
1005
|
+
a truncated message's last header may claim more than arrived. */
|
|
1006
|
+
size_t room = (size_t)((char *)m.msg_control + m.msg_controllen - (char *)CMSG_DATA(h));
|
|
1007
|
+
size_t claim = h->cmsg_len > CMSG_LEN(0) ? h->cmsg_len - CMSG_LEN(0) : 0;
|
|
1008
|
+
size_t count = (claim < room ? claim : room) / sizeof(int);
|
|
1009
|
+
for (size_t k = 0; k < count; k++) {
|
|
1010
|
+
int got;
|
|
1011
|
+
memcpy(&got, CMSG_DATA(h) + k * sizeof(int), sizeof got);
|
|
1012
|
+
if (!kept) {
|
|
1013
|
+
kept = 1;
|
|
1014
|
+
#if !defined(__linux__)
|
|
1015
|
+
if (fcntl(got, F_SETFD, FD_CLOEXEC) != 0) {
|
|
1016
|
+
err = nish_net_fail();
|
|
1017
|
+
close(got);
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
#endif
|
|
1021
|
+
out[0] = got;
|
|
1022
|
+
} else {
|
|
1023
|
+
close(got);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return err != 0 ? err : (int32_t)n;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/* `peerCredentials(fd, out)`: the process at the other end of a connected
|
|
1031
|
+
Unix socket, as `out[0..2]` = pid, uid, gid, and 0; an `out` shorter than
|
|
1032
|
+
three is -22 before anything is asked. The kernel recorded them when the
|
|
1033
|
+
connection was made (`connect`, or `socketpair` for a pair), so a peer that
|
|
1034
|
+
has since changed its uid, or passed the socket to another process, still
|
|
1035
|
+
reads as it was then (RT-39). A socket with no peer, listening or never
|
|
1036
|
+
connected, is -107 (ENOTCONN) on both: Linux's `SO_PEERCRED` would answer
|
|
1037
|
+
the listener's own credentials, or none, so `getpeername` is asked first;
|
|
1038
|
+
a peer that has since closed keeps its connect-time answer. Linux reads
|
|
1039
|
+
`SO_PEERCRED`. Darwin reads uid
|
|
1040
|
+
and gid from `getpeereid` and the pid from `LOCAL_PEERPID` where the SDK
|
|
1041
|
+
names it, else -1. */
|
|
1042
|
+
int32_t nish_peer_credentials(int32_t fd, nish_array *out) {
|
|
1043
|
+
if (out->len < 3) return -22;
|
|
1044
|
+
int32_t kind = nish_unix_only(fd);
|
|
1045
|
+
if (kind != 0) return kind;
|
|
1046
|
+
int32_t *o = (int32_t *)out->data;
|
|
1047
|
+
#if defined(__linux__)
|
|
1048
|
+
struct sockaddr_un a;
|
|
1049
|
+
socklen_t an = sizeof a;
|
|
1050
|
+
if (getpeername(fd, (struct sockaddr *)&a, &an) != 0) return nish_net_fail();
|
|
1051
|
+
struct ucred c;
|
|
1052
|
+
socklen_t n = sizeof c;
|
|
1053
|
+
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &c, &n) != 0) return nish_net_fail();
|
|
1054
|
+
o[0] = (int32_t)c.pid;
|
|
1055
|
+
o[1] = (int32_t)c.uid;
|
|
1056
|
+
o[2] = (int32_t)c.gid;
|
|
1057
|
+
#else
|
|
1058
|
+
uid_t uid;
|
|
1059
|
+
gid_t gid;
|
|
1060
|
+
if (getpeereid(fd, &uid, &gid) != 0) return nish_net_fail();
|
|
1061
|
+
int32_t pid = -1;
|
|
1062
|
+
#if defined(LOCAL_PEERPID)
|
|
1063
|
+
pid_t p;
|
|
1064
|
+
socklen_t n = sizeof p;
|
|
1065
|
+
if (getsockopt(fd, SOL_LOCAL, LOCAL_PEERPID, &p, &n) == 0) pid = (int32_t)p;
|
|
1066
|
+
#endif
|
|
1067
|
+
o[0] = pid;
|
|
1068
|
+
o[1] = (int32_t)uid;
|
|
1069
|
+
o[2] = (int32_t)gid;
|
|
1070
|
+
#endif
|
|
1071
|
+
return 0;
|
|
1072
|
+
}
|
|
692
1073
|
#endif
|
package/runtime/runtime-os.c
CHANGED
|
@@ -358,19 +358,23 @@ _Bool nish_mkdir(const nish_str *path) {
|
|
|
358
358
|
value rather than through a child that has already run.
|
|
359
359
|
|
|
360
360
|
`out` and `err` are paths for the child's stdout and stderr, or NULL to let
|
|
361
|
-
it inherit this process's; `dup`, when not NULL, is
|
|
362
|
-
child takes as its stdin, stdout and stderr
|
|
363
|
-
|
|
361
|
+
it inherit this process's; `dup`, when not NULL, is `n` descriptors the
|
|
362
|
+
child takes as `base`, `base + 1`, ...: its stdin, stdout and stderr for
|
|
363
|
+
`spawnPiped`'s pipes, and 3 onward for `spawnWithFds`. Every spawn builtin
|
|
364
|
+
starts its child here, so the argv vector and the redirects are written once; it is `static`, so a program
|
|
364
365
|
that spawns nothing loses it to `--gc-sections`. The answer is the child's
|
|
365
366
|
pid, or a negative errno: -EINVAL for an empty argv or an argument holding a
|
|
366
367
|
NUL, and `posix_spawnp`'s own error otherwise (-ENOENT for a command that is
|
|
367
368
|
not on PATH, on glibc). */
|
|
368
|
-
static int32_t nish_spawn_start(const nish_array *argv, const nish_str *out, const nish_str *err, const int *dup
|
|
369
|
+
static int32_t nish_spawn_start(const nish_array *argv, const nish_str *out, const nish_str *err, const int *dup,
|
|
370
|
+
int base, int n) {
|
|
369
371
|
#ifdef __wasi__
|
|
370
372
|
(void)argv;
|
|
371
373
|
(void)out;
|
|
372
374
|
(void)err;
|
|
373
375
|
(void)dup;
|
|
376
|
+
(void)base;
|
|
377
|
+
(void)n;
|
|
374
378
|
return -ENOSYS;
|
|
375
379
|
#else
|
|
376
380
|
if (!argv->len) return -EINVAL; /* argv[0] would read past an empty array */
|
|
@@ -395,7 +399,7 @@ static int32_t nish_spawn_start(const nish_array *argv, const nish_str *out, con
|
|
|
395
399
|
if (failed) return -failed;
|
|
396
400
|
/* A `dup2` action that could not be recorded would start the child on this
|
|
397
401
|
process's own stream in its place, so it fails the spawn. */
|
|
398
|
-
for (int k = 0; dup && k <
|
|
402
|
+
for (int k = 0; dup && k < n && !failed; k++) failed = posix_spawn_file_actions_adddup2(&fa, dup[k], base + k);
|
|
399
403
|
if (failed) {
|
|
400
404
|
posix_spawn_file_actions_destroy(&fa);
|
|
401
405
|
return -failed;
|
|
@@ -425,7 +429,7 @@ static int32_t nish_exit_code(int status) {
|
|
|
425
429
|
/* The two blocking builtins: start, then wait for that one child. A failed
|
|
426
430
|
start is -1, the answer they have always given. */
|
|
427
431
|
static int32_t nish_spawn_impl(const nish_array *argv, const nish_str *out, const nish_str *err) {
|
|
428
|
-
int32_t pid = nish_spawn_start(argv, out, err, 0);
|
|
432
|
+
int32_t pid = nish_spawn_start(argv, out, err, 0, 0, 0);
|
|
429
433
|
if (pid < 0) return -1;
|
|
430
434
|
#ifdef __wasi__
|
|
431
435
|
return -1;
|
|
@@ -456,7 +460,54 @@ int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_st
|
|
|
456
460
|
/* `spawn(argv)`: the child's pid, or a negative errno, without waiting. The
|
|
457
461
|
child is this process's until a `waitPid` reaps it; one never reaped stays
|
|
458
462
|
a zombie until the program exits, as it would in C. */
|
|
459
|
-
int32_t nish_spawn_child(const nish_array *argv) { return nish_spawn_start(argv, 0, 0, 0); }
|
|
463
|
+
int32_t nish_spawn_child(const nish_array *argv) { return nish_spawn_start(argv, 0, 0, 0, 0, 0); }
|
|
464
|
+
|
|
465
|
+
/* The most descriptors `spawnWithFds` hands on in one call. */
|
|
466
|
+
#define NISH_SPAWN_FDS 64
|
|
467
|
+
|
|
468
|
+
/* `spawnWithFds(argv, fds)`: `spawn`, with the child holding `fds[i]` as
|
|
469
|
+
descriptor `3 + i`, close-on-exec cleared there, and its pid or a negative
|
|
470
|
+
errno back. More than NISH_SPAWN_FDS descriptors, or a negative one, is
|
|
471
|
+
-EINVAL before anything is opened or started. Each source is first
|
|
472
|
+
duplicated, close-on-exec, to a scratch descriptor at or above `3 + n`, and
|
|
473
|
+
the child's `dup2` actions read only the scratch ones: a source that is
|
|
474
|
+
already its own target (`fds[0] == 3`) is then a `dup2` between two
|
|
475
|
+
different descriptors, which clears close-on-exec on every libc where a
|
|
476
|
+
`dup2` onto itself leaves it set, and a source that is an earlier entry's
|
|
477
|
+
target (`fds = [4, 3]`) is not overwritten before it is read. A source that
|
|
478
|
+
is not open is the `F_DUPFD_CLOEXEC`'s -EBADF. The scratch descriptors are
|
|
479
|
+
closed on every path out, so this process holds what it held before; every
|
|
480
|
+
descriptor of its own stays as it was, so the child also inherits whatever
|
|
481
|
+
this process holds without close-on-exec, which nothing the runtime opens
|
|
482
|
+
is (docs/security/runtime.md, RT-17, and RT-40 for what this call hands
|
|
483
|
+
on). The child shares each descriptor's open file: its offset and its
|
|
484
|
+
status flags, `O_NONBLOCK` among them. */
|
|
485
|
+
int32_t nish_spawn_with_fds(const nish_array *argv, const nish_array *fds) {
|
|
486
|
+
#ifdef __wasi__
|
|
487
|
+
(void)argv;
|
|
488
|
+
(void)fds;
|
|
489
|
+
return -ENOSYS;
|
|
490
|
+
#else
|
|
491
|
+
const int32_t *src = (const int32_t *)fds->data;
|
|
492
|
+
int n = (int)fds->len;
|
|
493
|
+
if (fds->len > NISH_SPAWN_FDS) return -EINVAL;
|
|
494
|
+
for (int k = 0; k < n; k++)
|
|
495
|
+
if (src[k] < 0) return -EINVAL;
|
|
496
|
+
int scratch[NISH_SPAWN_FDS];
|
|
497
|
+
int32_t rc = 0;
|
|
498
|
+
int made = 0;
|
|
499
|
+
for (; made < n; made++) {
|
|
500
|
+
scratch[made] = fcntl(src[made], F_DUPFD_CLOEXEC, 3 + n);
|
|
501
|
+
if (scratch[made] < 0) {
|
|
502
|
+
rc = -errno;
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (!rc) rc = nish_spawn_start(argv, 0, 0, scratch, 3, n);
|
|
507
|
+
while (made-- > 0) close(scratch[made]);
|
|
508
|
+
return rc;
|
|
509
|
+
#endif
|
|
510
|
+
}
|
|
460
511
|
|
|
461
512
|
/* `spawnPiped(argv, stdin, stdout, stderr)`: run the child as `nish_spawn`
|
|
462
513
|
does, with `stdin`'s bytes on its standard input and its standard output
|
|
@@ -525,7 +576,7 @@ int32_t nish_spawn_piped(const nish_array *argv, const nish_array *in, nish_arra
|
|
|
525
576
|
int32_t rc = -1;
|
|
526
577
|
if (nish_pipe(p) || nish_pipe(p + 2) || nish_pipe(p + 4)) goto done;
|
|
527
578
|
int child[3] = { p[0], p[3], p[5] };
|
|
528
|
-
int32_t pid = nish_spawn_start(argv, 0, 0, child);
|
|
579
|
+
int32_t pid = nish_spawn_start(argv, 0, 0, child, 0, 3);
|
|
529
580
|
/* The child holds its ends now; this process keeps only its own. */
|
|
530
581
|
for (int k = 0; k < 3; k++) {
|
|
531
582
|
close(child[k]);
|