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:
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;