hare

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

commit b7d0baf1c4869af7864c393a77c5cb8129f737c9
parent db7a4e7c87be21e73a899056302db3db733b37ca
Author: Drew DeVault <sir@cmpwn.com>
Date:   Mon, 21 Jun 2021 13:38:27 -0400

all: expand generic error handling

This cause things like ETIME to be converted to errors::timeout rather
than errors::opaque.

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

Diffstat:
Merrors/common.ha | 4++++
Merrors/rt.ha | 15++++++++++++++-
Mio/types.ha | 2+-
Mlinux/io_uring/cqe.ha | 13+++----------
Mlinux/io_uring/queue.ha | 6+++---
Mlinux/io_uring/uring.ha | 2+-
Mlinux/signalfd/signalfd.ha | 4++--
Mnet/+linux.ha | 2+-
Mnet/dns/error.ha | 6+++---
Anet/errors.ha | 22++++++++++++++++++++++
Mnet/listener.ha | 16++--------------
Mnet/tcp/+linux.ha | 6+++---
Mnet/tcp/listener.ha | 2+-
Mnet/udp/+linux.ha | 12++++++------
Mnet/unix/+linux.ha | 4++--
Mnet/unix/listener.ha | 2+-
Mos/exec/exec+linux.ha | 10+++++++---
Mos/exec/types.ha | 2+-
Mscripts/gen-stdlib | 1+
Mstdlib.mk | 2++
Munix/nice+linux.ha | 2+-
21 files changed, 81 insertions(+), 54 deletions(-)

diff --git a/errors/common.ha b/errors/common.ha @@ -25,6 +25,9 @@ export type timeout = !void; // The requested operation was cancelled. export type cancelled = !void; +// A connection attempt was refused. +export type refused = !void; + // A tagged union of all error types. export type error = !( busy | @@ -36,5 +39,6 @@ export type error = !( unsupported | timeout | cancelled | + refused | opaque ); diff --git a/errors/rt.ha b/errors/rt.ha @@ -2,7 +2,20 @@ use rt; // Wraps an [[rt::errno]] to produce an [[errors::opaque]]. This is a non-portable // interface which is mainly provided to support internal stdlib requirements. -export fn errno(errno: rt::errno) opaque = { +export fn errno(errno: rt::errno) error = { + switch (errno) { + rt::ECONNREFUSED => return refused, + rt::ECANCELED => return cancelled, + rt::EOVERFLOW => return overflow, + rt::EACCES => return noaccess, + rt::EINVAL => return invalid, + rt::EEXIST => return exists, + rt::ENOENT => return noentry, + rt::ETIME => return timeout, + rt::EBUSY => return busy, + * => void, + }; + static assert(size(rt::errno) <= size(opaque_data)); let err = opaque { strerror = &rt_strerror, ... }; let ptr = &err.data: *rt::errno; diff --git a/io/types.ha b/io/types.ha @@ -1,7 +1,7 @@ use errors; // Any error which may be returned from an I/O function. -export type error = !(errors::unsupported | errors::opaque); +export type error = !errors::error; // Indicates an end-of-file condition. export type EOF = void; diff --git a/linux/io_uring/cqe.ha b/linux/io_uring/cqe.ha @@ -28,16 +28,9 @@ export fn wait(ring: *io_uring) (*cqe | error) = { export fn peek(ring: *io_uring) (nullable *cqe | error) = get_cqe(ring, 0, 0); // Returns the result of a [[cqe]], or an error if unsuccessful. -export fn result(cqe: *cqe) (int | error) = { - // TODO: Flesh out more errors - return if (cqe.res < 0) switch (-cqe.res) { - rt::ETIME => errors::timeout, - rt::ECANCELED => errors::cancelled, - * => errors::errno(rt::wrap_errno(-cqe.res)), - } else { - cqe.res; - }; -}; +export fn result(cqe: *cqe) (int | error) = + if (cqe.res < 0) errors::errno(rt::wrap_errno(-cqe.res)) + else cqe.res; // Gets the user data field of a [[cqe]]. See [[set_user]] for the corresponding // SQE function. diff --git a/linux/io_uring/queue.ha b/linux/io_uring/queue.ha @@ -41,7 +41,7 @@ fn needs_flush(ring: *io_uring) bool = // Submits queued I/O asynchronously. Returns the number of submissions accepted // by the kernel. -export fn submit(ring: *io_uring) (uint | errors::opaque) = +export fn submit(ring: *io_uring) (uint | errors::error) = do_submit(ring, flush_sq(ring), 0u); // Submits queued I/O asynchronously and blocks until at least "wait" events are @@ -50,7 +50,7 @@ export fn submit(ring: *io_uring) (uint | errors::opaque) = // one event is completed. // // Returns the number of submissions accepted by the kernel. -export fn submit_wait(ring: *io_uring, wait: uint) (uint | errors::opaque) = +export fn submit_wait(ring: *io_uring, wait: uint) (uint | errors::error) = do_submit(ring, flush_sq(ring), wait); fn flush_sq(ring: *io_uring) uint = { @@ -76,7 +76,7 @@ fn do_submit( ring: *io_uring, submitted: uint, wait: uint, -) (uint | errors::opaque) = { +) (uint | errors::error) = { let flags: enter_flags = enter_flags::GETEVENTS; if (needs_enter(ring, &flags) || wait != 0) { return match (rt::io_uring_enter(ring.fd, diff --git a/linux/io_uring/uring.ha b/linux/io_uring/uring.ha @@ -1,7 +1,7 @@ use errors; // All errors which may be returned by this module. -export type error = !(errors::timeout | errors::cancelled | errors::opaque); +export type error = !errors::error; // Converts an [[error]] into a human-readable string. export fn strerror(err: error) const str = { diff --git a/linux/signalfd/signalfd.ha b/linux/signalfd/signalfd.ha @@ -35,7 +35,7 @@ export fn signalfd( fd: int, mask: *const rt::sigset, flags: int, -) (int | errors::opaque) = { +) (int | errors::error) = { return match (rt::signalfd(fd, mask, flags)) { fd: int => fd, err: rt::errno => errors::errno(err), @@ -43,7 +43,7 @@ export fn signalfd( }; // Reads pending signal info from a signalfd. -export fn readsignal(fd: int) (siginfo | errors::opaque) = { +export fn readsignal(fd: int) (siginfo | errors::error) = { let si = siginfo { ... }; return match (rt::read(fd, &si, size(siginfo))) { err: rt::errno => errors::errno(err), diff --git a/net/+linux.ha b/net/+linux.ha @@ -19,7 +19,7 @@ export fn listenerfd(l: *listener) (int | void) = { }; }; -export fn stream_accept(l: *listener) (*io::stream | io::error) = { +export fn stream_accept(l: *listener) (*io::stream | error) = { assert(l.accept == &stream_accept); let l = l: *stream_listener; let sn = rt::sockaddr {...}; diff --git a/net/dns/error.ha b/net/dns/error.ha @@ -1,5 +1,5 @@ -use errors; use fmt; +use net; // The DNS message was poorly formatted. export type format = !void; @@ -25,7 +25,7 @@ export type unknown_error = !u8; // All error types which might be returned from [[net::dns]] functions. export type error = (format | server_failure | name_error | not_implemented | refused | unknown_error - | errors::opaque); + | net::error); export fn strerror(err: error) const str = { static let buf: [64]u8 = [0...]; @@ -36,6 +36,6 @@ export fn strerror(err: error) const str = { not_implemented => "The name server does not support the requested kind of query", refused => "The name server refuses to perform the specified operation for policy reasons", ue: unknown_error => fmt::bsprintf(buf, "Unknown DNS error {}", ue: u8), - err: errors::opaque => errors::strerror(err), + err: net::error => net::strerror(err), }; }; diff --git a/net/errors.ha b/net/errors.ha @@ -0,0 +1,22 @@ +use errors; + +// All error types which can be returned from networking functions. +export type error = !errors::error; + +// Converts a [[net::error]] into a human-readable string. +export fn strerror(err: error) const str = errors::strerror(err); + +// TODO: listener should not be here, working around bug in harec forward +// references + +// A listener binds a socket and listens for incoming traffic for some +// unspecified protocol. This is generally most useful for providing an +// abstraction between a TCP socket and Unix socket (or any other stream +// oriented protocol), where the implementation which accepts and processes +// connections is not aware of the underlying transport. Most users will not +// need to use this interface directly, preferring functions such as +// [[net::tcp::accept]]. +export type listener = struct { + accept: nullable *fn(l: *listener) (*io::stream | error), + shutdown: nullable *fn(l: *listener) void, +}; diff --git a/net/listener.ha b/net/listener.ha @@ -1,23 +1,11 @@ use errors; use io; -// A listener binds a socket and listens for incoming traffic for some -// unspecified protocol. This is generally most useful for providing an -// abstraction between a TCP socket and Unix socket (or any other stream -// oriented protocol), where the implementation which accepts and processes -// connections is not aware of the underlying transport. Most users will not -// need to use this interface directly, preferring functions such as -// [[net::tcp::accept]]. -export type listener = struct { - accept: nullable *fn(l: *listener) (*io::stream | io::error), - shutdown: nullable *fn(l: *listener) void, -}; - // Accepts the next connection from a listener. Blocks until a new connection is // available. -export fn accept(l: *listener) (*io::stream | io::error) = { +export fn accept(l: *listener) (*io::stream | error) = { return match (l.accept) { - f: *fn(l: *listener) (*io::stream | io::error) => f(l), + f: *fn(l: *listener) (*io::stream | error) => f(l), null => errors::unsupported, }; }; diff --git a/net/tcp/+linux.ha b/net/tcp/+linux.ha @@ -11,7 +11,7 @@ export fn connect( addr: ip::addr, port: u16, options: connect_option... -) (*io::stream | io::error) = { +) (*io::stream | net::error) = { const sockaddr = ip::to_native(addr, port); const sockfd = match (rt::socket(match (addr) { ip::addr4 => rt::AF_INET: int, @@ -41,7 +41,7 @@ export fn listen( addr: ip::addr, port: u16, options: listen_option... -) (*net::listener | io::error) = { +) (*net::listener | net::error) = { const sockaddr = ip::to_native(addr, port); const sockfd = match (rt::socket(match (addr) { ip::addr4 => rt::AF_INET: int, @@ -116,7 +116,7 @@ fn setsockopt( sockfd: int, option: int, value: bool, -) (void | errors::opaque) = { +) (void | net::error) = { let val: int = if (value) 1 else 0; return match (rt::setsockopt(sockfd, rt::SOL_SOCKET, option, &val: *void, size(int): u32)) { diff --git a/net/tcp/listener.ha b/net/tcp/listener.ha @@ -3,7 +3,7 @@ use net; // Accepts the next connection from a listener. Blocks until a new connection is // available. This is a convenience wrapper around [[net::accept]]. -export fn accept(l: *net::listener) (*io::stream | io::error) = { +export fn accept(l: *net::listener) (*io::stream | net::error) = { return net::accept(l); }; diff --git a/net/udp/+linux.ha b/net/udp/+linux.ha @@ -9,7 +9,7 @@ export type socket = int; export fn connect( dest: ip::addr, port: u16, -) (socket | errors::opaque) = { +) (socket | net::error) = { const sockfd = match (rt::socket(match (dest) { ip::addr4 => rt::AF_INET: int, ip::addr6 => rt::AF_INET6: int, @@ -31,7 +31,7 @@ export fn listen( addr: ip::addr, port: u16, options: listen_option... -) (socket | errors::opaque) = { +) (socket | net::error) = { const sockfd = match (rt::socket(match (addr) { ip::addr4 => rt::AF_INET: int, ip::addr6 => rt::AF_INET6: int, @@ -70,7 +70,7 @@ export fn close(sock: socket) void = { }; // Sends a UDP packet to the destination previously specified by [[connect]]. -export fn send(sock: socket, buf: []u8) (size | errors::opaque) = { +export fn send(sock: socket, buf: []u8) (size | net::error) = { return match (rt::send(sock, buf: *[*]u8, len(buf), 0)) { sz: size => sz, err: rt::errno => errors::errno(err), @@ -83,7 +83,7 @@ export fn sendto( buf: []u8, dest: ip::addr, port: u16, -) (size | errors::opaque) = { +) (size | net::error) = { const sockaddr = ip::to_native(dest, port); const sz = size(rt::sockaddr): u32; return match (rt::sendto(sock, buf: *[*]u8, len(buf), @@ -99,13 +99,13 @@ export fn recvfrom( buf: []u8, src: nullable *ip::addr, port: nullable *u16, -) (size | errors::opaque) = { +) (size | net::error) = { let addrsz = size(rt::sockaddr): u32; const sockaddr = rt::sockaddr { ... }; const sz = match (rt::recvfrom(sock, buf: *[*]u8, len(buf), 0, &sockaddr, &addrsz)) { sz: size => sz, - err: rt::errno => errors::errno(err), + err: rt::errno => return errors::errno(err), }; assert(addrsz <= size(rt::sockaddr)); diff --git a/net/unix/+linux.ha b/net/unix/+linux.ha @@ -9,7 +9,7 @@ use types; // Opens a UNIX socket connection to the path. Blocks until the connection is // established. -export fn connect(addr: addr) (*io::stream | io::error) = { +export fn connect(addr: addr) (*io::stream | net::error) = { let sockaddr = match (to_native(addr)) { a: rt::sockaddr => a, invalid => return errors::unsupported, // path too long @@ -36,7 +36,7 @@ export fn connect(addr: addr) (*io::stream | io::error) = { export fn listen( addr: addr, options: listen_option... -) (*net::listener | io::error) = { +) (*net::listener | net::error) = { let sockaddr = match (to_native(addr)) { a: rt::sockaddr => a, invalid => return errors::unsupported, // path too long diff --git a/net/unix/listener.ha b/net/unix/listener.ha @@ -3,7 +3,7 @@ use net; // Accepts the next connection from a listener. Blocks until a new connection is // available. This is a convenience wrapper around [[net::accept]]. -export fn accept(l: *net::listener) (*io::stream | io::error) = { +export fn accept(l: *net::listener) (*io::stream | net::error) = { return net::accept(l); }; diff --git a/os/exec/exec+linux.ha b/os/exec/exec+linux.ha @@ -12,7 +12,7 @@ export fn fork() (int | void | error) = match (rt::fork()) { i: (int | void) => i, }; -fn open(path: str) (platform_cmd | errors::opaque) = { +fn open(path: str) (platform_cmd | error) = { match (rt::access(path, rt::X_OK)) { err: rt::errno => return errors::errno(err), b: bool => if (!b) return errors::errno(rt::EACCES), @@ -27,7 +27,7 @@ fn open(path: str) (platform_cmd | errors::opaque) = { fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!; -fn platform_exec(cmd: *command) errors::opaque = { +fn platform_exec(cmd: *command) error = { // We don't worry about freeing the return values from strings::to_c // because once we exec(2) our heap is fried anyway let argv: []nullable *const char = alloc([], len(cmd.argv) + 1z); @@ -50,7 +50,7 @@ fn platform_exec(cmd: *command) errors::opaque = { argv: *[*]nullable *const char, envp, rt::AT_EMPTY_PATH)); }; -fn platform_start(cmd: *command) (errors::opaque | process) = { +fn platform_start(cmd: *command) (process | errors::error) = { // TODO: Let the user configure clone more to their taste (e.g. SIGCHLD) let pipe: [2]int = [0...]; match (rt::pipe2(&pipe, rt::O_CLOEXEC)) { @@ -75,6 +75,10 @@ fn platform_start(cmd: *command) (errors::opaque | process) = { void => { rt::close(pipe[0])!; let err = platform_exec(cmd); + if (!(err is errors::opaque)) { + rt::exit(1); + }; + let err = err as errors::opaque; let err = &err.data: *rt::errno; rt::write(pipe[1], &err, size(int))!; rt::exit(1); diff --git a/os/exec/types.ha b/os/exec/types.ha @@ -11,7 +11,7 @@ export type command = struct { export type nocmd = !void; // All errors that can be returned from os::exec. -export type error = !(nocmd | errors::opaque); +export type error = !(nocmd | ...errors::error); // Returns a human-readable message for the given error. export fn strerror(err: error) const str = { diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -545,6 +545,7 @@ net() { printf '# net\n' gen_srcs net \ '$(PLATFORM).ha' \ + errors.ha \ listener.ha gen_ssa net io os strings net::ip errors rt fmt } diff --git a/stdlib.mk b/stdlib.mk @@ -819,6 +819,7 @@ $(HARECACHE)/linux/vdso/linux_vdso.ssa: $(stdlib_linux_vdso_srcs) $(stdlib_rt) $ # net stdlib_net_srcs= \ $(STDLIB)/net/$(PLATFORM).ha \ + $(STDLIB)/net/errors.ha \ $(STDLIB)/net/listener.ha $(HARECACHE)/net/net.ssa: $(stdlib_net_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_os) $(stdlib_strings) $(stdlib_net_ip) $(stdlib_errors) $(stdlib_rt) $(stdlib_fmt) @@ -1969,6 +1970,7 @@ $(TESTCACHE)/linux/vdso/linux_vdso.ssa: $(testlib_linux_vdso_srcs) $(testlib_rt) # net testlib_net_srcs= \ $(STDLIB)/net/$(PLATFORM).ha \ + $(STDLIB)/net/errors.ha \ $(STDLIB)/net/listener.ha $(TESTCACHE)/net/net.ssa: $(testlib_net_srcs) $(testlib_rt) $(testlib_io) $(testlib_os) $(testlib_strings) $(testlib_net_ip) $(testlib_errors) $(testlib_rt) $(testlib_fmt) diff --git a/unix/nice+linux.ha b/unix/nice+linux.ha @@ -5,7 +5,7 @@ use rt; // between -20 and 19 (inclusive); lower numbers represent a higher priority. // Generally, you must have elevated permissions to reduce your niceness, but // not to increase it. -export fn nice(inc: int) (void | errors::opaque) = { +export fn nice(inc: int) (void | errors::error) = { let prio = inc; if (inc > -40 && inc <= 40) { prio += rt::getpriority(rt::PRIO_PROCESS, 0) as int;