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:
parent
0f2683cb7d
commit
15bbc19501
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue