Ryujinx/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs
Mary 366fe2dbb2
bsd: Revamp API and make socket abstract (#2960)
* bsd: Revamp API and make socket abstract

This part of the code was really ancient and needed some love.
As such this commit aims at separating the socket core logic from the IClient class and make it uses more modern APIs to read/write/parse data.

* Address gdkchan's comment

* Move TryConvertSocketOption to WinSockHelper

* Allow reusing old fds and add missing locks around SocketInternal and ShutdownAllSockets

* bsd: ton of changes

- Make sockets per process
- Implement eventfds
- Rework Poll for support of eventfds
- Handle protocol auto selection by type (used by gRPC)
- Handle IPv6 socket creation

* Address most of gdkchan comments

* Fix inverted read logic for BSD socket read

* bsd: Make Poll abstract via IBsdSocketPollManager

* bsd: Improve naming of everything

* Fix build issue from last commit (missed to save on VC)

* Switch BsdContext registry to a concurrent dictionary

* bsd: Implement socket creation flags logic and the non blocking flag

* Remove unused enum from previous commit

* bsd: Fix poll logic when 0 fds are present for a given poll manager and when timeout is very small (or 0)

* Address gdkchan's comment
2022-01-12 19:31:08 +01:00

899 lines
32 KiB
C#

using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
[Service("bsd:s", true)]
[Service("bsd:u", false)]
class IClient : IpcService
{
private static readonly List<IPollManager> _pollManagers = new List<IPollManager>
{
EventFileDescriptorPollManager.Instance,
ManagedSocketPollManager.Instance
};
private BsdContext _context;
private bool _isPrivileged;
public IClient(ServiceCtx context, bool isPrivileged) : base(context.Device.System.BsdServer)
{
_isPrivileged = isPrivileged;
}
private ResultCode WriteBsdResult(ServiceCtx context, int result, LinuxError errorCode = LinuxError.SUCCESS)
{
if (errorCode != LinuxError.SUCCESS)
{
result = -1;
}
context.ResponseData.Write(result);
context.ResponseData.Write((int)errorCode);
return ResultCode.Success;
}
private static AddressFamily ConvertBsdAddressFamily(BsdAddressFamily family)
{
switch (family)
{
case BsdAddressFamily.Unspecified:
return AddressFamily.Unspecified;
case BsdAddressFamily.InterNetwork:
return AddressFamily.InterNetwork;
case BsdAddressFamily.InterNetworkV6:
return AddressFamily.InterNetworkV6;
case BsdAddressFamily.Unknown:
return AddressFamily.Unknown;
default:
throw new NotImplementedException(family.ToString());
}
}
private LinuxError SetResultErrno(IFileDescriptor socket, int result)
{
return result == 0 && !socket.Blocking ? LinuxError.EWOULDBLOCK : LinuxError.SUCCESS;
}
private ResultCode SocketInternal(ServiceCtx context, bool exempt)
{
BsdAddressFamily domain = (BsdAddressFamily)context.RequestData.ReadInt32();
BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32();
ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32();
BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift);
type &= BsdSocketType.TypeMask;
if (domain == BsdAddressFamily.Unknown)
{
return WriteBsdResult(context, -1, LinuxError.EPROTONOSUPPORT);
}
else if ((type == BsdSocketType.Seqpacket || type == BsdSocketType.Raw) && !_isPrivileged)
{
if (domain != BsdAddressFamily.InterNetwork || type != BsdSocketType.Raw || protocol != ProtocolType.Icmp)
{
return WriteBsdResult(context, -1, LinuxError.ENOENT);
}
}
AddressFamily netDomain = ConvertBsdAddressFamily(domain);
if (protocol == ProtocolType.IP)
{
if (type == BsdSocketType.Stream)
{
protocol = ProtocolType.Tcp;
}
else if (type == BsdSocketType.Dgram)
{
protocol = ProtocolType.Udp;
}
}
ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol);
newBsdSocket.Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking);
LinuxError errno = LinuxError.SUCCESS;
int newSockFd = _context.RegisterFileDescriptor(newBsdSocket);
if (newSockFd == -1)
{
errno = LinuxError.EBADF;
}
if (exempt)
{
newBsdSocket.Disconnect();
}
return WriteBsdResult(context, newSockFd, errno);
}
private void WriteSockAddr(ServiceCtx context, ulong bufferPosition, ISocket socket, bool isRemote)
{
IPEndPoint endPoint = isRemote ? socket.RemoteEndPoint : socket.LocalEndPoint;
context.Memory.Write(bufferPosition, BsdSockAddr.FromIPEndPoint(endPoint));
}
[CommandHipc(0)]
// Initialize(nn::socket::BsdBufferConfig config, u64 pid, u64 transferMemorySize, KObject<copy, transfer_memory>, pid) -> u32 bsd_errno
public ResultCode RegisterClient(ServiceCtx context)
{
_context = BsdContext.GetOrRegister(context.Request.HandleDesc.PId);
/*
typedef struct {
u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0.
u32 tcp_tx_buf_size; // Size of the TCP transfer (send) buffer (initial or fixed).
u32 tcp_rx_buf_size; // Size of the TCP recieve buffer (initial or fixed).
u32 tcp_tx_buf_max_size; // Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer is fixed to its initial value.
u32 tcp_rx_buf_max_size; // Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed to its initial value.
u32 udp_tx_buf_size; // Size of the UDP transfer (send) buffer (typically 0x2400 bytes).
u32 udp_rx_buf_size; // Size of the UDP receive buffer (typically 0xA500 bytes).
u32 sb_efficiency; // Number of buffers for each socket (standard values range from 1 to 8).
} BsdBufferConfig;
*/
// bsd_error
context.ResponseData.Write(0);
Logger.Stub?.PrintStub(LogClass.ServiceBsd);
// Close transfer memory immediately as we don't use it.
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return ResultCode.Success;
}
[CommandHipc(1)]
// StartMonitoring(u64, pid)
public ResultCode StartMonitoring(ServiceCtx context)
{
ulong unknown0 = context.RequestData.ReadUInt64();
Logger.Stub?.PrintStub(LogClass.ServiceBsd, new { unknown0 });
return ResultCode.Success;
}
[CommandHipc(2)]
// Socket(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno)
public ResultCode Socket(ServiceCtx context)
{
return SocketInternal(context, false);
}
[CommandHipc(3)]
// SocketExempt(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno)
public ResultCode SocketExempt(ServiceCtx context)
{
return SocketInternal(context, true);
}
[CommandHipc(4)]
// Open(u32 flags, array<unknown, 0x21> path) -> (i32 ret, u32 bsd_errno)
public ResultCode Open(ServiceCtx context)
{
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21();
int flags = context.RequestData.ReadInt32();
byte[] rawPath = new byte[bufferSize];
context.Memory.Read(bufferPosition, rawPath);
string path = Encoding.ASCII.GetString(rawPath);
WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP);
Logger.Stub?.PrintStub(LogClass.ServiceBsd, new { path, flags });
return ResultCode.Success;
}
[CommandHipc(5)]
// Select(u32 nfds, nn::socket::timeout timeout, buffer<nn::socket::fd_set, 0x21, 0> readfds_in, buffer<nn::socket::fd_set, 0x21, 0> writefds_in, buffer<nn::socket::fd_set, 0x21, 0> errorfds_in) -> (i32 ret, u32 bsd_errno, buffer<nn::socket::fd_set, 0x22, 0> readfds_out, buffer<nn::socket::fd_set, 0x22, 0> writefds_out, buffer<nn::socket::fd_set, 0x22, 0> errorfds_out)
public ResultCode Select(ServiceCtx context)
{
WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP);
Logger.Stub?.PrintStub(LogClass.ServiceBsd);
return ResultCode.Success;
}
[CommandHipc(6)]
// Poll(u32 nfds, u32 timeout, buffer<unknown, 0x21, 0> fds) -> (i32 ret, u32 bsd_errno, buffer<unknown, 0x22, 0>)
public ResultCode Poll(ServiceCtx context)
{
int fdsCount = context.RequestData.ReadInt32();
int timeout = context.RequestData.ReadInt32();
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21();
if (timeout < -1 || fdsCount < 0 || (ulong)(fdsCount * 8) > bufferSize)
{
return WriteBsdResult(context, -1, LinuxError.EINVAL);
}
PollEvent[] events = new PollEvent[fdsCount];
for (int i = 0; i < fdsCount; i++)
{
PollEventData pollEventData = context.Memory.Read<PollEventData>(bufferPosition + (ulong)(i * Unsafe.SizeOf<PollEventData>()));
IFileDescriptor fileDescriptor = _context.RetrieveFileDescriptor(pollEventData.SocketFd);
if (fileDescriptor == null)
{
return WriteBsdResult(context, -1, LinuxError.EBADF);
}
events[i] = new PollEvent(pollEventData, fileDescriptor);
}
List<PollEvent> discoveredEvents = new List<PollEvent>();
List<PollEvent>[] eventsByPollManager = new List<PollEvent>[_pollManagers.Count];
for (int i = 0; i < eventsByPollManager.Length; i++)
{
eventsByPollManager[i] = new List<PollEvent>();
foreach (PollEvent evnt in events)
{
if (_pollManagers[i].IsCompatible(evnt))
{
eventsByPollManager[i].Add(evnt);
discoveredEvents.Add(evnt);
}
}
}
foreach (PollEvent evnt in events)
{
if (!discoveredEvents.Contains(evnt))
{
Logger.Error?.Print(LogClass.ServiceBsd, $"Poll operation is not supported for {evnt.FileDescriptor.GetType().Name}!");
return WriteBsdResult(context, -1, LinuxError.EBADF);
}
}
int updateCount = 0;
LinuxError errno = LinuxError.SUCCESS;
if (fdsCount != 0)
{
bool IsUnexpectedLinuxError(LinuxError error)
{
return errno != LinuxError.SUCCESS && errno != LinuxError.ETIMEDOUT;
}
// Hybrid approach
long budgetLeftMilliseconds;
if (timeout == -1)
{
budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + uint.MaxValue;
}
else
{
budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + timeout;
}
do
{
for (int i = 0; i < eventsByPollManager.Length; i++)
{
if (eventsByPollManager[i].Count == 0)
{
continue;
}
errno = _pollManagers[i].Poll(eventsByPollManager[i], 0, out updateCount);
if (IsUnexpectedLinuxError(errno))
{
break;
}
if (updateCount > 0)
{
break;
}
}
// If we are here, that mean nothing was availaible, sleep for 50ms
context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000);
}
while (PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds);
}
else if (timeout == -1)
{
// FIXME: If we get a timeout of -1 and there is no fds to wait on, this should kill the KProces. (need to check that with re)
throw new InvalidOperationException();
}
else
{
context.Device.System.KernelContext.Syscall.SleepThread(timeout);
}
// TODO: Spanify
for (int i = 0; i < fdsCount; i++)
{
context.Memory.Write(bufferPosition + (ulong)(i * Unsafe.SizeOf<PollEventData>()), events[i].Data);
}
return WriteBsdResult(context, updateCount, errno);
}
[CommandHipc(7)]
// Sysctl(buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno, u32, buffer<unknown, 0x22, 0>)
public ResultCode Sysctl(ServiceCtx context)
{
WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP);
Logger.Stub?.PrintStub(LogClass.ServiceBsd);
return ResultCode.Success;
}
[CommandHipc(8)]
// Recv(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, array<i8, 0x22> message)
public ResultCode Recv(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32();
(ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22();
WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength);
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
int result = -1;
if (socket != null)
{
errno = socket.Receive(out result, receiveRegion.Memory.Span, socketFlags);
if (errno == LinuxError.SUCCESS)
{
SetResultErrno(socket, result);
receiveRegion.Dispose();
}
}
return WriteBsdResult(context, result, errno);
}
[CommandHipc(9)]
// RecvFrom(u32 sock, u32 flags) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<i8, 0x22, 0> message, buffer<nn::socket::sockaddr_in, 0x22, 0x10>)
public ResultCode RecvFrom(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32();
(ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(0);
(ulong sockAddrOutPosition, ulong sockAddrOutSize) = context.Request.GetBufferType0x22(1);
WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength);
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
int result = -1;
if (socket != null)
{
errno = socket.ReceiveFrom(out result, receiveRegion.Memory.Span, receiveRegion.Memory.Span.Length, socketFlags, out IPEndPoint endPoint);
if (errno == LinuxError.SUCCESS)
{
SetResultErrno(socket, result);
receiveRegion.Dispose();
context.Memory.Write(sockAddrOutPosition, BsdSockAddr.FromIPEndPoint(endPoint));
}
}
return WriteBsdResult(context, result, errno);
}
[CommandHipc(10)]
// Send(u32 socket, u32 flags, buffer<i8, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
public ResultCode Send(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32();
(ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21();
ReadOnlySpan<byte> sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize);
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
int result = -1;
if (socket != null)
{
errno = socket.Send(out result, sendBuffer, socketFlags);
if (errno == LinuxError.SUCCESS)
{
SetResultErrno(socket, result);
}
}
return WriteBsdResult(context, result, errno);
}
[CommandHipc(11)]
// SendTo(u32 socket, u32 flags, buffer<i8, 0x21, 0>, buffer<nn::socket::sockaddr_in, 0x21, 0x10>) -> (i32 ret, u32 bsd_errno)
public ResultCode SendTo(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32();
(ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(0);
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(1);
ReadOnlySpan<byte> sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize);
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
int result = -1;
if (socket != null)
{
IPEndPoint endPoint = context.Memory.Read<BsdSockAddr>(bufferPosition).ToIPEndPoint();
errno = socket.SendTo(out result, sendBuffer, sendBuffer.Length, socketFlags, endPoint);
if (errno == LinuxError.SUCCESS)
{
SetResultErrno(socket, result);
}
}
return WriteBsdResult(context, result, errno);
}
[CommandHipc(12)]
// Accept(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<nn::socket::sockaddr_in, 0x22, 0x10> addr)
public ResultCode Accept(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
(ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22();
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
errno = socket.Accept(out ISocket newSocket);
if (newSocket == null && errno == LinuxError.SUCCESS)
{
errno = LinuxError.EWOULDBLOCK;
}
else if (errno == LinuxError.SUCCESS)
{
int newSockFd = _context.RegisterFileDescriptor(newSocket);
if (newSockFd == -1)
{
errno = LinuxError.EBADF;
}
else
{
WriteSockAddr(context, bufferPos, newSocket, true);
}
WriteBsdResult(context, newSockFd, errno);
context.ResponseData.Write(0x10);
return ResultCode.Success;
}
}
return WriteBsdResult(context, -1, errno);
}
[CommandHipc(13)]
// Bind(u32 socket, buffer<nn::socket::sockaddr_in, 0x21, 0x10> addr) -> (i32 ret, u32 bsd_errno)
public ResultCode Bind(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21();
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
IPEndPoint endPoint = context.Memory.Read<BsdSockAddr>(bufferPosition).ToIPEndPoint();
errno = socket.Bind(endPoint);
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(14)]
// Connect(u32 socket, buffer<nn::socket::sockaddr_in, 0x21, 0x10>) -> (i32 ret, u32 bsd_errno)
public ResultCode Connect(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21();
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
IPEndPoint endPoint = context.Memory.Read<BsdSockAddr>(bufferPosition).ToIPEndPoint();
errno = socket.Connect(endPoint);
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(15)]
// GetPeerName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<nn::socket::sockaddr_in, 0x22, 0x10> addr)
public ResultCode GetPeerName(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22();
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
errno = LinuxError.SUCCESS;
WriteSockAddr(context, bufferPosition, socket, true);
WriteBsdResult(context, 0, errno);
context.ResponseData.Write(Unsafe.SizeOf<BsdSockAddr>());
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(16)]
// GetSockName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<nn::socket::sockaddr_in, 0x22, 0x10> addr)
public ResultCode GetSockName(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
(ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22();
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
errno = LinuxError.SUCCESS;
WriteSockAddr(context, bufferPos, socket, false);
WriteBsdResult(context, 0, errno);
context.ResponseData.Write(Unsafe.SizeOf<BsdSockAddr>());
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(17)]
// GetSockOpt(u32 socket, u32 level, u32 option_name) -> (i32 ret, u32 bsd_errno, u32, buffer<unknown, 0x22, 0>)
public ResultCode GetSockOpt(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32();
BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32();
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22();
WritableRegion optionValue = context.Memory.GetWritableRegion(bufferPosition, (int)bufferSize);
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
errno = socket.GetSocketOption(option, level, optionValue.Memory.Span);
if (errno == LinuxError.SUCCESS)
{
optionValue.Dispose();
}
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(18)]
// Listen(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno)
public ResultCode Listen(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
int backlog = context.RequestData.ReadInt32();
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
errno = socket.Listen(backlog);
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(19)]
// Ioctl(u32 fd, u32 request, u32 bufcount, buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno, buffer<unknown, 0x22, 0>, buffer<unknown, 0x22, 0>, buffer<unknown, 0x22, 0>, buffer<unknown, 0x22, 0>)
public ResultCode Ioctl(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
BsdIoctl cmd = (BsdIoctl)context.RequestData.ReadInt32();
int bufferCount = context.RequestData.ReadInt32();
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
switch (cmd)
{
case BsdIoctl.AtMark:
errno = LinuxError.SUCCESS;
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22();
// FIXME: OOB not implemented.
context.Memory.Write(bufferPosition, 0);
break;
default:
errno = LinuxError.EOPNOTSUPP;
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Ioctl Cmd: {cmd}");
break;
}
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(20)]
// Fcntl(u32 socket, u32 cmd, u32 arg) -> (i32 ret, u32 bsd_errno)
public ResultCode 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;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
errno = LinuxError.SUCCESS;
if (cmd == 0x3)
{
result = !socket.Blocking ? 0x800 : 0;
}
else if (cmd == 0x4 && arg == 0x800)
{
socket.Blocking = false;
result = 0;
}
else
{
errno = LinuxError.EOPNOTSUPP;
}
}
return WriteBsdResult(context, result, errno);
}
[CommandHipc(21)]
// SetSockOpt(u32 socket, u32 level, u32 option_name, buffer<unknown, 0x21, 0> option_value) -> (i32 ret, u32 bsd_errno)
public ResultCode SetSockOpt(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32();
BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32();
(ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21();
ReadOnlySpan<byte> optionValue = context.Memory.GetSpan(bufferPos, (int)bufferSize);
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
errno = socket.SetSocketOption(option, level, optionValue);
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(22)]
// Shutdown(u32 socket, u32 how) -> (i32 ret, u32 bsd_errno)
public ResultCode Shutdown(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
int how = context.RequestData.ReadInt32();
LinuxError errno = LinuxError.EBADF;
ISocket socket = _context.RetrieveSocket(socketFd);
if (socket != null)
{
errno = LinuxError.EINVAL;
if (how >= 0 && how <= 2)
{
errno = socket.Shutdown((BsdSocketShutdownFlags)how);
}
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(23)]
// ShutdownAllSockets(u32 how) -> (i32 ret, u32 bsd_errno)
public ResultCode ShutdownAllSockets(ServiceCtx context)
{
int how = context.RequestData.ReadInt32();
LinuxError errno = LinuxError.EINVAL;
if (how >= 0 && how <= 2)
{
errno = _context.ShutdownAllSockets((BsdSocketShutdownFlags)how);
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(24)]
// Write(u32 fd, buffer<i8, 0x21, 0> message) -> (i32 ret, u32 bsd_errno)
public ResultCode Write(ServiceCtx context)
{
int fd = context.RequestData.ReadInt32();
(ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21();
ReadOnlySpan<byte> sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize);
LinuxError errno = LinuxError.EBADF;
IFileDescriptor file = _context.RetrieveFileDescriptor(fd);
int result = -1;
if (file != null)
{
errno = file.Write(out result, sendBuffer);
if (errno == LinuxError.SUCCESS)
{
SetResultErrno(file, result);
}
}
return WriteBsdResult(context, result, errno);
}
[CommandHipc(25)]
// Read(u32 fd) -> (i32 ret, u32 bsd_errno, buffer<i8, 0x22, 0> message)
public ResultCode Read(ServiceCtx context)
{
int fd = context.RequestData.ReadInt32();
(ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22();
WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength);
LinuxError errno = LinuxError.EBADF;
IFileDescriptor file = _context.RetrieveFileDescriptor(fd);
int result = -1;
if (file != null)
{
errno = file.Read(out result, receiveRegion.Memory.Span);
if (errno == LinuxError.SUCCESS)
{
SetResultErrno(file, result);
receiveRegion.Dispose();
}
}
return WriteBsdResult(context, result, errno);
}
[CommandHipc(26)]
// Close(u32 fd) -> (i32 ret, u32 bsd_errno)
public ResultCode Close(ServiceCtx context)
{
int fd = context.RequestData.ReadInt32();
LinuxError errno = LinuxError.EBADF;
if (_context.CloseFileDescriptor(fd))
{
errno = LinuxError.SUCCESS;
}
return WriteBsdResult(context, 0, errno);
}
[CommandHipc(27)]
// DuplicateSocket(u32 fd, u64 reserved) -> (i32 ret, u32 bsd_errno)
public ResultCode DuplicateSocket(ServiceCtx context)
{
int fd = context.RequestData.ReadInt32();
ulong reserved = context.RequestData.ReadUInt64();
LinuxError errno = LinuxError.ENOENT;
int newSockFd = -1;
if (_isPrivileged)
{
errno = LinuxError.SUCCESS;
newSockFd = _context.DuplicateFileDescriptor(fd);
if (newSockFd == -1)
{
errno = LinuxError.EBADF;
}
}
return WriteBsdResult(context, newSockFd, errno);
}
[CommandHipc(31)] // 7.0.0+
// EventFd(u64 initval, nn::socket::EventFdFlags flags) -> (i32 ret, u32 bsd_errno)
public ResultCode EventFd(ServiceCtx context)
{
ulong initialValue = context.RequestData.ReadUInt64();
EventFdFlags flags = (EventFdFlags)context.RequestData.ReadUInt32();
EventFileDescriptor newEventFile = new EventFileDescriptor(initialValue, flags);
LinuxError errno = LinuxError.SUCCESS;
int newSockFd = _context.RegisterFileDescriptor(newEventFile);
if (newSockFd == -1)
{
errno = LinuxError.EBADF;
}
return WriteBsdResult(context, newSockFd, errno);
}
}
}