feat(libkernel_sys): implement fchdir
FreeBSD fchdir(2) changes the working directory to the directory referenced by a descriptor. The guest imports fchdir from libkernel_sys (PPSA99764, issue #1578) and the module did not export it. The descriptor's directory is resolved from the path recorded when the directory was opened on Windows, and from the /proc/self/fd link target on Linux. The guest working directory is then set through the libc routine shared with chdir. A descriptor that is not open returns EBADF and one that is not a directory returns ENOTDIR. AI-assisted: yes (cherry picked from commit 76329cbb08781e13d757c3ff955acf71f8bd506f)
This commit is contained in:
parent
99c1c26347
commit
5a8091fafd
|
|
@ -14,6 +14,7 @@ extern "C" void CxaFinalize_nid_no_patch(void* dsoHandle);
|
|||
extern "C" bool StopAtPrefixLetter_nid_no_patch(const char* str, char** endptr, int base);
|
||||
|
||||
extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path);
|
||||
extern "C" int SetWorkingDirectory_nid_no_patch(const std::filesystem::path& resolved);
|
||||
// Guest path aliases: a guest prefix such as "/savedata0" (a save-data mount point) resolves to a host
|
||||
// directory instead of the run directory; the prefix matches whole path components only.
|
||||
extern "C" void AddPathAlias_nid_no_patch(const char* guestPrefix, const char* hostPath);
|
||||
|
|
|
|||
|
|
@ -305,21 +305,31 @@ extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path) {
|
|||
return Resolve(state, path);
|
||||
}
|
||||
|
||||
extern "C" int SetWorkingDirectory_nid_no_patch(const std::filesystem::path& resolved) {
|
||||
auto& state = Directories();
|
||||
std::lock_guard lock(state.mutex);
|
||||
std::error_code error;
|
||||
const auto canonical = std::filesystem::canonical(resolved, error);
|
||||
if (error) return DirectoryFailure(error);
|
||||
if (!std::filesystem::is_directory(canonical, error)) return error ? DirectoryFailure(error) : 20;
|
||||
const auto relative = canonical.lexically_relative(state.root);
|
||||
if (relative.empty() || *relative.begin() == "..") return 45;
|
||||
state.current = canonical;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int APS5_VABI chdir_nid_postfix(const char* path) {
|
||||
if (!path) { errno = 14; return -1; }
|
||||
if (!*path) { errno = 2; return -1; }
|
||||
try {
|
||||
auto& state = Directories();
|
||||
std::lock_guard lock(state.mutex);
|
||||
std::error_code error;
|
||||
const auto resolved = std::filesystem::canonical(Resolve(state, path), error);
|
||||
if (error) { errno = DirectoryFailure(error); return -1; }
|
||||
if (!std::filesystem::is_directory(resolved, error)) {
|
||||
errno = error ? DirectoryFailure(error) : 20; return -1;
|
||||
std::filesystem::path resolved;
|
||||
{
|
||||
auto& state = Directories();
|
||||
std::lock_guard lock(state.mutex);
|
||||
resolved = Resolve(state, path);
|
||||
}
|
||||
const auto relative = resolved.lexically_relative(state.root);
|
||||
if (relative.empty() || *relative.begin() == "..") { errno = 45; return -1; }
|
||||
state.current = resolved;
|
||||
const int error = SetWorkingDirectory_nid_no_patch(resolved);
|
||||
if (error) { errno = error; return -1; }
|
||||
return 0;
|
||||
} catch (const std::bad_alloc&) { errno = 12; return -1; }
|
||||
catch (const std::filesystem::filesystem_error& error) { errno = DirectoryFailure(error.code()); return -1; }
|
||||
|
|
|
|||
|
|
@ -92,4 +92,22 @@ int ReadDirectoryDescriptor(int fd, char* buf, int nbytes) {
|
|||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace File {
|
||||
|
||||
std::optional<std::filesystem::path> DirectoryDescriptorPath(int fd) {
|
||||
if (fd < 0) return std::nullopt;
|
||||
std::error_code error;
|
||||
const auto link = std::filesystem::path("/proc/self/fd") / std::to_string(fd);
|
||||
const auto target = std::filesystem::read_symlink(link, error);
|
||||
if (error) return std::nullopt;
|
||||
if (!std::filesystem::is_directory(target, error)) return std::nullopt;
|
||||
return target;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
#include <system_error>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libkernel/File/include/DirectoryDescriptor.hpp"
|
||||
#include "prx/libkernel/File/include/File.hpp"
|
||||
|
||||
extern "C" int* APS5_VABI __error_nid_postfix();
|
||||
|
||||
|
|
@ -17,10 +19,12 @@ namespace {
|
|||
constexpr int GuestEnoent = 2;
|
||||
constexpr int GuestEperm = 1;
|
||||
constexpr int GuestEio = 5;
|
||||
constexpr int GuestEbadf = 9;
|
||||
constexpr int GuestEfault = 14;
|
||||
constexpr int GuestEnotdir = 20;
|
||||
constexpr int GuestEinval = 22;
|
||||
constexpr int GuestEexist = 17;
|
||||
constexpr int GuestExdev = 18;
|
||||
constexpr int GuestEinval = 22;
|
||||
|
||||
std::atomic<std::uint16_t> creationMask{022};
|
||||
|
||||
|
|
@ -87,4 +91,13 @@ int APS5_VABI link_nid_postfix(const char* existing, const char* created) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI fchdir_nid_postfix(int fd) {
|
||||
if (!DescriptorIsOpen_nid_no_patch(fd)) return static_cast<int>(Fail(GuestEbadf));
|
||||
const auto path = File::DirectoryDescriptorPath(fd);
|
||||
if (!path) return static_cast<int>(Fail(GuestEnotdir));
|
||||
const int error = SetWorkingDirectory_nid_no_patch(*path);
|
||||
if (error != 0) return static_cast<int>(Fail(error));
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libkernel/File/include/FileFlags.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
|
@ -11,6 +12,11 @@
|
|||
|
||||
extern "C" {
|
||||
std::int64_t APS5_VABI readlink_nid_postfix(const char*, char*, std::size_t);
|
||||
int APS5_VABI fchdir_nid_postfix(int);
|
||||
int APS5_VABI chdir_nid_postfix(const char*);
|
||||
char* APS5_VABI getcwd_nid_postfix(char*, std::size_t);
|
||||
int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t);
|
||||
int APS5_VABI sceKernelClose(int);
|
||||
int APS5_VABI setsid_nid_postfix(void);
|
||||
int APS5_VABI link_nid_postfix(const char*, const char*);
|
||||
std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t);
|
||||
|
|
@ -74,6 +80,20 @@ int main() {
|
|||
Require(link_nid_postfix(nullptr, linked.c_str()) == -1 && Error() == 14);
|
||||
Require(link_nid_postfix(original.c_str(), "") == -1 && Error() == 2);
|
||||
Require(std::filesystem::remove(linked) && std::filesystem::hard_link_count(original) == 1);
|
||||
const int directory = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
|
||||
Require(directory >= 0);
|
||||
const int fileDescriptor = sceKernelOpen(file.c_str(), SCE_KERNEL_O_RDONLY, 0);
|
||||
Require(fileDescriptor >= 0);
|
||||
Require(fchdir_nid_postfix(-1) == -1 && Error() == 9);
|
||||
Require(fchdir_nid_postfix(fileDescriptor) == -1 && Error() == 20);
|
||||
Require(fchdir_nid_postfix(directory) == 0);
|
||||
char directoryPath[1024];
|
||||
Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath);
|
||||
Require(std::strcmp(directoryPath, ("/" + root.filename().string()).c_str()) == 0);
|
||||
Require(sceKernelClose(fileDescriptor) == 0);
|
||||
Require(sceKernelClose(directory) == 0);
|
||||
Require(fchdir_nid_postfix(directory) == -1 && Error() == 9);
|
||||
Require(chdir_nid_postfix("/") == 0);
|
||||
std::filesystem::remove_all(root);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -349,6 +349,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [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
|
||||
- [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.
|
||||
- [Windows file timestamps](../../core/libs/prx/libkernel/File/include/WindowsFileTime.hpp) use the host filesystem's timestamp range and resolution. `utimes` and `futimes` reject timestamps outside nonnegative signed 64-bit Windows file-time ticks with `EINVAL`, including exactly 1601-01-01 00:00:00 UTC, whose zero value means "leave unchanged" to `SetFileTime`. Guest directory descriptors still resolve through their recorded path. Filesystems with coarser timestamp resolution cannot retain every guest microsecond
|
||||
- [fchdir](../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) changes the guest working directory to the descriptor's directory: on Windows through the path recorded when the descriptor was opened, on Linux through the `/proc/self/fd` link target. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. FreeBSD `change_dir`'s search-permission check (`EACCES`) is not modelled, and a directory renamed after it was opened is still reached through the recorded path. Console behaviour is unverified
|
||||
- [Windows file identity](../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) - `stat` and `fstat` report the volume serial number as `st_dev` and the 64-bit file index folded into the guest's 32-bit `st_ino` (never 0), so two different files can in principle share an `st_ino`; the fold is not taken from a reference. `st_nlink` is clamped to 65535, the width of the guest field. The call keeps the `_wstat64` values when `GetFileInformationByHandle` fails
|
||||
- On Linux, [direct memory](../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) lives in one sparse memfd at file offset = physical address, so the [GPU imports](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) can share one udmabuf buffer object per 64 MiB of it. Released direct memory is not punched out of the file, because a live import of its chunk pins the pages and the GPU would keep reading the old ones: its pages stay committed (the process keeps the high-water mark of the direct memory it used) and are zeroed when the range is allocated again.
|
||||
- [Per-face depth bias](../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver): Vulkan has one depth bias per draw, so a draw that rasterizes both faces with different front and back bias (PA_SU_SC_MODE_CNTL, PA_SU_POLY_OFFSET_FRONT/BACK_*) is recorded twice, front faces with the back faces culled and then back faces with the front faces culled. The vertex stages run twice, so a vertex shader that writes memory writes twice, and all front faces land before all back faces instead of in primitive order, which differs only for blending or stencil updates that depend on the order.
|
||||
|
|
|
|||
Loading…
Reference in New Issue