feat(libkernel_sys): implement uuidgen

Generate version 1 UUIDs as FreeBSD 11's kern_uuidgen does: consecutive
timestamps from the guest CLOCK_REALTIME, a 14-bit clock sequence that is
randomised on first use and incremented when the clock has not passed the
last batch, and a random multicast node. A count outside 1 to 2048 fails
with EINVAL and a null store with EFAULT.
This commit is contained in:
Nikolay Polukhin authored and gazaret committed 2026-10-26 11:10:39 +05:00
1 parent 14c99b52ac
commit a05498c099
6 files changed
+177 -5

No files matched your search

+11
View File
@@ -510,6 +510,17 @@ if(BUILD_TESTING)
PASS_REGULAR_EXPRESSION "C\\+\\+ exception St9bad_alloc raised on thread [0-9]+ cannot be delivered: 0x[0-9a-f]+ undeliverable_host_exception_tests\\.exe\\+0x[0-9a-f]+ has no unwind data")
endif()
add_test_executable(guest_kernel_sys_uuid_tests tests/GuestKernelSysUuid.cpp)
target_include_directories(guest_kernel_sys_uuid_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_kernel_sys_uuid_tests PRIVATE libkernel_sys libkernel libc)
configure_windows_unwind(guest_kernel_sys_uuid_tests)
add_test(NAME guest_kernel_sys_uuid COMMAND guest_kernel_sys_uuid_tests)
set_tests_properties(guest_kernel_sys_uuid PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_kernel_sys_uuid PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
endif()
add_test_executable(crash_text_tests tests/CrashText.cpp)
target_include_directories(crash_text_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if(MINGW)
@@ -1,3 +1,5 @@
add_library(${LIB_NAME} SHARED EXCLUDE_FROM_ALL Export.cpp Export.Discovery.cpp Aio.cpp ThreadQueries.cpp)
target_include_directories(${LIB_NAME} PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(${LIB_NAME})
target_sources(${LIB_NAME} PRIVATE Uuid.cpp)
@@ -2355,11 +2355,6 @@ int APS5_VABI utrace_nid_postfix(void) {
return 0;
}
int APS5_VABI uuidgen_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI vfork_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
+91
View File
@@ -0,0 +1,91 @@
#include <array>
#include <cstdint>
#include <mutex>
#include <stdexcept>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HostEntropy.hpp"
extern "C" int* APS5_VABI __error_nid_postfix();
extern "C" int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp);
namespace {
struct GuestUuid {
std::uint32_t timeLow;
std::uint16_t timeMid;
std::uint16_t timeHiAndVersion;
std::uint8_t clockSeqHiAndReserved;
std::uint8_t clockSeqLow;
std::array<std::uint8_t, 6> node;
};
static_assert(sizeof(GuestUuid) == 16);
constexpr int GuestEfault = 14;
constexpr int GuestEinval = 22;
constexpr int GuestClockRealtime = 0;
constexpr int UuidgenMaxCount = 2048;
constexpr std::uint64_t UuidTimeMask = (std::uint64_t{1} << 60) - 1;
constexpr std::uint64_t UuidGregorianOffset = 0x01B21DD213814000ULL;
std::mutex uuidMutex;
std::array<std::uint8_t, 6> uuidNode{};
bool uuidNodeReady = false;
std::uint64_t uuidLastTime = 0;
std::uint16_t uuidLastSequence = 0;
int Fail(int error) {
*__error_nid_postfix() = error;
return -1;
}
std::uint64_t UuidTime() {
KernelTimespec now{};
if (clock_gettime_nid_postfix(GuestClockRealtime, &now) != 0) throw std::runtime_error("uuidgen: clock_gettime(CLOCK_REALTIME) failed");
const auto ticks = UuidGregorianOffset + static_cast<std::uint64_t>(now.tv_sec) * 10000000ULL + static_cast<std::uint64_t>(now.tv_nsec) / 100;
return ticks & UuidTimeMask;
}
}
extern "C" {
int APS5_VABI uuidgen_nid_postfix(GuestUuid* store, int count) {
if (count < 1 || count > UuidgenMaxCount) return Fail(GuestEinval);
std::uint64_t time = 0;
std::uint16_t sequence = 0;
std::array<std::uint8_t, 6> node{};
{
std::lock_guard lock(uuidMutex);
if (!uuidNodeReady) {
HostEntropy_nid_no_patch(uuidNode.data(), uuidNode.size());
uuidNode[0] |= 0x01;
uuidNodeReady = true;
}
node = uuidNode;
time = UuidTime();
if (uuidLastTime == 0) {
HostEntropy_nid_no_patch(&sequence, sizeof(sequence));
sequence &= 0x3fff;
} else if (uuidLastTime >= time) {
sequence = static_cast<std::uint16_t>((uuidLastSequence + 1) & 0x3fff);
} else {
sequence = uuidLastSequence;
}
uuidLastSequence = sequence;
uuidLastTime = (time + static_cast<std::uint64_t>(count) - 1) & UuidTimeMask;
}
if (store == nullptr) return Fail(GuestEfault);
const auto variantSequence = static_cast<std::uint16_t>(sequence | 0x8000);
for (int index = 0; index < count; ++index, ++time) {
store[index].timeLow = static_cast<std::uint32_t>(time);
store[index].timeMid = static_cast<std::uint16_t>(time >> 32);
store[index].timeHiAndVersion = static_cast<std::uint16_t>(((time >> 48) & 0x0fff) | 0x1000);
store[index].clockSeqHiAndReserved = static_cast<std::uint8_t>(variantSequence >> 8);
store[index].clockSeqLow = static_cast<std::uint8_t>(variantSequence);
store[index].node = node;
}
return 0;
}
}
+72
View File
@@ -0,0 +1,72 @@
#include "SceTypes.hpp"
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <source_location>
extern "C" {
int APS5_VABI uuidgen_nid_postfix(void*, int);
int* APS5_VABI __error_nid_postfix();
}
namespace {
struct Uuid {
std::uint32_t timeLow;
std::uint16_t timeMid;
std::uint16_t timeHiAndVersion;
std::uint8_t clockSeqHiAndReserved;
std::uint8_t clockSeqLow;
std::uint8_t node[6];
};
void Require(bool condition, std::source_location location = std::source_location::current()) {
if (!condition) {
std::fprintf(stderr, "uuidgen check failed at line %u (guest errno %d)\n", location.line(), *__error_nid_postfix());
std::abort();
}
}
int Error() {
return *__error_nid_postfix();
}
std::uint64_t Timestamp(const Uuid& uuid) {
return (static_cast<std::uint64_t>(uuid.timeHiAndVersion & 0x0fff) << 48) | (static_cast<std::uint64_t>(uuid.timeMid) << 32) | uuid.timeLow;
}
std::uint16_t Sequence(const Uuid& uuid) {
return static_cast<std::uint16_t>(((uuid.clockSeqHiAndReserved & 0x3f) << 8) | uuid.clockSeqLow);
}
}
int main() {
std::array<Uuid, 3> first{};
std::array<Uuid, 2> second{};
Require(uuidgen_nid_postfix(first.data(), 0) == -1 && Error() == 22);
Require(uuidgen_nid_postfix(first.data(), 2049) == -1 && Error() == 22);
Require(uuidgen_nid_postfix(nullptr, 1) == -1 && Error() == 14);
Require(uuidgen_nid_postfix(first.data(), static_cast<int>(first.size())) == 0);
Require(uuidgen_nid_postfix(second.data(), static_cast<int>(second.size())) == 0);
for (const auto& uuid : first) {
Require((uuid.timeHiAndVersion >> 12) == 1);
Require((uuid.clockSeqHiAndReserved & 0xc0) == 0x80);
Require((uuid.node[0] & 0x01) != 0);
Require(std::memcmp(uuid.node, first[0].node, sizeof(uuid.node)) == 0);
Require(Sequence(uuid) == Sequence(first[0]));
}
Require(Timestamp(first[1]) == Timestamp(first[0]) + 1 && Timestamp(first[2]) == Timestamp(first[0]) + 2);
Require(std::memcmp(second[0].node, first[0].node, sizeof(first[0].node)) == 0);
const bool later = Timestamp(second[0]) > Timestamp(first[2]) && Sequence(second[0]) == Sequence(first[0]);
const bool bumped = Timestamp(second[0]) <= Timestamp(first[2]) && Sequence(second[0]) == ((Sequence(first[0]) + 1) & 0x3fff);
Require(later || bumped);
const auto unixTicks = static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch()).count() / 100);
const auto gregorianTicks = unixTicks + 0x01B21DD213814000ULL;
const auto stamp = Timestamp(first[0]);
Require(stamp + 600000000ULL > gregorianTicks && stamp < gregorianTicks + 600000000ULL);
}
+1
View File
@@ -23,6 +23,7 @@
- [link](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows resolves a symbolic link source through the host, which follows longer chains than FreeBSD: a chain of 40 links is linked, where FreeBSD 11.4 returns `ELOOP` after 32 (`MAXSYMLINKS`). A loop returns `ELOOP` as on FreeBSD
- [pthread_attr_setcreatesuspend_np, pthread_resume_np, pthread_resume_all_np, pthread_multi_np, scePthreadAttrSetcreatesuspend, scePthreadResume, scePthreadResumeAll and scePthreadMulti](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - when a thread created suspended stops relative to its signal mask is unmeasured on a console. Return values and errors follow FreeBSD 11.4 libthr ([`thr_attr.c`](https://github.com/freebsd/freebsd-src/blob/8c639945/lib/libthr/thread/thr_attr.c#L369-L381), [`thr_resume_np.c`](https://github.com/freebsd/freebsd-src/blob/8c639945/lib/libthr/thread/thr_resume_np.c#L47-L97), [`thr_multi_np.c`](https://github.com/freebsd/freebsd-src/blob/8c639945/lib/libthr/thread/thr_multi_np.c#L40-L51)) and the code of firmware 13.42's `libkernel_sys.sprx` ([#9573](https://github.com/boykopovar/AnyPS5/pull/9573#issuecomment-6393877843)), where the sce forms add `0x80020000` to a non-zero errno
- [fstatat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `stat`, or to `lstat` with `AT_SYMLINK_NOFOLLOW` (0x200). Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory, and flags other than `AT_SYMLINK_NOFOLLOW` return `EINVAL`. PPSA74126 imports it. Console behaviour is unverified
- [uuidgen](../../../core/libs/prx/libkernel_sys/Uuid.cpp) (libkernel_sys) - the node is 48 random bits with the multicast bit set, chosen once per process, where the console's kernel uses the address of its first Ethernet interface; that the PS5 kernel registers one is assumed, not measured. Otherwise it follows FreeBSD 11's [`kern_uuidgen` and `sys_uuidgen`](https://github.com/freebsd/freebsd-src/blob/53c4471833e8e5e57b4d1b501b1a52b343bb0451/sys/kern/kern_uuid.c#L129-L198): version 1 UUIDs with consecutive timestamps from the guest `CLOCK_REALTIME`, a random 14-bit clock sequence that is incremented when the clock has not passed the last batch, and `EINVAL` for a count outside 1 to 2048. A null store returns `EFAULT` after the state advances, as `copyout` fails there; other invalid addresses are not detected
- [unlinkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rmdir` (with `AT_REMOVEDIR`, 0x800) or `unlink`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Removing a directory without `AT_REMOVEDIR` fails with `EPERM`, removing `.` or `..` with `AT_REMOVEDIR` fails with `EINVAL`, and flags other than `AT_REMOVEDIR` fail with `EINVAL`. Console behaviour is unverified
- [renameat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rename`. Any other relative path is joined to its descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to relative descriptors, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Console behaviour is unverified
- [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask