mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-05 13:20:11 +00:00
Merge pull request #15210 from Danigamer05/feat/libc-sce-v2-filesystem-calls-20261102
feat(libc): export the std::__sce_v2 filesystem calls
This commit is contained in:
8 files changed
+497
No files matched your search
@@ -145,6 +145,7 @@ target_sources(libc PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Directory.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Filesystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/VersionedFilesystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/VersionedFilesystemCalls.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rename.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Errors.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Syslog.cpp
|
||||
@@ -301,6 +302,17 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_sce_v2_filesystem PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_sce_v2_filesystem_calls_tests ${LIBS_INCLUDE_DIR}/tests/GuestSceV2FilesystemCalls.cpp)
|
||||
target_include_directories(guest_sce_v2_filesystem_calls_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_sce_v2_filesystem_calls_tests PRIVATE libc libkernel zarchive)
|
||||
configure_windows_unwind(guest_sce_v2_filesystem_calls_tests)
|
||||
add_test(NAME guest_sce_v2_filesystem_calls COMMAND guest_sce_v2_filesystem_calls_tests)
|
||||
set_tests_properties(guest_sce_v2_filesystem_calls PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_sce_v2_filesystem_calls PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
|
||||
@@ -237,6 +237,22 @@ int APS5_VABI _ZNSt8__sce_v210filesystem10_Close_dirEPv_nid_postfix(void* handle
|
||||
bool APS5_VABI _ZNSt8__sce_v210filesystem12_Current_getERA260_c_nid_postfix(char* target);
|
||||
void APS5_VABI _ZNSt8__sce_v210filesystem18_Xfilesystem_errorEPKcNS_10error_codeE_nid_postfix();
|
||||
void APS5_VABI _ZNSt8__sce_v210filesystem18_Xfilesystem_errorEPKcRKNS0_4pathENS_10error_codeE_nid_postfix();
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix(const char* path, int* perms, int* error);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsE_nid_postfix(const char* path, int* perms);
|
||||
std::uint64_t APS5_VABI _ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix(const char* path, int* error);
|
||||
std::uint64_t APS5_VABI _ZNSt8__sce_v210filesystem10_File_sizeEPKc_nid_postfix(const char* path);
|
||||
std::int64_t APS5_VABI _ZNSt8__sce_v210filesystem16_Last_write_timeEPKc_nid_postfix(const char* path);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem16_Last_write_timeEPKcPlS3__nid_postfix(const char* path, std::int64_t* seconds, std::int64_t* nanoseconds);
|
||||
bool APS5_VABI _ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix(const char* path, std::int64_t microseconds);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix(const char* path, const char* existing);
|
||||
bool APS5_VABI _ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix(const char* path);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix(const char* path);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix(const char* from, const char* to);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix(const char* path, std::uint64_t size);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix(const char* path, int perms);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix(const char* from, const char* to);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem8_StatvfsEPKcRNS0_10space_infoE_nid_postfix(const char* path, std::uint64_t* space);
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem13_Current_set2EPKc_nid_postfix(const char* path);
|
||||
char* APS5_VABI getenv_nid_postfix(const char* name);
|
||||
int APS5_VABI setenv_nid_postfix(const char* name, const char* value, int overwrite);
|
||||
int APS5_VABI unsetenv_nid_postfix(const char* name);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "SceTypes.hpp"
|
||||
#include "general/VabiMacros.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
struct FilesystemForwards_nid_no_patch {
|
||||
int (APS5_VABI* stat)(const char*, FileStat*);
|
||||
int (APS5_VABI* fstat)(int, FileStat*);
|
||||
int (APS5_VABI* open)(const char*, int, std::uint16_t);
|
||||
std::int64_t (APS5_VABI* read)(int, void*, std::size_t);
|
||||
std::int64_t (APS5_VABI* write)(int, const void*, std::size_t);
|
||||
int (APS5_VABI* close)(int);
|
||||
int (APS5_VABI* mkdir)(const char*, std::uint16_t);
|
||||
int (APS5_VABI* unlink)(const char*);
|
||||
int (APS5_VABI* rename)(const char*, const char*);
|
||||
int (APS5_VABI* truncate)(const char*, std::int64_t);
|
||||
int (APS5_VABI* chmod)(const char*, std::uint16_t);
|
||||
int (APS5_VABI* fchmod)(int, std::uint16_t);
|
||||
int (APS5_VABI* utimes)(const char*, const KernelTimeval*);
|
||||
int (APS5_VABI* posixStat)(const char*, FileStat*);
|
||||
int (APS5_VABI* posixRmdir)(const char*);
|
||||
};
|
||||
|
||||
extern "C" void RegisterFilesystemForwards_nid_no_patch(const FilesystemForwards_nid_no_patch* forwards);
|
||||
@@ -0,0 +1,212 @@
|
||||
#include "prx/libc/include/Api.hpp"
|
||||
#include "prx/libc/include/FilesystemForwards.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uint32_t KernelErrnoBase = 0x80020000u;
|
||||
constexpr std::uint32_t KernelExists = 0x80020011u;
|
||||
constexpr std::uint32_t KernelNoSuchFile = 0x80020002u;
|
||||
constexpr std::uint64_t NotFoundCodes = 0x2000000000140001ull;
|
||||
constexpr int OutOfMemory = 12;
|
||||
constexpr int NotSupported = 45;
|
||||
constexpr int NoSystemCall = 78;
|
||||
constexpr int TypeNotFound = -1;
|
||||
constexpr int TypeNone = 0;
|
||||
constexpr int TypeUnknown = 8;
|
||||
constexpr int TypesByMode[] = {6, 5, 8, 2, 8, 4, 8, 1, 8, 3, 8, 7};
|
||||
constexpr std::uint16_t DefaultDirectoryMode = 0777;
|
||||
constexpr std::uint16_t CopiedPermissions = 06777;
|
||||
constexpr int CopyTargetFlags = 0x601;
|
||||
constexpr std::uint16_t CopyTargetMode = 0777;
|
||||
constexpr std::size_t CopyBlock = 0x10000;
|
||||
constexpr std::int64_t MicrosecondsPerSecond = 1000000;
|
||||
constexpr std::int64_t NanosecondsPerMicrosecond = 1000;
|
||||
|
||||
std::atomic<const FilesystemForwards_nid_no_patch*> registeredForwards{nullptr};
|
||||
|
||||
const FilesystemForwards_nid_no_patch& Kernel(const char* function) {
|
||||
const auto* forwards = registeredForwards.load(std::memory_order_acquire);
|
||||
if (!forwards) throw std::runtime_error(std::string(function) + ": libkernel filesystem calls are not registered");
|
||||
return *forwards;
|
||||
}
|
||||
|
||||
int ErrorOf(int result) {
|
||||
const auto code = static_cast<std::uint32_t>(result);
|
||||
return (code & 0xffff0000u) == KernelErrnoBase ? static_cast<int>(code & 0xffffu) : 1;
|
||||
}
|
||||
|
||||
int StatusOf(int result) {
|
||||
return result == 0 ? 0 : ErrorOf(result);
|
||||
}
|
||||
|
||||
bool NotFound(int result) {
|
||||
const auto index = static_cast<std::uint32_t>(result) - KernelNoSuchFile;
|
||||
return index <= 0x3du && ((NotFoundCodes >> index) & 1u) != 0;
|
||||
}
|
||||
|
||||
int TypeOf(std::uint16_t mode) {
|
||||
const auto index = static_cast<std::uint32_t>(mode) - 0x1000u;
|
||||
return index >= 0xc000u ? TypeUnknown : TypesByMode[index >> 12];
|
||||
}
|
||||
|
||||
int StatusType(const char* function, const char* path, int* perms, int* error) {
|
||||
FileStat status{};
|
||||
const int result = Kernel(function).stat(path, &status);
|
||||
int type = TypeNone;
|
||||
if (NotFound(result)) {
|
||||
type = TypeNotFound;
|
||||
} else if (result == 0) {
|
||||
if (perms) *perms = status.st_mode & 0xfff;
|
||||
type = TypeOf(status.st_mode);
|
||||
}
|
||||
if (error) *error = StatusOf(result);
|
||||
return type;
|
||||
}
|
||||
|
||||
int CopyContents(const FilesystemForwards_nid_no_patch& kernel, int source, int target, std::byte* buffer) {
|
||||
for (;;) {
|
||||
const auto count = kernel.read(source, buffer, CopyBlock);
|
||||
if (count == 0) {
|
||||
FileStat status{};
|
||||
int result = kernel.fstat(source, &status);
|
||||
if (result == 0) result = kernel.fchmod(target, static_cast<std::uint16_t>(status.st_mode & CopiedPermissions));
|
||||
return StatusOf(result);
|
||||
}
|
||||
if (count < 0) return StatusOf(static_cast<int>(count));
|
||||
for (std::int64_t done = 0; done < count;) {
|
||||
const auto written = kernel.write(target, buffer + done, static_cast<std::size_t>(count - done));
|
||||
if (written < 0) return StatusOf(static_cast<int>(written));
|
||||
done += written;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void RegisterFilesystemForwards_nid_no_patch(const FilesystemForwards_nid_no_patch* forwards) {
|
||||
registeredForwards.store(forwards, std::memory_order_release);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix(const char* path, int* perms, int* error) {
|
||||
return StatusType(__func__, path, perms, error);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsE_nid_postfix(const char* path, int* perms) {
|
||||
return StatusType(__func__, path, perms, nullptr);
|
||||
}
|
||||
|
||||
std::uint64_t APS5_VABI _ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix(const char* path, int* error) {
|
||||
FileStat status{};
|
||||
const int result = Kernel(__func__).stat(path, &status);
|
||||
if (result != 0 && error) *error = result;
|
||||
return result != 0 ? UINT64_MAX : static_cast<std::uint64_t>(status.st_size);
|
||||
}
|
||||
|
||||
std::uint64_t APS5_VABI _ZNSt8__sce_v210filesystem10_File_sizeEPKc_nid_postfix(const char* path) {
|
||||
FileStat status{};
|
||||
return Kernel(__func__).stat(path, &status) != 0 ? UINT64_MAX : static_cast<std::uint64_t>(status.st_size);
|
||||
}
|
||||
|
||||
std::int64_t APS5_VABI _ZNSt8__sce_v210filesystem16_Last_write_timeEPKc_nid_postfix(const char* path) {
|
||||
FileStat status{};
|
||||
return Kernel(__func__).posixStat(path, &status) != 0 ? -1 : status.st_mtim.tv_sec;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem16_Last_write_timeEPKcPlS3__nid_postfix(const char* path, std::int64_t* seconds, std::int64_t* nanoseconds) {
|
||||
FileStat status{};
|
||||
const int result = Kernel(__func__).stat(path, &status);
|
||||
if (result != 0) return ErrorOf(result);
|
||||
*seconds = status.st_mtim.tv_sec;
|
||||
*nanoseconds = status.st_mtim.tv_nsec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool APS5_VABI _ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix(const char* path, std::int64_t microseconds) {
|
||||
const auto& kernel = Kernel(__func__);
|
||||
FileStat status{};
|
||||
if (kernel.stat(path, &status) != 0) return false;
|
||||
std::int64_t seconds = microseconds / MicrosecondsPerSecond;
|
||||
std::int64_t remainder = microseconds - seconds * MicrosecondsPerSecond;
|
||||
if (remainder < 0) {
|
||||
--seconds;
|
||||
remainder += MicrosecondsPerSecond;
|
||||
}
|
||||
const KernelTimeval times[2] = {{status.st_atim.tv_sec, status.st_atim.tv_nsec / NanosecondsPerMicrosecond}, {seconds, remainder}};
|
||||
return kernel.utimes(path, times) == 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix(const char* path, const char* existing) {
|
||||
const auto& kernel = Kernel(__func__);
|
||||
std::uint16_t mode = DefaultDirectoryMode;
|
||||
if (existing) {
|
||||
FileStat status{};
|
||||
const int result = kernel.stat(existing, &status);
|
||||
if (result != 0) return -ErrorOf(result);
|
||||
mode = status.st_mode;
|
||||
}
|
||||
const int result = kernel.mkdir(path, mode);
|
||||
if (result == 0) return 1;
|
||||
if (static_cast<std::uint32_t>(result) == KernelExists) return 0;
|
||||
return -ErrorOf(result);
|
||||
}
|
||||
|
||||
bool APS5_VABI _ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix(const char* path) {
|
||||
return Kernel(__func__).posixRmdir(path) == 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix(const char* path) {
|
||||
return StatusOf(Kernel(__func__).unlink(path));
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix(const char* from, const char* to) {
|
||||
return StatusOf(Kernel(__func__).rename(from, to));
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix(const char* path, std::uint64_t size) {
|
||||
return StatusOf(Kernel(__func__).truncate(path, static_cast<std::int64_t>(size)));
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix(const char* path, int perms) {
|
||||
return StatusOf(Kernel(__func__).chmod(path, static_cast<std::uint16_t>(perms)));
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix(const char* from, const char* to) {
|
||||
const auto& kernel = Kernel(__func__);
|
||||
const int source = kernel.open(from, 0, 0);
|
||||
if (source < 0) return ErrorOf(source);
|
||||
const int target = kernel.open(to, CopyTargetFlags, CopyTargetMode);
|
||||
if (target < 0) {
|
||||
kernel.close(source);
|
||||
return ErrorOf(target);
|
||||
}
|
||||
const std::unique_ptr<std::byte[]> buffer(new (std::nothrow) std::byte[CopyBlock]);
|
||||
const int result = buffer ? CopyContents(kernel, source, target, buffer.get()) : OutOfMemory;
|
||||
kernel.close(target);
|
||||
kernel.close(source);
|
||||
return result;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem8_StatvfsEPKcRNS0_10space_infoE_nid_postfix(const char*, std::uint64_t* space) {
|
||||
space[0] = UINT64_MAX;
|
||||
space[1] = UINT64_MAX;
|
||||
space[2] = UINT64_MAX;
|
||||
return NoSystemCall;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNSt8__sce_v210filesystem13_Current_set2EPKc_nid_postfix(const char* path) {
|
||||
return std::strncmp(path, "/", 2) == 0 ? 0 : NotSupported;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Equeue/src/Kqueue.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/File/src/DirectoryDescriptor.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/File/src/FileLock.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/File/src/FilesystemForwards.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/File/src/NativeStat.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/File/src/Terminal.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/File/src/Open.cpp
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "prx/libkernel/include/Api.hpp"
|
||||
#include "prx/libkernel/File/include/File.hpp"
|
||||
#include "prx/libc/include/FilesystemForwards.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
struct FilesystemForwardsRegistration {
|
||||
FilesystemForwardsRegistration() {
|
||||
static const FilesystemForwards_nid_no_patch forwards{
|
||||
sceKernelStat,
|
||||
sceKernelFstat,
|
||||
sceKernelOpen,
|
||||
sceKernelRead,
|
||||
sceKernelWrite,
|
||||
sceKernelClose,
|
||||
sceKernelMkdir,
|
||||
sceKernelUnlink,
|
||||
sceKernelRename,
|
||||
sceKernelTruncate_nid_postfix,
|
||||
sceKernelChmod_nid_postfix,
|
||||
sceKernelFchmod,
|
||||
sceKernelUtimes_nid_postfix,
|
||||
stat_nid_postfix,
|
||||
rmdir_nid_postfix,
|
||||
};
|
||||
RegisterFilesystemForwards_nid_no_patch(&forwards);
|
||||
}
|
||||
~FilesystemForwardsRegistration() { RegisterFilesystemForwards_nid_no_patch(nullptr); }
|
||||
FilesystemForwardsRegistration(const FilesystemForwardsRegistration&) = delete;
|
||||
FilesystemForwardsRegistration& operator=(const FilesystemForwardsRegistration&) = delete;
|
||||
};
|
||||
|
||||
const FilesystemForwardsRegistration registration;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
#include "AppArchiveFixture.hpp"
|
||||
#include "prx/libc/include/Api.hpp"
|
||||
#include "prx/libkernel/include/Api.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int TypeNotFound = -1;
|
||||
constexpr int TypeRegular = 1;
|
||||
constexpr int TypeDirectory = 2;
|
||||
constexpr int NoSuchFile = 2;
|
||||
constexpr int NotDirectory = 20;
|
||||
constexpr int IsDirectory = 21;
|
||||
constexpr int Invalid = 22;
|
||||
constexpr int NameTooLong = 63;
|
||||
constexpr int NoSystemCall = 78;
|
||||
constexpr int NotSupported = 45;
|
||||
constexpr std::uint32_t KernelNoSuchFile = 0x80020002u;
|
||||
constexpr std::uint32_t KernelNotDirectory = 0x80020014u;
|
||||
|
||||
void Require(bool condition, const char* message) {
|
||||
if (condition) return;
|
||||
std::fprintf(stderr, "sce v2 filesystem calls: %s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void Write(const char* path, std::size_t bytes, unsigned seed) {
|
||||
std::ofstream file(path, std::ios::binary | std::ios::trunc);
|
||||
for (std::size_t index = 0; index < bytes; ++index) file.put(static_cast<char>((index * 131 + seed) & 0xff));
|
||||
}
|
||||
|
||||
std::string Read(const char* path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
return {std::istreambuf_iterator<char>(file), {}};
|
||||
}
|
||||
|
||||
FileStat Status(const char* path) {
|
||||
FileStat status{};
|
||||
Require(stat_nid_postfix(path, &status) == 0, "stat of a test file");
|
||||
return status;
|
||||
}
|
||||
|
||||
void Prepare() {
|
||||
std::error_code ignored;
|
||||
for (const char* name : {"f", "g", "big", "d", "e", "ro", "new", "made", "copy", "renamed"}) {
|
||||
std::filesystem::permissions(name, std::filesystem::perms::owner_all, std::filesystem::perm_options::add, ignored);
|
||||
std::filesystem::remove_all(name, ignored);
|
||||
}
|
||||
Write("f", 7, 1);
|
||||
Write("g", 3, 2);
|
||||
Write("big", 200000, 4);
|
||||
std::filesystem::create_directories("d/sub");
|
||||
Write("d/sub/h", 5, 5);
|
||||
std::filesystem::create_directory("e");
|
||||
Write("ro", 4, 6);
|
||||
std::filesystem::permissions("ro", std::filesystem::perms::owner_read | std::filesystem::perms::group_read | std::filesystem::perms::others_read);
|
||||
}
|
||||
|
||||
void TestStatus() {
|
||||
Prepare();
|
||||
int perms = -1;
|
||||
int error = -1;
|
||||
Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix("f", &perms, &error) == TypeRegular && error == 0, "a regular file");
|
||||
Require(perms == (Status("f").st_mode & 0xfff), "the permission bits of a regular file");
|
||||
Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix("d", nullptr, nullptr) == TypeDirectory, "a directory without outputs");
|
||||
Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsE_nid_postfix("d/", &perms) == TypeDirectory && perms == (Status("d").st_mode & 0xfff), "a directory with a trailing slash");
|
||||
const struct {
|
||||
const char* path;
|
||||
int error;
|
||||
} missing[] = {{"missing", NoSuchFile}, {"f/x", NotDirectory}, {"", NoSuchFile}};
|
||||
for (const auto& entry : missing) {
|
||||
perms = 0x1234;
|
||||
Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix(entry.path, &perms, &error) == TypeNotFound && error == entry.error && perms == 0x1234, "a path that is not found");
|
||||
Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsE_nid_postfix(entry.path, &perms) == TypeNotFound && perms == 0x1234, "a path that is not found without an error output");
|
||||
}
|
||||
const std::string longName(300, 'n');
|
||||
Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix(longName.c_str(), &perms, &error) == TypeNotFound && error == NameTooLong, "a name that is too long");
|
||||
}
|
||||
|
||||
void TestSizes() {
|
||||
Prepare();
|
||||
int error = 0x5678;
|
||||
Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix("big", &error) == 200000 && error == 0x5678, "the size of a file leaves the error output alone");
|
||||
Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKc_nid_postfix("f") == 7, "the size of a file without an error output");
|
||||
Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix("missing", &error) == UINT64_MAX && static_cast<std::uint32_t>(error) == KernelNoSuchFile, "a missing file reports the kernel error");
|
||||
Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix("f/x", &error) == UINT64_MAX && static_cast<std::uint32_t>(error) == KernelNotDirectory, "a file used as a directory reports the kernel error");
|
||||
Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix("missing", nullptr) == UINT64_MAX, "a missing file without an error output");
|
||||
Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKc_nid_postfix("missing") == UINT64_MAX, "a missing file through the short form");
|
||||
}
|
||||
|
||||
void TestTimes() {
|
||||
Prepare();
|
||||
const struct {
|
||||
std::int64_t microseconds, seconds, nanoseconds;
|
||||
} times[] = {{-1500001, -2, 499999000}, {1700000000123456, 1700000000, 123456000}, {999999, 0, 999999000}, {-1000000, -1, 0}, {-1, -1, 999999000}};
|
||||
for (const auto& time : times) {
|
||||
Require(_ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix("f", time.microseconds), "set the write time of a file");
|
||||
std::int64_t seconds = 0;
|
||||
std::int64_t nanoseconds = 0;
|
||||
Require(_ZNSt8__sce_v210filesystem16_Last_write_timeEPKcPlS3__nid_postfix("f", &seconds, &nanoseconds) == 0, "read the write time of a file");
|
||||
Require(seconds == time.seconds && nanoseconds == time.nanoseconds, "the write time is floored to whole seconds and microseconds");
|
||||
Require(_ZNSt8__sce_v210filesystem16_Last_write_timeEPKc_nid_postfix("f") == time.seconds, "the write time in seconds");
|
||||
}
|
||||
Require(_ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix("d", 1), "set the write time of a directory");
|
||||
Require(!_ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix("missing", 0), "a missing file has no write time to set");
|
||||
std::int64_t seconds = 0x1111;
|
||||
std::int64_t nanoseconds = 0x2222;
|
||||
Require(_ZNSt8__sce_v210filesystem16_Last_write_timeEPKcPlS3__nid_postfix("missing", &seconds, &nanoseconds) == NoSuchFile && seconds == 0x1111 && nanoseconds == 0x2222, "a missing file leaves the time outputs alone");
|
||||
Require(_ZNSt8__sce_v210filesystem16_Last_write_timeEPKc_nid_postfix("missing") == -1, "a missing file has no write time");
|
||||
}
|
||||
|
||||
void TestDirectories() {
|
||||
Prepare();
|
||||
Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("made", nullptr) == 1 && std::filesystem::is_directory("made"), "make a directory");
|
||||
Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("made", nullptr) == 0, "an existing directory is not made again");
|
||||
Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("f", nullptr) == 0, "an existing file is reported as existing");
|
||||
Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("missing/x", nullptr) == -NoSuchFile, "a missing parent");
|
||||
Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("f/x", nullptr) == -NotDirectory, "a file used as a parent");
|
||||
Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("new", "missing") == -NoSuchFile && !std::filesystem::exists("new"), "a missing template stops before making the directory");
|
||||
Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("new", "d") == 1 && std::filesystem::is_directory("new"), "make a directory from a template");
|
||||
const std::string longName(300, 'n');
|
||||
Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix(longName.c_str(), nullptr) == -NameTooLong, "a name that is too long");
|
||||
Require(_ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix("made") && !std::filesystem::exists("made"), "remove an empty directory");
|
||||
Require(!_ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix("d") && std::filesystem::exists("d/sub/h"), "a directory that is not empty stays");
|
||||
Require(!_ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix("f") && std::filesystem::exists("f"), "a file is not removed as a directory");
|
||||
Require(!_ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix("missing"), "a missing directory");
|
||||
}
|
||||
|
||||
void TestEntries() {
|
||||
Prepare();
|
||||
Require(_ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix("f/x") == NotDirectory, "unlink below a file");
|
||||
Require(_ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix("f") == 0 && !std::filesystem::exists("f"), "unlink a file");
|
||||
Require(_ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix("missing") == NoSuchFile, "unlink a missing file");
|
||||
Require(_ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix("g", "renamed") == 0 && Read("renamed").size() == 3 && !std::filesystem::exists("g"), "rename a file");
|
||||
Require(_ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix("missing", "new") == NoSuchFile, "rename a missing file");
|
||||
Require(_ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix("renamed", "d") == IsDirectory && std::filesystem::exists("renamed"), "rename a file over a directory");
|
||||
Require(_ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix("renamed", "missing/x") == NoSuchFile, "rename into a missing directory");
|
||||
Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("renamed", 100000) == 0 && Status("renamed").st_size == 100000, "grow a file");
|
||||
Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("renamed", 2) == 0 && Status("renamed").st_size == 2, "shrink a file");
|
||||
Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("renamed", 0x8000000000000000ull) == Invalid, "a size above the signed range");
|
||||
Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("d", 0) == IsDirectory, "resize a directory");
|
||||
Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("missing", 0) == NoSuchFile, "resize a missing file");
|
||||
Require(_ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix("renamed", 0444) == 0 && (Status("renamed").st_mode & 0222) == 0, "make a file read only");
|
||||
Require(_ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix("renamed", 0x1ffff) == 0 && (Status("renamed").st_mode & 0200) != 0, "only the low sixteen permission bits are passed");
|
||||
Require(_ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix("missing", 0444) == NoSuchFile, "change a missing file");
|
||||
}
|
||||
|
||||
void TestCopy() {
|
||||
Prepare();
|
||||
const auto contents = Read("big");
|
||||
Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("big", "copy") == 0 && Read("copy") == contents, "copy a file larger than one block");
|
||||
Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("g", "copy") == 0 && Read("copy") == Read("g"), "copy over a longer file truncates it");
|
||||
Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("ro", "new") == 0 && Read("new") == Read("ro"), "copy a read-only file");
|
||||
Require((Status("new").st_mode & 07777) == (Status("ro").st_mode & 07777), "the copy takes the permissions of its source");
|
||||
Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("missing", "made") == NoSuchFile && !std::filesystem::exists("made"), "a missing source creates nothing");
|
||||
Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("f", "missing/x") == NoSuchFile, "a target in a missing directory");
|
||||
Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("f", "d") == IsDirectory, "a directory target");
|
||||
Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("d", "made") == IsDirectory, "a directory source");
|
||||
}
|
||||
|
||||
void TestUnsupported() {
|
||||
std::uint64_t space[4] = {1, 2, 3, 4};
|
||||
Require(_ZNSt8__sce_v210filesystem8_StatvfsEPKcRNS0_10space_infoE_nid_postfix("f", space) == NoSystemCall, "statvfs is not supported");
|
||||
Require(space[0] == UINT64_MAX && space[1] == UINT64_MAX && space[2] == UINT64_MAX && space[3] == 4, "statvfs reports unknown space and writes three fields");
|
||||
Require(_ZNSt8__sce_v210filesystem13_Current_set2EPKc_nid_postfix("/") == 0, "the root stays the current directory");
|
||||
for (const char* path : {"", ".", "d", "//", "/d", "f/"}) Require(_ZNSt8__sce_v210filesystem13_Current_set2EPKc_nid_postfix(path) == NotSupported, "any other current directory is not supported");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
const auto directory = AppArchiveFixture::EnterFreshRoot("anyps5-sce-v2-filesystem-calls");
|
||||
TestStatus();
|
||||
TestSizes();
|
||||
TestTimes();
|
||||
TestDirectories();
|
||||
TestEntries();
|
||||
TestCopy();
|
||||
TestUnsupported();
|
||||
Prepare();
|
||||
std::error_code ignored;
|
||||
std::filesystem::current_path(std::filesystem::temp_directory_path(), ignored);
|
||||
std::filesystem::remove_all(directory, ignored);
|
||||
return 0;
|
||||
}
|
||||
@@ -158,6 +158,7 @@
|
||||
- [ftrylockfile](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - tries the host CRT stream lock that `flockfile`, `funlockfile` and each locking host stdio call on the stream take, so it returns -1 while another thread holds the stream through `flockfile` or is inside such a call, and 0 when it acquires the lock or already owns it (recursively, released by as many `funlockfile` calls), as FreeBSD 11 [`_ftrylockfile`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/_flock_stub.c#L89-L104) does. A libc export holds the lock only for each host call it makes, not across several host calls or while it updates the guest `FILE` prefix. Linux uses glibc `ftrylockfile`. UCRT has no try variant of `_lock_file`, so Windows calls `TryEnterCriticalSection` on the `CRITICAL_SECTION` that `_lock_file` enters, found through the private UCRT stream layout (`__crt_stdio_stream_data` in `ucrt/inc/corecrt_internal_stdio.h`, unchanged from Windows SDK 10.0.19041 to 10.0.26100); another CRT or a change of that layout breaks it. A null or closed stream throws where FreeBSD dereferences it
|
||||
- [sinf, cosf, tanf and sincosf](../../../core/libs/prx/libc/src/Math.cpp) (libc) are FreeBSD msun's [`s_sinf.c`, `s_cosf.c` and `s_tanf.c`](https://github.com/freebsd/freebsd-src/tree/release/11.4.0/lib/msun/src) over its double-precision kernels and `e_rem_pio2f.c`, with `k_rem_pio2.c` (for `|x| >= 2^28 * pi/2`) ported only for one input word and `prec` 0; `sincosf` returns the two. As in the `libc.prx` that titles ship, only an infinite argument sets `errno` (`EDOM`); NaN leaves it unchanged. Floating-point exceptions are not compared. All four give the same bits and `errno` as the copies bundled with PPSA07923, PPSA05916 and PPSA13934 for all 2^32 arguments, run on a Windows host.
|
||||
- [fgetln](../../../core/libs/prx/libc/src/StandardStreams.cpp) (libSceLibcInternal, implemented in libc) throws: in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x5FE80) the export is a `hlt` instruction, which faults in user mode and ends the process
|
||||
- [Versioned std::__sce_v2::filesystem calls](../../../core/libs/prx/libc/src/VersionedFilesystemCalls.cpp) (libc) - console execution, firmware and SDK coverage are unverified. `_Lstat`, `_File_size`, `_Last_write_time`, `_Set_last_write_time`, `_Make_dir`, `_Remove_dir`, `_Unlink`, `_Rename`, `_Resize`, `_Chmod`, `_Copy_file`, `_Statvfs` and `_Current_set2` follow the `libc.prx` bundled with PPSA03671 v01.001.005. libkernel registers the kernel calls they make, as libc cannot link it, and they are made in the module's order: kernel errors in the errno range give their errno, others give 1, and `_File_size` stores the kernel error code itself. As in the module, `_Statvfs` returns `ENOSYS` with unknown space and `_Current_set2` accepts only "/". `_Copy_file` copies 64 KiB at a time through a host buffer instead of the module's heap.
|
||||
- [Wide-character conversions](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. The multibyte conversions follow the C locale of the console's `libSceLibcInternal.sprx`: UTF-8 of at most three bytes, so `_Getmbcurmax` returns 3, overlong forms and encoded surrogates decode, and four-byte forms are invalid. `__mb_cur_max` stays 1 like the console's variable; `___mb_cur_max` is not in that module and also stays 1, unverified. Other locales are not supported. With a null destination, `wcstombs` returns the byte count; the console's count is wrong when the capacity argument equals the running count, because it reads past the terminator
|
||||
- [realpath](../../../core/libs/prx/libc/src/RealPath.cpp) (libc) follows FreeBSD 11's [`realpath.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/stdlib/realpath.c) with two gaps. A host symbolic link with a relative target under the guest root is expanded as `realpath1` does (a dangling link fails with `ENOENT`, more than 32 links with `ELOOP`), but a link with an absolute target is not expanded, nor is anything below it, because the target is a host path with no guest path to map to. Only Windows symbolic links are expanded there, not junctions. Expansion works on the guest path, so a host link whose text climbs above a path alias root is resolved against the guest tree, not the host one. Otherwise `..` drops the previous component lexically, as `Resolve` does for every guest path.
|
||||
- [Paths below a Windows symbolic link](../../../core/libs/prx/libc/include/FilesystemError.hpp) (libc) chooses `ENOENT` or `ENOTDIR` for a path below a link by reading the link text on Windows, and three cases still differ from FreeBSD 11.4's `namei`: below a chain of 33 or more links the answer is `ENOTDIR`, not `ELOOP`; a link made with the directory flag that points at a regular file, or at a link that runs through one, answers `ENOENT`, not `ENOTDIR`, because Windows fails its open with `ERROR_DIRECTORY`; and below a link loop `lstat`, `chmod`, `fchmodat`, `linkat`, `readlink`, `access`, `rename` and `truncate` answer `ENOTDIR`, not `ELOOP`.
|
||||
|
||||
Reference in new issue
Block a user