Merge pull request #3798 from YossiYad/carry/posixforwebkit-getservbyname
feat(libScePosixForWebKit): implement getservbyname
This commit is contained in:
commit
0aa4cb1c2f
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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