From bb1a4cbec40fcb55492b4aa87a187606e5caea03 Mon Sep 17 00:00:00 2001 From: kjhq Date: Thu, 8 Oct 2026 08:26:08 +0000 Subject: [PATCH] fix(libkernel): flush only the paths the guest changed in sceKernelSync on Windows Opening a volume needs administrator rights, so the volume flush skipped every volume in a normal user session. The libc, libkernel, save data and app content file functions now record each host file and directory they change. On Windows sceKernelSync flushes the recorded paths that changed since the previous call, plus their changed parent directories, through handles a normal user can open. Linux still calls sync(). --- core/libs/prx/libSceAppContent/Export.cpp | 5 +- core/libs/prx/libSceSaveData/SaveDataCore.hpp | 15 ++- core/libs/prx/libc/include/General.hpp | 4 + core/libs/prx/libc/src/FileAndHeap.cpp | 11 +- core/libs/prx/libc/src/Filesystem.cpp | 3 + core/libs/prx/libc/src/General.cpp | 106 ++++++++++++++++++ core/libs/prx/libkernel/CMakeLists.txt | 9 ++ core/libs/prx/libkernel/File/src/Open.cpp | 3 + core/libs/prx/libkernel/File/src/Stdio.cpp | 11 +- .../libs/prx/libkernel/System/src/Process.cpp | 39 +------ core/libs/tests/GuestKernelSync.cpp | 73 ++++++++++++ docs/dev/TechnicalDebt.md | 2 + 12 files changed, 235 insertions(+), 46 deletions(-) create mode 100644 core/libs/tests/GuestKernelSync.cpp diff --git a/core/libs/prx/libSceAppContent/Export.cpp b/core/libs/prx/libSceAppContent/Export.cpp index dc2b86143..4d0b5e3d5 100644 --- a/core/libs/prx/libSceAppContent/Export.cpp +++ b/core/libs/prx/libSceAppContent/Export.cpp @@ -31,6 +31,7 @@ static std::filesystem::path TemporaryDirectory(const AppContentMountPoint* moun static void ClearDirectory(const std::filesystem::path& directory) { for (const auto& entry : std::filesystem::directory_iterator(directory)) std::filesystem::remove_all(entry.path()); + RecordWrittenPath_nid_no_patch(directory); } extern "C" { @@ -82,7 +83,7 @@ int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMount return 0; } const auto directory = ResolvePath_nid_no_patch(DOWNLOAD_MOUNT_POINT); - std::filesystem::create_directories(directory); + if (std::filesystem::create_directories(directory)) RecordWrittenPath_nid_no_patch(directory); std::uint64_t usedKb = 0; for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) { if (entry.is_regular_file()) usedKb += (entry.file_size() + 1023u) / 1024u; @@ -116,7 +117,7 @@ int APS5_VABI sceAppContentTemporaryDataMount2(uint32_t option, AppContentMountP std::memset(mount_point->data, 0, sizeof(mount_point->data)); std::memcpy(mount_point->data, TEMPORARY_MOUNT_POINT, sizeof(TEMPORARY_MOUNT_POINT)); const auto directory = TemporaryDirectory(mount_point); - std::filesystem::create_directories(directory); + if (std::filesystem::create_directories(directory)) RecordWrittenPath_nid_no_patch(directory); if (option == TEMPORARY_DATA_OPTION_FORMAT) ClearDirectory(directory); return 0; } diff --git a/core/libs/prx/libSceSaveData/SaveDataCore.hpp b/core/libs/prx/libSceSaveData/SaveDataCore.hpp index 1a810cb07..200b60771 100644 --- a/core/libs/prx/libSceSaveData/SaveDataCore.hpp +++ b/core/libs/prx/libSceSaveData/SaveDataCore.hpp @@ -203,7 +203,7 @@ const std::filesystem::path& save_root() { } bool ensure_save_root(std::error_code& error) { - std::filesystem::create_directories(save_root(), error); + if (std::filesystem::create_directories(save_root(), error)) RecordWrittenPath_nid_no_patch(save_root()); if (error) { error.clear(); } @@ -233,6 +233,7 @@ void store_aliases_locked(State& st) { for (const auto& [alias, dir_name] : st.aliases) { file << alias << '\t' << dir_name << '\n'; } + RecordWrittenPath_nid_no_patch(save_root() / "_aliases"); } std::string save_alias_locked(State& st, const std::string& dir_name) { @@ -289,7 +290,9 @@ std::filesystem::path save_blocks_path(const std::filesystem::path& directory) { } bool write_blob(const std::filesystem::path& path, const void* data, std::size_t size) { - return replace_file(path, data, size); + if (!replace_file(path, data, size)) return false; + RecordWrittenPath_nid_no_patch(path); + return true; } bool read_blob(const std::filesystem::path& path, void* data, std::size_t size) { @@ -499,6 +502,7 @@ int mount_internal(const SceSaveDataDirName* dir_name, std::uint32_t mount_mode, if (!std::filesystem::create_directories(directory, error) || error) { return SD_ERROR_INTERNAL; } + RecordWrittenPath_nid_no_patch(directory); created = true; if (write_u64(save_blocks_path(directory), blocks) != SD_OK) { std::error_code cleanup; @@ -579,6 +583,7 @@ int delete_internal(std::int32_t user_id, const SceSaveDataDirName* dir_name, return SD_ERROR_NOT_FOUND; } std::filesystem::remove_all(directory, error); + RecordWrittenPath_nid_no_patch(directory); if (error) { return SD_ERROR_INTERNAL; } @@ -1286,7 +1291,8 @@ int set_local_storage_app_status_internal(const SceSaveDataDirName* dir_name, return SD_ERROR_PARAMETER; } std::error_code error; - std::filesystem::create_directories(app_status_path(dir_name->data).parent_path(), error); + if (std::filesystem::create_directories(app_status_path(dir_name->data).parent_path(), error)) + RecordWrittenPath_nid_no_patch(app_status_path(dir_name->data).parent_path()); if (error) { return SD_ERROR_INTERNAL; } @@ -1301,7 +1307,8 @@ int delete_local_storage_app_status_internal(const SceSaveDataDirName* dir_name, return SD_ERROR_PARAMETER; } std::error_code error; - std::filesystem::remove(app_status_path(dir_name->data), error); + if (std::filesystem::remove(app_status_path(dir_name->data), error)) + RecordWrittenPath_nid_no_patch(app_status_path(dir_name->data)); return error ? SD_ERROR_INTERNAL : SD_OK; } diff --git a/core/libs/prx/libc/include/General.hpp b/core/libs/prx/libc/include/General.hpp index 29ed9fda5..6f696024e 100644 --- a/core/libs/prx/libc/include/General.hpp +++ b/core/libs/prx/libc/include/General.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "general/LogMacros.hpp" #include "general/VabiMacros.hpp" @@ -16,6 +17,9 @@ extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path); // 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); extern "C" void RemovePathAlias_nid_no_patch(const char* guestPrefix); +extern "C" void RecordWrittenPath_nid_no_patch(const std::filesystem::path& path); +extern "C" std::vector WrittenPaths_nid_no_patch(); +extern "C" void SyncWrittenPaths_nid_no_patch(); #define APS5_INVALID_ARG_EX throw std::invalid_argument(std::string(__func__) + ": invalid argument") diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index c6a5de95d..bb36a2ab6 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/ApplicationHeap.hpp" @@ -20,6 +21,10 @@ static std::string NativeFileMode(const char* mode) { return result; } +static bool WritesFile(const char* mode) { + return std::strpbrk(mode, "wa+") != nullptr; +} + extern "C" { [[noreturn]] void APS5_VABI _ZSt11_Xbad_allocv_nid_postfix(); @@ -57,7 +62,10 @@ FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode if (!valid) { errno = 22; return nullptr; } try { const auto path = *filename ? ResolvePath_nid_no_patch(filename).string() : std::string{}; - if (stream->Reopen(path.c_str(), NativeFileMode(mode).c_str())) return stream; + if (stream->Reopen(path.c_str(), NativeFileMode(mode).c_str())) { + if (!path.empty() && WritesFile(mode)) RecordWrittenPath_nid_no_patch(path); + return stream; + } const int error = errno; if (stream->IsDynamic()) delete stream; errno = error; @@ -94,6 +102,7 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) throw std::runtime_error(std::string(__func__) + ": " + FOPEN_MSG_OPEN_FAILED + ": \"" + abs_path + "\": " + reason); } // APS5_LOG_OUT("success: \"%s\"", abs_path.c_str()); + if (WritesFile(mode)) RecordWrittenPath_nid_no_patch(fpath); auto stream = std::make_unique(handle.get(), true); handle.release(); return stream.release(); diff --git a/core/libs/prx/libc/src/Filesystem.cpp b/core/libs/prx/libc/src/Filesystem.cpp index d7f191170..2e2d2bd3b 100644 --- a/core/libs/prx/libc/src/Filesystem.cpp +++ b/core/libs/prx/libc/src/Filesystem.cpp @@ -99,6 +99,8 @@ extern "C" int APS5_VABI rename_nid_postfix(const char* from, const char* to) { std::error_code error; std::filesystem::rename(source, destination, error); if (error) { errno = FilesystemError(error); return -1; } + RecordWrittenPath_nid_no_patch(source); + RecordWrittenPath_nid_no_patch(destination); return 0; } catch (const std::bad_alloc&) { errno = 12; return -1; } catch (const std::filesystem::filesystem_error& error) { @@ -132,6 +134,7 @@ extern "C" int APS5_VABI remove_nid_postfix(const char* path) { errno = error ? FilesystemError(error) : 2; return -1; } + RecordWrittenPath_nid_no_patch(resolved); return 0; } catch (const std::bad_alloc&) { errno = 12; return -1; } catch (const std::filesystem::filesystem_error& error) { diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index 7d9d8d137..38eb37efb 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -7,6 +7,21 @@ #include #include #include +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#endif #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestHeap.hpp" @@ -69,6 +84,41 @@ std::filesystem::path Resolve(WorkingDirectory& state, const char* path) { if (auto aliased = ResolveAlias(guest.relative_path().generic_string())) return *aliased; return (state.root / guest.relative_path()).make_preferred(); } +struct WrittenPathRegistry { + std::mutex syncMutex; + std::mutex mutex; + std::map dirty; +#ifdef _WIN32 + FILETIME mark = [] { FILETIME now; GetSystemTimeAsFileTime(&now); return now; }(); +#endif +}; +WrittenPathRegistry& Written() { static WrittenPathRegistry registry; return registry; } +#ifdef _WIN32 +std::optional WriteTime(const std::filesystem::path& path) { + WIN32_FILE_ATTRIBUTE_DATA data; + if (GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &data)) return data.ftLastWriteTime; + const auto error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) return std::nullopt; + throw std::system_error(static_cast(error), std::system_category(), "Reading the write time of " + path.string()); +} +bool ChangedSince(const std::filesystem::path& path, const FILETIME& mark) { + const auto time = WriteTime(path); + return time && CompareFileTime(&*time, &mark) >= 0; +} +void FlushPath(const std::filesystem::path& path) { + const auto handle = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + const auto error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) return; + if (error == ERROR_ACCESS_DENIED || error == ERROR_SHARING_VIOLATION) return; + throw std::system_error(static_cast(error), std::system_category(), "Opening " + path.string() + " for sync"); + } + const std::unique_ptr owner(handle, &CloseHandle); + if (!FlushFileBuffers(handle)) + throw std::system_error(static_cast(GetLastError()), std::system_category(), "Flushing " + path.string()); +} +#endif int DirectoryFailure(const std::error_code& error) { if (error == std::errc::permission_denied) return 13; if (error == std::errc::not_a_directory) return 20; @@ -105,6 +155,62 @@ extern "C" void RemovePathAlias_nid_no_patch(const char* guestPrefix) { std::erase_if(aliases.entries, [&](const auto& entry) { return entry.first == prefix; }); } +extern "C" void RecordWrittenPath_nid_no_patch(const std::filesystem::path& path) { + auto& registry = Written(); + std::lock_guard lock(registry.mutex); + registry.dirty[path.lexically_normal()] = true; +} + +extern "C" std::vector WrittenPaths_nid_no_patch() { + auto& registry = Written(); + std::lock_guard lock(registry.mutex); + std::vector paths; + for (const auto& entry : registry.dirty) paths.push_back(entry.first); + return paths; +} + +extern "C" void SyncWrittenPaths_nid_no_patch() { + auto& registry = Written(); + std::lock_guard serial(registry.syncMutex); +#ifdef _WIN32 + FILETIME start; + GetSystemTimeAsFileTime(&start); + FILETIME mark; + std::vector> entries; + { + std::lock_guard lock(registry.mutex); + mark = registry.mark; + registry.mark = start; + for (auto& [path, dirty] : registry.dirty) { + entries.emplace_back(path, dirty); + dirty = false; + } + } + std::set flushed; + std::vector missing; + for (const auto& [path, dirty] : entries) { + const auto time = WriteTime(path); + if (!time) missing.push_back(path); + if (!dirty && !(time && CompareFileTime(&*time, &mark) >= 0)) continue; + if (time && flushed.insert(path).second) FlushPath(path); + for (auto ancestor = path.parent_path(); ancestor.has_relative_path() && !flushed.contains(ancestor) && ChangedSince(ancestor, mark); + ancestor = ancestor.parent_path()) { + flushed.insert(ancestor); + FlushPath(ancestor); + } + } + std::lock_guard lock(registry.mutex); + for (const auto& path : missing) { + const auto entry = registry.dirty.find(path); + if (entry != registry.dirty.end() && !entry->second) registry.dirty.erase(entry); + } +#else + ::sync(); + std::lock_guard lock(registry.mutex); + registry.dirty.clear(); +#endif +} + extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path) { if (!path) { APS5_INVALID_ARG_EX; } auto& state = Directories(); diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index daa45a8c7..31ad9ae4b 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -129,6 +129,15 @@ if(BUILD_TESTING) set_tests_properties(guest_filesystem PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sync_tests ${LIBS_INCLUDE_DIR}/tests/GuestKernelSync.cpp) + target_include_directories(guest_kernel_sync_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_kernel_sync_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_kernel_sync_tests) + add_test(NAME guest_kernel_sync COMMAND guest_kernel_sync_tests) + if(WIN32) + set_tests_properties(guest_kernel_sync PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_aio_tests ${LIBS_INCLUDE_DIR}/tests/GuestAio.cpp) target_include_directories(guest_aio_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_aio_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 807fd4bba..2e323a8d9 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -116,6 +116,8 @@ int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) { if (fd < 0) { return SceErrorFromErrno(errno); } + if ((flags & SCE_KERNEL_O_ACCMODE) != SCE_KERNEL_O_RDONLY || (flags & (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_TRUNC))) + RecordWrittenPath_nid_no_patch(native); return fd; } @@ -189,6 +191,7 @@ int APS5_VABI sceKernelUnlink(const char* path) { if (NativeUnlink(native) != 0) { return SceErrorFromErrno(errno); } + RecordWrittenPath_nid_no_patch(native); return 0; } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index d1aaae10d..3c5d7e716 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -43,6 +43,7 @@ static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) { return ::_wmkdir(path.wstring().c_str()); } static int NativeChmod(const std::filesystem::path& path, int mode) { + RecordWrittenPath_nid_no_patch(path); return ::_wchmod(path.wstring().c_str(), mode); } static std::optional NativeDescriptorPath(int descriptor) { @@ -73,6 +74,7 @@ static int NativeFtruncate(int descriptor, std::int64_t length) { return static_cast(::_chsize_s(descriptor, length)); } static int NativeUtimes(const std::filesystem::path& path, const KernelTimeval* times) { + RecordWrittenPath_nid_no_patch(path); if (times == nullptr) return ::_wutime(path.wstring().c_str(), nullptr); struct _utimbuf values{static_cast(times[0].tv_sec), static_cast(times[1].tv_sec)}; return ::_wutime(path.wstring().c_str(), &values); @@ -318,6 +320,7 @@ int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) { if (NativeMkdir(native, mode) != 0) { throw std::runtime_error(std::string(__func__) + ": mkdir failed for " + native.string() + ", errno=" + std::to_string(errno)); } + RecordWrittenPath_nid_no_patch(native); return 0; } @@ -503,6 +506,7 @@ int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode) { if (std::filesystem::exists(native, error)) return SceErrorFromErrno(GUEST_EEXIST); if (!std::filesystem::exists(native.parent_path(), error)) return SceErrorFromErrno(GUEST_ENOENT); if (!std::filesystem::create_directory(native, error)) return SceErrorFromErrno(error.value() ? error.value() : GUEST_EIO); + RecordWrittenPath_nid_no_patch(native); return 0; } @@ -633,8 +637,11 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) { const auto source = ResolvePath_nid_no_patch(from); std::error_code error; if (!std::filesystem::exists(source, error)) return SceErrorFromErrno(GUEST_ENOENT); - std::filesystem::rename(source, ResolvePath_nid_no_patch(to), error); + const auto destination = ResolvePath_nid_no_patch(to); + std::filesystem::rename(source, destination, error); if (error) return SceErrorFromErrno(GUEST_EIO); + RecordWrittenPath_nid_no_patch(source); + RecordWrittenPath_nid_no_patch(destination); return 0; } @@ -645,6 +652,7 @@ int APS5_VABI sceKernelRmdir(const char* path) { if (!std::filesystem::is_directory(native, error)) return SceErrorFromErrno(std::filesystem::exists(native, error) ? GUEST_ENOTDIR : GUEST_ENOENT); if (!std::filesystem::is_empty(native, error)) return SceErrorFromErrno(GUEST_ENOTEMPTY); if (!std::filesystem::remove(native, error)) return SceErrorFromErrno(GUEST_EIO); + RecordWrittenPath_nid_no_patch(native); return 0; } @@ -679,6 +687,7 @@ int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t lengt if (!std::filesystem::exists(native, error)) return SceErrorFromErrno(GUEST_ENOENT); std::filesystem::resize_file(native, static_cast(length), error); if (error) return SceErrorFromErrno(GUEST_EIO); + RecordWrittenPath_nid_no_patch(native); return 0; } diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index cc9e1301f..13e447cd1 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -89,39 +89,6 @@ void validateSchedulingPolicy(int policy) { throw std::invalid_argument("Unsupported guest scheduling policy"); } -#ifdef _WIN32 -void syncVolumes() { - std::array name{}; - const auto first = FindFirstVolumeW(name.data(), static_cast(name.size())); - if (first == INVALID_HANDLE_VALUE) - throw std::system_error(GetLastError(), std::system_category(), "Enumerating volumes for sync"); - const std::unique_ptr search(first, &FindVolumeClose); - for (;;) { - DWORD flags = 0; - if (!GetVolumeInformationW(name.data(), nullptr, 0, nullptr, nullptr, &flags, nullptr, 0)) - throw std::system_error(GetLastError(), std::system_category(), "Reading volume properties for sync"); - if ((flags & FILE_READ_ONLY_VOLUME) == 0) { - std::wstring path(name.data()); - if (path.empty() || path.back() != L'\\') - throw std::runtime_error("Invalid volume path for sync"); - path.pop_back(); - const auto native = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); - if (native == INVALID_HANDLE_VALUE) - throw std::system_error(GetLastError(), std::system_category(), "Opening volume for sync"); - const std::unique_ptr volume(native, &CloseHandle); - if (!FlushFileBuffers(volume.get())) - throw std::system_error(GetLastError(), std::system_category(), "Flushing volume"); - } - if (FindNextVolumeW(search.get(), name.data(), static_cast(name.size()))) - continue; - const auto error = GetLastError(); - if (error != ERROR_NO_MORE_FILES) - throw std::system_error(error, std::system_category(), "Enumerating volumes for sync"); - break; - } -} -#endif - } struct GuestResourceUsage { @@ -232,11 +199,7 @@ int APS5_VABI sceKernelUuidCreate(std::uint32_t* uuid) { } void APS5_VABI sceKernelSync(void) { -#ifdef _WIN32 - syncVolumes(); -#else - ::sync(); -#endif + SyncWrittenPaths_nid_no_patch(); } int APS5_VABI sched_get_priority_max_nid_postfix(int policy) { diff --git a/core/libs/tests/GuestKernelSync.cpp b/core/libs/tests/GuestKernelSync.cpp new file mode 100644 index 000000000..bb227cb5e --- /dev/null +++ b/core/libs/tests/GuestKernelSync.cpp @@ -0,0 +1,73 @@ +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#endif +extern "C" { +int APS5_VABI sceKernelOpen(const char*, int, unsigned short); +int APS5_VABI sceKernelClose(int); +std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t); +int APS5_VABI sceKernelMkdir(const char*, unsigned short); +int APS5_VABI sceKernelRename(const char*, const char*); +int APS5_VABI sceKernelUnlink(const char*); +int APS5_VABI sceKernelChmod_nid_postfix(const char*, std::uint16_t); +void APS5_VABI sceKernelSync(); +void* APS5_VABI fopen_nid_postfix(const char*, const char*); +int APS5_VABI fclose_nid_postfix(void*); +} +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Kernel sync check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) +static bool Recorded(const char* guest) { + const auto paths = WrittenPaths_nid_no_patch(); + return std::find(paths.begin(), paths.end(), ResolvePath_nid_no_patch(guest).lexically_normal()) != paths.end(); +} +int main() { + std::filesystem::remove_all("kernel_sync_probe"); + Require(sceKernelMkdir("kernel_sync_probe", 0777) == 0); + Require(Recorded("kernel_sync_probe")); + std::ofstream("kernel_sync_probe/existing.bin") << "existing"; + const int reader = sceKernelOpen("kernel_sync_probe/existing.bin", 0x0, 0); + Require(reader >= 0 && sceKernelClose(reader) == 0); + void* stream = fopen_nid_postfix("kernel_sync_probe/existing.bin", "r"); + Require(stream != nullptr && fclose_nid_postfix(stream) == 0); + Require(!Recorded("kernel_sync_probe/existing.bin")); + stream = fopen_nid_postfix("kernel_sync_probe/existing.bin", "a"); + Require(stream != nullptr && fclose_nid_postfix(stream) == 0); + Require(Recorded("kernel_sync_probe/existing.bin")); + const int writer = sceKernelOpen("kernel_sync_probe/data.bin", 0x1 | 0x200 | 0x400, 0644); + Require(writer >= 0 && sceKernelWrite(writer, "data", 4) == 4 && sceKernelClose(writer) == 0); + Require(Recorded("kernel_sync_probe/data.bin")); + Require(sceKernelRename("kernel_sync_probe/data.bin", "kernel_sync_probe/moved.bin") == 0); + Require(Recorded("kernel_sync_probe/moved.bin")); + Require(sceKernelUnlink("kernel_sync_probe/moved.bin") == 0); + sceKernelSync(); + Require(!Recorded("kernel_sync_probe/data.bin") && !Recorded("kernel_sync_probe/moved.bin")); + sceKernelSync(); + const int locked = sceKernelOpen("kernel_sync_probe/read_only.bin", 0x1 | 0x200, 0644); + Require(locked >= 0 && sceKernelWrite(locked, "data", 4) == 4 && sceKernelClose(locked) == 0); + Require(sceKernelChmod_nid_postfix("kernel_sync_probe/read_only.bin", 0444) == 0); + Require(Recorded("kernel_sync_probe/read_only.bin")); + sceKernelSync(); + Require(sceKernelChmod_nid_postfix("kernel_sync_probe/read_only.bin", 0644) == 0); +#ifdef _WIN32 + const auto exclusive = CreateFileW(ResolvePath_nid_no_patch("kernel_sync_probe/read_only.bin").c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + Require(exclusive != INVALID_HANDLE_VALUE); + sceKernelSync(); + CloseHandle(exclusive); +#endif + sceKernelSync(); + std::filesystem::remove_all("kernel_sync_probe"); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 1787c8127..760be9ca7 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -234,6 +234,8 @@ Throughout the project, every function at every stage either **does exactly what - [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported - [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries - [sceKernelPread, sceKernelPwrite, sceKernelPreadv, sceKernelPwritev](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on Windows transfer at the given offset with an overlapped `ReadFile`/`WriteFile` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console. Handles that are not disk files return `ESPIPE`. `readv` on a pipe or console handle stops after the first iovec read, a short read the console is assumed to allow; each iovec is transferred separately, so a failure after the first transfer returns the bytes already done +- [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows flushes only the host files and directories recorded through `RecordWrittenPath_nid_no_patch` (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by `sceKernelSync` +- [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way - [sceKernelInstallExceptionHandler](../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler, but a host fault still ends the process: only signals raised with `sceKernelRaiseException` are delivered. Unity's PS5Util module installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection - [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) delivers on Windows only and throws on Linux - [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) builds the handler's `ucontext` with FreeBSD's amd64 layout (16-byte sigmask, `mcontext` at 0x40, `mc_rsp` at 0xf8); the console's layout is not verified beyond the fields PS5Util reads