Epolls are file descriptors (#3622)

* net: Register epolls in file descriptor table

* fstat: stub epoll, resolver

* Fake async hostname resolution

* net: epoll fixes

* epoll: actually close the file descriptor
This commit is contained in:
Marcin Mikołajczyk
2025-09-22 21:57:51 +02:00
committed by GitHub
parent 50a3081084
commit 976d12f4e6
11 changed files with 324 additions and 68 deletions

View File

@@ -382,6 +382,8 @@ set(NETWORK_LIBS src/core/libraries/network/http.cpp
src/core/libraries/network/net_util.h
src/core/libraries/network/net_epoll.cpp
src/core/libraries/network/net_epoll.h
src/core/libraries/network/net_resolver.cpp
src/core/libraries/network/net_resolver.h
src/core/libraries/network/net_error.h
src/core/libraries/network/net.h
src/core/libraries/network/ssl.cpp

View File

@@ -235,6 +235,30 @@ File* HandleTable::GetSocket(int d) {
return file;
}
File* HandleTable::GetEpoll(int d) {
std::scoped_lock lock{m_mutex};
if (d < 0 || d >= m_files.size()) {
return nullptr;
}
auto file = m_files.at(d);
if (file->type != Core::FileSys::FileType::Epoll) {
return nullptr;
}
return file;
}
File* HandleTable::GetResolver(int d) {
std::scoped_lock lock{m_mutex};
if (d < 0 || d >= m_files.size()) {
return nullptr;
}
auto file = m_files.at(d);
if (file->type != Core::FileSys::FileType::Resolver) {
return nullptr;
}
return file;
}
File* HandleTable::GetFile(const std::filesystem::path& host_name) {
for (auto* file : m_files) {
if (file != nullptr && file->m_host_name == host_name) {

View File

@@ -15,7 +15,9 @@
namespace Libraries::Net {
struct Socket;
}
struct Epoll;
struct Resolver;
} // namespace Libraries::Net
namespace Core::FileSys {
@@ -78,6 +80,8 @@ enum class FileType {
Directory,
Device,
Socket,
Epoll,
Resolver
};
struct File {
@@ -90,6 +94,8 @@ struct File {
std::shared_ptr<Directories::BaseDirectory> directory; // only valid for type == Directory
std::shared_ptr<Devices::BaseDevice> device; // only valid for type == Device
std::shared_ptr<Libraries::Net::Socket> socket; // only valid for type == Socket
std::shared_ptr<Libraries::Net::Epoll> epoll; // only valid for type == Epoll
std::shared_ptr<Libraries::Net::Resolver> resolver; // only valid for type == Resolver
};
class HandleTable {
@@ -101,6 +107,8 @@ public:
void DeleteHandle(int d);
File* GetFile(int d);
File* GetSocket(int d);
File* GetEpoll(int d);
File* GetResolver(int d);
File* GetFile(const std::filesystem::path& host_name);
int GetFileDescriptor(File* file);

View File

@@ -3,6 +3,7 @@
#include <map>
#include <ranges>
#include <magic_enum/magic_enum.hpp>
#include "common/assert.h"
#include "common/error.h"
@@ -701,8 +702,13 @@ s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) {
// Socket functions handle errnos internally
return file->socket->fstat(sb);
}
case Core::FileSys::FileType::Epoll:
case Core::FileSys::FileType::Resolver: {
LOG_ERROR(Kernel_Fs, "(STUBBED) file type {}", magic_enum::enum_name(file->type.load()));
break;
}
default:
UNREACHABLE();
UNREACHABLE_MSG("{}", u32(file->type.load()));
}
return ORBIS_OK;
}

View File

@@ -22,6 +22,7 @@
#include "core/libraries/network/net.h"
#include "net_epoll.h"
#include "net_error.h"
#include "net_resolver.h"
#include "net_util.h"
#include "netctl.h"
#include "sockets.h"
@@ -621,16 +622,17 @@ int PS4_SYSV_ABI sceNetEpollAbort() {
int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, OrbisNetId id,
OrbisNetEpollEvent* event) {
auto epoll = Common::Singleton<EpollTable>::Instance()->GetEpoll(epollid);
if (!epoll) {
auto file = FDTable::Instance()->GetEpoll(epollid);
if (!file) {
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
auto epoll = file->epoll;
LOG_WARNING(Lib_Net, "called, epollid = {} ({}), op = {}, id = {}", epollid, epoll->name,
magic_enum::enum_name(op), id);
auto find_id = [&](OrbisNetId id) {
return std::ranges::find_if(epoll->events, [&](const auto& el) { return el.first == id; });
return std::ranges::find_if(epoll->events, [&](auto& el) { return el.first == id; });
};
switch (op) {
@@ -644,17 +646,33 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
return ORBIS_NET_ERROR_EEXIST;
}
auto file = FDTable::Instance()->GetSocket(id);
auto file = FDTable::Instance()->GetFile(id);
if (!file) {
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", id);
LOG_ERROR(Lib_Net, "file id is invalid = {}", id);
return ORBIS_NET_ERROR_EBADF;
}
epoll_event native_event = {.events = ConvertEpollEventsIn(event->events),
.data = {.fd = id}};
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *file->socket->Native(), &native_event) ==
0);
epoll->events.emplace_back(id, *event);
switch (file->type) {
case Core::FileSys::FileType::Socket: {
epoll_event native_event = {.events = ConvertEpollEventsIn(event->events),
.data = {.fd = id}};
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *file->socket->Native(),
&native_event) == 0);
epoll->events.emplace_back(id, *event);
break;
}
case Core::FileSys::FileType::Resolver: {
epoll->async_resolutions.emplace_back(id);
epoll->events.emplace_back(id, *event);
break;
}
default: {
LOG_ERROR(Lib_Net, "file type {} ({}) passed", magic_enum::enum_name(file->type.load()),
file->m_guest_name);
break;
}
}
break;
}
case ORBIS_NET_EPOLL_CTL_MOD: {
@@ -663,7 +681,33 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
return ORBIS_NET_ERROR_EINVAL;
}
UNREACHABLE();
const auto it = find_id(id);
if (it == epoll->events.end()) {
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
auto file = FDTable::Instance()->GetFile(id);
if (!file) {
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "file id is invalid = {}", id);
return ORBIS_NET_ERROR_EBADF;
}
switch (file->type) {
case Core::FileSys::FileType::Socket: {
epoll_event native_event = {.events = ConvertEpollEventsIn(event->events),
.data = {.fd = id}};
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_MOD, *file->socket->Native(),
&native_event) == 0);
*it = {id, *event};
break;
}
default:
LOG_ERROR(Lib_Net, "file type {} ({}) passed", magic_enum::enum_name(file->type.load()),
file->m_guest_name);
break;
}
break;
}
case ORBIS_NET_EPOLL_CTL_DEL: {
@@ -678,14 +722,30 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
return ORBIS_NET_ERROR_EBADF;
}
auto file = FDTable::Instance()->GetSocket(id);
auto file = FDTable::Instance()->GetFile(id);
if (!file) {
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", id);
LOG_ERROR(Lib_Net, "file id is invalid = {}", id);
return ORBIS_NET_ERROR_EBADF;
}
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *file->socket->Native(), nullptr) == 0);
epoll->events.erase(it);
switch (file->type) {
case Core::FileSys::FileType::Socket: {
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *file->socket->Native(), nullptr) ==
0);
epoll->events.erase(it);
break;
}
case Core::FileSys::FileType::Resolver: {
std::erase(epoll->async_resolutions, id);
epoll->events.erase(it);
break;
}
default:
LOG_ERROR(Lib_Net, "file type {} ({}) passed", magic_enum::enum_name(file->type.load()),
file->m_guest_name);
break;
}
break;
}
default:
@@ -703,46 +763,58 @@ int PS4_SYSV_ABI sceNetEpollCreate(const char* name, int flags) {
return ORBIS_NET_ERROR_EINVAL;
}
auto epoll = Common::Singleton<EpollTable>::Instance()->CreateHandle(name);
return epoll;
auto fd = FDTable::Instance()->CreateHandle();
auto* epoll = FDTable::Instance()->GetFile(fd);
epoll->is_opened = true;
epoll->type = Core::FileSys::FileType::Epoll;
epoll->epoll = std::make_shared<Epoll>(name);
epoll->m_guest_name = name;
return fd;
}
int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid) {
auto epoll = Common::Singleton<EpollTable>::Instance()->GetEpoll(epollid);
if (!epoll) {
auto file = FDTable::Instance()->GetEpoll(epollid);
if (!file) {
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
LOG_INFO(Lib_Net, "called, epollid = {} ({})", epollid, epoll->name);
LOG_INFO(Lib_Net, "called, epollid = {} ({})", epollid, file->epoll->name);
epoll->Destroy();
file->epoll->Destroy();
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, int maxevents,
int timeout) {
auto epoll = Common::Singleton<EpollTable>::Instance()->GetEpoll(epollid);
if (!epoll) {
auto file = FDTable::Instance()->GetEpoll(epollid);
if (!file) {
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
auto epoll = file->epoll;
LOG_INFO(Lib_Net, "called, epollid = {} ({}), maxevents = {}, timeout = {}", epollid,
epoll->name, maxevents, timeout);
int sockets_waited_on = (epoll->events.size() - epoll->async_resolutions.size()) > 0;
std::vector<epoll_event> native_events{static_cast<size_t>(maxevents)};
int result = ORBIS_OK;
if (sockets_waited_on) {
#ifdef __linux__
const timespec epoll_timeout{.tv_sec = timeout / 1000000,
.tv_nsec = (timeout % 1000000) * 1000};
int result = epoll_pwait2(epoll->epoll_fd, native_events.data(), maxevents,
const timespec epoll_timeout{.tv_sec = timeout / 1000000,
.tv_nsec = (timeout % 1000000) * 1000};
result = epoll_pwait2(epoll->epoll_fd, native_events.data(), maxevents,
timeout < 0 ? nullptr : &epoll_timeout, nullptr);
#else
int result = epoll_wait(epoll->epoll_fd, native_events.data(), maxevents,
result = epoll_wait(epoll->epoll_fd, native_events.data(), maxevents,
timeout < 0 ? timeout : timeout / 1000);
#endif
}
int i = 0;
if (result < 0) {
LOG_ERROR(Lib_Net, "epoll_wait failed with {}", Common::GetLastErrorMsg());
switch (errno) {
@@ -762,7 +834,7 @@ int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events,
} else if (result == 0) {
LOG_DEBUG(Lib_Net, "timed out");
} else {
for (int i = 0; i < result; ++i) {
for (; i < result; ++i) {
const auto& current_event = native_events[i];
LOG_DEBUG(Lib_Net, "native_event[{}] = ( .events = {}, .data = {:#x} )", i,
current_event.events, current_event.data.u64);
@@ -779,7 +851,36 @@ int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events,
}
}
return result;
if (result >= 0) {
while (!epoll->async_resolutions.empty()) {
if (i == maxevents) {
break;
}
auto rid = epoll->async_resolutions.front();
epoll->async_resolutions.pop_front();
auto file = FDTable::Instance()->GetResolver(rid);
if (!file) {
LOG_ERROR(Lib_Net, "resolver {} does not exist", rid);
continue;
}
file->resolver->Resolve();
const auto it =
std::ranges::find_if(epoll->events, [&](auto& el) { return el.first == rid; });
ASSERT(it != epoll->events.end());
events[i] = {
.events = ORBIS_NET_EPOLLDESCID,
.ident = static_cast<u64>(rid),
.data = it->second.data,
};
LOG_DEBUG(Lib_Net, "event[{}] = ( .events = {:#x}, .ident = {}, .data = {:#x} )", i,
events[i].events, events[i].ident, events[i].data.data_u64);
++i;
}
}
return i;
}
int* PS4_SYSV_ABI sceNetErrnoLoc() {
@@ -1255,18 +1356,30 @@ int PS4_SYSV_ABI sceNetResolverConnectDestroy() {
}
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
LOG_ERROR(Lib_Net, "(STUBBED) called, name = {}, poolid = {}, flags = {}", name, poolid, flags);
static int id = 1;
return id++;
LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", name, poolid, flags);
if (flags != 0) {
*sceNetErrnoLoc() = ORBIS_NET_EINVAL;
return ORBIS_NET_ERROR_EINVAL;
}
auto fd = FDTable::Instance()->CreateHandle();
auto* resolver = FDTable::Instance()->GetFile(fd);
resolver->is_opened = true;
resolver->type = Core::FileSys::FileType::Resolver;
resolver->resolver = std::make_shared<Resolver>(name, poolid, flags);
resolver->m_guest_name = name;
return fd;
}
int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
LOG_ERROR(Lib_Net, "(STUBBED) called rid = {}", resolverid);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetResolverGetError() {
LOG_ERROR(Lib_Net, "(STUBBED) called");
int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status) {
LOG_ERROR(Lib_Net, "(STUBBED) called rid = {}", resolverid);
*status = 0;
return ORBIS_OK;
}
@@ -1286,36 +1399,24 @@ int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* host
"called, resolverid = {}, hostname = {}, timeout = {}, retry = {}, flags = {}",
resolverid, hostname, timeout, retry, flags);
auto file = FDTable::Instance()->GetResolver(resolverid);
if (!file) {
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
if ((flags & ORBIS_NET_RESOLVER_ASYNC) != 0) {
// moves processing to EpollWait
LOG_ERROR(Lib_Net, "async resolution is not implemented");
*sceNetErrnoLoc() = ORBIS_NET_RESOLVER_EINTERNAL;
auto ret = -ORBIS_NET_RESOLVER_EINTERNAL | ORBIS_NET_ERROR_BASE;
return ret;
return file->resolver->ResolveAsync(hostname, addr, timeout, retry, flags);
}
const addrinfo hints = {
.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG,
.ai_family = AF_INET,
};
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
auto ret = netinfo->ResolveHostname(hostname, addr);
addrinfo* info = nullptr;
auto gai_result = getaddrinfo(hostname, nullptr, &hints, &info);
auto ret = ORBIS_OK;
if (gai_result != 0) {
// handle more errors
LOG_ERROR(Lib_Net, "address resolution for {} failed: {}", hostname, gai_result);
*sceNetErrnoLoc() = ORBIS_NET_ERETURN;
ret = -ORBIS_NET_ERETURN | ORBIS_NET_ERROR_BASE;
} else {
ASSERT(info && info->ai_addr);
in_addr resolved_addr = ((sockaddr_in*)info->ai_addr)->sin_addr;
LOG_DEBUG(Lib_Net, "resolved address for {}: {}", hostname, inet_ntoa(resolved_addr));
addr->inaddr_addr = resolved_addr.s_addr;
if (ret != 0) {
*sceNetErrnoLoc() = ret;
ret = -ret | ORBIS_NET_ERROR_BASE;
}
freeaddrinfo(info);
return ret;
}

View File

@@ -427,7 +427,7 @@ int PS4_SYSV_ABI sceNetResolverConnectCreate();
int PS4_SYSV_ABI sceNetResolverConnectDestroy();
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags);
int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid);
int PS4_SYSV_ABI sceNetResolverGetError();
int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status);
int PS4_SYSV_ABI sceNetResolverStartAton();
int PS4_SYSV_ABI sceNetResolverStartAton6();
int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* hostname,

View File

@@ -6,6 +6,7 @@
#include "common/types.h"
#include "core/libraries/network/net.h"
#include <deque>
#include <mutex>
#include <vector>
@@ -28,8 +29,9 @@ using epoll_handle = int;
struct Epoll {
std::vector<std::pair<u32 /*netId*/, OrbisNetEpollEvent>> events{};
const char* name;
std::string name;
epoll_handle epoll_fd;
std::deque<u32> async_resolutions{};
explicit Epoll(const char* name_) : name(name_), epoll_fd(epoll_create1(0)) {
#ifdef _WIN32
@@ -37,7 +39,7 @@ struct Epoll {
#else
ASSERT(epoll_fd != -1);
#endif
if (name == nullptr) {
if (name_ == nullptr) {
name = "anon";
}
}
@@ -49,11 +51,11 @@ struct Epoll {
void Destroy() noexcept {
events.clear();
#ifdef _WIN32
epoll_fd = nullptr;
epoll_close(epoll_fd);
epoll_fd = nullptr;
#else
epoll_fd = -1;
close(epoll_fd);
epoll_fd = -1;
#endif
name = "";
destroyed = true;

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/singleton.h"
#include "common/types.h"
#include "core/libraries/error_codes.h"
#include "net_error.h"
#include "net_resolver.h"
#include "net_util.h"
namespace Libraries::Net {
int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry,
int flags) {
std::scoped_lock lock{m_mutex};
if (async_resolution) {
*sceNetErrnoLoc() = ORBIS_NET_RESOLVER_EBUSY;
return ORBIS_NET_ERROR_RESOLVER_EBUSY;
}
async_resolution = AsyncResolution{hostname, addr, timeout, retry, flags};
return ORBIS_OK;
}
void Resolver::Resolve() {
if (async_resolution) {
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr);
resolution_error = ret;
} else {
LOG_ERROR(Lib_Net, "async resolution has not been set-up");
}
}
} // namespace Libraries::Net

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/network/net.h"
#include <mutex>
#include <vector>
namespace Libraries::Net {
struct Resolver {
public:
Resolver(const char* name, int poolid, int flags) : name(name), poolid(poolid), flags(flags) {}
int ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, int flags);
void Resolve();
private:
struct AsyncResolution {
const char* hostname;
OrbisNetInAddr* addr;
int timeout;
int retry;
int flags;
};
std::string name;
int poolid;
int flags;
std::optional<AsyncResolution> async_resolution{};
int resolution_error = ORBIS_OK;
std::mutex m_mutex;
};
} // namespace Libraries::Net

View File

@@ -36,6 +36,11 @@ typedef int net_socket;
#include <mutex>
#include <vector>
#include <string.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "net.h"
#include "net_error.h"
#include "net_util.h"
namespace NetUtil {
@@ -367,4 +372,30 @@ bool NetUtilInternal::RetrieveIp() {
return true;
}
int NetUtilInternal::ResolveHostname(const char* hostname, Libraries::Net::OrbisNetInAddr* addr) {
const addrinfo hints = {
.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG,
.ai_family = AF_INET,
};
addrinfo* info = nullptr;
auto gai_result = getaddrinfo(hostname, nullptr, &hints, &info);
auto ret = ORBIS_OK;
if (gai_result != 0) {
// handle more errors
LOG_ERROR(Lib_Net, "address resolution for {} failed: {}", hostname, gai_result);
ret = ORBIS_NET_ERETURN;
} else {
ASSERT(info && info->ai_addr);
in_addr resolved_addr = ((sockaddr_in*)info->ai_addr)->sin_addr;
LOG_DEBUG(Lib_Net, "resolved address for {}: {}", hostname, inet_ntoa(resolved_addr));
addr->inaddr_addr = resolved_addr.s_addr;
}
freeaddrinfo(info);
return ret;
}
} // namespace NetUtil

View File

@@ -6,6 +6,10 @@
#include <mutex>
#include "common/types.h"
namespace Libraries::Net {
struct OrbisNetInAddr;
}
namespace NetUtil {
class NetUtilInternal {
@@ -29,5 +33,6 @@ public:
bool RetrieveDefaultGateway();
bool RetrieveNetmask();
bool RetrieveIp();
int ResolveHostname(const char* hostname, Libraries::Net::OrbisNetInAddr* addr);
};
} // namespace NetUtil