diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 6a62a8778..bd62cf849 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -22,6 +22,9 @@ constexpr int GuestEfault = 14; constexpr int GuestEinval = 22; constexpr int GuestEexist = 17; constexpr int GuestExdev = 18; +#ifdef _WIN32 +constexpr int WindowsPrivilegeNotHeld = 1314; +#endif std::atomic creationMask{022}; @@ -103,4 +106,28 @@ int APS5_VABI fchdir_nid_postfix(int fd) { return 0; } +int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { + if (target == nullptr || created == nullptr) return static_cast(Fail(GuestEfault)); + if (*target == '\0' || *created == '\0') return static_cast(Fail(GuestEnoent)); + if (*target == '/') { + NotImplemented_nid_no_patch("symlink: absolute targets"); + return -1; + } + const auto native = ResolvePath_nid_no_patch(created); + std::error_code error; + if (std::filesystem::exists(std::filesystem::symlink_status(native, error))) return static_cast(Fail(GuestEexist)); + if (!std::filesystem::is_directory(native.parent_path(), error)) return static_cast(Fail(GuestEnoent)); + const std::filesystem::path contents(target); + if (std::filesystem::is_directory(native.parent_path() / contents, error)) + std::filesystem::create_directory_symlink(contents, native, error); + else + std::filesystem::create_symlink(contents, native, error); +#ifdef _WIN32 + if (error.category() == std::system_category() && error.value() == WindowsPrivilegeNotHeld) return static_cast(Fail(GuestEperm)); +#endif + if (error) throw std::system_error(error, "symlink"); + RecordWrittenPath_nid_no_patch(native); + return 0; +} + } diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index b4530aa24..d8181f026 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -23,6 +23,7 @@ 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*); +int APS5_VABI symlink_nid_postfix(const char*, const char*); std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t); int* APS5_VABI __error_nid_postfix(); } @@ -108,6 +109,23 @@ int main() { Require(sceKernelClose(directory) == 0); Require(fchdir_nid_postfix(directory) == -1 && Error() == 9); Require(chdir_nid_postfix("/") == 0); + const auto symbolic = (root / "symbolic").string(); + const int created = symlink_nid_postfix("original.bin", symbolic.c_str()); + if (created == 0) { + Require(std::filesystem::is_symlink(std::filesystem::symlink_status(symbolic))); + Require(readlink_nid_postfix(symbolic.c_str(), buffer, sizeof(buffer)) == 12 && std::memcmp(buffer, "original.bin", 12) == 0); + Require(std::filesystem::file_size(symbolic) == std::filesystem::file_size(original)); + Require(symlink_nid_postfix("original.bin", symbolic.c_str()) == -1 && Error() == 17); + const auto dangling = (root / "dangling").string(); + Require(symlink_nid_postfix("missing.bin", dangling.c_str()) == 0); + Require(readlink_nid_postfix(dangling.c_str(), buffer, sizeof(buffer)) == 11 && !std::filesystem::exists(dangling)); + } else { + Require(created == -1 && Error() == 1); + std::fprintf(stderr, "symbolic links unavailable: the symlink success cases not tested\n"); + } + Require(symlink_nid_postfix("original.bin", (root / "missing" / "symbolic").string().c_str()) == -1 && Error() == 2); + Require(symlink_nid_postfix(nullptr, symbolic.c_str()) == -1 && Error() == 14); + Require(symlink_nid_postfix("original.bin", "") == -1 && Error() == 2); std::filesystem::remove_all(root); return 0; }