~sircmpwn/hare-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
17 3

stdlib bug: not setting CLOEXEC correctly in net/tcp/+linux.ha

Details
Message ID
<0167d398-e0f1-e44b-cc68-1be57c1e693e@gmail.com>
DKIM signature
missing
Download raw message
(Happens in other places too, run git grep 'F_SETFL.*O_CLOEXEC')

> 	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
> 	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;
The right way to do this would be>  	let flags = rt::fcntl(sockfd, 
rt::F_GETFD, 0)!;
>  	rt::fcntl(sockfd, rt::F_SETFD, flags | rt::FD_CLOEXEC)!;

However it's better to set the flag atomically when the socket is 
created. Traditionally, there wasn't an API to do this with sockets, 
however most modern systems have those:

- socket(2)'s second argument can be OR'd with SOCK_CLOEXEC 
(socketpair(2) also supports this, btw)
- accept(2) has a variant called accept4, which takes a flags argument 
in which you can pass SOCK_CLOEXEC.

These were added in Linux 2.6.27/28 (2008), FreeBSD 10.0 (2014), OpenBSD 
5.7 (2015), NetBSD 6.0/8.0 (2012/2018) (NetBSD also has their own thing 
called paccept).



Extra info on why the original code doesn't do anything:

The O_ flags can be divided into three categories
- access modes (O_RDONLY, O_WRONLY, O_RDWR)
- creation flags (e.g. O_CREAT, O_EXCL, O_CLOEXEC)
- status flags (e.g. O_APPEND, O_DIRECT, O_NONBLOCK)

The first two groups of flags are exclusive to the open(2) call, and 
only the third can be used with F_GETFL/F_SETFL. All the other flags are 
simply ignored.

The reason why O_CLOEXEC is not part of the third group is because it's 
not the actual CLOEXEC flag. The real one is FD_CLOEXEC which is set 
using the F_SETFD fcntl. All O_CLOEXEC does is tell open(2) to set the 
FD_CLOEXEC atomically. The aforementioned SOCK_CLOEXEC does the same 
with the socket functions.

The distinction between F_SETFD and F_SETFL is that the former works on 
the file descriptor itself, while the latter works on the underlying 
open file object (i.e. O_ flags are shared across dup). Notably, 
FD_CLOEXEC is the only FD_ flag.
Details
Message ID
<CJSOA0YZTFN2.AGRW5K1HX233@taiga>
In-Reply-To
<0167d398-e0f1-e44b-cc68-1be57c1e693e@gmail.com> (view parent)
DKIM signature
missing
Download raw message
We have added the appropriate change for accept4, can you prepare a
patch for socket (and socketpair) as well?

[PATCH hare 1/4] net: set CLOEXEC on new sockets atomically

Details
Message ID
<20220507122949.954864-1-egor@opensrc.club>
In-Reply-To
<CJSOA0YZTFN2.AGRW5K1HX233@taiga> (view parent)
DKIM signature
missing
Download raw message
Patch: +13 -33
---
 net/tcp/+freebsd.ha    | 8 ++------
 net/tcp/+linux.ha      | 8 ++------
 net/udp/+freebsd.ha    | 6 ++----
 net/udp/+linux.ha      | 6 ++----
 net/unix/+freebsd.ha   | 8 ++------
 net/unix/+linux.ha     | 8 ++------
 net/unix/socketpair.ha | 2 +-
 7 files changed, 13 insertions(+), 33 deletions(-)

diff --git a/net/tcp/+freebsd.ha b/net/tcp/+freebsd.ha
index 826d01e4..f6764ff5 100644
--- a/net/tcp/+freebsd.ha
+++ b/net/tcp/+freebsd.ha
@@ -22,14 +22,12 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is keepalive right now
@@ -57,14 +55,12 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
diff --git a/net/tcp/+linux.ha b/net/tcp/+linux.ha
index 4925844f..6b1f8625 100644
--- a/net/tcp/+linux.ha
+++ b/net/tcp/+linux.ha
@@ -22,14 +22,12 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is keepalive right now
@@ -57,14 +55,12 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
diff --git a/net/udp/+freebsd.ha b/net/udp/+freebsd.ha
index 1d29eb42..a3533b69 100644
--- a/net/udp/+freebsd.ha
+++ b/net/udp/+freebsd.ha
@@ -19,7 +19,7 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,14 +48,12 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	const sockaddr = ip::to_native(addr, port);
	const sz = ip::native_addrlen(addr);
diff --git a/net/udp/+linux.ha b/net/udp/+linux.ha
index 386bc111..9d213ebb 100644
--- a/net/udp/+linux.ha
+++ b/net/udp/+linux.ha
@@ -19,7 +19,7 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,14 +48,12 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	const sockaddr = ip::to_native(addr, port);
	const sz = size(rt::sockaddr): u32;
diff --git a/net/unix/+freebsd.ha b/net/unix/+freebsd.ha
index 6e021bae..2af52d00 100644
--- a/net/unix/+freebsd.ha
+++ b/net/unix/+freebsd.ha
@@ -19,14 +19,12 @@ export fn connect(addr: addr) (io::file | net::error) = {
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	const sz = size(rt::sockaddr_un): u32;
	match (rt::connect(sockfd, &sockaddr, sz)) {
@@ -49,14 +47,12 @@ export fn listen(
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
diff --git a/net/unix/+linux.ha b/net/unix/+linux.ha
index c45a25fe..1ea8427a 100644
--- a/net/unix/+linux.ha
+++ b/net/unix/+linux.ha
@@ -20,14 +20,12 @@ export fn connect(addr: addr) (io::file | net::error) = {
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	const sz = size(rt::sockaddr_un): u32;
	match (rt::connect(sockfd, &sockaddr, sz)) {
@@ -50,14 +48,12 @@ export fn listen(
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
diff --git a/net/unix/socketpair.ha b/net/unix/socketpair.ha
index 2d19aba0..c5fb35f4 100644
--- a/net/unix/socketpair.ha
+++ b/net/unix/socketpair.ha
@@ -11,7 +11,7 @@ use io;
// domain and returns an unnamed pair of sockets of type [[rt::SOCK_STREAM]].
export fn socketpair() ((io::file, io::file) | net::error) = {
	let sv: [2]int = [0...];
	match (rt::socketpair(rt::AF_UNIX : int, rt::SOCK_STREAM : int, 0, &sv)) {
	match (rt::socketpair(rt::AF_UNIX : int, (rt::SOCK_STREAM | rt::SOCK_CLOEXEC) : int, 0, &sv)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case =>
-- 
2.30.2

[PATCH hare 2/4] net: invert the meaning of CLOEXEC in sockflags

Details
Message ID
<20220507122949.954864-2-egor@opensrc.club>
In-Reply-To
<20220507122949.954864-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +8 -14
Currently if you want a file descriptor without CLOEXEC, you have to
pass a dummy 0 argument. And if you pass NONBLOCK, you have to remember
to also pass CLOEXEC, since in most cases that's what you actually want.

This will also make it easier to add socketflags to connect/socket
functions, since they already take sockopts and so you can't just
len(flags) == 0 to detect that no SOCK flags have been specified.
---
 net/+freebsd.ha | 11 ++++-------
 net/+linux.ha   | 11 ++++-------
 2 files changed, 8 insertions(+), 14 deletions(-)

diff --git a/net/+freebsd.ha b/net/+freebsd.ha
index f14da01a..85be875a 100644
--- a/net/+freebsd.ha
+++ b/net/+freebsd.ha
@@ -12,27 +12,24 @@ use strings;

// Optional flags to [[accept]] to be set on the returned [[io::file]].
// See the O_CLOEXEC and O_NONBLOCK sections of open(2) for details.
// Note that CLOEXEC is on by default, and NOCLOEXEC flag disables it.
export type sockflags = enum int {
	CLOEXEC = rt::SOCK_CLOEXEC,
	NOCLOEXEC = rt::SOCK_CLOEXEC,
	NONBLOCK = rt::SOCK_NONBLOCK
};

// Accepts the next connection from a socket. Blocks until a new connection is
// available. Optionally accepts CLOEXEC and NONBLOCK flags. If flags are
// available. Optionally accepts NOCLOEXEC and NONBLOCK flags. If flags are
// supplied, the [[io::file]] returned will have the supplied flags set.
// If no flags are supplied, CLOEXEC is used by default.
export fn accept(sock: io::file, flags: sockflags...) (io::file | error) = {
	let sn = rt::sockaddr {...};
	const sz = size(rt::sockaddr): u32;
	// Default to CLOEXEC if no flags are supplied
	if (len(flags) == 0) {
		flags = [sockflags::CLOEXEC];
	};
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const fd = match (rt::accept4(sock, &sn, &sz, f)) {
	case let err: rt::errno =>
		return errors::errno(err);
diff --git a/net/+linux.ha b/net/+linux.ha
index a6546bb7..2a0227ce 100644
--- a/net/+linux.ha
+++ b/net/+linux.ha
@@ -12,27 +12,24 @@ use strings;

// Optional flags to [[accept]] to be set on the returned [[io::file]].
// See the O_CLOEXEC and O_NONBLOCK sections of open(2) for details.
// Note that CLOEXEC is on by default, and NOCLOEXEC flag disables it.
export type sockflags = enum int {
	CLOEXEC = rt::SOCK_CLOEXEC,
	NOCLOEXEC = rt::SOCK_CLOEXEC,
	NONBLOCK = rt::SOCK_NONBLOCK
};

// Accepts the next connection from a socket. Blocks until a new connection is
// available. Optionally accepts CLOEXEC and NONBLOCK flags. If flags are
// available. Optionally accepts NOCLOEXEC and NONBLOCK flags. If flags are
// supplied, the [[io::file]] returned will have the supplied flags set.
// If no flags are supplied, CLOEXEC is used by default.
export fn accept(sock: io::file, flags: sockflags...) (io::file | error) = {
	let sn = rt::sockaddr {...};
	const sz = size(rt::sockaddr): u32;
	// Default to CLOEXEC if no flags are supplied
	if (len(flags) == 0) {
		flags = [sockflags::CLOEXEC];
	};
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const fd = match (rt::accept4(sock, &sn, &sz, f: int)) {
	case let err: rt::errno =>
		return errors::errno(err);
-- 
2.30.2

[PATCH hare 3/4] net: add sockflags to connect/accept/socketpair

Details
Message ID
<20220507122949.954864-3-egor@opensrc.club>
In-Reply-To
<20220507122949.954864-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +176 -34
---
 net/tcp/+freebsd.ha    | 31 ++++++++++++++++++++++++++-----
 net/tcp/+linux.ha      | 31 ++++++++++++++++++++++++++-----
 net/tcp/options.ha     |  6 ++++--
 net/udp/+freebsd.ha    | 29 +++++++++++++++++++++++++----
 net/udp/+linux.ha      | 29 +++++++++++++++++++++++++----
 net/udp/options.ha     |  6 +++++-
 net/unix/+freebsd.ha   | 31 ++++++++++++++++++++++++++-----
 net/unix/+linux.ha     | 31 ++++++++++++++++++++++++++-----
 net/unix/options.ha    |  6 +++++-
 net/unix/socketpair.ha | 10 ++++++++--
 10 files changed, 176 insertions(+), 34 deletions(-)

diff --git a/net/tcp/+freebsd.ha b/net/tcp/+freebsd.ha
index f6764ff5..142504ea 100644
--- a/net/tcp/+freebsd.ha
+++ b/net/tcp/+freebsd.ha
@@ -22,7 +22,16 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -30,8 +39,11 @@ export fn connect(
	};

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is keepalive right now
		setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		match (options[i]) {
		case keepalive =>
			setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		case => void;
		};
	};
	const sz = ip::native_addrlen(addr);
	match (rt::connect(sockfd, &sockaddr, sz)) {
@@ -55,7 +67,16 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -73,7 +94,7 @@ export fn listen(
			setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		case let b: backlog =>
			bk = b;
		case let p: portassignment => void;
		case => void;
		};
	};

diff --git a/net/tcp/+linux.ha b/net/tcp/+linux.ha
index 6b1f8625..61fb0d47 100644
--- a/net/tcp/+linux.ha
+++ b/net/tcp/+linux.ha
@@ -22,7 +22,16 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -30,8 +39,11 @@ export fn connect(
	};

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is keepalive right now
		setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		match (options[i]) {
		case keepalive =>
			setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		case => void;
		};
	};
	const sz = size(rt::sockaddr): u32;
	match (rt::connect(sockfd, &sockaddr, sz)) {
@@ -55,7 +67,16 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -73,7 +94,7 @@ export fn listen(
			setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		case let b: backlog =>
			bk = b;
		case let p: portassignment => void;
		case => void;
		};
	};

diff --git a/net/tcp/options.ha b/net/tcp/options.ha
index 0477c8de..10b92cd2 100644
--- a/net/tcp/options.ha
+++ b/net/tcp/options.ha
@@ -1,5 +1,6 @@
// License: MPL-2.0
// (c) 2021 Drew DeVault <sir@cmpwn.com>
use net;

// Enables keep-alive for a socket.
export type keepalive = void;
@@ -20,7 +21,7 @@ export type reuseaddr = void;
export type portassignment = *u16;

// Options for [[connect]].
export type connect_option = keepalive;
export type connect_option = (keepalive | net::sockflags);

// Options for [[listen]].
export type listen_option = (
@@ -28,4 +29,5 @@ export type listen_option = (
	reuseport |
	reuseaddr |
	backlog |
	portassignment);
	portassignment |
	net::sockflags);
diff --git a/net/udp/+freebsd.ha b/net/udp/+freebsd.ha
index a3533b69..5d436d81 100644
--- a/net/udp/+freebsd.ha
+++ b/net/udp/+freebsd.ha
@@ -12,6 +12,7 @@ use rt;
export fn connect(
	dest: ip::addr,
	port: u16,
	options: connect_option...
) (io::file | net::error) = {
	const family = match (dest) {
	case ip::addr4 =>
@@ -19,7 +20,13 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		// Only sockflags for now
		f |= options[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,7 +55,16 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -64,7 +80,12 @@ export fn listen(
	};

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is portassignment right now
		let portout = match (options[i]) {
		case let p: portassignment =>
			yield p;
		case =>
			continue;
		};
		let sn = rt::sockaddr {...};
		let al = size(rt::sockaddr): u32;
		match (rt::getsockname(sockfd, &sn, &al)) {
@@ -73,7 +94,7 @@ export fn listen(
		case int => void;
		};
		const addr = ip::from_native(sn);
		*options[i] = addr.1;
		*portout = addr.1;
	};

	return io::fdopen(sockfd);
diff --git a/net/udp/+linux.ha b/net/udp/+linux.ha
index 9d213ebb..c64d464f 100644
--- a/net/udp/+linux.ha
+++ b/net/udp/+linux.ha
@@ -12,6 +12,7 @@ use rt;
export fn connect(
	dest: ip::addr,
	port: u16,
	options: connect_option...
) (io::file | net::error) = {
	const family = match (dest) {
	case ip::addr4 =>
@@ -19,7 +20,13 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		// Only sockflags for now
		f |= options[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,7 +55,16 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -64,7 +80,12 @@ export fn listen(
	};

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is portassignment right now
		let portout = match (options[i]) {
		case let p: portassignment =>
			yield p;
		case =>
			continue;
		};
		let sn = rt::sockaddr {...};
		let al = size(rt::sockaddr): u32;
		match (rt::getsockname(sockfd, &sn, &al)) {
@@ -73,7 +94,7 @@ export fn listen(
		case int => void;
		};
		const addr = ip::from_native(sn);
		*options[i] = addr.1;
		*portout = addr.1;
	};

	return io::fdopen(sockfd);
diff --git a/net/udp/options.ha b/net/udp/options.ha
index e1d2ca84..ed012725 100644
--- a/net/udp/options.ha
+++ b/net/udp/options.ha
@@ -1,12 +1,16 @@
// License: MPL-2.0
// (c) 2021 Drew DeVault <sir@cmpwn.com>
use net;

// To have the system select an arbitrary unused port for [[listen]], set port to
// zero. To retrieve the assigned port, provide this as one of the options and
// the addressed u16 will be filled in with the port.
export type portassignment = *u16;

// Options for [[connect]].
export type connect_option = net::sockflags;

// Options available for [[listen]].
export type listen_option = portassignment;
export type listen_option = (portassignment | net::sockflags);

// TODO: Add send/recv flags
diff --git a/net/unix/+freebsd.ha b/net/unix/+freebsd.ha
index 2af52d00..b6c18f74 100644
--- a/net/unix/+freebsd.ha
+++ b/net/unix/+freebsd.ha
@@ -12,14 +12,23 @@ use types;

// Opens a UNIX socket connection to the path. Blocks until the connection is
// established.
export fn connect(addr: addr) (io::file | net::error) = {
export fn connect(
	addr: addr,
	options: connect_option...
) (io::file | net::error) = {
	let sockaddr = match (to_native(addr)) {
	case let a: rt::sockaddr =>
		yield a;
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		// Only sockflags for now
		f |= options[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -47,7 +56,16 @@ export fn listen(
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -56,8 +74,11 @@ export fn listen(

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
		// Only option is backlog right now
		bk = options[i];
		match (options[i]) {
		case let b: backlog =>
			bk = b;
		case => void;
		};
	};

	match (rt::bind(sockfd, &sockaddr, size(rt::sockaddr_un): u32)) {
diff --git a/net/unix/+linux.ha b/net/unix/+linux.ha
index 1ea8427a..3b86aae0 100644
--- a/net/unix/+linux.ha
+++ b/net/unix/+linux.ha
@@ -13,14 +13,23 @@ use types;

// Opens a UNIX socket connection to the path. Blocks until the connection is
// established.
export fn connect(addr: addr) (io::file | net::error) = {
export fn connect(
	addr: addr,
	options: connect_option...
) (io::file | net::error) = {
	let sockaddr = match (to_native(addr)) {
	case let a: rt::sockaddr =>
		yield a;
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		// Only sockflags for now
		f |= options[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,7 +57,16 @@ export fn listen(
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -57,8 +75,11 @@ export fn listen(

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
		// Only option is backlog right now
		bk = options[i];
		match (options[i]) {
		case let b: backlog =>
			bk = b;
		case => void;
		};
	};

	match (rt::bind(sockfd, &sockaddr, size(rt::sockaddr_un): u32)) {
diff --git a/net/unix/options.ha b/net/unix/options.ha
index c968f955..8ffe5a4c 100644
--- a/net/unix/options.ha
+++ b/net/unix/options.ha
@@ -1,9 +1,13 @@
// License: MPL-2.0
// (c) 2021 Drew DeVault <sir@cmpwn.com>
use net;

// Configures the backlog size for a listener. If not specified, a sensible
// default (10) is used.
export type backlog = u32;

// Options for [[connect]].
export type connect_option = net::sockflags;

// Options for [[listen]].
export type listen_option = backlog;
export type listen_option = (backlog | net::sockflags);
diff --git a/net/unix/socketpair.ha b/net/unix/socketpair.ha
index c5fb35f4..24133be4 100644
--- a/net/unix/socketpair.ha
+++ b/net/unix/socketpair.ha
@@ -9,9 +9,15 @@ use io;

// A thin wrapper around socketpair(2) that presumes [[rt::AF_UNIX]] for the
// domain and returns an unnamed pair of sockets of type [[rt::SOCK_STREAM]].
export fn socketpair() ((io::file, io::file) | net::error) = {
export fn socketpair(flags: net::sockflags...) ((io::file, io::file) | net::error) = {
	let sv: [2]int = [0...];
	match (rt::socketpair(rt::AF_UNIX : int, (rt::SOCK_STREAM | rt::SOCK_CLOEXEC) : int, 0, &sv)) {
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	match (rt::socketpair(rt::AF_UNIX : int, (rt::SOCK_STREAM | f) : int, 0, &sv)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case =>
-- 
2.30.2

[PATCH hare 4/4] unix: invert the meaning of CLOEXEC in pipe_flag

Details
Message ID
<20220507122949.954864-4-egor@opensrc.club>
In-Reply-To
<20220507122949.954864-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +8 -6
for consistency with sockets
---
Cc: Drew DeVault <sir@cmpwn.com>
should I also do this for os::open too?

 unix/+freebsd/pipe.ha | 7 ++++---
 unix/+linux/pipe.ha   | 7 ++++---
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/unix/+freebsd/pipe.ha b/unix/+freebsd/pipe.ha
index cc3d9e94..12b10511 100644
--- a/unix/+freebsd/pipe.ha
+++ b/unix/+freebsd/pipe.ha
@@ -6,9 +6,9 @@ use io;
use rt;

// Flags to use for the [[io::file]]s returned by [[pipe]]
// Only CLOEXEC and NONBLOCK are guaranteed to be available.
// Only NOCLOEXEC and NONBLOCK are guaranteed to be available.
export type pipe_flag = enum {
	CLOEXEC = rt::O_CLOEXEC,
	NOCLOEXEC = rt::O_CLOEXEC,
	DIRECT = rt::O_DIRECT,
	NONBLOCK = rt::O_NONBLOCK,
};
@@ -19,10 +19,11 @@ export type pipe_flag = enum {
// recommended that you add it unless you know that you don't want it.
export fn pipe(flags: pipe_flag...) ((io::file, io::file) | errors::error) = {
	let fds: [2]int = [0...];
	let flag: pipe_flag = if (len(flags) == 0) pipe_flag::CLOEXEC else 0;
	let flag: pipe_flag = 0;
	for (let i = 0z; i < len(flags); i += 1) {
		flag |= flags[i];
	};
	flag ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flag)) {
	case void => void;
	case let e: rt::errno =>
diff --git a/unix/+linux/pipe.ha b/unix/+linux/pipe.ha
index aadefd1f..742cf7a4 100644
--- a/unix/+linux/pipe.ha
+++ b/unix/+linux/pipe.ha
@@ -5,9 +5,9 @@ use io;
use rt;

// Flags to use for the [[io::file]]s returned by [[pipe]]
// Only CLOEXEC and NONBLOCK are guaranteed to be available.
// Only NOCLOEXEC and NONBLOCK are guaranteed to be available.
export type pipe_flag = enum {
	CLOEXEC = rt::O_CLOEXEC,
	NOCLOEXEC = rt::O_CLOEXEC,
	DIRECT = rt::O_DIRECT,
	NONBLOCK = rt::O_NONBLOCK,
};
@@ -18,10 +18,11 @@ export type pipe_flag = enum {
// recommended that you add it unless you know that you don't want it.
export fn pipe(flags: pipe_flag...) ((io::file, io::file) | errors::error) = {
	let fds: [2]int = [0...];
	let flag: pipe_flag = if (len(flags) == 0) pipe_flag::CLOEXEC else 0;
	let flag: pipe_flag = 0;
	for (let i = 0z; i < len(flags); i += 1) {
		flag |= flags[i];
	};
	flag ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flag)) {
	case void => void;
	case let e: rt::errno =>
-- 
2.30.2

[hare/patches] build success

builds.sr.ht <builds@sr.ht>
Details
Message ID
<CJTJ5FFU732S.3JARWWHTD0KXX@cirno2>
In-Reply-To
<20220507122949.954864-4-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
hare/patches: SUCCESS in 1m29s

[stdlib bug: not setting CLOEXEC correctly in net/tcp/+linux.ha][0] from [Egor][1]

[0]: https://lists.sr.ht/~sircmpwn/hare-dev/patches/32081
[1]: egor@opensrc.club

✓ #753258 SUCCESS hare/patches/alpine.yml  https://builds.sr.ht/~sircmpwn/job/753258
✓ #753259 SUCCESS hare/patches/freebsd.yml https://builds.sr.ht/~sircmpwn/job/753259

Re: [PATCH hare 4/4] unix: invert the meaning of CLOEXEC in pipe_flag

Details
Message ID
<CJV3TKTY8Y9E.NVK6T907H3C8@taiga>
In-Reply-To
<20220507122949.954864-4-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
I think that inverting CLOEXEC is a good idea, but can you do it
everywhere (e.g. with os::open too)?

[PATCH hare v2 0/7] CLOEXEC fixes

Details
Message ID
<20220510170356.1022176-1-egor@opensrc.club>
In-Reply-To
<20220507122949.954864-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
The old patches are the same, I just added signed-off-by lines to the
commit messages.

Egor (7):
  net: set CLOEXEC on new sockets atomically
  net: invert the meaning of CLOEXEC in sockflags
  net: add sockflags to connect/accept/socketpair
  unix: invert the meaning of CLOEXEC in pipe_flag
  fs: invert the meaning of NOCTTY/CLOEXEC in flags
  fix dup without CLOEXEC in linux dirfs_clone
  os::exec: clear FD_CLOEXEC when dup2ing fd to itself

 fs/types.ha               |  6 ++++--
 net/+freebsd.ha           | 11 ++++-------
 net/+linux.ha             | 11 ++++-------
 net/tcp/+freebsd.ha       | 35 ++++++++++++++++++++++++++---------
 net/tcp/+linux.ha         | 35 ++++++++++++++++++++++++++---------
 net/tcp/options.ha        |  6 ++++--
 net/udp/+freebsd.ha       | 31 +++++++++++++++++++++++++------
 net/udp/+linux.ha         | 31 +++++++++++++++++++++++++------
 net/udp/options.ha        |  6 +++++-
 net/unix/+freebsd.ha      | 35 ++++++++++++++++++++++++++---------
 net/unix/+linux.ha        | 35 ++++++++++++++++++++++++++---------
 net/unix/options.ha       |  6 +++++-
 net/unix/socketpair.ha    | 10 ++++++++--
 os/+freebsd/dirfdfs.ha    | 24 +++++++++---------------
 os/+linux/dirfdfs.ha      | 20 +++++---------------
 os/exec/exec+freebsd.ha   | 18 ++++++++++++++----
 os/exec/exec+linux.ha     | 18 ++++++++++++++----
 os/fs.ha                  | 12 ++++--------
 rt/+linux/types.ha        |  2 ++
 temp/+freebsd.ha          |  2 +-
 temp/+linux.ha            |  4 ++--
 unix/+freebsd/pipe.ha     |  7 ++++---
 unix/+linux/pipe.ha       |  7 ++++---
 unix/tty/+freebsd/open.ha |  2 +-
 unix/tty/+linux/open.ha   |  2 +-
 25 files changed, 249 insertions(+), 127 deletions(-)

-- 
2.30.2

[PATCH hare v2 1/7] net: set CLOEXEC on new sockets atomically

Details
Message ID
<20220510170356.1022176-2-egor@opensrc.club>
In-Reply-To
<20220510170356.1022176-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +13 -33
Signed-off-by: Egor <egor@opensrc.club>
---
 net/tcp/+freebsd.ha    | 8 ++------
 net/tcp/+linux.ha      | 8 ++------
 net/udp/+freebsd.ha    | 6 ++----
 net/udp/+linux.ha      | 6 ++----
 net/unix/+freebsd.ha   | 8 ++------
 net/unix/+linux.ha     | 8 ++------
 net/unix/socketpair.ha | 2 +-
 7 files changed, 13 insertions(+), 33 deletions(-)

diff --git a/net/tcp/+freebsd.ha b/net/tcp/+freebsd.ha
index 826d01e4..f6764ff5 100644
--- a/net/tcp/+freebsd.ha
+++ b/net/tcp/+freebsd.ha
@@ -22,14 +22,12 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is keepalive right now
@@ -57,14 +55,12 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
diff --git a/net/tcp/+linux.ha b/net/tcp/+linux.ha
index 4925844f..6b1f8625 100644
--- a/net/tcp/+linux.ha
+++ b/net/tcp/+linux.ha
@@ -22,14 +22,12 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is keepalive right now
@@ -57,14 +55,12 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
diff --git a/net/udp/+freebsd.ha b/net/udp/+freebsd.ha
index 1d29eb42..a3533b69 100644
--- a/net/udp/+freebsd.ha
+++ b/net/udp/+freebsd.ha
@@ -19,7 +19,7 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,14 +48,12 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	const sockaddr = ip::to_native(addr, port);
	const sz = ip::native_addrlen(addr);
diff --git a/net/udp/+linux.ha b/net/udp/+linux.ha
index 386bc111..9d213ebb 100644
--- a/net/udp/+linux.ha
+++ b/net/udp/+linux.ha
@@ -19,7 +19,7 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,14 +48,12 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM, 0)) {
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	const sockaddr = ip::to_native(addr, port);
	const sz = size(rt::sockaddr): u32;
diff --git a/net/unix/+freebsd.ha b/net/unix/+freebsd.ha
index 6e021bae..2af52d00 100644
--- a/net/unix/+freebsd.ha
+++ b/net/unix/+freebsd.ha
@@ -19,14 +19,12 @@ export fn connect(addr: addr) (io::file | net::error) = {
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	const sz = size(rt::sockaddr_un): u32;
	match (rt::connect(sockfd, &sockaddr, sz)) {
@@ -49,14 +47,12 @@ export fn listen(
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
diff --git a/net/unix/+linux.ha b/net/unix/+linux.ha
index c45a25fe..1ea8427a 100644
--- a/net/unix/+linux.ha
+++ b/net/unix/+linux.ha
@@ -20,14 +20,12 @@ export fn connect(addr: addr) (io::file | net::error) = {
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	const sz = size(rt::sockaddr_un): u32;
	match (rt::connect(sockfd, &sockaddr, sz)) {
@@ -50,14 +48,12 @@ export fn listen(
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM, 0)) {
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
		yield fd;
	};
	let flags = rt::fcntl(sockfd, rt::F_GETFL, 0)!;
	rt::fcntl(sockfd, rt::F_SETFL, flags | rt::O_CLOEXEC)!;

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
diff --git a/net/unix/socketpair.ha b/net/unix/socketpair.ha
index 2d19aba0..c5fb35f4 100644
--- a/net/unix/socketpair.ha
+++ b/net/unix/socketpair.ha
@@ -11,7 +11,7 @@ use io;
// domain and returns an unnamed pair of sockets of type [[rt::SOCK_STREAM]].
export fn socketpair() ((io::file, io::file) | net::error) = {
	let sv: [2]int = [0...];
	match (rt::socketpair(rt::AF_UNIX : int, rt::SOCK_STREAM : int, 0, &sv)) {
	match (rt::socketpair(rt::AF_UNIX : int, (rt::SOCK_STREAM | rt::SOCK_CLOEXEC) : int, 0, &sv)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case =>
-- 
2.30.2

[PATCH hare v2 2/7] net: invert the meaning of CLOEXEC in sockflags

Details
Message ID
<20220510170356.1022176-3-egor@opensrc.club>
In-Reply-To
<20220510170356.1022176-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +8 -14
Currently if you want a file descriptor without CLOEXEC, you have to
pass a dummy 0 argument. And if you pass NONBLOCK, you have to remember
to also pass CLOEXEC, since in most cases that's what you actually want.

This will also make it easier to add socketflags to connect/socket
functions, since they already take sockopts and so you can't just
len(flags) == 0 to detect that no SOCK flags have been specified.

Signed-off-by: Egor <egor@opensrc.club>
---
 net/+freebsd.ha | 11 ++++-------
 net/+linux.ha   | 11 ++++-------
 2 files changed, 8 insertions(+), 14 deletions(-)

diff --git a/net/+freebsd.ha b/net/+freebsd.ha
index f14da01a..85be875a 100644
--- a/net/+freebsd.ha
+++ b/net/+freebsd.ha
@@ -12,27 +12,24 @@ use strings;

// Optional flags to [[accept]] to be set on the returned [[io::file]].
// See the O_CLOEXEC and O_NONBLOCK sections of open(2) for details.
// Note that CLOEXEC is on by default, and NOCLOEXEC flag disables it.
export type sockflags = enum int {
	CLOEXEC = rt::SOCK_CLOEXEC,
	NOCLOEXEC = rt::SOCK_CLOEXEC,
	NONBLOCK = rt::SOCK_NONBLOCK
};

// Accepts the next connection from a socket. Blocks until a new connection is
// available. Optionally accepts CLOEXEC and NONBLOCK flags. If flags are
// available. Optionally accepts NOCLOEXEC and NONBLOCK flags. If flags are
// supplied, the [[io::file]] returned will have the supplied flags set.
// If no flags are supplied, CLOEXEC is used by default.
export fn accept(sock: io::file, flags: sockflags...) (io::file | error) = {
	let sn = rt::sockaddr {...};
	const sz = size(rt::sockaddr): u32;
	// Default to CLOEXEC if no flags are supplied
	if (len(flags) == 0) {
		flags = [sockflags::CLOEXEC];
	};
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const fd = match (rt::accept4(sock, &sn, &sz, f)) {
	case let err: rt::errno =>
		return errors::errno(err);
diff --git a/net/+linux.ha b/net/+linux.ha
index a6546bb7..2a0227ce 100644
--- a/net/+linux.ha
+++ b/net/+linux.ha
@@ -12,27 +12,24 @@ use strings;

// Optional flags to [[accept]] to be set on the returned [[io::file]].
// See the O_CLOEXEC and O_NONBLOCK sections of open(2) for details.
// Note that CLOEXEC is on by default, and NOCLOEXEC flag disables it.
export type sockflags = enum int {
	CLOEXEC = rt::SOCK_CLOEXEC,
	NOCLOEXEC = rt::SOCK_CLOEXEC,
	NONBLOCK = rt::SOCK_NONBLOCK
};

// Accepts the next connection from a socket. Blocks until a new connection is
// available. Optionally accepts CLOEXEC and NONBLOCK flags. If flags are
// available. Optionally accepts NOCLOEXEC and NONBLOCK flags. If flags are
// supplied, the [[io::file]] returned will have the supplied flags set.
// If no flags are supplied, CLOEXEC is used by default.
export fn accept(sock: io::file, flags: sockflags...) (io::file | error) = {
	let sn = rt::sockaddr {...};
	const sz = size(rt::sockaddr): u32;
	// Default to CLOEXEC if no flags are supplied
	if (len(flags) == 0) {
		flags = [sockflags::CLOEXEC];
	};
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const fd = match (rt::accept4(sock, &sn, &sz, f: int)) {
	case let err: rt::errno =>
		return errors::errno(err);
-- 
2.30.2

[PATCH hare v2 3/7] net: add sockflags to connect/accept/socketpair

Details
Message ID
<20220510170356.1022176-4-egor@opensrc.club>
In-Reply-To
<20220510170356.1022176-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +176 -34
Signed-off-by: Egor <egor@opensrc.club>
---
 net/tcp/+freebsd.ha    | 31 ++++++++++++++++++++++++++-----
 net/tcp/+linux.ha      | 31 ++++++++++++++++++++++++++-----
 net/tcp/options.ha     |  6 ++++--
 net/udp/+freebsd.ha    | 29 +++++++++++++++++++++++++----
 net/udp/+linux.ha      | 29 +++++++++++++++++++++++++----
 net/udp/options.ha     |  6 +++++-
 net/unix/+freebsd.ha   | 31 ++++++++++++++++++++++++++-----
 net/unix/+linux.ha     | 31 ++++++++++++++++++++++++++-----
 net/unix/options.ha    |  6 +++++-
 net/unix/socketpair.ha | 10 ++++++++--
 10 files changed, 176 insertions(+), 34 deletions(-)

diff --git a/net/tcp/+freebsd.ha b/net/tcp/+freebsd.ha
index f6764ff5..142504ea 100644
--- a/net/tcp/+freebsd.ha
+++ b/net/tcp/+freebsd.ha
@@ -22,7 +22,16 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -30,8 +39,11 @@ export fn connect(
	};

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is keepalive right now
		setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		match (options[i]) {
		case keepalive =>
			setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		case => void;
		};
	};
	const sz = ip::native_addrlen(addr);
	match (rt::connect(sockfd, &sockaddr, sz)) {
@@ -55,7 +67,16 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -73,7 +94,7 @@ export fn listen(
			setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		case let b: backlog =>
			bk = b;
		case let p: portassignment => void;
		case => void;
		};
	};

diff --git a/net/tcp/+linux.ha b/net/tcp/+linux.ha
index 6b1f8625..61fb0d47 100644
--- a/net/tcp/+linux.ha
+++ b/net/tcp/+linux.ha
@@ -22,7 +22,16 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -30,8 +39,11 @@ export fn connect(
	};

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is keepalive right now
		setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		match (options[i]) {
		case keepalive =>
			setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		case => void;
		};
	};
	const sz = size(rt::sockaddr): u32;
	match (rt::connect(sockfd, &sockaddr, sz)) {
@@ -55,7 +67,16 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -73,7 +94,7 @@ export fn listen(
			setsockopt(sockfd, rt::SO_KEEPALIVE, true)?;
		case let b: backlog =>
			bk = b;
		case let p: portassignment => void;
		case => void;
		};
	};

diff --git a/net/tcp/options.ha b/net/tcp/options.ha
index 0477c8de..10b92cd2 100644
--- a/net/tcp/options.ha
+++ b/net/tcp/options.ha
@@ -1,5 +1,6 @@
// License: MPL-2.0
// (c) 2021 Drew DeVault <sir@cmpwn.com>
use net;

// Enables keep-alive for a socket.
export type keepalive = void;
@@ -20,7 +21,7 @@ export type reuseaddr = void;
export type portassignment = *u16;

// Options for [[connect]].
export type connect_option = keepalive;
export type connect_option = (keepalive | net::sockflags);

// Options for [[listen]].
export type listen_option = (
@@ -28,4 +29,5 @@ export type listen_option = (
	reuseport |
	reuseaddr |
	backlog |
	portassignment);
	portassignment |
	net::sockflags);
diff --git a/net/udp/+freebsd.ha b/net/udp/+freebsd.ha
index a3533b69..5d436d81 100644
--- a/net/udp/+freebsd.ha
+++ b/net/udp/+freebsd.ha
@@ -12,6 +12,7 @@ use rt;
export fn connect(
	dest: ip::addr,
	port: u16,
	options: connect_option...
) (io::file | net::error) = {
	const family = match (dest) {
	case ip::addr4 =>
@@ -19,7 +20,13 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		// Only sockflags for now
		f |= options[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,7 +55,16 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -64,7 +80,12 @@ export fn listen(
	};

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is portassignment right now
		let portout = match (options[i]) {
		case let p: portassignment =>
			yield p;
		case =>
			continue;
		};
		let sn = rt::sockaddr {...};
		let al = size(rt::sockaddr): u32;
		match (rt::getsockname(sockfd, &sn, &al)) {
@@ -73,7 +94,7 @@ export fn listen(
		case int => void;
		};
		const addr = ip::from_native(sn);
		*options[i] = addr.1;
		*portout = addr.1;
	};

	return io::fdopen(sockfd);
diff --git a/net/udp/+linux.ha b/net/udp/+linux.ha
index 9d213ebb..c64d464f 100644
--- a/net/udp/+linux.ha
+++ b/net/udp/+linux.ha
@@ -12,6 +12,7 @@ use rt;
export fn connect(
	dest: ip::addr,
	port: u16,
	options: connect_option...
) (io::file | net::error) = {
	const family = match (dest) {
	case ip::addr4 =>
@@ -19,7 +20,13 @@ export fn connect(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		// Only sockflags for now
		f |= options[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,7 +55,16 @@ export fn listen(
	case ip::addr6 =>
		yield rt::AF_INET6: int;
	};
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(family, rt::SOCK_DGRAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -64,7 +80,12 @@ export fn listen(
	};

	for (let i = 0z; i < len(options); i += 1) {
		// The only option is portassignment right now
		let portout = match (options[i]) {
		case let p: portassignment =>
			yield p;
		case =>
			continue;
		};
		let sn = rt::sockaddr {...};
		let al = size(rt::sockaddr): u32;
		match (rt::getsockname(sockfd, &sn, &al)) {
@@ -73,7 +94,7 @@ export fn listen(
		case int => void;
		};
		const addr = ip::from_native(sn);
		*options[i] = addr.1;
		*portout = addr.1;
	};

	return io::fdopen(sockfd);
diff --git a/net/udp/options.ha b/net/udp/options.ha
index e1d2ca84..ed012725 100644
--- a/net/udp/options.ha
+++ b/net/udp/options.ha
@@ -1,12 +1,16 @@
// License: MPL-2.0
// (c) 2021 Drew DeVault <sir@cmpwn.com>
use net;

// To have the system select an arbitrary unused port for [[listen]], set port to
// zero. To retrieve the assigned port, provide this as one of the options and
// the addressed u16 will be filled in with the port.
export type portassignment = *u16;

// Options for [[connect]].
export type connect_option = net::sockflags;

// Options available for [[listen]].
export type listen_option = portassignment;
export type listen_option = (portassignment | net::sockflags);

// TODO: Add send/recv flags
diff --git a/net/unix/+freebsd.ha b/net/unix/+freebsd.ha
index 2af52d00..b6c18f74 100644
--- a/net/unix/+freebsd.ha
+++ b/net/unix/+freebsd.ha
@@ -12,14 +12,23 @@ use types;

// Opens a UNIX socket connection to the path. Blocks until the connection is
// established.
export fn connect(addr: addr) (io::file | net::error) = {
export fn connect(
	addr: addr,
	options: connect_option...
) (io::file | net::error) = {
	let sockaddr = match (to_native(addr)) {
	case let a: rt::sockaddr =>
		yield a;
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		// Only sockflags for now
		f |= options[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -47,7 +56,16 @@ export fn listen(
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -56,8 +74,11 @@ export fn listen(

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
		// Only option is backlog right now
		bk = options[i];
		match (options[i]) {
		case let b: backlog =>
			bk = b;
		case => void;
		};
	};

	match (rt::bind(sockfd, &sockaddr, size(rt::sockaddr_un): u32)) {
diff --git a/net/unix/+linux.ha b/net/unix/+linux.ha
index 1ea8427a..3b86aae0 100644
--- a/net/unix/+linux.ha
+++ b/net/unix/+linux.ha
@@ -13,14 +13,23 @@ use types;

// Opens a UNIX socket connection to the path. Blocks until the connection is
// established.
export fn connect(addr: addr) (io::file | net::error) = {
export fn connect(
	addr: addr,
	options: connect_option...
) (io::file | net::error) = {
	let sockaddr = match (to_native(addr)) {
	case let a: rt::sockaddr =>
		yield a;
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		// Only sockflags for now
		f |= options[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -48,7 +57,16 @@ export fn listen(
	case invalid =>
		return errors::unsupported; // path too long
	};
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | rt::SOCK_CLOEXEC, 0)) {
	let f = 0i;
	for (let i = 0z; i < len(options); i += 1) {
		match (options[i]) {
		case let fl: net::sockflags =>
			f |= fl;
		case => void;
		};
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	const sockfd = match (rt::socket(rt::AF_UNIX: int, rt::SOCK_STREAM | f, 0)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let fd: int =>
@@ -57,8 +75,11 @@ export fn listen(

	let bk: u32 = 10;
	for (let i = 0z; i < len(options); i += 1) {
		// Only option is backlog right now
		bk = options[i];
		match (options[i]) {
		case let b: backlog =>
			bk = b;
		case => void;
		};
	};

	match (rt::bind(sockfd, &sockaddr, size(rt::sockaddr_un): u32)) {
diff --git a/net/unix/options.ha b/net/unix/options.ha
index c968f955..8ffe5a4c 100644
--- a/net/unix/options.ha
+++ b/net/unix/options.ha
@@ -1,9 +1,13 @@
// License: MPL-2.0
// (c) 2021 Drew DeVault <sir@cmpwn.com>
use net;

// Configures the backlog size for a listener. If not specified, a sensible
// default (10) is used.
export type backlog = u32;

// Options for [[connect]].
export type connect_option = net::sockflags;

// Options for [[listen]].
export type listen_option = backlog;
export type listen_option = (backlog | net::sockflags);
diff --git a/net/unix/socketpair.ha b/net/unix/socketpair.ha
index c5fb35f4..24133be4 100644
--- a/net/unix/socketpair.ha
+++ b/net/unix/socketpair.ha
@@ -9,9 +9,15 @@ use io;

// A thin wrapper around socketpair(2) that presumes [[rt::AF_UNIX]] for the
// domain and returns an unnamed pair of sockets of type [[rt::SOCK_STREAM]].
export fn socketpair() ((io::file, io::file) | net::error) = {
export fn socketpair(flags: net::sockflags...) ((io::file, io::file) | net::error) = {
	let sv: [2]int = [0...];
	match (rt::socketpair(rt::AF_UNIX : int, (rt::SOCK_STREAM | rt::SOCK_CLOEXEC) : int, 0, &sv)) {
	// Apply any supplied flags
	let f = 0i;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];
	};
	f ^= rt::SOCK_CLOEXEC; // invert CLOEXEC
	match (rt::socketpair(rt::AF_UNIX : int, (rt::SOCK_STREAM | f) : int, 0, &sv)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case =>
-- 
2.30.2

[PATCH hare v2 4/7] unix: invert the meaning of CLOEXEC in pipe_flag

Details
Message ID
<20220510170356.1022176-5-egor@opensrc.club>
In-Reply-To
<20220510170356.1022176-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +8 -6
for consistency with sockets

Signed-off-by: Egor <egor@opensrc.club>
---
 unix/+freebsd/pipe.ha | 7 ++++---
 unix/+linux/pipe.ha   | 7 ++++---
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/unix/+freebsd/pipe.ha b/unix/+freebsd/pipe.ha
index cc3d9e94..12b10511 100644
--- a/unix/+freebsd/pipe.ha
+++ b/unix/+freebsd/pipe.ha
@@ -6,9 +6,9 @@ use io;
use rt;

// Flags to use for the [[io::file]]s returned by [[pipe]]
// Only CLOEXEC and NONBLOCK are guaranteed to be available.
// Only NOCLOEXEC and NONBLOCK are guaranteed to be available.
export type pipe_flag = enum {
	CLOEXEC = rt::O_CLOEXEC,
	NOCLOEXEC = rt::O_CLOEXEC,
	DIRECT = rt::O_DIRECT,
	NONBLOCK = rt::O_NONBLOCK,
};
@@ -19,10 +19,11 @@ export type pipe_flag = enum {
// recommended that you add it unless you know that you don't want it.
export fn pipe(flags: pipe_flag...) ((io::file, io::file) | errors::error) = {
	let fds: [2]int = [0...];
	let flag: pipe_flag = if (len(flags) == 0) pipe_flag::CLOEXEC else 0;
	let flag: pipe_flag = 0;
	for (let i = 0z; i < len(flags); i += 1) {
		flag |= flags[i];
	};
	flag ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flag)) {
	case void => void;
	case let e: rt::errno =>
diff --git a/unix/+linux/pipe.ha b/unix/+linux/pipe.ha
index aadefd1f..742cf7a4 100644
--- a/unix/+linux/pipe.ha
+++ b/unix/+linux/pipe.ha
@@ -5,9 +5,9 @@ use io;
use rt;

// Flags to use for the [[io::file]]s returned by [[pipe]]
// Only CLOEXEC and NONBLOCK are guaranteed to be available.
// Only NOCLOEXEC and NONBLOCK are guaranteed to be available.
export type pipe_flag = enum {
	CLOEXEC = rt::O_CLOEXEC,
	NOCLOEXEC = rt::O_CLOEXEC,
	DIRECT = rt::O_DIRECT,
	NONBLOCK = rt::O_NONBLOCK,
};
@@ -18,10 +18,11 @@ export type pipe_flag = enum {
// recommended that you add it unless you know that you don't want it.
export fn pipe(flags: pipe_flag...) ((io::file, io::file) | errors::error) = {
	let fds: [2]int = [0...];
	let flag: pipe_flag = if (len(flags) == 0) pipe_flag::CLOEXEC else 0;
	let flag: pipe_flag = 0;
	for (let i = 0z; i < len(flags); i += 1) {
		flag |= flags[i];
	};
	flag ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flag)) {
	case void => void;
	case let e: rt::errno =>
-- 
2.30.2

[PATCH hare v2 5/7] fs: invert the meaning of NOCTTY/CLOEXEC in flags

Details
Message ID
<20220510170356.1022176-6-egor@opensrc.club>
In-Reply-To
<20220510170356.1022176-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +26 -44
the same reasoning as with sockets and pipes

Signed-off-by: Egor <egor@opensrc.club>
---
 fs/types.ha               |  6 ++++--
 os/+freebsd/dirfdfs.ha    | 24 +++++++++---------------
 os/+linux/dirfdfs.ha      | 18 ++++--------------
 os/fs.ha                  | 12 ++++--------
 temp/+freebsd.ha          |  2 +-
 temp/+linux.ha            |  4 ++--
 unix/tty/+freebsd/open.ha |  2 +-
 unix/tty/+linux/open.ha   |  2 +-
 8 files changed, 26 insertions(+), 44 deletions(-)

diff --git a/fs/types.ha b/fs/types.ha
index e5a46871..3cd06d26 100644
--- a/fs/types.ha
+++ b/fs/types.ha
@@ -168,13 +168,15 @@ export fn dirent_finish(e: *dirent) void = free(e.name);

// Flags to use for opening a file. Not all operating systems support all flags;
// at a minimum, RDONLY, WRONLY, RDWR, and CREATE will be supported.
// Note that NOCTTY and CLOEXEC are on by default, and the CTTY/NOCLOEXEC flags
// respectively disable them.
export type flags = enum int {
	RDONLY		= 0,
	WRONLY		= 1,
	RDWR		= 2,
	CREATE		= 0o100,
	EXCL		= 0o200,
	NOCTTY		= 0o400,
	CTTY		= 0o400,
	TRUNC		= 0o1000,
	APPEND		= 0o2000,
	NONBLOCK	= 0o4000,
@@ -184,7 +186,7 @@ export type flags = enum int {
	DIRECTORY	= 0o200000,
	NOFOLLOW	= 0o400000,
	NOATIME		= 0o1000000,
	CLOEXEC		= 0o2000000,
	NOCLOEXEC	= 0o2000000,
	PATH		= 0o10000000,
	TMPFILE		= 0o20200000,
};
diff --git a/os/+freebsd/dirfdfs.ha b/os/+freebsd/dirfdfs.ha
index 8b349dfd..ebc1be10 100644
--- a/os/+freebsd/dirfdfs.ha
+++ b/os/+freebsd/dirfdfs.ha
@@ -19,11 +19,6 @@ type os_filesystem = struct {

// Opens a file descriptor as an [[fs::fs]]. This file descriptor must be a
// directory file. The file will be closed when the fs is closed.
//
// If no other flags are provided to [[fs::open]] and [[fs::create]] when used with
// a dirfdfs, [[fs::flags::NOCTTY]] and [[fs::flags::CLOEXEC]] are used when opening
// the file. If you pass your own flags, it is recommended that you add these
// unless you know that you do not want them.
export fn dirfdopen(fd: io::file) *fs::fs = {
	let ofs = alloc(os_filesystem { ... });
	let fs = static_dirfdopen(fd, ofs);
@@ -112,7 +107,7 @@ fn _fs_open(
};

fn fsflags_to_bsd(flags: fs::flags) int = {
	let out = 0;
	let out = rt::O_NOCTTY | rt::O_CLOEXEC;
	if (flags & fs::flags::WRONLY > 0) {
		out |= rt::O_WRONLY;
	};
@@ -125,8 +120,8 @@ fn fsflags_to_bsd(flags: fs::flags) int = {
	if (flags & fs::flags::EXCL > 0) {
		out |= rt::O_EXCL;
	};
	if (flags & fs::flags::NOCTTY > 0) {
		out |= rt::O_NOCTTY;
	if (flags & fs::flags::CTTY > 0) {
		out &= ~rt::O_NOCTTY;
	};
	if (flags & fs::flags::TRUNC > 0) {
		out |= rt::O_TRUNC;
@@ -152,8 +147,8 @@ fn fsflags_to_bsd(flags: fs::flags) int = {
	if (flags & fs::flags::NOFOLLOW > 0) {
		out |= rt::O_NOFOLLOW;
	};
	if (flags & fs::flags::CLOEXEC > 0) {
		out |= rt::O_CLOEXEC;
	if (flags & fs::flags::NOCLOEXEC > 0) {
		out &= ~rt::O_CLOEXEC;
	};
	if (flags & fs::flags::PATH > 0) {
		abort("fs::flags::PATH is not supported on FreeBSD");
@@ -173,12 +168,10 @@ fn fs_open_file(
	flags: fs::flags...
) (io::file | fs::error) = {
	let oflags = fs::flags::RDONLY;
	if (len(flags) == 0z) {
		oflags |= fs::flags::NOCTTY | fs::flags::CLOEXEC;
	};
	for (let i = 0z; i < len(flags); i += 1z) {
		oflags |= flags[i];
	};
	oflags ^= fs::flags::CTTY | fs::flags::NOCLOEXEC; // invert NOCTTY/CLOEXEC
	return _fs_open(fs, path, fsflags_to_bsd(oflags), 0);
};

@@ -194,13 +187,14 @@ fn fs_create_file(
	mode: fs::mode,
	flags: fs::flags...
) (io::file | fs::error) = {
	let oflags = fs::flags::RDONLY;
	let oflags = 0;
	if (len(flags) == 0z) {
		oflags |= fs::flags::NOCTTY | fs::flags::CLOEXEC;
		oflags |= fs::flags::WRONLY;
	};
	for (let i = 0z; i < len(flags); i += 1z) {
		oflags |= flags[i];
	};
	oflags ^= fs::flags::CTTY | fs::flags::NOCLOEXEC; // invert NOCTTY/CLOEXEC
	oflags |= fs::flags::CREATE;
	return _fs_open(fs, path, fsflags_to_bsd(oflags), mode)?;
};
diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha
index 0c3779de..bb0dfaa5 100644
--- a/os/+linux/dirfdfs.ha
+++ b/os/+linux/dirfdfs.ha
@@ -51,11 +51,6 @@ type os_filesystem = struct {

// Opens a file descriptor as an [[fs::fs]]. This file descriptor must be a
// directory file. The file will be closed when the fs is closed.
//
// If no other flags are provided to [[fs::open]] and [[fs::create]] when used with
// a dirfdfs, [[fs::flags::NOCTTY]] and [[fs::flags::CLOEXEC]] are used when opening
// the file. If you pass your own flags, it is recommended that you add these
// unless you know that you do not want them.
export fn dirfdopen(fd: io::file, resolve: resolve_flags...) *fs::fs = {
	let ofs = alloc(os_filesystem { ... });
	let fs = static_dirfdopen(fd, ofs);
@@ -180,15 +175,11 @@ fn fs_open_file(
	path: str,
	flags: fs::flags...
) (io::file | fs::error) = {
	let oflags = 0;
	if (len(flags) == 0z) {
		oflags |= (fs::flags::NOCTTY
			| fs::flags::CLOEXEC
			| fs::flags::RDONLY): int;
	};
	let oflags = fs::flags::RDONLY: int;
	for (let i = 0z; i < len(flags); i += 1z) {
		oflags |= flags[i]: int;
	};
	oflags ^= fs::flags::CTTY | fs::flags::NOCLOEXEC; // invert NOCTTY/CLOEXEC

	if ((oflags: fs::flags & fs::flags::DIRECTORY) == fs::flags::DIRECTORY) {
		// This is arch-specific
@@ -217,13 +208,12 @@ fn fs_create_file(
) (io::file | fs::error) = {
	let oflags = 0;
	if (len(flags) == 0z) {
		oflags |= (fs::flags::NOCTTY
			| fs::flags::CLOEXEC
			| fs::flags::WRONLY): int;
		oflags |= fs::flags::WRONLY;
	};
	for (let i = 0z; i < len(flags); i += 1z) {
		oflags |= flags[i]: int;
	};
	oflags ^= fs::flags::CTTY | fs::flags::NOCLOEXEC; // invert NOCTTY/CLOEXEC
	oflags |= fs::flags::CREATE: int;

	let oh = rt::open_how {
diff --git a/os/fs.ha b/os/fs.ha
index 9b66c621..12874996 100644
--- a/os/fs.ha
+++ b/os/fs.ha
@@ -69,19 +69,15 @@ export fn readlink(path: str) (str | fs::error) = fs::readlink(cwd, path);

// Opens a file.
//
// If no flags are provided, [[fs::flags::RDONLY]], [[fs::flags::NOCTTY]],
// [[fs::flags::CLOEXEC]] are used when opening the file. If you pass your own
// flags, it is recommended that you add the latter two unless you know that you
// do not want them.
// If no flags are provided, [[fs::flags::RDONLY]] is used when opening the
// file.
export fn open(path: str, flags: fs::flags...) (io::file | fs::error) =
	fs::open_file(cwd, path, flags...);

// Creates a new file and opens it for writing.
//
// If no flags are provided, [[fs::flags::WRONLY]], [[fs::flags::NOCTTY]],
// [[fs::flags::CLOEXEC]] are used when opening the file. If you pass your own
// flags, it is recommended that you add the latter two unless you know that you
// do not want them.
// If no flags are provided, [[fs::flags::WRONLY]] is used when opening the
// file.
//
// Only the permission bits of the mode are used. If other bits are set, they
// are discarded.
diff --git a/temp/+freebsd.ha b/temp/+freebsd.ha
index d58ed937..1ae217fc 100644
--- a/temp/+freebsd.ha
+++ b/temp/+freebsd.ha
@@ -53,7 +53,7 @@ export fn named(
	assert(len(mode) == 0 || len(mode) == 1);

	let fmode = if (len(mode) != 0) mode[0] else 0o644: fs::mode;
	let oflags = fs::flags::EXCL | fs::flags::CLOEXEC;
	let oflags = fs::flags::EXCL;
	if (iomode == io::mode::RDWR) {
		oflags |= fs::flags::RDWR;
	} else {
diff --git a/temp/+linux.ha b/temp/+linux.ha
index eb3d7db9..11a7fdbb 100644
--- a/temp/+linux.ha
+++ b/temp/+linux.ha
@@ -31,7 +31,7 @@ export fn file(
	assert(iomode == io::mode::WRITE || iomode == io::mode::RDWR);
	assert(len(mode) == 0 || len(mode) == 1);
	let fmode = if (len(mode) != 0) mode[0] else 0o644: fs::mode;
	let oflags = fs::flags::TMPFILE | fs::flags::EXCL | fs::flags::CLOEXEC;
	let oflags = fs::flags::TMPFILE | fs::flags::EXCL;
	if (iomode == io::mode::RDWR) {
		oflags |= fs::flags::RDWR;
	} else {
@@ -67,7 +67,7 @@ export fn named(
	assert(len(mode) == 0 || len(mode) == 1);

	let fmode = if (len(mode) != 0) mode[0] else 0o644: fs::mode;
	let oflags = fs::flags::EXCL | fs::flags::CLOEXEC;
	let oflags = fs::flags::EXCL;
	if (iomode == io::mode::RDWR) {
		oflags |= fs::flags::RDWR;
	} else {
diff --git a/unix/tty/+freebsd/open.ha b/unix/tty/+freebsd/open.ha
index 21be066c..a35dcdc5 100644
--- a/unix/tty/+freebsd/open.ha
+++ b/unix/tty/+freebsd/open.ha
@@ -9,7 +9,7 @@ use os;
// Returns a stream connected to the TTY of the current process. The caller must
// close it using [[io::close]].
export fn open() (io::file | error) = {
	match (os::open("/dev/tty", fs::flags::RDWR, fs::flags::CLOEXEC)) {
	match (os::open("/dev/tty", fs::flags::RDWR)) {
	case let f: io::file =>
		return f;
	case fs::error =>
diff --git a/unix/tty/+linux/open.ha b/unix/tty/+linux/open.ha
index 4b42a415..b6df34d5 100644
--- a/unix/tty/+linux/open.ha
+++ b/unix/tty/+linux/open.ha
@@ -11,7 +11,7 @@ use os;
// Returns a stream connected to the TTY of the current process. The caller must
// close it using [[io::close]].
export fn open() (io::file | error) = {
	match (os::open("/dev/tty", fs::flags::RDWR, fs::flags::CLOEXEC)) {
	match (os::open("/dev/tty", fs::flags::RDWR)) {
	case let f: io::file =>
		return f;
	case fs::error =>
-- 
2.30.2

[PATCH hare v2 6/7] fix dup without CLOEXEC in linux dirfs_clone

Details
Message ID
<20220510170356.1022176-7-egor@opensrc.club>
In-Reply-To
<20220510170356.1022176-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +1 -1
Signed-off-by: Egor <egor@opensrc.club>
---
 os/+linux/dirfdfs.ha | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha
index bb0dfaa5..ce6b9476 100644
--- a/os/+linux/dirfdfs.ha
+++ b/os/+linux/dirfdfs.ha
@@ -98,7 +98,7 @@ export fn dirfs_clone(fs: *fs::fs, resolve: resolve_flags...) *fs::fs = {
	for (let i = 0z; i < len(resolve); i += 1) {
		new.resolve |= resolve[i];
	};
	new.dirfd = rt::dup(new.dirfd) as int;
	new.dirfd = rt::fcntl(new.dirfd, rt::F_DUPFD_CLOEXEC, 0) as int;
	return &new.fs;
};

-- 
2.30.2

[PATCH hare v2 7/7] os::exec: clear FD_CLOEXEC when dup2ing fd to itself

Details
Message ID
<20220510170356.1022176-8-egor@opensrc.club>
In-Reply-To
<20220510170356.1022176-1-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Patch: +30 -8
If you try to dup2 a fd to itself, it's a no-op. Since we want the child
process to inherit the fd, we must ensure to clear CLOEXEC in this case.

posix_spawn_file_actions_adddup2 in musl and glibc works similarly.

Signed-off-by: Egor <egor@opensrc.club>
---
 os/exec/exec+freebsd.ha | 18 ++++++++++++++----
 os/exec/exec+linux.ha   | 18 ++++++++++++++----
 rt/+linux/types.ha      |  2 ++
 3 files changed, 30 insertions(+), 8 deletions(-)

diff --git a/os/exec/exec+freebsd.ha b/os/exec/exec+freebsd.ha
index 59333def..c3362add 100644
--- a/os/exec/exec+freebsd.ha
+++ b/os/exec/exec+freebsd.ha
@@ -119,10 +119,20 @@ fn platform_exec(cmd: *command) error = {
			continue;
		};

		match (rt::dup2(from, cmd.files[i].1)) {
		case int => void;
		case let e: rt::errno =>
			return errors::errno(e);
		if (cmd.files[i].1 == from) {
			let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) {
			case let flags: int =>
				yield flags;
			case let e: rt::errno =>
				return errors::errno(e);
			};
			rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!;
		} else {
			match (rt::dup2(from, cmd.files[i].1)) {
			case int => void;
			case let e: rt::errno =>
				return errors::errno(e);
			};
		};
	};

diff --git a/os/exec/exec+linux.ha b/os/exec/exec+linux.ha
index fdcecf1b..691fc9a7 100644
--- a/os/exec/exec+linux.ha
+++ b/os/exec/exec+linux.ha
@@ -120,10 +120,20 @@ fn platform_exec(cmd: *command) error = {
			continue;
		};

		match (rt::dup2(from, cmd.files[i].1)) {
		case int => void;
		case let e: rt::errno =>
			return errors::errno(e);
		if (cmd.files[i].1 == from) {
			let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) {
			case let flags: int =>
				yield flags;
			case let e: rt::errno =>
				return errors::errno(e);
			};
			rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!;
		} else {
			match (rt::dup2(from, cmd.files[i].1)) {
			case int => void;
			case let e: rt::errno =>
				return errors::errno(e);
			};
		};
	};

diff --git a/rt/+linux/types.ha b/rt/+linux/types.ha
index af519ff8..49c89909 100644
--- a/rt/+linux/types.ha
+++ b/rt/+linux/types.ha
@@ -242,6 +242,8 @@ export def F_SETOWN_EX: int = 15;
export def F_GETOWN_EX: int = 16;
export def F_GETOWNER_UIDS: int = 17;

export def FD_CLOEXEC: int = 1;

export type st_flock = struct {
	l_type: i16,
	l_whence: i16,
-- 
2.30.2
Details
Message ID
<c453c26a-7260-982c-c5e2-cbed6b96c313@gmail.com>
In-Reply-To
<CJSOA0YZTFN2.AGRW5K1HX233@taiga> (view parent)
DKIM signature
missing
Download raw message
Whoops, looks like I shouldn't have threaded the new patch set to the 
previous one, sorry about that.

Re: [PATCH hare v2 7/7] os::exec: clear FD_CLOEXEC when dup2ing fd to itself

Details
Message ID
<CK15NUWHY3Z2.KUG0U32QU26U@taiga>
In-Reply-To
<20220510170356.1022176-8-egor@opensrc.club> (view parent)
DKIM signature
missing
Download raw message
Nice work. Thanks!

To git@git.sr.ht:~sircmpwn/hare
   70a2d3ad..c308da08  master -> master
Reply to thread Export thread (mbox)