aboutsummaryrefslogtreecommitdiff
path: root/core/net/errors_linux.odin
diff options
context:
space:
mode:
authorLaytan Laats <laytanlaats@hotmail.com>2025-04-05 15:53:11 +0200
committerLaytan Laats <laytanlaats@hotmail.com>2025-04-05 17:35:19 +0200
commitff7d55a8e1ab8fd031378d9994f4aaf8394e7255 (patch)
tree78e4dab5313eed54d40496f2aaa195972ff69347 /core/net/errors_linux.odin
parentf796b67a67bdd316ecf830b13cbd43fed7c0a64b (diff)
net: rework errors to be cross-platform
Diffstat (limited to 'core/net/errors_linux.odin')
-rw-r--r--core/net/errors_linux.odin384
1 files changed, 236 insertions, 148 deletions
diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin
index 3cd51e6fd..da9811d71 100644
--- a/core/net/errors_linux.odin
+++ b/core/net/errors_linux.odin
@@ -21,181 +21,269 @@ package net
Feoramund: FreeBSD platform code
*/
-import "core:c"
+import "core:reflect"
import "core:sys/linux"
-Create_Socket_Error :: enum c.int {
- None = 0,
- Family_Not_Supported_For_This_Socket = c.int(linux.Errno.EAFNOSUPPORT),
- No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE),
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- No_Memory_Available_Available = c.int(linux.Errno.ENOMEM),
- Protocol_Unsupported_By_System = c.int(linux.Errno.EPROTONOSUPPORT),
- Wrong_Protocol_For_Socket = c.int(linux.Errno.EPROTONOSUPPORT),
- Family_And_Socket_Type_Mismatch = c.int(linux.Errno.EPROTONOSUPPORT),
+@(private="file", thread_local)
+_last_error: linux.Errno
+
+_last_platform_error :: proc() -> i32 {
+ return i32(_last_error)
}
-Dial_Error :: enum c.int {
- None = 0,
- Port_Required = -1,
-
- Address_In_Use = c.int(linux.Errno.EADDRINUSE),
- In_Progress = c.int(linux.Errno.EINPROGRESS),
- Cannot_Use_Any_Address = c.int(linux.Errno.EADDRNOTAVAIL),
- Wrong_Family_For_Socket = c.int(linux.Errno.EAFNOSUPPORT),
- Refused = c.int(linux.Errno.ECONNREFUSED),
- Is_Listening_Socket = c.int(linux.Errno.EACCES),
- Already_Connected = c.int(linux.Errno.EISCONN),
- Network_Unreachable = c.int(linux.Errno.ENETUNREACH), // Device is offline
- Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), // Remote host cannot be reached
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Timeout = c.int(linux.Errno.ETIMEDOUT),
-
- // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
- Would_Block = c.int(linux.Errno.EWOULDBLOCK),
+_last_platform_error_string :: proc() -> string {
+ description, _ := reflect.enum_name_from_value(_last_error)
+ return description
}
-Bind_Error :: enum c.int {
- None = 0,
- Address_In_Use = c.int(linux.Errno.EADDRINUSE), // Another application is currently bound to this endpoint.
- Given_Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), // The address is not a local address on this machine.
- Broadcast_Disabled = c.int(linux.Errno.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
- Address_Family_Mismatch = c.int(linux.Errno.EFAULT), // The address family of the address does not match that of the socket.
- Already_Bound = c.int(linux.Errno.EINVAL), // The socket is already bound to an address.
- No_Ports_Available = c.int(linux.Errno.ENOBUFS), // There are not enough ephemeral ports available.
+_set_last_platform_error :: proc(err: i32) {
+ _last_error = linux.Errno(err)
}
-Listen_Error :: enum c.int {
- None = 0,
- Address_In_Use = c.int(linux.Errno.EADDRINUSE),
- Already_Connected = c.int(linux.Errno.EISCONN),
- No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE),
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Listening_Not_Supported_For_This_Socket = c.int(linux.Errno.EOPNOTSUPP),
+_create_socket_error :: proc(errno: linux.Errno) -> Create_Socket_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
+ return .Insufficient_Resources
+ case .EAFNOSUPPORT, .EPROTOTYPE:
+ return .Invalid_Argument
+ case .EACCES, .EPERM:
+ return .Insufficient_Permissions
+ case:
+ return .Unknown
+ }
}
-Accept_Error :: enum c.int {
- None = 0,
- Not_Listening = c.int(linux.Errno.EINVAL),
- No_Socket_Descriptors_Available_For_Client_Socket = c.int(linux.Errno.EMFILE),
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Not_Connection_Oriented_Socket = c.int(linux.Errno.EOPNOTSUPP),
+_dial_error :: proc(errno: linux.Errno) -> Dial_Error {
+ assert(errno != nil)
+ _last_error = errno
- // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
- Would_Block = c.int(linux.Errno.EWOULDBLOCK),
+ #partial switch errno {
+ case .EAGAIN:
+ return .Insufficient_Resources
+ case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
+ return .Invalid_Argument
+ case .EISCONN:
+ return .Already_Connected
+ case .EALREADY:
+ return .Already_Connecting
+ case .EADDRINUSE:
+ return .Address_In_Use
+ case .ENETUNREACH:
+ return .Network_Unreachable
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .ECONNREFUSED:
+ return .Refused
+ case .ECONNRESET:
+ return .Reset
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EINPROGRESS:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case .EACCES:
+ return .Broadcast_Not_Supported
+ case:
+ return .Unknown
+ }
}
-TCP_Recv_Error :: enum c.int {
- None = 0,
- Shutdown = c.int(linux.Errno.ESHUTDOWN),
- Not_Connected = c.int(linux.Errno.ENOTCONN),
- Connection_Broken = c.int(linux.Errno.ENETRESET),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Aborted = c.int(linux.Errno.ECONNABORTED),
-
- // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
- Connection_Closed = c.int(linux.Errno.ECONNRESET),
- Offline = c.int(linux.Errno.ENETDOWN),
- Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH),
- Interrupted = c.int(linux.Errno.EINTR),
- Timeout = c.int(linux.Errno.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
+_bind_error :: proc(errno: linux.Errno) -> Bind_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
+ return .Insufficient_Resources
+ case .EINVAL:
+ return .Already_Bound
+ case .EBADF:
+ return .Invalid_Argument
+ case .EACCES:
+ return .Insufficient_Permissions_For_Address
+ case .EADDRINUSE:
+ return .Address_In_Use
+ case:
+ return .Unknown
+ }
}
-UDP_Recv_Error :: enum c.int {
- None = 0,
+_listen_error :: proc(errno: linux.Errno) -> Listen_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .ENOTSOCK:
+ return .Invalid_Argument
+ case .EDESTADDRREQ, .EOPNOTSUPP:
+ return .Unsupported_Socket
+ case .EINVAL:
+ return .Already_Connected
+ case:
+ return .Unknown
+ }
+}
+
+_accept_error :: proc(errno: linux.Errno) -> Accept_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EMFILE, .ENFILE, .ENOBUFS, .ENOMEM:
+ return .Insufficient_Resources
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .EINVAL:
+ return .Not_Listening
+ case .ECONNABORTED:
+ return .Aborted
+ case .EWOULDBLOCK:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
+}
- Buffer_Too_Small = c.int(linux.Errno.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
- Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
- Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
- Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory.
- Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
+_tcp_recv_error :: proc(errno: linux.Errno) -> TCP_Recv_Error {
+ assert(errno != nil)
+ _last_error = errno
- // The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout.
- // NOTE: No, really. Presumably this means something different for nonblocking sockets...
- Timeout = c.int(linux.Errno.EWOULDBLOCK),
- Socket_Not_Bound = c.int(linux.Errno.EINVAL), // The socket must be bound for this operation, but isn't.
+ #partial switch errno {
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .ENOTCONN:
+ return .Not_Connected
+ case .ECONNREFUSED:
+ return .Connection_Closed
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-TCP_Send_Error :: enum c.int {
- None = 0,
- Aborted = c.int(linux.Errno.ECONNABORTED),
- Connection_Closed = c.int(linux.Errno.ECONNRESET),
- Not_Connected = c.int(linux.Errno.ENOTCONN),
- Shutdown = c.int(linux.Errno.ESHUTDOWN),
-
- // The send queue was full.
- // This is usually a transient issue.
- //
- // This also shouldn't normally happen on Linux, as data is dropped if it
- // doesn't fit in the send queue.
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- Offline = c.int(linux.Errno.ENETDOWN),
- Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH),
- Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
- Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
- Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
+_udp_recv_error :: proc(errno: linux.Errno) -> UDP_Recv_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .ECONNREFUSED, .ENOTCONN:
+ return .Connection_Refused
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-// TODO
-UDP_Send_Error :: enum c.int {
- None = 0,
- Message_Too_Long = c.int(linux.Errno.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent.
-
- // TODO: not sure what the exact circumstances for this is yet
- Network_Unreachable = c.int(linux.Errno.ENETUNREACH),
- No_Outbound_Ports_Available = c.int(linux.Errno.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
-
- // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
- // NOTE: No, really. Presumably this means something different for nonblocking sockets...
- Timeout = c.int(linux.Errno.EWOULDBLOCK),
- Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
- Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
- Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory.
- Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
-
- // The send queue was full.
- // This is usually a transient issue.
- //
- // This also shouldn't normally happen on Linux, as data is dropped if it
- // doesn't fit in the send queue.
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- No_Memory_Available = c.int(linux.Errno.ENOMEM), // No memory was available to properly manage the send queue.
+_tcp_send_error :: proc(errno: linux.Errno) -> TCP_Send_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
+ return .Invalid_Argument
+ case .ENOBUFS, .ENOMEM:
+ return .Insufficient_Resources
+ case .ECONNRESET, .EPIPE:
+ return .Connection_Closed
+ case .ENOTCONN:
+ return .Not_Connected
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .EHOSTDOWN:
+ return .Host_Unreachable
+ case .ENETDOWN:
+ return .Network_Unreachable
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-// TODO(flysand): slight regression
-Shutdown_Manner :: enum c.int {
- Receive = c.int(linux.Shutdown_How.RD),
- Send = c.int(linux.Shutdown_How.WR),
- Both = c.int(linux.Shutdown_How.RDWR),
+_udp_send_error :: proc(errno: linux.Errno) -> UDP_Send_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
+ return .Invalid_Argument
+ case .ENOBUFS, .ENOMEM:
+ return .Insufficient_Resources
+ case .ECONNRESET, .EPIPE:
+ return .Connection_Refused
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .EHOSTDOWN:
+ return .Host_Unreachable
+ case .ENETDOWN:
+ return .Network_Unreachable
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Shutdown_Error :: enum c.int {
- None = 0,
- Aborted = c.int(linux.Errno.ECONNABORTED),
- Reset = c.int(linux.Errno.ECONNRESET),
- Offline = c.int(linux.Errno.ENETDOWN),
- Not_Connected = c.int(linux.Errno.ENOTCONN),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Invalid_Manner = c.int(linux.Errno.EINVAL),
+_shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
}
-Socket_Option_Error :: enum c.int {
- None = 0,
- Offline = c.int(linux.Errno.ENETDOWN),
- Timeout_When_Keepalive_Set = c.int(linux.Errno.ENETRESET),
- Invalid_Option_For_Socket = c.int(linux.Errno.ENOPROTOOPT),
- Reset_When_Keepalive_Set = c.int(linux.Errno.ENOTCONN),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
+_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .ENOMEM, .ENOBUFS:
+ return .Insufficient_Resources
+ case .EBADF, .ENOTSOCK:
+ return .Invalid_Socket
+ case .ENOPROTOOPT, .EINVAL:
+ return .Invalid_Option
+ case .EFAULT, .EDOM:
+ return .Invalid_Value
+ case:
+ return .Unknown
+ }
}
-Set_Blocking_Error :: enum c.int {
- None = 0,
+_set_blocking_error :: proc(errno: linux.Errno) -> Set_Blocking_Error {
+ assert(errno != nil)
+ _last_error = errno
- // TODO: add errors occuring on followig calls:
- // flags, _ := linux.Errno.fcntl(sd, linux.Errno.F_GETFL, 0)
- // linux.Errno.fcntl(sd, linux.Errno.F_SETFL, flags | int(linux.Errno.O_NONBLOCK))
-} \ No newline at end of file
+ #partial switch errno {
+ case .EBADF:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
+}