hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit f12a6fae426f9e68662b4af392c7de728aa5e0a7
parent 2d5e0c7b62267950a4c0910b42aeae6720f47b09
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun, 21 Nov 2021 07:49:54 +0100

net: add sendmsg, recvmsg support

This is necessary for advanced socket usage for features like
transferring file descriptors and peer credentials over Unix sockets, or
configuring TCP parameters.

FreeBSD compiles but does not appear to work. Not sure why.

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mnet/+freebsd.ha | 35+++++++++++++++++++++++++++++++++++
Mnet/+linux.ha | 35+++++++++++++++++++++++++++++++++++
Anet/msg.ha | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anet/unix/cmsg.ha | 32++++++++++++++++++++++++++++++++
Mrt/+freebsd/socket.ha | 28++++++++++++++++++++++++++++
Mrt/+freebsd/syscalls.ha | 10++++++++++
Mrt/+freebsd/types.ha | 5+++++
Mrt/+linux/socket.ha | 30++++++++++++++++++++++++++++++
Mrt/+linux/types.ha | 27---------------------------
Mscripts/gen-stdlib | 8++++++--
Mstdlib.mk | 16++++++++++++----
11 files changed, 300 insertions(+), 33 deletions(-)

diff --git a/net/+freebsd.ha b/net/+freebsd.ha @@ -39,3 +39,38 @@ export fn stream_shutdown(l: *listener) void = { rt::close(l.fd)!; free(l); }; + +fn msg_to_native(msg: *msghdr) *rt::msghdr = { + let native = &msg.native; + if (len(msg.vectors) != 0) { + native.msg_iov = msg.vectors: *[*]rt::iovec; + native.msg_iovlen = len(msg.vectors): int; + }; + if (len(msg.control) != 0) { + native.msg_control = msg.control: *[*]u8; + native.msg_controllen = len(msg.control): rt::socklen_t; + }; + return native; +}; + +// Sends a message to a socket. See [[newmsg]] for details. +export fn sendmsg(sock: io::file, msg: *msghdr) (size | error) = { + // TODO: Flags + match (rt::sendmsg(sock, msg_to_native(msg), 0)) { + case let n: int => + return n: size; + case let err: rt::errno => + return errors::errno(err); + }; +}; + +// Receives a message from a socket. See [[newmsg]] for details. +export fn recvmsg(sock: io::file, msg: *msghdr) (size | error) = { + // TODO: Flags + match (rt::recvmsg(sock, msg_to_native(msg), 0)) { + case let n: int => + return n: size; + case let err: rt::errno => + return errors::errno(err); + }; +}; diff --git a/net/+linux.ha b/net/+linux.ha @@ -39,3 +39,38 @@ export fn stream_shutdown(l: *listener) void = { rt::close(l.fd)!; free(l); }; + +fn msg_to_native(msg: *msghdr) *rt::msghdr = { + let native = &msg.native; + if (len(msg.vectors) != 0) { + native.msg_iov = msg.vectors: *[*]rt::iovec; + native.msg_iovlen = len(msg.vectors); + }; + if (len(msg.control) != 0) { + native.msg_control = msg.control: *[*]u8; + native.msg_controllen = len(msg.control); + }; + return native; +}; + +// Sends a message to a socket. See [[newmsg]] for details. +export fn sendmsg(sock: io::file, msg: *msghdr) (size | error) = { + // TODO: Flags + match (rt::sendmsg(sock, msg_to_native(msg), 0)) { + case let n: int => + return n: size; + case let err: rt::errno => + return errors::errno(err); + }; +}; + +// Receives a message from a socket. See [[newmsg]] for details. +export fn recvmsg(sock: io::file, msg: *msghdr) (size | error) = { + // TODO: Flags + match (rt::recvmsg(sock, msg_to_native(msg), 0)) { + case let n: int => + return n: size; + case let err: rt::errno => + return errors::errno(err); + }; +}; diff --git a/net/msg.ha b/net/msg.ha @@ -0,0 +1,107 @@ +// TODO: +// - Set name field +// - Figure out the portability mess that is this interface +use rt; +use slice; +use fmt; + +export type msghdr = struct { + native: rt::msghdr, + vectors: []rt::iovec, + control: []u8, +}; + +// Creates a new message header for advanced socket usage, with configurable I/O +// vectors, control messages, and other details, for use with [[sendmsg]] and +// [[recvmsg]]. +// +// The user must call [[finish]] when they are done using this message for +// sending or receiving. The same message may be used for multiple operations +// before calling [[finish]]. [[reset]] may be used to "reset" a [[msghdr]] to +// an empty list of I/O vectors and control messages without freeing the +// underlying memory, which may be useful if future messages are expected to +// have similar characteristics. +export fn newmsg() msghdr = msghdr { ... }; + +// Frees resources associated with a [[msghdr]]. +export fn finish(msg: *msghdr) void = { + free(msg.control); + free(msg.vectors); +}; + +// Resets a message header, clearing out any I/O vectors or control messages, +// without freeing the underlying memory. This allows the user to configure new +// vectors or control messages without a re-allocation, which improves +// performance if the new configuration fits into the same amount of memory. +export fn reset(msg: *msghdr) void = { + slice::trunc(&msg.control: *[]void); + slice::trunc(&msg.vectors: *[]void); +}; + +// Adds an I/O vector to the message. +export fn addvector(msg: *msghdr, vec: []u8) void = { + append(msg.vectors, rt::iovec { + iov_base = vec: *[*]u8, + iov_len = len(vec), + }); +}; + +// Sets flags for this message. +export fn setflags(msg: *msghdr, flags: int) void = { + msg.native.msg_flags = flags; +}; + +// Adds a control message of the desired length to a [[msghdr]], returning a +// buffer in which the ancillary data may be written in a domain-specific +// format. +// +// This is a low-level interface, and is not generally used by users. More +// often, users will call functions like [[net::unix::addfiles]] or +// [[net::unix::prepfiles]], which provide a high-level interface to this +// function for domain-specific use-cases. +export fn addcontrol( + msg: *msghdr, + length: size, + level: int, + ctype: int, +) []u8 = { + const prev = len(msg.control); + const space = cmsg_space(length); + append(msg.control, [0...], space); + let newbuf = msg.control[prev..prev + space]: *[*]rt::cmsghdr; + newbuf[0].cmsg_len = cmsg_len(length): uint; + newbuf[0].cmsg_level = level; + newbuf[0].cmsg_type = ctype; + let user = &newbuf[1]: *[*]u8; + return user[..length]; +}; + +// Retrieves a control header from a message, returning a slice of +// domain-specific data. +// +// This is a low-level interface, and is not generally used by users. More +// often, users will call functions like [[net::unix::addfiles]] or +// [[net::unix::prepfiles]], which provide a high-level interface to this +// function for domain-specific use-cases. +export fn getcontrol( + msg: *msghdr, + length: size, + level: int, + ctype: int, +) ([]u8 | void) = { + let native = &msg.native; + let cbuf = native.msg_control: *[*]u8; + for (let i = 0z; i < native.msg_controllen) { + let next = &cbuf[i]: *rt::cmsg; + if (next.hdr.cmsg_len >= length + && next.hdr.cmsg_level == level + && next.hdr.cmsg_type == ctype) { + return next.cmsg_data[..length]; + }; + i += next.hdr.cmsg_len; + }; +}; + +fn cmsg_align(z: size) size = (z + size(size) - 1) & ~(size(size) - 1); +fn cmsg_len(z: size) size = cmsg_align(size(rt::cmsghdr) + z); +fn cmsg_space(z: size) size = cmsg_align(z) + cmsg_align(size(rt::cmsghdr)); diff --git a/net/unix/cmsg.ha b/net/unix/cmsg.ha @@ -0,0 +1,32 @@ +use net; +use io; +use rt; + +// Adds a list of file descriptors to the ancillary data for a sendmsg +// operation. +export fn addfiles(buf: *net::msghdr, files: io::file...) void = { + const msgsz = size(io::file) * len(files); + let buf = net::addcontrol(buf, msgsz, rt::SOL_SOCKET, rt::SCM_RIGHTS); + let buf = buf: *[*]io::file; + buf[..len(files)] = files[..]; +}; + +// Prepares an ancillary data buffer to receive files during a recvmsg +// operation. +export fn allocfiles(buf: *net::msghdr, nfile: size) void = { + const msgsz = size(io::file) * nfile; + net::addcontrol(buf, msgsz, rt::SOL_SOCKET, rt::SCM_RIGHTS); +}; + +// Receives files from an ancillary data buffer which was previously prepared +// with [[prepfiles]]. +export fn recvfiles(buf: *net::msghdr, nfile: size) []io::file = { + match (net::getcontrol(buf, + nfile * size(io::file), + rt::SOL_SOCKET, rt::SCM_RIGHTS)) { + case let buf: []u8 => + return (buf: *[*]io::file)[..nfile]; + case void => + return []; + }; +}; diff --git a/rt/+freebsd/socket.ha b/rt/+freebsd/socket.ha @@ -1,4 +1,5 @@ export type sa_family_t = u8; +export type socklen_t = u32; export type in_addr = struct { s_addr: u32 @@ -45,6 +46,33 @@ export type sockaddr = struct { }, }; +export def SCM_RIGHTS: int = 0x01; +export def SCM_CREDENTIALS: int = 0x02; + +export type msghdr = struct { + msg_name: nullable *void, + msg_namelen: socklen_t, + + msg_iov: nullable *[*]iovec, + msg_iovlen: int, + + msg_control: nullable *void, + msg_controllen: socklen_t, + + msg_flags: int +}; + +export type cmsghdr = struct { + cmsg_len: socklen_t, + cmsg_level: int, + cmsg_type: int, +}; + +export type cmsg = struct { + hdr: cmsghdr, + cmsg_data: [*]u8, +}; + export def AF_UNSPEC: sa_family_t = 0; export def AF_UNIX: sa_family_t = 1; export def AF_LOCAL: sa_family_t = AF_UNIX; diff --git a/rt/+freebsd/syscalls.ha b/rt/+freebsd/syscalls.ha @@ -428,6 +428,16 @@ export fn send(sockfd: int, buf: *void, len_: size, flags: int) (size | errno) = return sendto(sockfd, buf, len_, flags, null, 0); }; +export fn sendmsg(fd: int, msg: *const msghdr, flags: int) (int | errno) = { + return wrap_return(syscall3(SYS_sendmsg, + fd: u64, msg: uintptr: u64, flags: u64))?: int; +}; + +export fn recvmsg(fd: int, msg: *const msghdr, flags: int) (int | errno) = { + return wrap_return(syscall3(SYS_recvmsg, + fd: u64, msg: uintptr: u64, flags: u64))?: int; +}; + export fn getsockopt(sockfd: int, level: int, optname: int, optval: nullable *void, optlen: nullable *u32) (int | errno) = { return wrap_return(syscall5(SYS_getsockopt, sockfd: u64, level: u64, optname: u64, diff --git a/rt/+freebsd/types.ha b/rt/+freebsd/types.ha @@ -111,6 +111,11 @@ export type freebsd11_dirent = struct { d_name: [*]char, }; +export type iovec = struct { + iov_base: *void, + iov_len: size +}; + export type winsize = struct { ws_row: u16, ws_col: u16, diff --git a/rt/+linux/socket.ha b/rt/+linux/socket.ha @@ -1,4 +1,5 @@ export type sa_family_t = u16; +export type socklen_t = uint; export type in_addr = struct { s_addr: u32 @@ -42,6 +43,35 @@ export type sockaddr = struct { }, }; +export def SCM_RIGHTS: int = 0x01; +export def SCM_CREDENTIALS: int = 0x02; + +export type msghdr = struct { + msg_name: nullable *void, + msg_namelen: u32, + + msg_iov: nullable *[*]iovec, + msg_iovlen: size, + + msg_control: nullable *void, + msg_controllen: size, + + msg_flags: int +}; + +export type cmsghdr = struct { + // XXX: This structure differs on big-endian machines + cmsg_len: socklen_t, + _padding: int, + cmsg_level: int, + cmsg_type: int, +}; + +export type cmsg = struct { + hdr: cmsghdr, + cmsg_data: [*]u8, +}; + // domain for socket(2) export def AF_UNSPEC: u16 = 0; export def AF_UNIX: u16 = 1; // Unix domain sockets diff --git a/rt/+linux/types.ha b/rt/+linux/types.ha @@ -539,33 +539,6 @@ export type iovec = struct { iov_len: size }; -export def SCM_RIGHTS: int = 0x01; -export def SCM_CREDENTIALS: int = 0x02; - -export type msghdr = struct { - msg_name: nullable *void, - msg_namelen: u32, - - msg_iov: *[*]iovec, - msg_iovlen: size, - - msg_control: *void, - msg_controllen: size, - - msg_flags: int -}; - -export type cmsghdr = struct { - cmsg_len: size, - cmsg_level: int, - cmsg_type: int, -}; - -export type cmsg = struct { - hdr: cmsghdr, - cmsg_data: [*]u8, -}; - export def PRIO_PROCESS: int = 0; export def PRIO_PGRP: int = 1; export def PRIO_USER: int = 2; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -764,13 +764,15 @@ net() { gen_srcs -plinux net \ +linux.ha \ errors.ha \ - listener.ha + listener.ha \ + msg.ha gen_ssa -plinux net io os strings net::ip errors rt fmt gen_srcs -pfreebsd net \ +freebsd.ha \ errors.ha \ - listener.ha + listener.ha \ + msg.ha gen_ssa -pfreebsd net io os strings net::ip errors rt fmt } @@ -848,6 +850,7 @@ net_unix() { gen_srcs -plinux net::unix \ +linux.ha \ addr.ha \ + cmsg.ha \ dial.ha \ listener.ha \ options.ha \ @@ -857,6 +860,7 @@ net_unix() { gen_srcs -pfreebsd net::unix \ +freebsd.ha \ addr.ha \ + cmsg.ha \ dial.ha \ listener.ha \ options.ha \ diff --git a/stdlib.mk b/stdlib.mk @@ -1258,7 +1258,8 @@ $(HARECACHE)/math/math-any.ssa: $(stdlib_math_any_srcs) $(stdlib_rt) $(stdlib_ty stdlib_net_linux_srcs= \ $(STDLIB)/net/+linux.ha \ $(STDLIB)/net/errors.ha \ - $(STDLIB)/net/listener.ha + $(STDLIB)/net/listener.ha \ + $(STDLIB)/net/msg.ha $(HARECACHE)/net/net-linux.ssa: $(stdlib_net_linux_srcs) $(stdlib_rt) $(stdlib_io_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_net_ip_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_rt_$(PLATFORM)) $(stdlib_fmt_$(PLATFORM)) @printf 'HAREC \t$@\n' @@ -1270,7 +1271,8 @@ $(HARECACHE)/net/net-linux.ssa: $(stdlib_net_linux_srcs) $(stdlib_rt) $(stdlib_i stdlib_net_freebsd_srcs= \ $(STDLIB)/net/+freebsd.ha \ $(STDLIB)/net/errors.ha \ - $(STDLIB)/net/listener.ha + $(STDLIB)/net/listener.ha \ + $(STDLIB)/net/msg.ha $(HARECACHE)/net/net-freebsd.ssa: $(stdlib_net_freebsd_srcs) $(stdlib_rt) $(stdlib_io_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_net_ip_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_rt_$(PLATFORM)) $(stdlib_fmt_$(PLATFORM)) @printf 'HAREC \t$@\n' @@ -1377,6 +1379,7 @@ $(HARECACHE)/net/udp/net_udp-freebsd.ssa: $(stdlib_net_udp_freebsd_srcs) $(stdli stdlib_net_unix_linux_srcs= \ $(STDLIB)/net/unix/+linux.ha \ $(STDLIB)/net/unix/addr.ha \ + $(STDLIB)/net/unix/cmsg.ha \ $(STDLIB)/net/unix/dial.ha \ $(STDLIB)/net/unix/listener.ha \ $(STDLIB)/net/unix/options.ha \ @@ -1392,6 +1395,7 @@ $(HARECACHE)/net/unix/net_unix-linux.ssa: $(stdlib_net_unix_linux_srcs) $(stdlib stdlib_net_unix_freebsd_srcs= \ $(STDLIB)/net/unix/+freebsd.ha \ $(STDLIB)/net/unix/addr.ha \ + $(STDLIB)/net/unix/cmsg.ha \ $(STDLIB)/net/unix/dial.ha \ $(STDLIB)/net/unix/listener.ha \ $(STDLIB)/net/unix/options.ha \ @@ -3039,7 +3043,8 @@ $(TESTCACHE)/math/math-any.ssa: $(testlib_math_any_srcs) $(testlib_rt) $(testlib testlib_net_linux_srcs= \ $(STDLIB)/net/+linux.ha \ $(STDLIB)/net/errors.ha \ - $(STDLIB)/net/listener.ha + $(STDLIB)/net/listener.ha \ + $(STDLIB)/net/msg.ha $(TESTCACHE)/net/net-linux.ssa: $(testlib_net_linux_srcs) $(testlib_rt) $(testlib_io_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_net_ip_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_rt_$(PLATFORM)) $(testlib_fmt_$(PLATFORM)) @printf 'HAREC \t$@\n' @@ -3051,7 +3056,8 @@ $(TESTCACHE)/net/net-linux.ssa: $(testlib_net_linux_srcs) $(testlib_rt) $(testli testlib_net_freebsd_srcs= \ $(STDLIB)/net/+freebsd.ha \ $(STDLIB)/net/errors.ha \ - $(STDLIB)/net/listener.ha + $(STDLIB)/net/listener.ha \ + $(STDLIB)/net/msg.ha $(TESTCACHE)/net/net-freebsd.ssa: $(testlib_net_freebsd_srcs) $(testlib_rt) $(testlib_io_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_net_ip_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_rt_$(PLATFORM)) $(testlib_fmt_$(PLATFORM)) @printf 'HAREC \t$@\n' @@ -3160,6 +3166,7 @@ $(TESTCACHE)/net/udp/net_udp-freebsd.ssa: $(testlib_net_udp_freebsd_srcs) $(test testlib_net_unix_linux_srcs= \ $(STDLIB)/net/unix/+linux.ha \ $(STDLIB)/net/unix/addr.ha \ + $(STDLIB)/net/unix/cmsg.ha \ $(STDLIB)/net/unix/dial.ha \ $(STDLIB)/net/unix/listener.ha \ $(STDLIB)/net/unix/options.ha \ @@ -3175,6 +3182,7 @@ $(TESTCACHE)/net/unix/net_unix-linux.ssa: $(testlib_net_unix_linux_srcs) $(testl testlib_net_unix_freebsd_srcs= \ $(STDLIB)/net/unix/+freebsd.ha \ $(STDLIB)/net/unix/addr.ha \ + $(STDLIB)/net/unix/cmsg.ha \ $(STDLIB)/net/unix/dial.ha \ $(STDLIB)/net/unix/listener.ha \ $(STDLIB)/net/unix/options.ha \