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