From b5f7d8106b20a1f8abb1f7dbc2c48f1da4648aab Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Sun, 21 Oct 2018 00:08:58 +0200 Subject: [PATCH] Complete rewrite of bsd IClient (#460) This should provide accurate behaviours. This implementation has been tested with ftpd and libtransistor bsd tests. This implementation lacks OOB support. --- Ryujinx.HLE/HOS/Services/Bsd/BsdError.cs | 8 - Ryujinx.HLE/HOS/Services/Bsd/BsdIoctl.cs | 7 + Ryujinx.HLE/HOS/Services/Bsd/BsdSocket.cs | 5 - Ryujinx.HLE/HOS/Services/Bsd/IClient.cs | 1267 +++++++++++++++----- Ryujinx.HLE/HOS/Services/Bsd/PollEvent.cs | 28 + Ryujinx.HLE/HOS/Services/ServiceFactory.cs | 4 +- Ryujinx.HLE/Utilities/LinuxError.cs | 152 +++ Ryujinx.HLE/Utilities/WSAError.cs | 135 +++ 8 files changed, 1304 insertions(+), 302 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/Services/Bsd/BsdError.cs create mode 100644 Ryujinx.HLE/HOS/Services/Bsd/BsdIoctl.cs create mode 100644 Ryujinx.HLE/HOS/Services/Bsd/PollEvent.cs create mode 100644 Ryujinx.HLE/Utilities/LinuxError.cs create mode 100644 Ryujinx.HLE/Utilities/WSAError.cs diff --git a/Ryujinx.HLE/HOS/Services/Bsd/BsdError.cs b/Ryujinx.HLE/HOS/Services/Bsd/BsdError.cs deleted file mode 100644 index 675edcc3dd..0000000000 --- a/Ryujinx.HLE/HOS/Services/Bsd/BsdError.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Bsd -{ - //bsd_errno == (SocketException.ErrorCode - 10000) - enum BsdError - { - Timeout = 60 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bsd/BsdIoctl.cs b/Ryujinx.HLE/HOS/Services/Bsd/BsdIoctl.cs new file mode 100644 index 0000000000..15fc7a8278 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bsd/BsdIoctl.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Bsd +{ + enum BsdIoctl + { + AtMark = 0x40047307, + } +} diff --git a/Ryujinx.HLE/HOS/Services/Bsd/BsdSocket.cs b/Ryujinx.HLE/HOS/Services/Bsd/BsdSocket.cs index 2361ed31b7..2786da1364 100644 --- a/Ryujinx.HLE/HOS/Services/Bsd/BsdSocket.cs +++ b/Ryujinx.HLE/HOS/Services/Bsd/BsdSocket.cs @@ -1,4 +1,3 @@ -using System.Net; using System.Net.Sockets; namespace Ryujinx.HLE.HOS.Services.Bsd @@ -9,10 +8,6 @@ namespace Ryujinx.HLE.HOS.Services.Bsd public int Type; public int Protocol; - public IPAddress IpAddress; - - public IPEndPoint RemoteEP; - public Socket Handle; } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs index e2cd0dcdb7..37d0fcfdba 100644 --- a/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs +++ b/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs @@ -1,45 +1,265 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.Utilities; using System.Collections.Generic; -using System.IO; using System.Net; using System.Net.Sockets; -using System.Threading.Tasks; +using System.Text; namespace Ryujinx.HLE.HOS.Services.Bsd { class IClient : IpcService { + + private static Dictionary ErrorMap = new Dictionary + { + // WSAEINTR + {WSAError.WSAEINTR, LinuxError.EINTR}, + // WSAEWOULDBLOCK + {WSAError.WSAEWOULDBLOCK, LinuxError.EWOULDBLOCK}, + // WSAEINPROGRESS + {WSAError.WSAEINPROGRESS, LinuxError.EINPROGRESS}, + // WSAEALREADY + {WSAError.WSAEALREADY, LinuxError.EALREADY}, + // WSAENOTSOCK + {WSAError.WSAENOTSOCK, LinuxError.ENOTSOCK}, + // WSAEDESTADDRREQ + {WSAError.WSAEDESTADDRREQ, LinuxError.EDESTADDRREQ}, + // WSAEMSGSIZE + {WSAError.WSAEMSGSIZE, LinuxError.EMSGSIZE}, + // WSAEPROTOTYPE + {WSAError.WSAEPROTOTYPE, LinuxError.EPROTOTYPE}, + // WSAENOPROTOOPT + {WSAError.WSAENOPROTOOPT, LinuxError.ENOPROTOOPT}, + // WSAEPROTONOSUPPORT + {WSAError.WSAEPROTONOSUPPORT, LinuxError.EPROTONOSUPPORT}, + // WSAESOCKTNOSUPPORT + {WSAError.WSAESOCKTNOSUPPORT, LinuxError.ESOCKTNOSUPPORT}, + // WSAEOPNOTSUPP + {WSAError.WSAEOPNOTSUPP, LinuxError.EOPNOTSUPP}, + // WSAEPFNOSUPPORT + {WSAError.WSAEPFNOSUPPORT, LinuxError.EPFNOSUPPORT}, + // WSAEAFNOSUPPORT + {WSAError.WSAEAFNOSUPPORT, LinuxError.EAFNOSUPPORT}, + // WSAEADDRINUSE + {WSAError.WSAEADDRINUSE, LinuxError.EADDRINUSE}, + // WSAEADDRNOTAVAIL + {WSAError.WSAEADDRNOTAVAIL, LinuxError.EADDRNOTAVAIL}, + // WSAENETDOWN + {WSAError.WSAENETDOWN, LinuxError.ENETDOWN}, + // WSAENETUNREACH + {WSAError.WSAENETUNREACH, LinuxError.ENETUNREACH}, + // WSAENETRESET + {WSAError.WSAENETRESET, LinuxError.ENETRESET}, + // WSAECONNABORTED + {WSAError.WSAECONNABORTED, LinuxError.ECONNABORTED}, + // WSAECONNRESET + {WSAError.WSAECONNRESET, LinuxError.ECONNRESET}, + // WSAENOBUFS + {WSAError.WSAENOBUFS, LinuxError.ENOBUFS}, + // WSAEISCONN + {WSAError.WSAEISCONN, LinuxError.EISCONN}, + // WSAENOTCONN + {WSAError.WSAENOTCONN, LinuxError.ENOTCONN}, + // WSAESHUTDOWN + {WSAError.WSAESHUTDOWN, LinuxError.ESHUTDOWN}, + // WSAETOOMANYREFS + {WSAError.WSAETOOMANYREFS, LinuxError.ETOOMANYREFS}, + // WSAETIMEDOUT + {WSAError.WSAETIMEDOUT, LinuxError.ETIMEDOUT}, + // WSAECONNREFUSED + {WSAError.WSAECONNREFUSED, LinuxError.ECONNREFUSED}, + // WSAELOOP + {WSAError.WSAELOOP, LinuxError.ELOOP}, + // WSAENAMETOOLONG + {WSAError.WSAENAMETOOLONG, LinuxError.ENAMETOOLONG}, + // WSAEHOSTDOWN + {WSAError.WSAEHOSTDOWN, LinuxError.EHOSTDOWN}, + // WSAEHOSTUNREACH + {WSAError.WSAEHOSTUNREACH, LinuxError.EHOSTUNREACH}, + // WSAENOTEMPTY + {WSAError.WSAENOTEMPTY, LinuxError.ENOTEMPTY}, + // WSAEUSERS + {WSAError.WSAEUSERS, LinuxError.EUSERS}, + // WSAEDQUOT + {WSAError.WSAEDQUOT, LinuxError.EDQUOT}, + // WSAESTALE + {WSAError.WSAESTALE, LinuxError.ESTALE}, + // WSAEREMOTE + {WSAError.WSAEREMOTE, LinuxError.EREMOTE}, + // WSAEINVAL + {WSAError.WSAEINVAL, LinuxError.EINVAL}, + // WSAEFAULT + {WSAError.WSAEFAULT, LinuxError.EFAULT}, + // NOERROR + {0, 0} + }; + private Dictionary m_Commands; public override IReadOnlyDictionary Commands => m_Commands; + private bool IsPrivileged; + private List Sockets = new List(); - public IClient() + public IClient(bool IsPrivileged) { m_Commands = new Dictionary() { - { 0, Initialize }, - { 1, StartMonitoring }, - { 2, Socket }, - { 6, Poll }, - { 8, Recv }, - { 10, Send }, - { 11, SendTo }, - { 12, Accept }, - { 13, Bind }, - { 14, Connect }, - { 18, Listen }, - { 21, SetSockOpt }, - { 24, Write }, - { 25, Read }, - { 26, Close } + { 0, RegisterClient }, + { 1, StartMonitoring }, + { 2, Socket }, + { 3, SocketExempt }, + { 4, Open }, + { 5, Select }, + { 6, Poll }, + { 7, Sysctl }, + { 8, Recv }, + { 9, RecvFrom }, + { 10, Send }, + { 11, SendTo }, + { 12, Accept }, + { 13, Bind }, + { 14, Connect }, + { 15, GetPeerName }, + { 16, GetSockName }, + { 17, GetSockOpt }, + { 18, Listen }, + { 19, Ioctl }, + { 20, Fcntl }, + { 21, SetSockOpt }, + { 22, Shutdown }, + { 23, ShutdownAllSockets }, + { 24, Write }, + { 25, Read }, + { 26, Close }, + { 27, DuplicateSocket }, }; + + this.IsPrivileged = IsPrivileged; } - //(u32, u32, u32, u32, u32, u32, u32, u32, u64 pid, u64 transferMemorySize, pid, KObject) -> u32 bsd_errno - public long Initialize(ServiceCtx Context) + private LinuxError ConvertError(WSAError ErrorCode) + { + LinuxError Errno; + + if (!ErrorMap.TryGetValue(ErrorCode, out Errno)) + { + Errno = (LinuxError)ErrorCode; + } + + return Errno; + } + + private long WriteWinSock2Error(ServiceCtx Context, WSAError ErrorCode) + { + return WriteBsdResult(Context, -1, ConvertError(ErrorCode)); + } + + private long WriteBsdResult(ServiceCtx Context, int Result, LinuxError ErrorCode = 0) + { + if (ErrorCode != LinuxError.SUCCESS) + { + Result = -1; + } + + Context.ResponseData.Write(Result); + Context.ResponseData.Write((int)ErrorCode); + + return 0; + } + + private BsdSocket RetrieveSocket(int SocketFd) + { + if (SocketFd >= 0 && Sockets.Count > SocketFd) + { + return Sockets[SocketFd]; + } + + return null; + } + + private LinuxError SetResultErrno(Socket Socket, int Result) + { + return Result == 0 && !Socket.Blocking ? LinuxError.EWOULDBLOCK : LinuxError.SUCCESS; + } + + private AddressFamily ConvertFromBsd(int Domain) + { + if (Domain == 2) + { + return AddressFamily.InterNetwork; + } + + // FIXME: AF_ROUTE ignored, is that really needed? + return AddressFamily.Unknown; + } + + private long SocketInternal(ServiceCtx Context, bool Exempt) + { + AddressFamily Domain = (AddressFamily)Context.RequestData.ReadInt32(); + SocketType Type = (SocketType)Context.RequestData.ReadInt32(); + ProtocolType Protocol = (ProtocolType)Context.RequestData.ReadInt32(); + + if (Domain == AddressFamily.Unknown) + { + return WriteBsdResult(Context, -1, LinuxError.EPROTONOSUPPORT); + } + else if ((Type == SocketType.Seqpacket || Type == SocketType.Raw) && !IsPrivileged) + { + if (Domain != AddressFamily.InterNetwork || Type != SocketType.Raw || Protocol != ProtocolType.Icmp) + { + return WriteBsdResult(Context, -1, LinuxError.ENOENT); + } + } + + BsdSocket NewBsdSocket = new BsdSocket + { + Family = (int)Domain, + Type = (int)Type, + Protocol = (int)Protocol, + Handle = new Socket(Domain, Type, Protocol) + }; + + Sockets.Add(NewBsdSocket); + + if (Exempt) + { + NewBsdSocket.Handle.Disconnect(true); + } + + return WriteBsdResult(Context, Sockets.Count - 1); + } + + private IPEndPoint ParseSockAddr(ServiceCtx Context, long BufferPosition, long BufferSize) + { + int Size = Context.Memory.ReadByte(BufferPosition); + int Family = Context.Memory.ReadByte(BufferPosition + 1); + int Port = EndianSwap.Swap16(Context.Memory.ReadUInt16(BufferPosition + 2)); + + byte[] RawIp = Context.Memory.ReadBytes(BufferPosition + 4, 4); + + return new IPEndPoint(new IPAddress(RawIp), Port); + } + + private void WriteSockAddr(ServiceCtx Context, long BufferPosition, IPEndPoint EndPoint) + { + Context.Memory.WriteByte(BufferPosition, 0); + Context.Memory.WriteByte(BufferPosition + 1, (byte)EndPoint.AddressFamily); + Context.Memory.WriteUInt16(BufferPosition + 2, EndianSwap.Swap16((ushort)EndPoint.Port)); + Context.Memory.WriteBytes(BufferPosition + 4, EndPoint.Address.GetAddressBytes()); + } + + private void WriteSockAddr(ServiceCtx Context, long BufferPosition, BsdSocket Socket, bool IsRemote) + { + IPEndPoint EndPoint = (IsRemote ? Socket.Handle.RemoteEndPoint : Socket.Handle.LocalEndPoint) as IPEndPoint; + + WriteSockAddr(Context, BufferPosition, EndPoint); + } + + // Initialize(nn::socket::BsdBufferConfig config, u64 pid, u64 transferMemorySize, KObject, pid) -> u32 bsd_errno + public long RegisterClient(ServiceCtx Context) { /* typedef struct { @@ -54,447 +274,920 @@ namespace Ryujinx.HLE.HOS.Services.Bsd } BsdBufferConfig; */ + // bsd_error Context.ResponseData.Write(0); - //Todo: Stub + Logger.PrintStub(LogClass.ServiceBsd, "Stubbed."); return 0; } - //(u64, pid) + // StartMonitoring(u64, pid) public long StartMonitoring(ServiceCtx Context) { - //Todo: Stub + ulong Unknown0 = Context.RequestData.ReadUInt64(); + + Logger.PrintStub(LogClass.ServiceBsd, $"Stubbed. Unknown0: {Unknown0}"); return 0; } - //(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + // Socket(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) public long Socket(ServiceCtx Context) { - BsdSocket NewBsdSocket = new BsdSocket - { - Family = Context.RequestData.ReadInt32(), - Type = Context.RequestData.ReadInt32(), - Protocol = Context.RequestData.ReadInt32() - }; + return SocketInternal(Context, false); + } - Sockets.Add(NewBsdSocket); + // SocketExempt(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public long SocketExempt(ServiceCtx Context) + { + return SocketInternal(Context, true); + } - NewBsdSocket.Handle = new Socket((AddressFamily)NewBsdSocket.Family, - (SocketType)NewBsdSocket.Type, - (ProtocolType)NewBsdSocket.Protocol); + // Open(u32 flags, array path) -> (i32 ret, u32 bsd_errno) + public long Open(ServiceCtx Context) + { + (long BufferPosition, long BufferSize) = Context.Request.GetBufferType0x21(); - Context.ResponseData.Write(Sockets.Count - 1); - Context.ResponseData.Write(0); + int Flags = Context.RequestData.ReadInt32(); + + byte[] RawPath = Context.Memory.ReadBytes(BufferPosition, BufferSize); + string Path = Encoding.ASCII.GetString(RawPath); + + WriteBsdResult(Context, -1, LinuxError.EOPNOTSUPP); + + Logger.PrintStub(LogClass.ServiceBsd, $"Stubbed. Path: {Path} - " + + $"Flags: {Flags}"); return 0; } - //(u32, u32, buffer) -> (i32 ret, u32 bsd_errno, buffer) + // Select(u32 nfds, nn::socket::timeout timeout, buffer readfds_in, buffer writefds_in, buffer errorfds_in) -> (i32 ret, u32 bsd_errno, buffer readfds_out, buffer writefds_out, buffer errorfds_out) + public long Select(ServiceCtx Context) + { + WriteBsdResult(Context, -1, LinuxError.EOPNOTSUPP); + + Logger.PrintStub(LogClass.ServiceBsd, $"Stubbed."); + + return 0; + } + + // Poll(u32 nfds, u32 timeout, buffer fds) -> (i32 ret, u32 bsd_errno, buffer) public long Poll(ServiceCtx Context) { - int PollCount = Context.RequestData.ReadInt32(); - int TimeOut = Context.RequestData.ReadInt32(); + int FdsCount = Context.RequestData.ReadInt32(); + int Timeout = Context.RequestData.ReadInt32(); - //https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/poll.h - //https://msdn.microsoft.com/fr-fr/library/system.net.sockets.socket.poll(v=vs.110).aspx - //https://github.com/switchbrew/libnx/blob/e0457c4534b3c37426d83e1a620f82cb28c3b528/nx/source/services/bsd.c#L343 - //https://github.com/TuxSH/ftpd/blob/switch_pr/source/ftp.c#L1634 - //https://linux.die.net/man/2/poll + (long BufferPosition, long BufferSize) = Context.Request.GetBufferType0x21(); - byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); - int SocketId = Get32(SentBuffer, 0); - int RequestedEvents = Get16(SentBuffer, 4); - int ReturnedEvents = Get16(SentBuffer, 6); + if (Timeout < -1 || FdsCount < 0 || (FdsCount * 8) > BufferSize) + { + return WriteBsdResult(Context, -1, LinuxError.EINVAL); + } - //Todo: Stub - Need to implemented the Type-22 buffer. + PollEvent[] Events = new PollEvent[FdsCount]; - Context.ResponseData.Write(1); - Context.ResponseData.Write(0); + for (int i = 0; i < FdsCount; i++) + { + int SocketFd = Context.Memory.ReadInt32(BufferPosition + i * 8); + + BsdSocket Socket = RetrieveSocket(SocketFd); + + if (Socket == null) + { + return WriteBsdResult(Context, -1, LinuxError.EBADF); + } + + PollEvent.EventTypeMask InputEvents = (PollEvent.EventTypeMask)Context.Memory.ReadInt16(BufferPosition + i * 8 + 4); + PollEvent.EventTypeMask OutputEvents = (PollEvent.EventTypeMask)Context.Memory.ReadInt16(BufferPosition + i * 8 + 6); + + Events[i] = new PollEvent(SocketFd, Socket, InputEvents, OutputEvents); + } + + List ReadEvents = new List(); + List WriteEvents = new List(); + List ErrorEvents = new List(); + + foreach (PollEvent Event in Events) + { + bool IsValidEvent = false; + + if ((Event.InputEvents & PollEvent.EventTypeMask.Input) != 0) + { + ReadEvents.Add(Event.Socket.Handle); + ErrorEvents.Add(Event.Socket.Handle); + + IsValidEvent = true; + } + + if ((Event.InputEvents & PollEvent.EventTypeMask.UrgentInput) != 0) + { + ReadEvents.Add(Event.Socket.Handle); + ErrorEvents.Add(Event.Socket.Handle); + + IsValidEvent = true; + } + + if ((Event.InputEvents & PollEvent.EventTypeMask.Output) != 0) + { + WriteEvents.Add(Event.Socket.Handle); + ErrorEvents.Add(Event.Socket.Handle); + + IsValidEvent = true; + } + + if ((Event.InputEvents & PollEvent.EventTypeMask.Error) != 0) + { + ErrorEvents.Add(Event.Socket.Handle); + IsValidEvent = true; + } + + if (!IsValidEvent) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Poll input event type: {Event.InputEvents}"); + return WriteBsdResult(Context, -1, LinuxError.EINVAL); + } + } + + try + { + System.Net.Sockets.Socket.Select(ReadEvents, WriteEvents, ErrorEvents, Timeout); + } + catch (SocketException Exception) + { + return WriteWinSock2Error(Context, (WSAError)Exception.ErrorCode); + } + + for (int i = 0; i < FdsCount; i++) + { + PollEvent Event = Events[i]; + Context.Memory.WriteInt32(BufferPosition + i * 8, Event.SocketFd); + Context.Memory.WriteInt16(BufferPosition + i * 8 + 4, (short)Event.InputEvents); + + PollEvent.EventTypeMask OutputEvents = 0; + + Socket Socket = Event.Socket.Handle; + + if (ErrorEvents.Contains(Socket)) + { + OutputEvents |= PollEvent.EventTypeMask.Error; + + if (!Socket.Connected || !Socket.IsBound) + { + OutputEvents |= PollEvent.EventTypeMask.Disconnected; + } + } + + if (ReadEvents.Contains(Socket)) + { + if ((Event.InputEvents & PollEvent.EventTypeMask.Input) != 0) + { + OutputEvents |= PollEvent.EventTypeMask.Input; + } + } + + if (WriteEvents.Contains(Socket)) + { + OutputEvents |= PollEvent.EventTypeMask.Output; + } + + Context.Memory.WriteInt16(BufferPosition + i * 8 + 6, (short)OutputEvents); + } + + return WriteBsdResult(Context, ReadEvents.Count + WriteEvents.Count + ErrorEvents.Count, LinuxError.SUCCESS); + } + + // Sysctl(buffer, buffer) -> (i32 ret, u32 bsd_errno, u32, buffer) + public long Sysctl(ServiceCtx Context) + { + WriteBsdResult(Context, -1, LinuxError.EOPNOTSUPP); + + Logger.PrintStub(LogClass.ServiceBsd, $"Stubbed."); return 0; } - //(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, buffer message) + // Recv(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, array message) public long Recv(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); - int SocketFlags = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); + SocketFlags SocketFlags = (SocketFlags)Context.RequestData.ReadInt32(); (long ReceivePosition, long ReceiveLength) = Context.Request.GetBufferType0x22(); - byte[] ReceivedBuffer = new byte[ReceiveLength]; + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + int Result = -1; - try + if (Socket != null) { - int BytesRead = Sockets[SocketId].Handle.Receive(ReceivedBuffer); + if (SocketFlags != SocketFlags.None && (SocketFlags & SocketFlags.OutOfBand) == 0 + && (SocketFlags & SocketFlags.Peek) == 0) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Recv flags: {SocketFlags}"); + return WriteBsdResult(Context, -1, LinuxError.EOPNOTSUPP); + } - Context.Memory.WriteBytes(ReceivePosition, ReceivedBuffer); + byte[] ReceivedBuffer = new byte[ReceiveLength]; - Context.ResponseData.Write(BytesRead); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + try + { + Result = Socket.Handle.Receive(ReceivedBuffer, SocketFlags); + Errno = SetResultErrno(Socket.Handle, Result); + + Context.Memory.WriteBytes(ReceivePosition, ReceivedBuffer); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + } } - return 0; + return WriteBsdResult(Context, Result, Errno); } - //(u32 socket, u32 flags, buffer) -> (i32 ret, u32 bsd_errno) + // RecvFrom(u32 sock, u32 flags) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer message, buffer) + public long RecvFrom(ServiceCtx Context) + { + int SocketFd = Context.RequestData.ReadInt32(); + SocketFlags SocketFlags = (SocketFlags)Context.RequestData.ReadInt32(); + + (long ReceivePosition, long ReceiveLength) = Context.Request.GetBufferType0x22(); + (long SockAddrInPosition, long SockAddrInSize) = Context.Request.GetBufferType0x21(); + (long SockAddrOutPosition, long SockAddrOutSize) = Context.Request.GetBufferType0x22(1); + + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + int Result = -1; + + if (Socket != null) + { + if (SocketFlags != SocketFlags.None && (SocketFlags & SocketFlags.OutOfBand) == 0 + && (SocketFlags & SocketFlags.Peek) == 0) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Recv flags: {SocketFlags}"); + + return WriteBsdResult(Context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] ReceivedBuffer = new byte[ReceiveLength]; + EndPoint EndPoint = ParseSockAddr(Context, SockAddrInPosition, SockAddrInSize); + + try + { + Result = Socket.Handle.ReceiveFrom(ReceivedBuffer, ReceivedBuffer.Length, SocketFlags, ref EndPoint); + Errno = SetResultErrno(Socket.Handle, Result); + + Context.Memory.WriteBytes(ReceivePosition, ReceivedBuffer); + WriteSockAddr(Context, SockAddrOutPosition, (IPEndPoint)EndPoint); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + } + } + + return WriteBsdResult(Context, Result, Errno); + } + + // Send(u32 socket, u32 flags, buffer) -> (i32 ret, u32 bsd_errno) public long Send(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); - int SocketFlags = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); + SocketFlags SocketFlags = (SocketFlags)Context.RequestData.ReadInt32(); - (long SentPosition, long SentSize) = Context.Request.GetBufferType0x21(); + (long SendPosition, long SendSize) = Context.Request.GetBufferType0x21(); - byte[] SentBuffer = Context.Memory.ReadBytes(SentPosition, SentSize); + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + int Result = -1; - try + if (Socket != null) { - int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); + if (SocketFlags != SocketFlags.None && SocketFlags != SocketFlags.OutOfBand + && SocketFlags != SocketFlags.Peek && SocketFlags != SocketFlags.DontRoute) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Send flags: {SocketFlags}"); + + return WriteBsdResult(Context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] SendBuffer = Context.Memory.ReadBytes(SendPosition, SendSize); + + try + { + Result = Socket.Handle.Send(SendBuffer, SocketFlags); + Errno = SetResultErrno(Socket.Handle, Result); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + } - Context.ResponseData.Write(BytesSent); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); } - return 0; + return WriteBsdResult(Context, Result, Errno); } - //(u32 socket, u32 flags, buffer, buffer) -> (i32 ret, u32 bsd_errno) + // SendTo(u32 socket, u32 flags, buffer, buffer) -> (i32 ret, u32 bsd_errno) public long SendTo(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); - int SocketFlags = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); + SocketFlags SocketFlags = (SocketFlags)Context.RequestData.ReadInt32(); - byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); + (long SendPosition, long SendSize) = Context.Request.GetBufferType0x21(); + (long BufferPosition, long BufferSize) = Context.Request.GetBufferType0x21(1); - (long AddressPosition, long AddressSize) = Context.Request.GetBufferType0x21(Index: 1); + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + int Result = -1; - byte[] AddressBuffer = Context.Memory.ReadBytes(AddressPosition, AddressSize); - - if (!Sockets[SocketId].Handle.Connected) + if (Socket != null) { + if (SocketFlags != SocketFlags.None && SocketFlags != SocketFlags.OutOfBand + && SocketFlags != SocketFlags.Peek && SocketFlags != SocketFlags.DontRoute) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Send flags: {SocketFlags}"); + + return WriteBsdResult(Context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] SendBuffer = Context.Memory.ReadBytes(SendPosition, SendSize); + EndPoint EndPoint = ParseSockAddr(Context, BufferPosition, BufferSize); + try { - ParseAddrBuffer(SocketId, AddressBuffer); - - Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP); + Result = Socket.Handle.SendTo(SendBuffer, SendBuffer.Length, SocketFlags, EndPoint); + Errno = SetResultErrno(Socket.Handle, Result); } - catch (SocketException Ex) + catch (SocketException Exception) { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + Errno = ConvertError((WSAError)Exception.ErrorCode); } + } - try - { - int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); - - Context.ResponseData.Write(BytesSent); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - - return 0; + return WriteBsdResult(Context, Result, Errno); } - //(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + // Accept(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) public long Accept(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); - (long AddrBufferPosition, long AddrBuffSize) = Context.Request.GetBufferType0x22(); + (long BufferPos, long BufferSize) = Context.Request.GetBufferType0x22(); - Socket HandleAccept = null; + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); - Task TimeOut = Task.Factory.StartNew(() => + if (Socket != null) { + Errno = LinuxError.SUCCESS; + + Socket NewSocket = null; + try { - HandleAccept = Sockets[SocketId].Handle.Accept(); + NewSocket = Socket.Handle.Accept(); } - catch (SocketException Ex) + catch (SocketException Exception) { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + Errno = ConvertError((WSAError)Exception.ErrorCode); } - }); - TimeOut.Wait(10000); - - if (HandleAccept != null) - { - BsdSocket NewBsdSocket = new BsdSocket + if (NewSocket == null && Errno == LinuxError.SUCCESS) { - IpAddress = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint).Address, - RemoteEP = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint), - Handle = HandleAccept - }; - - Sockets.Add(NewBsdSocket); - - using (MemoryStream MS = new MemoryStream()) + Errno = LinuxError.EWOULDBLOCK; + } + else if (Errno == LinuxError.SUCCESS) { - BinaryWriter Writer = new BinaryWriter(MS); + BsdSocket NewBsdSocket = new BsdSocket + { + Family = (int)NewSocket.AddressFamily, + Type = (int)NewSocket.SocketType, + Protocol = (int)NewSocket.ProtocolType, + Handle = NewSocket, + }; - Writer.Write((byte)0); + Sockets.Add(NewBsdSocket); - Writer.Write((byte)NewBsdSocket.Handle.AddressFamily); + WriteSockAddr(Context, BufferPos, NewBsdSocket, true); - Writer.Write((short)((IPEndPoint)NewBsdSocket.Handle.LocalEndPoint).Port); + WriteBsdResult(Context, Sockets.Count - 1, Errno); - byte[] IpAddress = NewBsdSocket.IpAddress.GetAddressBytes(); + Context.ResponseData.Write(0x10); - Writer.Write(IpAddress); - - Context.Memory.WriteBytes(AddrBufferPosition, MS.ToArray()); - - Context.ResponseData.Write(Sockets.Count - 1); - Context.ResponseData.Write(0); - Context.ResponseData.Write(MS.Length); + return 0; } } - else - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write((int)BsdError.Timeout); - } - return 0; + return WriteBsdResult(Context, -1, Errno); } - //(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) + // Bind(u32 socket, buffer addr) -> (i32 ret, u32 bsd_errno) public long Bind(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); - (long AddressPosition, long AddressSize) = Context.Request.GetBufferType0x21(); + (long BufferPos, long BufferSize) = Context.Request.GetBufferType0x21(); - byte[] AddressBuffer = Context.Memory.ReadBytes(AddressPosition, AddressSize); + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); - try + if (Socket != null) { - ParseAddrBuffer(SocketId, AddressBuffer); + Errno = LinuxError.SUCCESS; - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + try + { + IPEndPoint EndPoint = ParseSockAddr(Context, BufferPos, BufferSize); + + Socket.Handle.Bind(EndPoint); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + } } - return 0; + return WriteBsdResult(Context, 0, Errno); } - //(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) + // Connect(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) public long Connect(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); - (long AddressPosition, long AddressSize) = Context.Request.GetBufferType0x21(); + (long BufferPos, long BufferSize) = Context.Request.GetBufferType0x21(); - byte[] AddressBuffer = Context.Memory.ReadBytes(AddressPosition, AddressSize); + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); - try + if (Socket != null) { - ParseAddrBuffer(SocketId, AddressBuffer); + Errno = LinuxError.SUCCESS; + try + { + IPEndPoint EndPoint = ParseSockAddr(Context, BufferPos, BufferSize); - Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP); - - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + Socket.Handle.Connect(EndPoint); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + } } - return 0; + return WriteBsdResult(Context, 0, Errno); } - //(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno) + // GetPeerName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + public long GetPeerName(ServiceCtx Context) + { + int SocketFd = Context.RequestData.ReadInt32(); + + (long BufferPos, long BufferSize) = Context.Request.GetBufferType0x22(); + + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + + if (Socket != null) + { + Errno = LinuxError.SUCCESS; + + WriteSockAddr(Context, BufferPos, Socket, true); + WriteBsdResult(Context, 0, Errno); + Context.ResponseData.Write(0x10); + } + + return WriteBsdResult(Context, 0, Errno); + } + + // GetSockName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + public long GetSockName(ServiceCtx Context) + { + int SocketFd = Context.RequestData.ReadInt32(); + + (long BufferPos, long BufferSize) = Context.Request.GetBufferType0x22(); + + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + + if (Socket != null) + { + Errno = LinuxError.SUCCESS; + + WriteSockAddr(Context, BufferPos, Socket, false); + WriteBsdResult(Context, 0, Errno); + Context.ResponseData.Write(0x10); + } + + return WriteBsdResult(Context, 0, Errno); + } + + // GetSockOpt(u32 socket, u32 level, u32 option_name) -> (i32 ret, u32 bsd_errno, u32, buffer) + public long GetSockOpt(ServiceCtx Context) + { + int SocketFd = Context.RequestData.ReadInt32(); + int Level = Context.RequestData.ReadInt32(); + int OptionName = Context.RequestData.ReadInt32(); + + (long BufferPosition, long BufferSize) = Context.Request.GetBufferType0x22(); + + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + + if (Socket != null) + { + Errno = LinuxError.ENOPROTOOPT; + + if (Level == 0xFFFF) + { + Errno = HandleGetSocketOption(Context, Socket, (SocketOptionName)OptionName, BufferPosition, BufferSize); + } + else + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported GetSockOpt Level: {(SocketOptionLevel)Level}"); + } + } + + return WriteBsdResult(Context, 0, Errno); + } + + // Listen(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno) public long Listen(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); - int BackLog = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); + int Backlog = Context.RequestData.ReadInt32(); - try - { - Sockets[SocketId].Handle.Bind(Sockets[SocketId].RemoteEP); - Sockets[SocketId].Handle.Listen(BackLog); + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) + if (Socket != null) { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + Errno = LinuxError.SUCCESS; + + try + { + Socket.Handle.Listen(Backlog); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + } } - return 0; + return WriteBsdResult(Context, 0, Errno); } - //(u32 socket, u32 level, u32 option_name, buffer) -> (i32 ret, u32 bsd_errno) + // Ioctl(u32 fd, u32 request, u32 bufcount, buffer, buffer, buffer, buffer) -> (i32 ret, u32 bsd_errno, buffer, buffer, buffer, buffer) + public long Ioctl(ServiceCtx Context) + { + int SocketFd = Context.RequestData.ReadInt32(); + BsdIoctl Cmd = (BsdIoctl)Context.RequestData.ReadInt32(); + int BufferCount = Context.RequestData.ReadInt32(); + + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + + if (Socket != null) + { + switch (Cmd) + { + case BsdIoctl.AtMark: + Errno = LinuxError.SUCCESS; + + (long BufferPosition, long BufferSize) = Context.Request.GetBufferType0x22(); + + // FIXME: OOB not implemented. + Context.Memory.WriteInt32(BufferPosition, 0); + break; + + default: + Errno = LinuxError.EOPNOTSUPP; + + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Ioctl Cmd: {Cmd}"); + break; + } + } + + return WriteBsdResult(Context, 0, Errno); + } + + // Fcntl(u32 socket, u32 cmd, u32 arg) -> (i32 ret, u32 bsd_errno) + public long Fcntl(ServiceCtx Context) + { + int SocketFd = Context.RequestData.ReadInt32(); + int Cmd = Context.RequestData.ReadInt32(); + int Arg = Context.RequestData.ReadInt32(); + + int Result = 0; + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + + if (Socket != null) + { + Errno = LinuxError.SUCCESS; + + if (Cmd == 0x3) + { + Result = !Socket.Handle.Blocking ? 0x800 : 0; + } + else if (Cmd == 0x4 && Arg == 0x800) + { + Socket.Handle.Blocking = false; + Result = 0; + } + else + { + Errno = LinuxError.EOPNOTSUPP; + } + } + + return WriteBsdResult(Context, Result, Errno); + } + + private LinuxError HandleGetSocketOption(ServiceCtx Context, BsdSocket Socket, SocketOptionName OptionName, long OptionValuePosition, long OptionValueSize) + { + try + { + byte[] OptionValue = new byte[OptionValueSize]; + + switch (OptionName) + { + case SocketOptionName.Broadcast: + case SocketOptionName.DontLinger: + case SocketOptionName.Debug: + case SocketOptionName.Error: + case SocketOptionName.KeepAlive: + case SocketOptionName.OutOfBandInline: + case SocketOptionName.ReceiveBuffer: + case SocketOptionName.ReceiveTimeout: + case SocketOptionName.SendBuffer: + case SocketOptionName.SendTimeout: + case SocketOptionName.Type: + case SocketOptionName.Linger: + Socket.Handle.GetSocketOption(SocketOptionLevel.Socket, OptionName, OptionValue); + Context.Memory.WriteBytes(OptionValuePosition, OptionValue); + + return LinuxError.SUCCESS; + + case (SocketOptionName)0x200: + Socket.Handle.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, OptionValue); + Context.Memory.WriteBytes(OptionValuePosition, OptionValue); + + return LinuxError.SUCCESS; + + default: + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported SetSockOpt OptionName: {OptionName}"); + + return LinuxError.EOPNOTSUPP; + } + } + catch (SocketException Exception) + { + return ConvertError((WSAError)Exception.ErrorCode); + } + } + + private LinuxError HandleSetSocketOption(ServiceCtx Context, BsdSocket Socket, SocketOptionName OptionName, long OptionValuePosition, long OptionValueSize) + { + try + { + switch (OptionName) + { + case SocketOptionName.Broadcast: + case SocketOptionName.DontLinger: + case SocketOptionName.Debug: + case SocketOptionName.Error: + case SocketOptionName.KeepAlive: + case SocketOptionName.OutOfBandInline: + case SocketOptionName.ReceiveBuffer: + case SocketOptionName.ReceiveTimeout: + case SocketOptionName.SendBuffer: + case SocketOptionName.SendTimeout: + case SocketOptionName.Type: + case SocketOptionName.ReuseAddress: + Socket.Handle.SetSocketOption(SocketOptionLevel.Socket, OptionName, Context.Memory.ReadInt32(OptionValuePosition)); + + return LinuxError.SUCCESS; + + case (SocketOptionName)0x200: + Socket.Handle.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, Context.Memory.ReadInt32(OptionValuePosition)); + + return LinuxError.SUCCESS; + + case SocketOptionName.Linger: + Socket.Handle.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, + new LingerOption(Context.Memory.ReadInt32(OptionValuePosition) != 0, Context.Memory.ReadInt32(OptionValuePosition + 4))); + + return LinuxError.SUCCESS; + + default: + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported SetSockOpt OptionName: {OptionName}"); + + return LinuxError.EOPNOTSUPP; + } + } + catch (SocketException Exception) + { + return ConvertError((WSAError)Exception.ErrorCode); + } + } + + // SetSockOpt(u32 socket, u32 level, u32 option_name, buffer option_value) -> (i32 ret, u32 bsd_errno) public long SetSockOpt(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); + int Level = Context.RequestData.ReadInt32(); + int OptionName = Context.RequestData.ReadInt32(); - SocketOptionLevel SocketLevel = (SocketOptionLevel)Context.RequestData.ReadInt32(); - SocketOptionName SocketOptionName = (SocketOptionName)Context.RequestData.ReadInt32(); + (long BufferPos, long BufferSize) = Context.Request.GetBufferType0x21(); - byte[] SocketOptionValue = Context.Memory.ReadBytes(Context.Request.PtrBuff[0].Position, - Context.Request.PtrBuff[0].Size); + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); - int OptionValue = Get32(SocketOptionValue, 0); - - try + if (Socket != null) { - Sockets[SocketId].Handle.SetSocketOption(SocketLevel, SocketOptionName, OptionValue); + Errno = LinuxError.ENOPROTOOPT; - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + if (Level == 0xFFFF) + { + Errno = HandleSetSocketOption(Context, Socket, (SocketOptionName)OptionName, BufferPos, BufferSize); + } + else + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported SetSockOpt Level: {(SocketOptionLevel)Level}"); + } } - return 0; + return WriteBsdResult(Context, 0, Errno); } - //(u32 socket, buffer message) -> (i32 ret, u32 bsd_errno) + // Shutdown(u32 socket, u32 how) -> (i32 ret, u32 bsd_errno) + public long Shutdown(ServiceCtx Context) + { + int SocketFd = Context.RequestData.ReadInt32(); + int How = Context.RequestData.ReadInt32(); + + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + + if (Socket != null) + { + Errno = LinuxError.EINVAL; + + if (How >= 0 && How <= 2) + { + Errno = LinuxError.SUCCESS; + + try + { + Socket.Handle.Shutdown((SocketShutdown)How); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + } + } + } + + return WriteBsdResult(Context, 0, Errno); + } + + // ShutdownAllSockets(u32 how) -> (i32 ret, u32 bsd_errno) + public long ShutdownAllSockets(ServiceCtx Context) + { + int How = Context.RequestData.ReadInt32(); + + LinuxError Errno = LinuxError.EINVAL; + + if (How >= 0 && How <= 2) + { + Errno = LinuxError.SUCCESS; + + foreach (BsdSocket Socket in Sockets) + { + if (Socket != null) + { + try + { + Socket.Handle.Shutdown((SocketShutdown)How); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + break; + } + } + } + } + + return WriteBsdResult(Context, 0, Errno); + } + + // Write(u32 socket, buffer message) -> (i32 ret, u32 bsd_errno) public long Write(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); - (long SentPosition, long SentSize) = Context.Request.GetBufferType0x21(); + (long SendPosition, long SendSize) = Context.Request.GetBufferType0x21(); - byte[] SentBuffer = Context.Memory.ReadBytes(SentPosition, SentSize); + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + int Result = -1; - try + if (Socket != null) { - //Logging.Debug("Wrote Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); + byte[] SendBuffer = Context.Memory.ReadBytes(SendPosition, SendSize); - int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); - - Context.ResponseData.Write(BytesSent); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + try + { + Result = Socket.Handle.Send(SendBuffer); + Errno = SetResultErrno(Socket.Handle, Result); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + } } - return 0; + return WriteBsdResult(Context, Result, Errno); } - //(u32 socket) -> (i32 ret, u32 bsd_errno, buffer message) + // Read(u32 socket) -> (i32 ret, u32 bsd_errno, buffer message) public long Read(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); (long ReceivePosition, long ReceiveLength) = Context.Request.GetBufferType0x22(); - byte[] ReceivedBuffer = new byte[ReceiveLength]; + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); + int Result = -1; - try + if (Socket != null) { - int BytesRead = Sockets[SocketId].Handle.Receive(ReceivedBuffer); + byte[] ReceivedBuffer = new byte[ReceiveLength]; - Context.Memory.WriteBytes(ReceivePosition, ReceivedBuffer); - - Context.ResponseData.Write(BytesRead); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + try + { + Result = Socket.Handle.Receive(ReceivedBuffer); + Errno = SetResultErrno(Socket.Handle, Result); + } + catch (SocketException Exception) + { + Errno = ConvertError((WSAError)Exception.ErrorCode); + } } - return 0; + return WriteBsdResult(Context, Result, Errno); } - //(u32 socket) -> (i32 ret, u32 bsd_errno) + // Close(u32 socket) -> (i32 ret, u32 bsd_errno) public long Close(ServiceCtx Context) { - int SocketId = Context.RequestData.ReadInt32(); + int SocketFd = Context.RequestData.ReadInt32(); - try - { - Sockets[SocketId].Handle.Close(); - Sockets[SocketId] = null; + LinuxError Errno = LinuxError.EBADF; + BsdSocket Socket = RetrieveSocket(SocketFd); - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) + if (Socket != null) { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); + Socket.Handle.Close(); + + Sockets[SocketFd] = null; + + Errno = LinuxError.SUCCESS; } - return 0; + return WriteBsdResult(Context, 0, Errno); } - public void ParseAddrBuffer(int SocketId, byte[] AddrBuffer) + // DuplicateSocket(u32 socket, u64 reserved) -> (i32 ret, u32 bsd_errno) + public long DuplicateSocket(ServiceCtx Context) { - using (MemoryStream MS = new MemoryStream(AddrBuffer)) + int SocketFd = Context.RequestData.ReadInt32(); + ulong Reserved = Context.RequestData.ReadUInt64(); + + LinuxError Errno = LinuxError.ENOENT; + int NewSockFd = -1; + + if (IsPrivileged) { - BinaryReader Reader = new BinaryReader(MS); + Errno = LinuxError.EBADF; - int Size = Reader.ReadByte(); - int Family = Reader.ReadByte(); - int Port = EndianSwap.Swap16(Reader.ReadUInt16()); + BsdSocket OldSocket = RetrieveSocket(SocketFd); - string IpAddress = Reader.ReadByte().ToString() + "." + - Reader.ReadByte().ToString() + "." + - Reader.ReadByte().ToString() + "." + - Reader.ReadByte().ToString(); - - Sockets[SocketId].IpAddress = IPAddress.Parse(IpAddress); - Sockets[SocketId].RemoteEP = new IPEndPoint(Sockets[SocketId].IpAddress, Port); + if (OldSocket != null) + { + Sockets.Add(OldSocket); + NewSockFd = Sockets.Count - 1; + } } - } - private int Get16(byte[] Data, int Address) - { - return - Data[Address + 0] << 0 | - Data[Address + 1] << 8; - } - - private int Get32(byte[] Data, int Address) - { - return - Data[Address + 0] << 0 | - Data[Address + 1] << 8 | - Data[Address + 2] << 16 | - Data[Address + 3] << 24; + return WriteBsdResult(Context, NewSockFd, Errno); } } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/HOS/Services/Bsd/PollEvent.cs b/Ryujinx.HLE/HOS/Services/Bsd/PollEvent.cs new file mode 100644 index 0000000000..49cd4877ee --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bsd/PollEvent.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Services.Bsd +{ + class PollEvent + { + public enum EventTypeMask + { + Input = 1, + UrgentInput = 2, + Output = 4, + Error = 8, + Disconnected = 0x10, + Invalid = 0x20, + } + + public int SocketFd { get; private set; } + public BsdSocket Socket { get; private set; } + public EventTypeMask InputEvents { get; private set; } + public EventTypeMask OutputEvents { get; private set; } + + public PollEvent(int SocketFd, BsdSocket Socket, EventTypeMask InputEvents, EventTypeMask OutputEvents) + { + this.SocketFd = SocketFd; + this.Socket = Socket; + this.InputEvents = InputEvents; + this.OutputEvents = OutputEvents; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs index f701dd0537..29f1c0e775 100644 --- a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs +++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs @@ -72,10 +72,10 @@ namespace Ryujinx.HLE.HOS.Services return new Bcat.IServiceCreator(); case "bsd:s": - return new IClient(); + return new IClient(true); case "bsd:u": - return new IClient(); + return new IClient(false); case "caps:a": return new IAlbumAccessorService(); diff --git a/Ryujinx.HLE/Utilities/LinuxError.cs b/Ryujinx.HLE/Utilities/LinuxError.cs new file mode 100644 index 0000000000..5c322f83cf --- /dev/null +++ b/Ryujinx.HLE/Utilities/LinuxError.cs @@ -0,0 +1,152 @@ +namespace Ryujinx.HLE.Utilities +{ + enum LinuxError + { + SUCCESS = 0, + EPERM = 1 /* Operation not permitted */, + ENOENT = 2 /* No such file or directory */, + ESRCH = 3 /* No such process */, + EINTR = 4 /* Interrupted system call */, + EIO = 5 /* I/O error */, + ENXIO = 6 /* No such device or address */, + E2BIG = 7 /* Argument list too long */, + ENOEXEC = 8 /* Exec format error */, + EBADF = 9 /* Bad file number */, + ECHILD = 10 /* No child processes */, + EAGAIN = 11 /* Try again */, + ENOMEM = 12 /* Out of memory */, + EACCES = 13 /* Permission denied */, + EFAULT = 14 /* Bad address */, + ENOTBLK = 15 /* Block device required */, + EBUSY = 16 /* Device or resource busy */, + EEXIST = 17 /* File exists */, + EXDEV = 18 /* Cross-device link */, + ENODEV = 19 /* No such device */, + ENOTDIR = 20 /* Not a directory */, + EISDIR = 21 /* Is a directory */, + EINVAL = 22 /* Invalid argument */, + ENFILE = 23 /* File table overflow */, + EMFILE = 24 /* Too many open files */, + ENOTTY = 25 /* Not a typewriter */, + ETXTBSY = 26 /* Text file busy */, + EFBIG = 27 /* File too large */, + ENOSPC = 28 /* No space left on device */, + ESPIPE = 29 /* Illegal seek */, + EROFS = 30 /* Read-only file system */, + EMLINK = 31 /* Too many links */, + EPIPE = 32 /* Broken pipe */, + EDOM = 33 /* Math argument out of domain of func */, + ERANGE = 34 /* Math result not representable */, + EDEADLK = 35 /* Resource deadlock would occur */, + ENAMETOOLONG = 36 /* File name too long */, + ENOLCK = 37 /* No record locks available */, + + /* + * This error code is special: arch syscall entry code will return + * -ENOSYS if users try to call a syscall that doesn't exist. To keep + * failures of syscalls that really do exist distinguishable from + * failures due to attempts to use a nonexistent syscall, syscall + * implementations should refrain from returning -ENOSYS. + */ + ENOSYS = 38 /* Invalid system call number */, + ENOTEMPTY = 39 /* Directory not empty */, + ELOOP = 40 /* Too many symbolic links encountered */, + EWOULDBLOCK = EAGAIN /* Operation would block */, + ENOMSG = 42 /* No message of desired type */, + EIDRM = 43 /* Identifier removed */, + ECHRNG = 44 /* Channel number out of range */, + EL2NSYNC = 45 /* Level 2 not synchronized */, + EL3HLT = 46 /* Level 3 halted */, + EL3RST = 47 /* Level 3 reset */, + ELNRNG = 48 /* Link number out of range */, + EUNATCH = 49 /* Protocol driver not attached */, + ENOCSI = 50 /* No CSI structure available */, + EL2HLT = 51 /* Level 2 halted */, + EBADE = 52 /* Invalid exchange */, + EBADR = 53 /* Invalid request descriptor */, + EXFULL = 54 /* Exchange full */, + ENOANO = 55 /* No anode */, + EBADRQC = 56 /* Invalid request code */, + EBADSLT = 57 /* Invalid slot */, + EDEADLOCK = EDEADLK, + EBFONT = 59 /* Bad font file format */, + ENOSTR = 60 /* Device not a stream */, + ENODATA = 61 /* No data available */, + ETIME = 62 /* Timer expired */, + ENOSR = 63 /* Out of streams resources */, + ENONET = 64 /* Machine is not on the network */, + ENOPKG = 65 /* Package not installed */, + EREMOTE = 66 /* Object is remote */, + ENOLINK = 67 /* Link has been severed */, + EADV = 68 /* Advertise error */, + ESRMNT = 69 /* Srmount error */, + ECOMM = 70 /* Communication error on send */, + EPROTO = 71 /* Protocol error */, + EMULTIHOP = 72 /* Multihop attempted */, + EDOTDOT = 73 /* RFS specific error */, + EBADMSG = 74 /* Not a data message */, + EOVERFLOW = 75 /* Value too large for defined data type */, + ENOTUNIQ = 76 /* Name not unique on network */, + EBADFD = 77 /* File descriptor in bad state */, + EREMCHG = 78 /* Remote address changed */, + ELIBACC = 79 /* Can not access a needed shared library */, + ELIBBAD = 80 /* Accessing a corrupted shared library */, + ELIBSCN = 81 /* .lib section in a.out corrupted */, + ELIBMAX = 82 /* Attempting to link in too many shared libraries */, + ELIBEXEC = 83 /* Cannot exec a shared library directly */, + EILSEQ = 84 /* Illegal byte sequence */, + ERESTART = 85 /* Interrupted system call should be restarted */, + ESTRPIPE = 86 /* Streams pipe error */, + EUSERS = 87 /* Too many users */, + ENOTSOCK = 88 /* Socket operation on non-socket */, + EDESTADDRREQ = 89 /* Destination address required */, + EMSGSIZE = 90 /* Message too long */, + EPROTOTYPE = 91 /* Protocol wrong type for socket */, + ENOPROTOOPT = 92 /* Protocol not available */, + EPROTONOSUPPORT = 93 /* Protocol not supported */, + ESOCKTNOSUPPORT = 94 /* Socket type not supported */, + EOPNOTSUPP = 95 /* Operation not supported on transport endpoint */, + EPFNOSUPPORT = 96 /* Protocol family not supported */, + EAFNOSUPPORT = 97 /* Address family not supported by protocol */, + EADDRINUSE = 98 /* Address already in use */, + EADDRNOTAVAIL = 99 /* Cannot assign requested address */, + ENETDOWN = 100 /* Network is down */, + ENETUNREACH = 101 /* Network is unreachable */, + ENETRESET = 102 /* Network dropped connection because of reset */, + ECONNABORTED = 103 /* Software caused connection abort */, + ECONNRESET = 104 /* Connection reset by peer */, + ENOBUFS = 105 /* No buffer space available */, + EISCONN = 106 /* Transport endpoint is already connected */, + ENOTCONN = 107 /* Transport endpoint is not connected */, + ESHUTDOWN = 108 /* Cannot send after transport endpoint shutdown */, + ETOOMANYREFS = 109 /* Too many references: cannot splice */, + ETIMEDOUT = 110 /* Connection timed out */, + ECONNREFUSED = 111 /* Connection refused */, + EHOSTDOWN = 112 /* Host is down */, + EHOSTUNREACH = 113 /* No route to host */, + EALREADY = 114 /* Operation already in progress */, + EINPROGRESS = 115 /* Operation now in progress */, + ESTALE = 116 /* Stale file handle */, + EUCLEAN = 117 /* Structure needs cleaning */, + ENOTNAM = 118 /* Not a XENIX named type file */, + ENAVAIL = 119 /* No XENIX semaphores available */, + EISNAM = 120 /* Is a named type file */, + EREMOTEIO = 121 /* Remote I/O error */, + EDQUOT = 122 /* Quota exceeded */, + ENOMEDIUM = 123 /* No medium found */, + EMEDIUMTYPE = 124 /* Wrong medium type */, + ECANCELED = 125 /* Operation Canceled */, + ENOKEY = 126 /* Required key not available */, + EKEYEXPIRED = 127 /* Key has expired */, + EKEYREVOKED = 128 /* Key has been revoked */, + EKEYREJECTED = 129 /* Key was rejected by service */, + + /* for robust mutexes */ + EOWNERDEAD = 130 /* Owner died */, + ENOTRECOVERABLE = 131 /* State not recoverable */, + + ERFKILL = 132 /* Operation not possible due to RF-kill */, + + EHWPOISON = 133 /* Memory page has hardware error */, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Utilities/WSAError.cs b/Ryujinx.HLE/Utilities/WSAError.cs new file mode 100644 index 0000000000..55c04f2284 --- /dev/null +++ b/Ryujinx.HLE/Utilities/WSAError.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ryujinx.HLE.Utilities +{ + public enum WSAError + { + /* + * All Windows Sockets error constants are biased by WSABASEERR from + * the "normal" + */ + WSABASEERR = 10000, + + /* + * Windows Sockets definitions of regular Microsoft C error constants + */ + WSAEINTR = (WSABASEERR + 4), + WSAEBADF = (WSABASEERR + 9), + WSAEACCES = (WSABASEERR + 13), + WSAEFAULT = (WSABASEERR + 14), + WSAEINVAL = (WSABASEERR + 22), + WSAEMFILE = (WSABASEERR + 24), + + /* + * Windows Sockets definitions of regular Berkeley error constants + */ + WSAEWOULDBLOCK = (WSABASEERR + 35), + WSAEINPROGRESS = (WSABASEERR + 36), + WSAEALREADY = (WSABASEERR + 37), + WSAENOTSOCK = (WSABASEERR + 38), + WSAEDESTADDRREQ = (WSABASEERR + 39), + WSAEMSGSIZE = (WSABASEERR + 40), + WSAEPROTOTYPE = (WSABASEERR + 41), + WSAENOPROTOOPT = (WSABASEERR + 42), + WSAEPROTONOSUPPORT = (WSABASEERR + 43), + WSAESOCKTNOSUPPORT = (WSABASEERR + 44), + WSAEOPNOTSUPP = (WSABASEERR + 45), + WSAEPFNOSUPPORT = (WSABASEERR + 46), + WSAEAFNOSUPPORT = (WSABASEERR + 47), + WSAEADDRINUSE = (WSABASEERR + 48), + WSAEADDRNOTAVAIL = (WSABASEERR + 49), + WSAENETDOWN = (WSABASEERR + 50), + WSAENETUNREACH = (WSABASEERR + 51), + WSAENETRESET = (WSABASEERR + 52), + WSAECONNABORTED = (WSABASEERR + 53), + WSAECONNRESET = (WSABASEERR + 54), + WSAENOBUFS = (WSABASEERR + 55), + WSAEISCONN = (WSABASEERR + 56), + WSAENOTCONN = (WSABASEERR + 57), + WSAESHUTDOWN = (WSABASEERR + 58), + WSAETOOMANYREFS = (WSABASEERR + 59), + WSAETIMEDOUT = (WSABASEERR + 60), + WSAECONNREFUSED = (WSABASEERR + 61), + WSAELOOP = (WSABASEERR + 62), + WSAENAMETOOLONG = (WSABASEERR + 63), + WSAEHOSTDOWN = (WSABASEERR + 64), + WSAEHOSTUNREACH = (WSABASEERR + 65), + WSAENOTEMPTY = (WSABASEERR + 66), + WSAEPROCLIM = (WSABASEERR + 67), + WSAEUSERS = (WSABASEERR + 68), + WSAEDQUOT = (WSABASEERR + 69), + WSAESTALE = (WSABASEERR + 70), + WSAEREMOTE = (WSABASEERR + 71), + + /* + * Extended Windows Sockets error constant definitions + */ + WSASYSNOTREADY = (WSABASEERR + 91), + WSAVERNOTSUPPORTED = (WSABASEERR + 92), + WSANOTINITIALISED = (WSABASEERR + 93), + WSAEDISCON = (WSABASEERR + 101), + WSAENOMORE = (WSABASEERR + 102), + WSAECANCELLED = (WSABASEERR + 103), + WSAEINVALIDPROCTABLE = (WSABASEERR + 104), + WSAEINVALIDPROVIDER = (WSABASEERR + 105), + WSAEPROVIDERFAILEDINIT = (WSABASEERR + 106), + WSASYSCALLFAILURE = (WSABASEERR + 107), + WSASERVICE_NOT_FOUND = (WSABASEERR + 108), + WSATYPE_NOT_FOUND = (WSABASEERR + 109), + WSA_E_NO_MORE = (WSABASEERR + 110), + WSA_E_CANCELLED = (WSABASEERR + 111), + WSAEREFUSED = (WSABASEERR + 112), + + /* + * Error return codes from gethostbyname() and gethostbyaddr() + * (when using the resolver). Note that these errors are + * retrieved via WSAGetLastError() and must therefore follow + * the rules for avoiding clashes with error numbers from + * specific implementations or language run-time systems. + * For this reason the codes are based at WSABASEERR+1001. + * Note also that [WSA]NO_ADDRESS is defined only for + * compatibility purposes. + */ + + /* Authoritative Answer: Host not found */ + WSAHOST_NOT_FOUND = (WSABASEERR + 1001), + + /* Non-Authoritative: Host not found, or SERVERFAIL */ + WSATRY_AGAIN = (WSABASEERR + 1002), + + /* Non-recoverable errors, FORMERR, REFUSED, NOTIMP */ + WSANO_RECOVERY = (WSABASEERR + 1003), + + /* Valid name, no data record of requested type */ + WSANO_DATA = (WSABASEERR + 1004), + + /* + * Define QOS related error return codes + * + */ + WSA_QOS_RECEIVERS = (WSABASEERR + 1005), + /* at least one Reserve has arrived */ + WSA_QOS_SENDERS = (WSABASEERR + 1006), + /* at least one Path has arrived */ + WSA_QOS_NO_SENDERS = (WSABASEERR + 1007), + /* there are no senders */ + WSA_QOS_NO_RECEIVERS = (WSABASEERR + 1008), + /* there are no receivers */ + WSA_QOS_REQUEST_CONFIRMED = (WSABASEERR + 1009), + /* Reserve has been confirmed */ + WSA_QOS_ADMISSION_FAILURE = (WSABASEERR + 1010), + /* error due to lack of resources */ + WSA_QOS_POLICY_FAILURE = (WSABASEERR + 1011), + /* rejected for administrative reasons - bad credentials */ + WSA_QOS_BAD_STYLE = (WSABASEERR + 1012), + /* unknown or conflicting style */ + WSA_QOS_BAD_OBJECT = (WSABASEERR + 1013), + /* problem with some part of the filterspec or providerspecific + * buffer in general */ + WSA_QOS_TRAFFIC_CTRL_ERROR = (WSABASEERR + 1014), + /* problem with some part of the flowspec */ + WSA_QOS_GENERIC_ERROR = (WSABASEERR + 1015), + } +}