From 2910aa77b2feea39fc0618598e275b138a346e71 Mon Sep 17 00:00:00 2001 From: comex Date: Sun, 24 Jan 2021 15:17:02 -0500 Subject: [PATCH] [network] Error handling reform `network.cpp` has several error paths which either: - report "Unhandled host socket error=n" and return `SUCCESS`, or - switch on a few possible errors, log them, and translate them to Errno; the same switch statement is copied and pasted in multiple places in the code Convert these paths to use a helper function `GetAndLogLastError`, which is roughly the equivalent of one of the switch statements, but: - handling more cases (both ones that were already in `Errno`, and a few more I added), and - using OS functions to convert the error to a string when logging, so it'll describe the error even if it's not one of the ones in the switch statement. - To handle this, refactor the logic in `GetLastErrorMsg` to expose a new function `NativeErrorToString` which takes the error number explicitly as an argument. And improve the Windows version a bit. Also, add a test which exercises two random error paths. --- src/common/common_funcs.h | 6 +- src/common/misc.cpp | 44 +++++--- src/core/network/network.cpp | 173 +++++++++++++---------------- src/core/network/network.h | 6 + src/tests/CMakeLists.txt | 1 + src/tests/core/network/network.cpp | 28 +++++ 6 files changed, 147 insertions(+), 111 deletions(-) create mode 100644 src/tests/core/network/network.cpp diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 71b64e32a3..4ace2cd339 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -52,9 +52,13 @@ __declspec(dllimport) void __stdcall DebugBreak(void); // Generic function to get last error message. // Call directly after the command or use the error num. // This function might change the error code. -// Defined in Misc.cpp. +// Defined in misc.cpp. [[nodiscard]] std::string GetLastErrorMsg(); +// Like GetLastErrorMsg(), but passing an explicit error code. +// Defined in misc.cpp. +[[nodiscard]] std::string NativeErrorToString(int e); + #define DECLARE_ENUM_FLAG_OPERATORS(type) \ [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \ using T = std::underlying_type_t; \ diff --git a/src/common/misc.cpp b/src/common/misc.cpp index 1d5393597c..495385b9e3 100644 --- a/src/common/misc.cpp +++ b/src/common/misc.cpp @@ -12,27 +12,41 @@ #include "common/common_funcs.h" -// Generic function to get last error message. -// Call directly after the command or use the error num. -// This function might change the error code. -std::string GetLastErrorMsg() { - static constexpr std::size_t buff_size = 255; - char err_str[buff_size]; - +std::string NativeErrorToString(int e) { #ifdef _WIN32 - FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr); - return std::string(err_str, buff_size); -#elif defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) + LPSTR err_str; + + DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&err_str), 1, nullptr); + if (!res) { + return "(FormatMessageA failed to format error)"; + } + std::string ret(err_str); + LocalFree(err_str); + return ret; +#else + char err_str[255]; +#if defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) // Thread safe (GNU-specific) - const char* str = strerror_r(errno, err_str, buff_size); + const char* str = strerror_r(e, err_str, sizeof(err_str)); return std::string(str); #else // Thread safe (XSI-compliant) - const int success = strerror_r(errno, err_str, buff_size); - if (success != 0) { - return {}; + int second_err = strerror_r(e, err_str, sizeof(err_str)); + if (second_err != 0) { + return "(strerror_r failed to format error)"; } return std::string(err_str); +#endif // GLIBC etc. +#endif // _WIN32 +} + +std::string GetLastErrorMsg() { +#ifdef _WIN32 + return NativeErrorToString(GetLastError()); +#else + return NativeErrorToString(errno); #endif } diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp index 681e934688..526bfa1108 100644 --- a/src/core/network/network.cpp +++ b/src/core/network/network.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "common/common_funcs.h" #ifdef _WIN32 #define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname @@ -90,15 +91,36 @@ LINGER MakeLinger(bool enable, u32 linger_value) { return value; } -int LastError() { - return WSAGetLastError(); -} - bool EnableNonBlock(SOCKET fd, bool enable) { u_long value = enable ? 1 : 0; return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; } +Errno TranslateNativeError(int e) { + switch (e) { + case WSAEBADF: + return Errno::BADF; + case WSAEINVAL: + return Errno::INVAL; + case WSAEMFILE: + return Errno::MFILE; + case WSAENOTCONN: + return Errno::NOTCONN; + case WSAEWOULDBLOCK: + return Errno::AGAIN; + case WSAECONNREFUSED: + return Errno::CONNREFUSED; + case WSAEHOSTUNREACH: + return Errno::HOSTUNREACH; + case WSAENETDOWN: + return Errno::NETDOWN; + case WSAENETUNREACH: + return Errno::NETUNREACH; + default: + return Errno::OTHER; + } +} + #elif YUZU_UNIX // ^ _WIN32 v YUZU_UNIX using SOCKET = int; @@ -108,9 +130,6 @@ using ULONG = u64; constexpr SOCKET INVALID_SOCKET = -1; constexpr SOCKET SOCKET_ERROR = -1; -constexpr int WSAEWOULDBLOCK = EAGAIN; -constexpr int WSAENOTCONN = ENOTCONN; - constexpr int SD_RECEIVE = SHUT_RD; constexpr int SD_SEND = SHUT_WR; constexpr int SD_BOTH = SHUT_RDWR; @@ -162,10 +181,6 @@ linger MakeLinger(bool enable, u32 linger_value) { return value; } -int LastError() { - return errno; -} - bool EnableNonBlock(int fd, bool enable) { int flags = fcntl(fd, F_GETFD); if (flags == -1) { @@ -179,8 +194,43 @@ bool EnableNonBlock(int fd, bool enable) { return fcntl(fd, F_SETFD, flags) == 0; } +Errno TranslateNativeError(int e) { + switch (e) { + case EBADF: + return Errno::BADF; + case EINVAL: + return Errno::INVAL; + case EMFILE: + return Errno::MFILE; + case ENOTCONN: + return Errno::NOTCONN; + case EAGAIN: + return Errno::AGAIN; + case ECONNREFUSED: + return Errno::CONNREFUSED; + case EHOSTUNREACH: + return Errno::HOSTUNREACH; + case ENETDOWN: + return Errno::NETDOWN; + case ENETUNREACH: + return Errno::NETUNREACH; + default: + return Errno::OTHER; + } +} + #endif +Errno GetAndLogLastError() { +#ifdef _WIN32 + int e = WSAGetLastError(); +#else + int e = errno; +#endif + LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e)); + return TranslateNativeError(e); +} + int TranslateDomain(Domain domain) { switch (domain) { case Domain::INET: @@ -290,9 +340,7 @@ Errno SetSockOpt(SOCKET fd, int option, T value) { if (result != SOCKET_ERROR) { return Errno::SUCCESS; } - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return Errno::SUCCESS; + return GetAndLogLastError(); } } // Anonymous namespace @@ -308,14 +356,12 @@ NetworkInstance::~NetworkInstance() { std::pair GetHostIPv4Address() { std::array name{}; if (gethostname(name.data(), static_cast(name.size()) - 1) == SOCKET_ERROR) { - UNIMPLEMENTED_MSG("Unhandled gethostname error"); - return {IPv4Address{}, Errno::SUCCESS}; + return {IPv4Address{}, GetAndLogLastError()}; } hostent* const ent = gethostbyname(name.data()); if (!ent) { - UNIMPLEMENTED_MSG("Unhandled gethostbyname error"); - return {IPv4Address{}, Errno::SUCCESS}; + return {IPv4Address{}, GetAndLogLastError()}; } if (ent->h_addr_list == nullptr) { UNIMPLEMENTED_MSG("No addr provided in hostent->h_addr_list"); @@ -359,9 +405,7 @@ std::pair Poll(std::vector& pollfds, s32 timeout) { ASSERT(result == SOCKET_ERROR); - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return {-1, Errno::SUCCESS}; + return {-1, GetAndLogLastError()}; } Socket::~Socket() { @@ -380,9 +424,7 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { return Errno::SUCCESS; } - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return Errno::SUCCESS; + return GetAndLogLastError(); } std::pair Socket::Accept() { @@ -391,9 +433,7 @@ std::pair Socket::Accept() { const SOCKET new_socket = accept(fd, &addr, &addrlen); if (new_socket == INVALID_SOCKET) { - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return {AcceptResult{}, Errno::SUCCESS}; + return {AcceptResult{}, GetAndLogLastError()}; } AcceptResult result; @@ -412,23 +452,14 @@ Errno Socket::Connect(SockAddrIn addr_in) { return Errno::SUCCESS; } - switch (const int ec = LastError()) { - case WSAEWOULDBLOCK: - LOG_DEBUG(Service, "EAGAIN generated"); - return Errno::AGAIN; - default: - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return Errno::SUCCESS; - } + return GetAndLogLastError(); } std::pair Socket::GetPeerName() { sockaddr addr; socklen_t addrlen = sizeof(addr); if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) { - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return {SockAddrIn{}, Errno::SUCCESS}; + return {SockAddrIn{}, GetAndLogLastError()}; } ASSERT(addrlen == sizeof(sockaddr_in)); @@ -439,9 +470,7 @@ std::pair Socket::GetSockName() { sockaddr addr; socklen_t addrlen = sizeof(addr); if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) { - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return {SockAddrIn{}, Errno::SUCCESS}; + return {SockAddrIn{}, GetAndLogLastError()}; } ASSERT(addrlen == sizeof(sockaddr_in)); @@ -454,9 +483,7 @@ Errno Socket::Bind(SockAddrIn addr) { return Errno::SUCCESS; } - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return Errno::SUCCESS; + return GetAndLogLastError(); } Errno Socket::Listen(s32 backlog) { @@ -464,9 +491,7 @@ Errno Socket::Listen(s32 backlog) { return Errno::SUCCESS; } - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return Errno::SUCCESS; + return GetAndLogLastError(); } Errno Socket::Shutdown(ShutdownHow how) { @@ -489,14 +514,7 @@ Errno Socket::Shutdown(ShutdownHow how) { return Errno::SUCCESS; } - switch (const int ec = LastError()) { - case WSAENOTCONN: - LOG_ERROR(Service, "ENOTCONN generated"); - return Errno::NOTCONN; - default: - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return Errno::SUCCESS; - } + return GetAndLogLastError(); } std::pair Socket::Recv(int flags, std::vector& message) { @@ -509,17 +527,7 @@ std::pair Socket::Recv(int flags, std::vector& message) { return {static_cast(result), Errno::SUCCESS}; } - switch (const int ec = LastError()) { - case WSAEWOULDBLOCK: - LOG_DEBUG(Service, "EAGAIN generated"); - return {-1, Errno::AGAIN}; - case WSAENOTCONN: - LOG_ERROR(Service, "ENOTCONN generated"); - return {-1, Errno::NOTCONN}; - default: - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return {0, Errno::SUCCESS}; - } + return {-1, GetAndLogLastError()}; } std::pair Socket::RecvFrom(int flags, std::vector& message, SockAddrIn* addr) { @@ -541,17 +549,7 @@ std::pair Socket::RecvFrom(int flags, std::vector& message, Sock return {static_cast(result), Errno::SUCCESS}; } - switch (const int ec = LastError()) { - case WSAEWOULDBLOCK: - LOG_DEBUG(Service, "EAGAIN generated"); - return {-1, Errno::AGAIN}; - case WSAENOTCONN: - LOG_ERROR(Service, "ENOTCONN generated"); - return {-1, Errno::NOTCONN}; - default: - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return {-1, Errno::SUCCESS}; - } + return {-1, GetAndLogLastError()}; } std::pair Socket::Send(const std::vector& message, int flags) { @@ -564,18 +562,7 @@ std::pair Socket::Send(const std::vector& message, int flags) { return {static_cast(result), Errno::SUCCESS}; } - const int ec = LastError(); - switch (ec) { - case WSAEWOULDBLOCK: - LOG_DEBUG(Service, "EAGAIN generated"); - return {-1, Errno::AGAIN}; - case WSAENOTCONN: - LOG_ERROR(Service, "ENOTCONN generated"); - return {-1, Errno::NOTCONN}; - default: - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return {-1, Errno::SUCCESS}; - } + return {-1, GetAndLogLastError()}; } std::pair Socket::SendTo(u32 flags, const std::vector& message, @@ -597,9 +584,7 @@ std::pair Socket::SendTo(u32 flags, const std::vector& message, return {static_cast(result), Errno::SUCCESS}; } - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return {-1, Errno::SUCCESS}; + return {-1, GetAndLogLastError()}; } Errno Socket::Close() { @@ -642,9 +627,7 @@ Errno Socket::SetNonBlock(bool enable) { if (EnableNonBlock(fd, enable)) { return Errno::SUCCESS; } - const int ec = LastError(); - UNREACHABLE_MSG("Unhandled host socket error={}", ec); - return Errno::SUCCESS; + return GetAndLogLastError(); } bool Socket::IsOpened() const { diff --git a/src/core/network/network.h b/src/core/network/network.h index 76b2821f22..bd30f18993 100644 --- a/src/core/network/network.h +++ b/src/core/network/network.h @@ -7,6 +7,7 @@ #include #include +#include "common/common_funcs.h" #include "common/common_types.h" namespace Network { @@ -21,6 +22,11 @@ enum class Errno { MFILE, NOTCONN, AGAIN, + CONNREFUSED, + HOSTUNREACH, + NETDOWN, + NETUNREACH, + OTHER, }; /// Address families diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 4ea0076e96..d875c4fee1 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(tests common/param_package.cpp common/ring_buffer.cpp core/core_timing.cpp + core/network/network.cpp tests.cpp video_core/buffer_base.cpp ) diff --git a/src/tests/core/network/network.cpp b/src/tests/core/network/network.cpp new file mode 100644 index 0000000000..b21ad8911c --- /dev/null +++ b/src/tests/core/network/network.cpp @@ -0,0 +1,28 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "core/network/network.h" +#include "core/network/sockets.h" + +TEST_CASE("Network::Errors", "[core]") { + Network::NetworkInstance network_instance; // initialize network + + Network::Socket socks[2]; + for (Network::Socket& sock : socks) { + REQUIRE(sock.Initialize(Network::Domain::INET, Network::Type::STREAM, + Network::Protocol::TCP) == Network::Errno::SUCCESS); + } + + Network::SockAddrIn addr{ + Network::Domain::INET, + {127, 0, 0, 1}, + 1, // hopefully nobody running this test has something listening on port 1 + }; + REQUIRE(socks[0].Connect(addr) == Network::Errno::CONNREFUSED); + + std::vector message{1, 2, 3, 4}; + REQUIRE(socks[1].Recv(0, message).second == Network::Errno::NOTCONN); +}