Merge pull request #3798 from YossiYad/carry/posixforwebkit-getservbyname

feat(libScePosixForWebKit): implement getservbyname
This commit is contained in:
Dean 2026-10-14 09:24:05 +13:00 committed by GitHub
commit 0aa4cb1c2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 166 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

@ -10,12 +10,14 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include <cstdint>
#include <algorithm>
#include <array>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <new>
#include <string>
#include <type_traits>
#include <vector>
#include "GuestResolver.hpp"
@ -31,6 +33,66 @@ 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"}},
};
constexpr std::size_t ServiceNameSize = 11;
constexpr std::size_t ServiceAliasSize = 9;
constexpr std::size_t ServiceProtocolSize = 5;
constexpr std::size_t ServiceAliasCount = std::tuple_size_v<decltype(ServiceRecord::aliases)>;
template<typename TSelect>
constexpr std::size_t LongestServiceText(TSelect select) {
std::size_t longest = 0;
for (const auto& service : Services)
for (const char* text : select(service))
if (text) longest = std::max(longest, std::char_traits<char>::length(text));
return longest;
}
static_assert(ServiceNameSize == LongestServiceText([](const ServiceRecord& record) { return std::array{record.name}; }) + 1);
static_assert(ServiceAliasSize == LongestServiceText([](const ServiceRecord& record) { return record.aliases; }) + 1);
static_assert(ServiceProtocolSize == LongestServiceText([](const ServiceRecord& record) { return std::array{record.protocol}; }) + 1);
struct ServiceLookup {
GuestResolver::ServiceEntry entry;
std::array<char, ServiceNameSize> name;
std::array<char, ServiceProtocolSize> protocol;
std::array<std::array<char, ServiceAliasSize>, ServiceAliasCount> aliasNames;
std::array<char*, ServiceAliasCount + 1> aliases;
};
static_assert(std::is_trivially_destructible_v<ServiceLookup>);
template<std::size_t Size>
char* CopyServiceText(std::array<char, Size>& target, const char* text) {
std::memcpy(target.data(), text, std::strlen(text) + 1);
return target.data();
}
bool NumericAddress(const char* name, std::array<unsigned char, 4>& address) {
std::array<unsigned char, 3> parts{};
std::size_t count = 0;
@ -155,6 +217,27 @@ 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;
static thread_local ServiceLookup lookup;
std::size_t count = 0;
for (const char* alias : service.aliases) {
if (!alias) continue;
lookup.aliases[count] = CopyServiceText(lookup.aliasNames[count], alias);
++count;
}
lookup.aliases[count] = nullptr;
lookup.entry = {CopyServiceText(lookup.name, service.name), lookup.aliases.data(), htons(service.port),
CopyServiceText(lookup.protocol, service.protocol)};
return &lookup.entry;
}
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