Merge pull request #3545 from babyhuey/feat/posixforwebkit-fnmatch
feat(libScePosixForWebKit): implement fnmatch
This commit is contained in:
commit
f5824092cf
|
|
@ -17,6 +17,15 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_posix_filesystem PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePosixForWebKit>")
|
||||
endif()
|
||||
add_test_executable(guest_fnmatch_tests ${LIBS_INCLUDE_DIR}/tests/GuestFnmatch.cpp)
|
||||
target_include_directories(guest_fnmatch_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_fnmatch_tests PRIVATE libScePosixForWebKit libc)
|
||||
configure_windows_unwind(guest_fnmatch_tests)
|
||||
add_test(NAME guest_fnmatch COMMAND guest_fnmatch_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_fnmatch PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePosixForWebKit>")
|
||||
endif()
|
||||
add_test_executable(guest_resolver_tests ${LIBS_INCLUDE_DIR}/tests/GuestResolver.cpp)
|
||||
target_include_directories(guest_resolver_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_resolver_tests PRIVATE libScePosixForWebKit libSceNet libc)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,156 @@
|
|||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int FnmNomatch = 1;
|
||||
constexpr int FnmNoescape = 0x01;
|
||||
constexpr int FnmPathname = 0x02;
|
||||
constexpr int FnmPeriod = 0x04;
|
||||
constexpr int FnmLeadingDir = 0x08;
|
||||
constexpr int FnmCasefold = 0x10;
|
||||
|
||||
enum class RangeResult { Match, NoMatch, Error };
|
||||
|
||||
unsigned char FoldCase(unsigned char c, int flags) {
|
||||
return (flags & FnmCasefold) && c >= 'A' && c <= 'Z' ? static_cast<unsigned char>(c - 'A' + 'a') : c;
|
||||
}
|
||||
|
||||
RangeResult MatchRange(const char* pattern, unsigned char test, int flags, const char** next) {
|
||||
const bool negate = *pattern == '!' || *pattern == '^';
|
||||
if (negate) ++pattern;
|
||||
test = FoldCase(test, flags);
|
||||
bool ok = false;
|
||||
const char* const origin = pattern;
|
||||
for (;;) {
|
||||
if (*pattern == ']' && pattern > origin) {
|
||||
++pattern;
|
||||
break;
|
||||
}
|
||||
if (*pattern == '\0') return RangeResult::Error;
|
||||
if (*pattern == '/' && (flags & FnmPathname)) return RangeResult::NoMatch;
|
||||
if (*pattern == '\\' && !(flags & FnmNoescape)) ++pattern;
|
||||
const unsigned char low = FoldCase(static_cast<unsigned char>(*pattern), flags);
|
||||
if (*pattern != '\0') ++pattern;
|
||||
if (*pattern == '-' && pattern[1] != '\0' && pattern[1] != ']') {
|
||||
if (*++pattern == '\\' && !(flags & FnmNoescape)) ++pattern;
|
||||
const unsigned char high = static_cast<unsigned char>(*pattern);
|
||||
if (high == '\0') return RangeResult::Error;
|
||||
++pattern;
|
||||
if (low <= test && test <= FoldCase(high, flags)) ok = true;
|
||||
} else if (low == test) {
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
*next = pattern;
|
||||
return ok == negate ? RangeResult::NoMatch : RangeResult::Match;
|
||||
}
|
||||
|
||||
int MatchPattern(const char* pattern, const char* string, int flags) {
|
||||
const char* const start = string;
|
||||
const char* backtrackPattern = nullptr;
|
||||
const char* backtrackString = nullptr;
|
||||
const auto leadingPeriod = [&](const char* at) {
|
||||
return *at == '.' && (flags & FnmPeriod) && (at == start || ((flags & FnmPathname) && at[-1] == '/'));
|
||||
};
|
||||
for (;;) {
|
||||
const unsigned char pc = static_cast<unsigned char>(*pattern);
|
||||
if (pc != '\0') ++pattern;
|
||||
const unsigned char sc = static_cast<unsigned char>(*string);
|
||||
bool mismatch = false;
|
||||
bool literal = false;
|
||||
unsigned char expected = pc;
|
||||
switch (pc) {
|
||||
case '\0':
|
||||
if ((flags & FnmLeadingDir) && sc == '/') return 0;
|
||||
if (sc == '\0') return 0;
|
||||
mismatch = true;
|
||||
break;
|
||||
case '?':
|
||||
if (sc == '\0') return FnmNomatch;
|
||||
if ((sc == '/' && (flags & FnmPathname)) || leadingPeriod(string)) {
|
||||
mismatch = true;
|
||||
break;
|
||||
}
|
||||
++string;
|
||||
break;
|
||||
case '*': {
|
||||
char c = *pattern;
|
||||
while (c == '*') c = *++pattern;
|
||||
if (leadingPeriod(string)) {
|
||||
mismatch = true;
|
||||
break;
|
||||
}
|
||||
if (c == '\0') {
|
||||
if (flags & FnmPathname) return (flags & FnmLeadingDir) || std::strchr(string, '/') == nullptr ? 0 : FnmNomatch;
|
||||
return 0;
|
||||
}
|
||||
if (c == '/' && (flags & FnmPathname)) {
|
||||
string = std::strchr(string, '/');
|
||||
if (string == nullptr) return FnmNomatch;
|
||||
break;
|
||||
}
|
||||
backtrackPattern = pattern;
|
||||
backtrackString = string;
|
||||
break;
|
||||
}
|
||||
case '[': {
|
||||
if (sc == '\0') return FnmNomatch;
|
||||
if ((sc == '/' && (flags & FnmPathname)) || leadingPeriod(string)) {
|
||||
mismatch = true;
|
||||
break;
|
||||
}
|
||||
const char* next = nullptr;
|
||||
const RangeResult result = MatchRange(pattern, sc, flags, &next);
|
||||
if (result == RangeResult::Error) {
|
||||
literal = true;
|
||||
} else if (result == RangeResult::Match) {
|
||||
pattern = next;
|
||||
++string;
|
||||
} else {
|
||||
mismatch = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '\\':
|
||||
if (!(flags & FnmNoescape)) {
|
||||
expected = static_cast<unsigned char>(*pattern);
|
||||
if (expected == '\0') return FnmNomatch;
|
||||
++pattern;
|
||||
}
|
||||
literal = true;
|
||||
break;
|
||||
default:
|
||||
literal = true;
|
||||
break;
|
||||
}
|
||||
if (literal) {
|
||||
if (sc != '\0') ++string;
|
||||
if (FoldCase(expected, flags) != FoldCase(sc, flags)) mismatch = true;
|
||||
}
|
||||
if (mismatch) {
|
||||
if (backtrackPattern == nullptr) return FnmNomatch;
|
||||
const unsigned char bc = static_cast<unsigned char>(*backtrackString);
|
||||
if (bc == '\0') return FnmNomatch;
|
||||
if (bc == '/' && (flags & FnmPathname)) return FnmNomatch;
|
||||
++backtrackString;
|
||||
pattern = backtrackPattern;
|
||||
string = backtrackString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI fnmatch_nid_postfix(const char* pattern, const char* string, int flags) {
|
||||
if (pattern == nullptr || string == nullptr) throw std::invalid_argument("fnmatch: null pattern or string");
|
||||
return MatchPattern(pattern, string, flags);
|
||||
}
|
||||
|
||||
char* APS5_VABI strcasestr_nid_postfix(const char* text, const char* needle) {
|
||||
if (*needle == '\0') return const_cast<char*>(text);
|
||||
for (; *text != '\0'; ++text) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
extern "C" int APS5_VABI fnmatch_nid_postfix(const char*, const char*, int);
|
||||
struct Case {
|
||||
const char* pattern;
|
||||
const char* string;
|
||||
int flags;
|
||||
int result;
|
||||
};
|
||||
int main() {
|
||||
static const Case cases[] = {
|
||||
{"*.txt", "notes.txt", 0x00, 0},
|
||||
{"*.txt", "notes.TXT", 0x00, 1},
|
||||
{"*.txt", "notes.TXT", 0x10, 0},
|
||||
{"a?c", "abc", 0x00, 0},
|
||||
{"a?c", "ac", 0x00, 1},
|
||||
{"a[bc]d", "acd", 0x00, 0},
|
||||
{"a[!bc]d", "acd", 0x00, 1},
|
||||
{"a[^bc]d", "aed", 0x00, 0},
|
||||
{"a[a-c]d", "abd", 0x00, 0},
|
||||
{"a[a-c]d", "add", 0x00, 1},
|
||||
{"a[]]d", "a]d", 0x00, 0},
|
||||
{"a[a-]d", "a-d", 0x00, 0},
|
||||
{"[A-C]x", "bx", 0x10, 0},
|
||||
{"*", "dir/file", 0x00, 0},
|
||||
{"*", "dir/file", 0x02, 1},
|
||||
{"dir/*", "dir/file", 0x02, 0},
|
||||
{"*/file", "dir/file", 0x02, 0},
|
||||
{"dir?file", "dir/file", 0x02, 1},
|
||||
{"dir[/]file", "dir/file", 0x02, 1},
|
||||
{"*", ".hidden", 0x04, 1},
|
||||
{".*", ".hidden", 0x04, 0},
|
||||
{"?hidden", ".hidden", 0x04, 1},
|
||||
{"dir/*", "dir/.hidden", 0x06, 1},
|
||||
{"dir/*", "dir/.hidden", 0x04, 0},
|
||||
{"dir/.*", "dir/.hidden", 0x06, 0},
|
||||
{"dir", "dir/sub/file", 0x08, 0},
|
||||
{"dir/*", "dir/sub/file", 0x0a, 0},
|
||||
{"dir", "dirx", 0x08, 1},
|
||||
{"\\*", "*", 0x00, 0},
|
||||
{"\\*", "x", 0x00, 1},
|
||||
{"\\*", "\\*", 0x01, 0},
|
||||
{"a\\", "a", 0x00, 1},
|
||||
{"a\\", "a\\", 0x01, 0},
|
||||
{"a*b*c", "axxbyyc", 0x00, 0},
|
||||
{"a*b*c", "axxbyy", 0x00, 1},
|
||||
{"*a*", "bbb", 0x00, 1},
|
||||
{"**a", "ba", 0x00, 0},
|
||||
{"", "", 0x00, 0},
|
||||
{"", "x", 0x00, 1},
|
||||
{"x", "", 0x00, 1},
|
||||
{"[", "[", 0x00, 0},
|
||||
{"[a", "[a", 0x00, 0},
|
||||
{"a[", "a[", 0x00, 0},
|
||||
{"[!]x", "x", 0x00, 1},
|
||||
{"*/", "dir/", 0x02, 0},
|
||||
{"*/", "dir", 0x02, 1},
|
||||
{"[[:digit:]]", "7", 0x00, 1},
|
||||
{"[[:digit:]]", "d]", 0x00, 0},
|
||||
{"[[:digit:]]", ":]", 0x00, 0},
|
||||
{"a[[:alpha:]]", "ab", 0x00, 1},
|
||||
};
|
||||
for (const Case& entry : cases) {
|
||||
if (fnmatch_nid_postfix(entry.pattern, entry.string, entry.flags) != entry.result) {
|
||||
std::fprintf(stderr, "fnmatch(\"%s\", \"%s\", 0x%x) != %d\n", entry.pattern, entry.string, entry.flags, entry.result);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
bool threw = false;
|
||||
try {
|
||||
fnmatch_nid_postfix(nullptr, "x", 0);
|
||||
} catch (const std::invalid_argument&) {
|
||||
threw = true;
|
||||
}
|
||||
if (!threw) std::abort();
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -359,6 +359,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [libSceOpusDec](../../core/libs/prx/libSceOpusDec/Export.cpp) uses FFmpeg for 48000 Hz mono/stereo signed-16 decoding. The six signatures, 640-byte opaque state allocation and byte-count return follow [public PS5 decoder research](https://github.com/blackbearreloaded/ps5-hardware-audio-decoding/blob/26be5a12f92b2cdc67f8a9e082fd69ae0e9a35a2/docs/DECODING.md#direct-opus-abi) and [ProsperoRadio](https://github.com/blackbearreloaded/ProsperoRadio/blob/2c4029c3a8f43e9e1dfef98374d94d99b14d745d/src/opus_decoder.cpp). Console PCM equivalence and error codes are unverified. Other sample rates, packet loss concealment, invalid inputs and termination with live decoders throw. Context identifiers and host decoder state are private registries; the firmware's opaque state layout and error recovery are unknown.
|
||||
- [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 from the same thread-local host `std::random_device` as `arc4random_buf`, 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
|
||||
- [Dinkumware thread exports](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) follow the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`cthread.c`, `xtime.c`) and its `atomic.c`, [microsoft/STL `cthread.cpp`](https://github.com/microsoft/STL/blob/main/stl/src/cthread.cpp) and C11 Annex K, without console verification. `_Thrd_t` is taken to be the guest `pthread_t`, as the `std::_Pad::_Launch(pthread**)` export suggests: `_Thrd_join` returns `_Thrd_error` (4) when `pthread_join` fails and stores the thread's return value truncated to `int`. `_Thrd_sleep` reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest `CLOCK_REALTIME`, normalizes the nanoseconds and sleeps through `nanosleep`, so it is a cancellation point; a null xtime or one whose normalization overflows throws. The console's `_Thrd_sleep` (disassembled from a title's `libc.prx`) also takes an optional `xtime*` of remaining time and returns `int`; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched. `_Thrd_join` (32-bit store of the result, 0 or 4) and the spin lock (byte exchange loop, no yield) match that disassembly. The shared_ptr spin lock is one process-wide flag that yields while it spins. `wcstombs_s` is `wcsrtombs_s` with a fresh conversion state, so `*retval` excludes the terminating null as in Annex K, not as in the Microsoft CRT, and `_TRUNCATE` has no meaning
|
||||
- [_Cnd_signal, _Cnd_init_with_name](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - take a pointer to the handle slot like the libc `_Cnd_*` exports from [#3079](https://github.com/boykopovar/AnyPS5/pull/3079) and act on the same conditions. `_Cnd_signal` wakes one waiter and returns `_Thrd_success` (0), as `_Cnd_broadcast` does; whether the console maps a failing internal signal to `_Thrd_error` is not known. The signature `_Cnd_init_with_name(_Cnd_t*, const char* name)` is inferred from [fpPS4 `ps4_mtx_internal.pas`](https://github.com/red-prig/fpPS4/blob/trunk/src/libcinternal/ps4_mtx_internal.pas), where the PS4 `_Mtx_init` is `_Mtx_init_with_name` with the name `SceLibcI_Mtx` and the name only reaches the pthread object; here it is `_Cnd_init` and the name, which may be null, is ignored. Neither is confirmed on a PS5 `libc.prx`. `_Thrd_id` still throws: microsoft/STL returns an `unsigned int` thread id, but whether the PS5 returns the `pthread_t` or a kernel thread id, and at what width, is not known
|
||||
- [libSceZlib](../../core/libs/prx/libSceZlib/Export.cpp) completes RFC1950 decompression during submission and supports one outstanding request, null-timeout waits and one result retrieval. Timed waits, multiple outstanding requests, repeated retrieval and finalization with an unread result throw. The 64-bit initialization length and 64 KiB output limit with 2 KiB destination alignment follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Compression/Zlib.cs); the PS4 initialization length is 32 bits. shadPS4 also requires the output length to be a multiple of 2 KiB and returns `0x81120016` for invalid arguments; this implementation accepts any length up to 64 KiB (SharpProspero documents only the pointer alignment) and throws on invalid arguments. Scratch memory is not used, and its firmware size/alignment requirements are unknown. The initialization errors follow SharpProspero; malformed/checksum/truncated streams report `0x811200FF` and insufficient output `0x8112001C` as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/zlib/zlib.cpp). PS5 error precedence, scheduling, completion selection, result consumption and cancellation are unverified. The payload SDK's FreeBSD kernel `z_stream` and host errno are not this library's ABI.
|
||||
|
|
|
|||
Loading…
Reference in New Issue