feat(libScePosixForWebKit): implement getservbyname

Implement getservbyname (H1bArJPagV0) in libScePosixForWebKit. Cloud
Play (PPSA99600) imports it from this library only (registry index 408,
call site 0x1042124); once fnmatch (#3545) is in, its predicted load
stop is:

    FAIL: unresolved ELF import H1bArJPagV0 from libScePosixForWebKit.prx

The ps5-payload-dev SDK stubs (f5d40f02, sce_stubs/) declare
getservbyname in libScePosixForWebKit.c and not in libkernel.c or
libSceLibcInternal.c. The call site is the service-name branch of a
getaddrinfo inside the title: a non-numeric service is looked up with
protocol "tcp", "udp" or "sctp" and the port is read big-endian at
offset 16 of the result, which fixes the servent layout used here
(name, aliases, 32-bit port, protocol at 0, 8, 16, 24).

The guest has no /etc/services and the console's file is unknown, so the
function searches 37 lines of FreeBSD 11.4 etc/services (8c639945):
ftp-data, ftp, ssh, telnet, smtp, domain, tftp, http, pop3, ntp, imap,
https, rtsp, submission, imaps and pop3s with their aliases and
protocols, in file order. None of their names or aliases appears on an
earlier line of the full file, so these lookups answer as the full file
would. The search follows files_servent in lib/libc/net/getservent.c
(8c639945): exact, case-sensitive match on the name or an alias, a null
protocol takes the first match, the port in network byte order, the
result in per-thread storage like getserv's NSS TLS state. Any other
name returns null without touching errno, as for a name the file lacks.
The repository's getaddrinfo still resolves service names through the
host, so there was no table to share; this is recorded in
TechnicalDebt.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
screamm 2026-10-11 17:53:11 +02:00 committed by Yossi Yadgar
parent 0f2683cb7d
commit 15bbc19501
5 changed files with 144 additions and 0 deletions

View File

@ -36,4 +36,13 @@ if(BUILD_TESTING)
set_tests_properties(guest_resolver PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePosixForWebKit>")
endif()
add_test_executable(guest_service_database_tests ${LIBS_INCLUDE_DIR}/tests/GuestServiceDatabase.cpp)
target_include_directories(guest_service_database_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_service_database_tests PRIVATE libScePosixForWebKit libc)
configure_windows_unwind(guest_service_database_tests)
add_test(NAME guest_service_database COMMAND guest_service_database_tests)
if(WIN32)
set_tests_properties(guest_service_database PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePosixForWebKit>")
endif()
endif()

View File

@ -28,4 +28,15 @@ struct AddressInfo {
static_assert(sizeof(AddressInfo) == 48);
static_assert(offsetof(AddressInfo, canonicalName) == 24);
static_assert(offsetof(AddressInfo, address) == 32);
struct ServiceEntry {
char* name;
char** aliases;
std::int32_t port;
char* protocol;
};
static_assert(sizeof(ServiceEntry) == 32);
static_assert(offsetof(ServiceEntry, aliases) == 8);
static_assert(offsetof(ServiceEntry, port) == 16);
static_assert(offsetof(ServiceEntry, protocol) == 24);
}

View File

@ -31,6 +31,41 @@ struct HostLookup {
std::vector<char*> addresses;
};
struct ServiceRecord {
const char* name;
std::uint16_t port;
const char* protocol;
std::array<const char*, 2> aliases;
};
constexpr ServiceRecord Services[]{
{"ftp-data", 20, "tcp", {}}, {"ftp-data", 20, "udp", {}}, {"ftp-data", 20, "sctp", {}},
{"ftp", 21, "tcp", {}}, {"ftp", 21, "udp", {}}, {"ftp", 21, "sctp", {}},
{"ssh", 22, "tcp", {}}, {"ssh", 22, "udp", {}}, {"ssh", 22, "sctp", {}},
{"telnet", 23, "tcp", {}}, {"telnet", 23, "udp", {}},
{"smtp", 25, "tcp", {"mail"}}, {"smtp", 25, "udp", {"mail"}},
{"domain", 53, "tcp", {}}, {"domain", 53, "udp", {}},
{"tftp", 69, "tcp", {}}, {"tftp", 69, "udp", {}},
{"http", 80, "tcp", {"www", "www-http"}}, {"http", 80, "udp", {"www", "www-http"}},
{"http", 80, "sctp", {"www", "www-http"}},
{"pop3", 110, "tcp", {}}, {"pop3", 110, "udp", {}},
{"ntp", 123, "tcp", {}}, {"ntp", 123, "udp", {}},
{"imap", 143, "tcp", {"imap2", "imap4"}}, {"imap", 143, "udp", {"imap2", "imap4"}},
{"https", 443, "tcp", {}}, {"https", 443, "udp", {}}, {"https", 443, "sctp", {}},
{"rtsp", 554, "tcp", {}}, {"rtsp", 554, "udp", {}},
{"submission", 587, "tcp", {}}, {"submission", 587, "udp", {}},
{"imaps", 993, "tcp", {}}, {"imaps", 993, "udp", {}},
{"pop3s", 995, "tcp", {"spop3"}}, {"pop3s", 995, "udp", {"spop3"}},
};
struct ServiceLookup {
GuestResolver::ServiceEntry entry{};
std::string name;
std::string protocol;
std::vector<std::string> aliasNames;
std::vector<char*> aliases;
};
bool NumericAddress(const char* name, std::array<unsigned char, 4>& address) {
std::array<unsigned char, 3> parts{};
std::size_t count = 0;
@ -155,6 +190,32 @@ GuestResolver::HostEntry* APS5_VABI gethostbyname_nid_postfix(const char* name)
}
}
GuestResolver::ServiceEntry* APS5_VABI getservbyname_nid_postfix(const char* name, const char* protocol) {
for (const auto& service : Services) {
bool named = std::strcmp(name, service.name) == 0;
for (const char* alias : service.aliases)
named = named || (alias && std::strcmp(name, alias) == 0);
if (!named || (protocol && std::strcmp(protocol, service.protocol) != 0)) continue;
try {
static thread_local ServiceLookup lookup;
lookup.name = service.name;
lookup.protocol = service.protocol;
lookup.aliasNames.clear();
lookup.aliases.clear();
for (const char* alias : service.aliases)
if (alias) lookup.aliasNames.emplace_back(alias);
for (auto& alias : lookup.aliasNames) lookup.aliases.push_back(alias.data());
lookup.aliases.push_back(nullptr);
lookup.entry = {lookup.name.data(), lookup.aliases.data(), htons(service.port), lookup.protocol.data()};
return &lookup.entry;
} catch (const std::bad_alloc&) {
errno = ENOMEM;
return nullptr;
}
}
return nullptr;
}
void APS5_VABI freeaddrinfo_nid_postfix(GuestResolver::AddressInfo* first) {
while (first) {
auto* next = first->next;

View File

@ -0,0 +1,62 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libScePosixForWebKit/GuestResolver.hpp"
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <source_location>
#include <thread>
extern "C" GuestResolver::ServiceEntry* APS5_VABI getservbyname_nid_postfix(const char*, const char*);
void Require(bool condition, std::source_location location = std::source_location::current()) {
if (!condition) {
std::fprintf(stderr, "Service database check failed at line %u\n", location.line());
std::abort();
}
}
bool NetworkPort(const GuestResolver::ServiceEntry* entry, std::uint16_t port) {
unsigned char bytes[4];
std::memcpy(bytes, &entry->port, sizeof(bytes));
return bytes[0] == port >> 8 && bytes[1] == (port & 0xff) && bytes[2] == 0 && bytes[3] == 0;
}
int main() {
auto* http = getservbyname_nid_postfix("http", "tcp");
Require(http && std::strcmp(http->name, "http") == 0 && std::strcmp(http->protocol, "tcp") == 0);
Require(NetworkPort(http, 80));
Require(http->aliases && std::strcmp(http->aliases[0], "www") == 0);
Require(std::strcmp(http->aliases[1], "www-http") == 0 && http->aliases[2] == nullptr);
auto* https = getservbyname_nid_postfix("https", nullptr);
Require(https && std::strcmp(https->name, "https") == 0 && std::strcmp(https->protocol, "tcp") == 0);
Require(NetworkPort(https, 443) && https->aliases && https->aliases[0] == nullptr);
auto* domain = getservbyname_nid_postfix("domain", "udp");
Require(domain && std::strcmp(domain->name, "domain") == 0 && std::strcmp(domain->protocol, "udp") == 0);
Require(NetworkPort(domain, 53));
auto* www = getservbyname_nid_postfix("www", "sctp");
Require(www && std::strcmp(www->name, "http") == 0 && std::strcmp(www->protocol, "sctp") == 0);
Require(NetworkPort(www, 80));
auto* mail = getservbyname_nid_postfix("mail", nullptr);
Require(mail && std::strcmp(mail->name, "smtp") == 0 && NetworkPort(mail, 25));
errno = 0;
Require(getservbyname_nid_postfix("no-such-service", nullptr) == nullptr && errno == 0);
Require(getservbyname_nid_postfix("domain", "sctp") == nullptr);
Require(getservbyname_nid_postfix("ntp", "TCP") == nullptr);
Require(getservbyname_nid_postfix("HTTP", "tcp") == nullptr);
auto* ntp = getservbyname_nid_postfix("ntp", "udp");
Require(ntp && NetworkPort(ntp, 123));
std::thread([] {
auto* ssh = getservbyname_nid_postfix("ssh", "tcp");
Require(ssh && std::strcmp(ssh->name, "ssh") == 0 && NetworkPort(ssh, 22));
}).join();
Require(std::strcmp(ntp->name, "ntp") == 0 && std::strcmp(ntp->protocol, "udp") == 0 && NetworkPort(ntp, 123));
return 0;
}

View File

@ -9,3 +9,4 @@
- [mkstemp and isatty](../../../core/libs/prx/libScePosixForWebKit/Filesystem.cpp) (libScePosixForWebKit) follow FreeBSD's [temporary-file API](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/stdio/mktemp.c) and [terminal check](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/gen/isatty.c), without console verification. `mkstemp` replaces all trailing `X` characters, accepts a fixed name, and creates files through the guest path resolver with exclusive read/write access and mode 0600. Windows file permissions follow the existing kernel file implementation. `isatty` tests Windows console handles rather than all character devices; files, pipes and guest sockets return `ENOTTY`, closed descriptors return `EBADF`.
- [arc4random](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) returns four bytes of host OS entropy (`getentropy`, `rand_s` on Windows), as `arc4random_buf` does, uniform over the 32-bit range like FreeBSD 11.4's RC4 keystream in [arc4random.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/arc4random.c). The console's generator and its reseeding are unknown; `arc4random_uniform`, `arc4random_stir` and `arc4random_addrandom` are not in the module's export set and are absent
- [fnmatch](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) follows FreeBSD 11 [fnmatch.c](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/gen/fnmatch.c) in the C locale: each byte is one character, `FNM_CASEFOLD` folds ASCII letters only, and bracket ranges compare byte values. `[:class:]` names are not character classes: as in FreeBSD 11 through 14, `[[:digit:]]` is the set of `[`, `:`, `d`, `i`, `g`, `t` followed by a literal `]` (classes were added to `fnmatch.c` after 14). Multibyte characters and locale collation are not modelled. A null pattern or string throws
- [getservbyname](../../../core/libs/prx/libScePosixForWebKit/Resolver.cpp) (libScePosixForWebKit) searches a built-in copy of 37 lines of FreeBSD 11.4's [etc/services](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/etc/services) (ftp-data, ftp, ssh, telnet, smtp, domain, tftp, http, pop3, ntp, imap, https, rtsp, submission, imaps, pop3s with their aliases and protocols, in file order) the way [getservent.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/net/getservent.c) searches the file: case-sensitive name or alias, a null protocol takes the first match, the port in network byte order, the result in per-thread storage. The console's `/etc/services` is unknown, so any other service returns null without setting `errno`, as for a name the file lacks. `getaddrinfo` still resolves service names through the host's service database