// Exercise the guest ABI without linking unrelated emulator subsystems. #include "libs/libSaveData.cpp" #include #include #include #include #include #ifdef _WIN32 #include #else #include #include #endif namespace { std::string g_test_title = "PPSA21564"; std::filesystem::path g_mounted_directory; std::map g_test_mounts; } // namespace namespace Loader { bool SystemContentParamSfoGetString(const char*, std::string* value) { *value = g_test_title; return true; } void SymbolDatabase::Add(const SymbolResolve&, uint64_t, const std::string&) {} namespace Timer { double GetTimeMs() { return 0.0; } } // namespace Timer } // namespace Loader namespace Libs::LibKernel::FileSystem { void Mount(const std::filesystem::path& directory, const std::string& point) { g_mounted_directory = directory; g_test_mounts[point] = directory; } void Umount(const std::string& point) { g_test_mounts.erase(point); } std::filesystem::path GetRealFilename(const std::string& point) { return g_test_mounts.at(point); } } // namespace Libs::LibKernel::FileSystem namespace { using namespace Libs::SaveData; namespace fs = std::filesystem; #define CHECK(condition) \ do { \ if (!(condition)) { \ std::fprintf(stderr, "SaveDataMemoryTests:%d: %s\n", __LINE__, #condition); \ std::abort(); \ } \ } while (false) SaveDataMemorySetup2 SetupParam(int32_t user = 1, uint32_t slot = 0, size_t size = 16) { SaveDataMemorySetup2 setup {}; setup.user_id = user; setup.slot_id = slot; setup.memory_size = size; return setup; } size_t Setup(int32_t user = 1, uint32_t slot = 0, size_t size = 16, uint32_t option = 0) { auto setup = SetupParam(user, slot, size); setup.option = option; SaveDataMemorySetupResult result {}; CHECK(SaveDataSetupSaveDataMemory2(&setup, &result) == OK); return result.existed_memory_size; } int Set(void* bytes, size_t size, int64_t offset = 0, int32_t user = 1, uint32_t slot = 0) { SaveDataMemoryData data {bytes, size, offset, {}}; SaveDataMemorySet2 set {}; set.user_id = user; set.slot_id = slot; set.data = &data; set.data_num = 1; return SaveDataSetSaveDataMemory2(&set); } std::vector Get(size_t size = 16, int32_t user = 1, uint32_t slot = 0) { std::vector bytes(size); SaveDataMemoryData data {bytes.data(), size, 0, {}}; SaveDataMemoryGet2 get {}; get.user_id = user; get.slot_id = slot; get.data = &data; CHECK(SaveDataGetSaveDataMemory2(&get) == OK); return bytes; } void Reset(const char* title) { CHECK(SaveDataTerminate() == OK); g_test_title = title; } void TestValidationAndScatter() { Reset("VALIDATE"); uint8_t byte = 0x55; SaveDataMemoryGet2 get {}; get.user_id = 1; SaveDataMemorySync sync {}; sync.user_id = 1; CHECK(SaveDataGetSaveDataMemory2(&get) == SAVE_DATA_ERROR_MEMORY_NOT_READY); CHECK(Set(&byte, 1) == SAVE_DATA_ERROR_MEMORY_NOT_READY); CHECK(SaveDataSyncSaveDataMemory(&sync) == SAVE_DATA_ERROR_MEMORY_NOT_READY); CHECK(SaveDataSetupSaveDataMemory2(nullptr, nullptr) == SAVE_DATA_ERROR_PARAMETER); auto invalid_user = SetupParam(-1); CHECK(SaveDataSetupSaveDataMemory2(&invalid_user, nullptr) == SAVE_DATA_ERROR_INVALID_LOGIN_USER); for (auto invalid: {SetupParam(1, 4), SetupParam(1, 0, 0), SetupParam(1, 0, 32 * 1024 * 1024 + 1)}) { CHECK(SaveDataSetupSaveDataMemory2(&invalid, nullptr) == SAVE_DATA_ERROR_PARAMETER); } CHECK(Setup() == 0); auto setup = SetupParam(); CHECK(SaveDataSetupSaveDataMemory2(&setup, nullptr) == SAVE_DATA_ERROR_BUSY); std::array original {}; for (size_t i = 0; i < original.size(); ++i) { original[i] = static_cast(i); } CHECK(Set(original.data(), original.size()) == OK); SaveDataMemoryData data[] = {{&byte, 1, 2, {}}, {&byte, 1, 16, {}}}; SaveDataMemorySet2 set {}; set.user_id = 1; set.data = data; set.data_num = 2; CHECK(SaveDataSetSaveDataMemory2(&set) == SAVE_DATA_ERROR_PARAMETER); CHECK(Get() == std::vector(original.begin(), original.end())); data[1].offset = 12; CHECK(SaveDataSetSaveDataMemory2(&set) == OK); original[2] = original[12] = byte; CHECK(Get() == std::vector(original.begin(), original.end())); byte = 0xaa; set.data_num = 0; CHECK(SaveDataSetSaveDataMemory2(&set) == OK); original[2] = byte; CHECK(Get() == std::vector(original.begin(), original.end())); set.data = nullptr; CHECK(SaveDataSetSaveDataMemory2(&set) == OK); CHECK(Get() == std::vector(original.begin(), original.end())); set.data_num = 6; CHECK(SaveDataSetSaveDataMemory2(&set) == SAVE_DATA_ERROR_PARAMETER); set.data_num = 1; set.data = nullptr; CHECK(SaveDataSetSaveDataMemory2(&set) == SAVE_DATA_ERROR_PARAMETER); CHECK(Set(&byte, 1, -1) == SAVE_DATA_ERROR_PARAMETER); CHECK(Set(&byte, 1, std::numeric_limits::max()) == SAVE_DATA_ERROR_PARAMETER); CHECK(Set(&byte, std::numeric_limits::max(), 1) == SAVE_DATA_ERROR_PARAMETER); SaveDataMemoryData invalid {&byte, std::numeric_limits::max(), 1, {}}; get.data = &invalid; CHECK(SaveDataGetSaveDataMemory2(&get) == SAVE_DATA_ERROR_PARAMETER); CHECK(byte == 0xaa); CHECK(Get() == std::vector(original.begin(), original.end())); CHECK(SaveDataTerminate() == OK); CHECK(Setup() == original.size()); CHECK(Get() == std::vector(original.begin(), original.end())); } void TestIsolationAndSync() { Reset("ISOLATE"); uint8_t byte = 11; CHECK(Setup() == 0); CHECK(Set(&byte, 1) == OK); CHECK(Setup(2) == 0); CHECK(Setup(1, 1) == 0); CHECK(Get(16, 2) == std::vector(16)); CHECK(Get(16, 1, 1) == std::vector(16)); byte = 22; CHECK(Set(&byte, 1, 0, 2) == OK); byte = 33; CHECK(Set(&byte, 1, 0, 1, 1) == OK); CHECK(Get()[0] == 11); CHECK(Get(16, 2)[0] == 22); CHECK(Get(16, 1, 1)[0] == 33); SaveDataMemorySync sync {}; sync.user_id = 1; sync.slot_id = 1; CHECK(SaveDataSyncSaveDataMemory(&sync) == OK); SaveDataEvent event {}; CHECK(SaveDataGetEventResult(nullptr, &event) == OK); CHECK(event.type == 3 && event.error_code == OK && event.user_id == 1); CHECK(std::string(event.dir_name.data) == "sce_sdmemory1"); CHECK(SaveDataGetEventResult(nullptr, &event) == SAVE_DATA_ERROR_NOT_FOUND); sync.option = 1; CHECK(SaveDataSyncSaveDataMemory(&sync) == OK); CHECK(SaveDataGetEventResult(nullptr, &event) == SAVE_DATA_ERROR_NOT_FOUND); sync.option = 2; CHECK(SaveDataSyncSaveDataMemory(&sync) == SAVE_DATA_ERROR_PARAMETER); Reset("OTHER"); CHECK(Setup() == 0); CHECK(Get() == std::vector(16)); Reset("ISOLATE"); CHECK(Setup() == 16); CHECK(Setup(2) == 16); CHECK(Setup(1, 1) == 16); CHECK(Get()[0] == 11 && Get(16, 2)[0] == 22 && Get(16, 1, 1)[0] == 33); Reset("OTHER"); SceSaveDataTitleId title {}; std::strcpy(title.data, "ISOLATE"); SceSaveDataDirName name {}; std::strcpy(name.data, "sce_sdmemory"); struct SaveDataTransferringMount mount {}; mount.user_id = 2; mount.title_id = &title; mount.dir_name = &name; SaveDataMountResult result {}; CHECK(SaveDataTransferringMount(&mount, &result) == OK); CHECK(g_mounted_directory == fs::path("_SaveData/ISOLATE/sce_sdmemory/2")); CHECK(std::ifstream(g_mounted_directory / "memory.dat", std::ios::binary).get() == 22); CHECK(SaveDataUmount2(0, &result.mount_point) == OK); struct SaveDataDelete del {}; del.user_id = 1; del.title_id = &title; del.dir_name = &name; CHECK(SaveDataDelete(&del) == OK); Reset("ISOLATE"); CHECK(Setup() == 0); CHECK(Setup(2) == 16); CHECK(Get(16, 2)[0] == 22); } void TestFailedWritePreservesSave() { Reset("IOFAIL"); CHECK(Setup() == 0); uint8_t byte = 42; CHECK(Set(&byte, 1) == OK); fs::rename("_SaveData", "save-backup"); { std::ofstream blocker("_SaveData"); blocker << "not a directory"; } byte = 99; CHECK(Set(&byte, 1) < 0); CHECK(Get()[0] == 42); CHECK(fs::remove("_SaveData")); fs::rename("save-backup", "_SaveData"); CHECK(SaveDataTerminate() == OK); CHECK(Setup() == 16); CHECK(Get()[0] == 42); } void WriteRestartFixture() { auto setup = SetupParam(7, 3); setup.option = 1; std::array icon_bytes {1, 2, 3, 4}; SaveDataIcon icon {icon_bytes.data(), icon_bytes.size(), icon_bytes.size(), {}}; setup.init_icon = &icon; setup.icon_memory_size = icon_bytes.size(); SaveDataParam param {}; std::strcpy(param.title, "Astro progress"); param.user_param = 17; setup.init_param = ¶m; CHECK(SaveDataSetupSaveDataMemory2(&setup, nullptr) == OK); std::array progress {11, 22, 33, 44}; SaveDataMemoryData data {progress.data(), progress.size(), 4, {}}; SaveDataMemorySet2 set {}; set.user_id = 7; set.slot_id = 3; set.data = &data; set.icon = &icon; CHECK(SaveDataSetSaveDataMemory2(&set) == OK); set.data = nullptr; param.user_param = 18; std::strcpy(param.detail, "Coins, level state, rescued bots"); set.param = ¶m; CHECK(SaveDataSetSaveDataMemory2(&set) == OK); // No Sync or Terminate: Set must survive process exit on its own. } void ReadRestartFixture() { CHECK(Setup(7, 3, 24, 1) == 16); auto expected = std::vector(24); expected[4] = 11; expected[5] = 22; expected[6] = 33; expected[7] = 44; CHECK(Get(24, 7, 3) == expected); SaveDataParam param {}; std::array icon_bytes {9, 9, 9, 9}; SaveDataIcon icon {icon_bytes.data(), icon_bytes.size(), icon_bytes.size(), {}}; SaveDataMemoryGet2 get {}; get.user_id = 7; get.slot_id = 3; get.param = ¶m; get.icon = &icon; CHECK(SaveDataGetSaveDataMemory2(&get) == OK); CHECK(icon.data_size == 0); CHECK( std::all_of(icon_bytes.begin(), icon_bytes.end(), [](uint8_t byte) { return byte == 9; })); CHECK(std::string(param.title) == "Astro progress"); CHECK(std::string(param.detail) == "Coins, level state, rescued bots"); CHECK(param.user_param == 18); CHECK(SaveDataTerminate() == OK); CHECK(Setup(7, 3, 6) == 16); expected.resize(6); CHECK(Get(6, 7, 3) == expected); } SceSaveDataDirName DirName(const char* text) { SceSaveDataDirName name {}; std::snprintf(name.data, sizeof(name.data), "%s", text); return name; } std::vector Search(int32_t user, const SceSaveDataTitleId* title = nullptr) { SaveDataDirNameSearchCond cond {}; cond.user_id = user; cond.title_id = title; std::array names {}; SaveDataDirNameSearchResult result {}; result.dir_names = names.data(); result.dir_names_num = names.size(); CHECK(SaveDataDirNameSearch(&cond, &result) == OK); CHECK(result.hit_num == result.set_num && result.set_num <= names.size()); std::vector found; for (uint32_t i = 0; i < result.set_num; ++i) { found.emplace_back(names[i].data); } return found; } void TestClassicSavePaths() { Reset("CLASSIC"); const std::vector names {"save.1", "save1", "slot@A", "slotA"}; const auto existing = DirName("save.1"); struct SaveDataMount3 initial {}; initial.user_id = 1; initial.dir_name = &existing; initial.mount_mode = 4; initial.blocks = 48; SaveDataMountResult first {}; CHECK(SaveDataMount3(&initial, &first) == OK); std::ofstream(g_mounted_directory / "progress") << "save.1"; CHECK(SaveDataUmount2(0, &first.mount_point) == OK); CHECK(Search(1) == std::vector {"save.1"}); for (const auto& text: names) { auto name = DirName(text.c_str()); struct SaveDataMount3 mount {}; mount.user_id = 1; mount.dir_name = &name; mount.mount_mode = text == "save.1" ? 1 : 4; mount.blocks = 48; SaveDataMountResult result {}; CHECK(SaveDataMount3(&mount, &result) == OK); CHECK(g_mounted_directory == fs::path("_SaveData") / "CLASSIC" / text); if (text != "save.1") { std::ofstream saved(g_mounted_directory / "progress"); saved << text; } CHECK(SaveDataUmount2(0, &result.mount_point) == OK); } CHECK(Search(1) == names); SceSaveDataTitleId title {}; std::strcpy(title.data, "CLASSIC"); Reset("OTHER"); CHECK(Search(1).empty()); CHECK(Search(1, &title) == names); for (const auto& text: names) { auto name = DirName(text.c_str()); struct SaveDataTransferringMount mount {}; mount.user_id = 1; mount.title_id = &title; mount.dir_name = &name; SaveDataMountResult result {}; CHECK(SaveDataTransferringMount(&mount, &result) == OK); std::ifstream saved(g_mounted_directory / "progress"); std::string progress; saved >> progress; CHECK(progress == text); CHECK(SaveDataUmount2(0, &result.mount_point) == OK); } Reset("CLASSIC"); for (const char* text: {"", ".", "..", "../save1", "save/1", "save\\1"}) { auto name = DirName(text); struct SaveDataMount3 mount {}; mount.user_id = 1; mount.dir_name = &name; mount.mount_mode = 32; SaveDataMountResult result {}; g_mounted_directory.clear(); CHECK(SaveDataMount3(&mount, &result) == SAVE_DATA_ERROR_PARAMETER); CHECK(g_mounted_directory.empty()); struct SaveDataDelete del {}; del.user_id = 1; del.dir_name = &name; CHECK(SaveDataDelete(&del) == SAVE_DATA_ERROR_PARAMETER); } CHECK(Search(1) == names); } void TestSaveAllocations() { Reset("CAPACITY"); std::array names {DirName("Options"), DirName("Player")}; const std::array allocations {48, 96}; for (const auto blocks : {0u, 47u, 16385u}) { struct SaveDataMount3 invalid {}; invalid.user_id = 1; invalid.dir_name = &names[0]; invalid.mount_mode = 4; invalid.blocks = blocks; SaveDataMountResult result {}; CHECK(SaveDataMount3(&invalid, &result) == SAVE_DATA_ERROR_PARAMETER); CHECK(!fs::exists("_SaveData/CAPACITY/Options")); } for (size_t i = 0; i < names.size(); i++) { struct SaveDataMount3 mount {}; mount.user_id = 1; mount.dir_name = &names[i]; mount.mount_mode = 4; mount.blocks = allocations[i]; SaveDataMountResult result {}; CHECK(SaveDataMount3(&mount, &result) == OK); CHECK(result.mount_status == 1); std::ofstream(g_mounted_directory / "USR-DATA") << "saved payload"; CHECK(SaveDataUmount2(0, &result.mount_point) == OK); SaveDataMountInfo info {}; CHECK(SaveDataGetMountInfo(&result.mount_point, &info) == SAVE_DATA_ERROR_NOT_MOUNTED); } Reset("CAPACITY"); std::array found {}; std::array infos {}; SaveDataDirNameSearchCond cond {}; cond.user_id = 1; SaveDataDirNameSearchResult search {}; search.dir_names = found.data(); search.dir_names_num = found.size(); search.infos = infos.data(); CHECK(SaveDataDirNameSearch(&cond, &search) == OK && search.set_num == 2); for (size_t i = 0; i < names.size(); i++) { CHECK(std::string(found[i].data) == names[i].data); CHECK(infos[i].blocks == allocations[i] && infos[i].free_blocks == allocations[i]); struct SaveDataMount3 mount {}; mount.user_id = 1; mount.dir_name = &names[i]; mount.mount_mode = 34; mount.blocks = 16384; SaveDataMountResult result {}; CHECK(SaveDataMount3(&mount, &result) == OK && result.mount_status == 0); std::ofstream(g_mounted_directory / "USR-DATA", std::ios::app) << "more data"; SaveDataMountInfo info {}; CHECK(SaveDataGetMountInfo(&result.mount_point, &info) == OK); CHECK(info.blocks == allocations[i] && info.free_blocks == allocations[i]); CHECK(SaveDataUmount2(0, &result.mount_point) == OK); } CHECK(infos[0].blocks + infos[1].blocks == 144); const fs::path metadata = "_SaveData/CAPACITY/Options/sce_sys/blocks.bin"; fs::resize_file(metadata, 7); CHECK(SaveDataDirNameSearch(&cond, &search) == SAVE_DATA_ERROR_BROKEN); CHECK(fs::file_size(metadata) == 7); CHECK(fs::remove(metadata)); CHECK(SaveDataDirNameSearch(&cond, &search) == SAVE_DATA_ERROR_BROKEN); CHECK(!fs::exists(metadata)); } void TestClassicSaveParams() { Reset("PARAMS"); const auto name = DirName("slot"); struct SaveDataMount3 mount {}; mount.user_id = 1; mount.dir_name = &name; mount.mount_mode = 4; mount.blocks = 48; SaveDataMountResult mounted {}; CHECK(SaveDataMount3(&mount, &mounted) == OK); SaveDataParam param {}; std::strcpy(param.title, "First save"); std::strcpy(param.sub_title, "Chapter 2"); std::strcpy(param.detail, "Progress"); param.user_param = 42; CHECK(SaveDataSetParam(&mounted.mount_point, 0, nullptr, sizeof(param)) == SAVE_DATA_ERROR_PARAMETER); CHECK(SaveDataSetParam(&mounted.mount_point, 0, ¶m, sizeof(param) - 1) == SAVE_DATA_ERROR_PARAMETER); CHECK(SaveDataSetParam(&mounted.mount_point, 0, ¶m, sizeof(param)) == OK); const fs::path saved = "_SaveData/PARAMS/slot"; CHECK(fs::exists(saved / "sce_sys/param.bin")); fs::create_directory(saved / "nested"); std::ofstream(saved / "nested/progress") << "payload"; SaveDataParam loaded {}; size_t got = 0; CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), &got) == OK); CHECK(got == sizeof(loaded) && std::string(loaded.title) == "First save"); CHECK(std::string(loaded.sub_title) == "Chapter 2" && loaded.user_param == 42); CHECK(loaded.mtime > 0); fs::last_write_time(saved / "nested/progress", fs::last_write_time(saved / "sce_sys/param.bin") + std::chrono::hours(2)); CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK); const int64_t nested_mtime = loaded.mtime; CHECK(nested_mtime > static_cast(std::time(nullptr)) + 3600); fs::create_directory(saved / "sce_sys/param.bin.tmp"); std::strcpy(param.title, "Failed update"); CHECK(SaveDataSetParam(&mounted.mount_point, 0, ¶m, sizeof(param)) == SAVE_DATA_ERROR_INTERNAL); CHECK(fs::remove(saved / "sce_sys/param.bin.tmp")); CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK); CHECK(std::string(loaded.title) == "First save"); std::strcpy(param.title, "Updated save"); CHECK(SaveDataSetParam(&mounted.mount_point, 0, ¶m, sizeof(param)) == OK); CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK); CHECK(std::string(loaded.title) == "Updated save"); CHECK(SaveDataUmount2(0, &mounted.mount_point) == OK); CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == SAVE_DATA_ERROR_NOT_MOUNTED); Reset("OTHER"); SceSaveDataTitleId source_title {}; std::strcpy(source_title.data, "PARAMS"); struct SaveDataTransferringMount transfer {}; transfer.user_id = 1; transfer.title_id = &source_title; transfer.dir_name = &name; SaveDataMountResult transferred {}; CHECK(SaveDataTransferringMount(&transfer, &transferred) == OK); CHECK(SaveDataGetParam(&transferred.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK); CHECK(std::string(loaded.title) == "Updated save"); std::error_code alias_error; fs::create_directory_symlink("PARAMS", "_SaveData/P_ALIAS", alias_error); if (!alias_error) { SceSaveDataTitleId alias_title {}; std::strcpy(alias_title.data, "P_ALIAS"); transfer.title_id = &alias_title; SaveDataMountResult duplicate {}; CHECK(SaveDataTransferringMount(&transfer, &duplicate) == SAVE_DATA_ERROR_BUSY); transfer.title_id = &source_title; CHECK(fs::remove("_SaveData/P_ALIAS")); } #ifdef _WIN32 SceSaveDataTitleId lowercase_title {}; std::strcpy(lowercase_title.data, "params"); transfer.title_id = &lowercase_title; SaveDataMountResult duplicate {}; CHECK(SaveDataTransferringMount(&transfer, &duplicate) == SAVE_DATA_ERROR_BUSY); transfer.title_id = &source_title; #endif SaveDataMountResult other {}; CHECK(SaveDataMount3(&mount, &other) == OK); std::strcpy(param.title, "Other title"); CHECK(SaveDataSetParam(&other.mount_point, 0, ¶m, sizeof(param)) == OK); constexpr char typed_title[] = "Typed title"; CHECK(SaveDataSetParam(&other.mount_point, 1, typed_title, sizeof(typed_title)) == OK); char title[32] {}; CHECK(SaveDataGetParam(&other.mount_point, 1, title, sizeof(title), &got) == OK); CHECK(std::string(title) == typed_title && got == sizeof(typed_title)); const uint32_t user_param = 77; CHECK(SaveDataSetParam(&other.mount_point, 4, &user_param, sizeof(user_param)) == OK); CHECK(SaveDataGetParam(&other.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK); CHECK(std::string(loaded.title) == typed_title && loaded.user_param == user_param); CHECK(SaveDataGetParam(&other.mount_point, 5, &loaded.mtime, sizeof(loaded.mtime), nullptr) == OK); CHECK(loaded.mtime > 0); CHECK(SaveDataSetParam(&other.mount_point, 5, &loaded.mtime, sizeof(loaded.mtime)) == SAVE_DATA_ERROR_PARAMETER); CHECK(SaveDataGetParam(&transferred.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK); CHECK(std::string(loaded.title) == "Updated save"); CHECK(SaveDataUmount2(0, &other.mount_point) == OK); CHECK(SaveDataUmount2(0, &transferred.mount_point) == OK); std::array names {}; std::array params {}; SaveDataDirNameSearchCond cond {}; cond.user_id = 1; cond.title_id = &source_title; SaveDataDirNameSearchResult result {}; result.dir_names = names.data(); result.dir_names_num = names.size(); result.params = params.data(); CHECK(SaveDataDirNameSearch(&cond, &result) == OK && result.set_num == 1); CHECK(std::string(names[0].data) == "slot" && std::string(params[0].title) == "Updated save"); CHECK(params[0].mtime == nested_mtime); } void RunChild(const fs::path& executable, const char* mode) { #ifdef _WIN32 CHECK(_spawnl(_P_WAIT, executable.string().c_str(), executable.string().c_str(), mode, static_cast(nullptr)) == 0); #else const pid_t pid = fork(); CHECK(pid >= 0); if (pid == 0) { execl(executable.c_str(), executable.c_str(), mode, static_cast(nullptr)); _exit(127); } int status = 0; CHECK(waitpid(pid, &status, 0) == pid); CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0); #endif } } // namespace int main(int argc, char** argv) { if (argc == 2) { if (std::string_view(argv[1]) == "write") { WriteRestartFixture(); } else { CHECK(std::string_view(argv[1]) == "read"); ReadRestartFixture(); } return 0; } const auto executable = fs::absolute(argv[0]); const auto previous = fs::current_path(); const auto unique = std::chrono::steady_clock::now().time_since_epoch().count(); const auto temp = fs::temp_directory_path() / ("kyty_save_memory_" + std::to_string(unique)); CHECK(fs::create_directories(temp)); fs::current_path(temp); RunChild(executable, "write"); RunChild(executable, "read"); TestValidationAndScatter(); TestIsolationAndSync(); TestFailedWritePreservesSave(); TestClassicSavePaths(); TestSaveAllocations(); TestClassicSaveParams(); CHECK(SaveDataTerminate() == OK); fs::current_path(previous); fs::remove_all(temp); return 0; }