From 755bcc459bb9a5c7d521ea9046a8ac58c5256e73 Mon Sep 17 00:00:00 2001
From: comex <comexk@gmail.com>
Date: Sat, 1 Jul 2023 17:28:43 -0700
Subject: [PATCH] Improve behavior when sending to closed connection

- On Unix, this would previously kill the Yuzu process with SIGPIPE.
  Send MSG_NOSIGNAL to opt out of this.

- Add support for the proper error code in this situation, EPIPE.

- Windows has nonstandard behavior in this situation; translate it to
  the standard behavior.  Kind of pointless, but isn't it nice to be
  correct?
---
 src/core/hle/service/sockets/sockets.h        |  2 ++
 .../hle/service/sockets/sockets_translate.cpp |  4 +++
 src/core/internal_network/network.cpp         | 35 +++++++++++++++----
 src/core/internal_network/network.h           |  2 ++
 4 files changed, 36 insertions(+), 7 deletions(-)

diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index 77426c46e3..f86af01a43 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -18,7 +18,9 @@ enum class Errno : u32 {
     AGAIN = 11,
     INVAL = 22,
     MFILE = 24,
+    PIPE = 32,
     MSGSIZE = 90,
+    CONNABORTED = 103,
     CONNRESET = 104,
     NOTCONN = 107,
     TIMEDOUT = 110,
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
index c1187209ff..aed05250c7 100644
--- a/src/core/hle/service/sockets/sockets_translate.cpp
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) {
         return Errno::INVAL;
     case Network::Errno::MFILE:
         return Errno::MFILE;
+    case Network::Errno::PIPE:
+        return Errno::PIPE;
     case Network::Errno::NOTCONN:
         return Errno::NOTCONN;
     case Network::Errno::TIMEDOUT:
         return Errno::TIMEDOUT;
+    case Network::Errno::CONNABORTED:
+        return Errno::CONNABORTED;
     case Network::Errno::CONNRESET:
         return Errno::CONNRESET;
     case Network::Errno::INPROGRESS:
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index bda9fa2e00..5d28300e6e 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -39,6 +39,11 @@ namespace Network {
 
 namespace {
 
+enum class CallType {
+    Send,
+    Other,
+};
+
 #ifdef _WIN32
 
 using socklen_t = int;
@@ -96,7 +101,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) {
     return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR;
 }
 
-Errno TranslateNativeError(int e) {
+Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
     switch (e) {
     case 0:
         return Errno::SUCCESS;
@@ -112,6 +117,14 @@ Errno TranslateNativeError(int e) {
         return Errno::AGAIN;
     case WSAECONNREFUSED:
         return Errno::CONNREFUSED;
+    case WSAECONNABORTED:
+        if (call_type == CallType::Send) {
+            // Winsock yields WSAECONNABORTED from `send` in situations where Unix
+            // systems, and actual Switches, yield EPIPE.
+            return Errno::PIPE;
+        } else {
+            return Errno::CONNABORTED;
+        }
     case WSAECONNRESET:
         return Errno::CONNRESET;
     case WSAEHOSTUNREACH:
@@ -198,7 +211,7 @@ bool EnableNonBlock(int fd, bool enable) {
     return fcntl(fd, F_SETFL, flags) == 0;
 }
 
-Errno TranslateNativeError(int e) {
+Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
     switch (e) {
     case 0:
         return Errno::SUCCESS;
@@ -208,6 +221,10 @@ Errno TranslateNativeError(int e) {
         return Errno::INVAL;
     case EMFILE:
         return Errno::MFILE;
+    case EPIPE:
+        return Errno::PIPE;
+    case ECONNABORTED:
+        return Errno::CONNABORTED;
     case ENOTCONN:
         return Errno::NOTCONN;
     case EAGAIN:
@@ -236,13 +253,13 @@ Errno TranslateNativeError(int e) {
 
 #endif
 
-Errno GetAndLogLastError() {
+Errno GetAndLogLastError(CallType call_type = CallType::Other) {
 #ifdef _WIN32
     int e = WSAGetLastError();
 #else
     int e = errno;
 #endif
-    const Errno err = TranslateNativeError(e);
+    const Errno err = TranslateNativeError(e, call_type);
     if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) {
         // These happen during normal operation, so only log them at debug level.
         LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
@@ -731,13 +748,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) {
     ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
     ASSERT(flags == 0);
 
+    int native_flags = 0;
+#if YUZU_UNIX
+    native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE
+#endif
     const auto result = send(fd, reinterpret_cast<const char*>(message.data()),
-                             static_cast<int>(message.size()), 0);
+                             static_cast<int>(message.size()), native_flags);
     if (result != SOCKET_ERROR) {
         return {static_cast<s32>(result), Errno::SUCCESS};
     }
 
-    return {-1, GetAndLogLastError()};
+    return {-1, GetAndLogLastError(CallType::Send)};
 }
 
 std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
@@ -759,7 +780,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
         return {static_cast<s32>(result), Errno::SUCCESS};
     }
 
-    return {-1, GetAndLogLastError()};
+    return {-1, GetAndLogLastError(CallType::Send)};
 }
 
 Errno Socket::Close() {
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h
index badcb83698..c7e20ae348 100644
--- a/src/core/internal_network/network.h
+++ b/src/core/internal_network/network.h
@@ -33,10 +33,12 @@ enum class Errno {
     BADF,
     INVAL,
     MFILE,
+    PIPE,
     NOTCONN,
     AGAIN,
     CONNREFUSED,
     CONNRESET,
+    CONNABORTED,
     HOSTUNREACH,
     NETDOWN,
     NETUNREACH,