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().
This commit is contained in:
parent
e705d430d6
commit
bb1a4cbec4
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <stdexcept>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#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<std::filesystem::path> 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")
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <limits>
|
||||
#include <utility>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
#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<FileStream>(handle.get(), true);
|
||||
handle.release();
|
||||
return stream.release();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,21 @@
|
|||
#include <optional>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <system_error>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#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<std::filesystem::path, bool> dirty;
|
||||
#ifdef _WIN32
|
||||
FILETIME mark = [] { FILETIME now; GetSystemTimeAsFileTime(&now); return now; }();
|
||||
#endif
|
||||
};
|
||||
WrittenPathRegistry& Written() { static WrittenPathRegistry registry; return registry; }
|
||||
#ifdef _WIN32
|
||||
std::optional<FILETIME> 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<int>(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<int>(error), std::system_category(), "Opening " + path.string() + " for sync");
|
||||
}
|
||||
const std::unique_ptr<void, decltype(&CloseHandle)> owner(handle, &CloseHandle);
|
||||
if (!FlushFileBuffers(handle))
|
||||
throw std::system_error(static_cast<int>(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<std::filesystem::path> WrittenPaths_nid_no_patch() {
|
||||
auto& registry = Written();
|
||||
std::lock_guard lock(registry.mutex);
|
||||
std::vector<std::filesystem::path> 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<std::pair<std::filesystem::path, bool>> 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<std::filesystem::path> flushed;
|
||||
std::vector<std::filesystem::path> 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();
|
||||
|
|
|
|||
|
|
@ -129,6 +129,15 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_filesystem PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
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:$<TARGET_FILE_DIR:libkernel>")
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<std::filesystem::path> NativeDescriptorPath(int descriptor) {
|
||||
|
|
@ -73,6 +74,7 @@ static int NativeFtruncate(int descriptor, std::int64_t length) {
|
|||
return static_cast<int>(::_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<time_t>(times[0].tv_sec), static_cast<time_t>(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<std::uintmax_t>(length), error);
|
||||
if (error) return SceErrorFromErrno(GUEST_EIO);
|
||||
RecordWrittenPath_nid_no_patch(native);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,39 +89,6 @@ void validateSchedulingPolicy(int policy) {
|
|||
throw std::invalid_argument("Unsupported guest scheduling policy");
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void syncVolumes() {
|
||||
std::array<wchar_t, 32768> name{};
|
||||
const auto first = FindFirstVolumeW(name.data(), static_cast<DWORD>(name.size()));
|
||||
if (first == INVALID_HANDLE_VALUE)
|
||||
throw std::system_error(GetLastError(), std::system_category(), "Enumerating volumes for sync");
|
||||
const std::unique_ptr<void, decltype(&FindVolumeClose)> 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<void, decltype(&CloseHandle)> 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<DWORD>(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) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
#include "prx/libc/include/General.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#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");
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue