diff --git a/CMakeLists.txt b/CMakeLists.txt index e80e24f1..9766387f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -454,6 +454,9 @@ add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL target_link_libraries(event_queue_lifetime_tests common fmt::fmt) target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers}) +add_kyty_full_emulator_test(kernel_file_system_tests + "${KYTY_TESTS_DIR}/KernelFileSystemTests.cpp") + add_executable(sync_on_address_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/SyncOnAddressTests.cpp" "${KYTY_SOURCE_DIR}/kernel/syncOnAddress.cpp" @@ -514,6 +517,7 @@ if(BUILD_TESTING) add_test(NAME shader_stage_runtime COMMAND $) add_test(NAME resource_tracking COMMAND $) add_test(NAME event_queue_lifetime COMMAND $) + add_test(NAME kernel_file_system COMMAND $) add_test(NAME sync_on_address COMMAND $) add_test(NAME audio_out2_port COMMAND $) add_test(NAME shader_recompiler_compute COMMAND $) @@ -567,6 +571,7 @@ if(BUILD_TESTING) shader_stage_runtime_tests resource_tracking_tests event_queue_lifetime_tests + kernel_file_system_tests sync_on_address_tests shader_recompiler_compute_tests virtual_memory_allocation_tests diff --git a/src/kernel/fileSystem.cpp b/src/kernel/fileSystem.cpp index f2e9c0f1..be91483e 100644 --- a/src/kernel/fileSystem.cpp +++ b/src/kernel/fileSystem.cpp @@ -43,7 +43,7 @@ public: }; MountPoints() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); } - virtual ~MountPoints() { KYTY_NOT_IMPLEMENTED; } + virtual ~MountPoints() = default; KYTY_CLASS_NO_COPY(MountPoints); @@ -76,7 +76,7 @@ struct File { class FileDescriptors { public: FileDescriptors() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); } - virtual ~FileDescriptors() { KYTY_NOT_IMPLEMENTED; } + virtual ~FileDescriptors() = default; KYTY_CLASS_NO_COPY(FileDescriptors); @@ -193,8 +193,10 @@ void FileDescriptors::CloseAll() { Common::LockGuard lock(m_mutex); for (auto& f: m_files) { - if (f != nullptr && f->opened) { - f->f.Close(); + if (f != nullptr) { + if (f->opened) { + f->f.Close(); + } delete f; f = nullptr; } @@ -1054,9 +1056,6 @@ int KYTY_SYSV_ABI KernelRename(const char* from, const char* to) { auto real_from = g_mount_points->GetRealFilename(from_path); auto real_to = g_mount_points->GetRealFilename(to_path); - EXIT_NOT_IMPLEMENTED(g_files->GetFile(real_from) != nullptr); - EXIT_NOT_IMPLEMENTED(g_files->GetFile(real_to) != nullptr); - if (!Common::File::IsFileExisting(real_from)) { return KERNEL_ERROR_ENOENT; } diff --git a/tests/KernelFileSystemTests.cpp b/tests/KernelFileSystemTests.cpp new file mode 100644 index 00000000..5ca8ed21 --- /dev/null +++ b/tests/KernelFileSystemTests.cpp @@ -0,0 +1,101 @@ +#include "common/emulatorConfig.h" +#include "common/file.h" +#include "common/logging/log.h" +#include "common/subsystems.h" +#include "common/threads.h" +#include "kernel/fileSystem.h" +#include "libs/errno.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +namespace FileSystem = Libs::LibKernel::FileSystem; + +void Check(bool value, const char *text) { + if (!value) { + std::fprintf(stderr, "KernelFileSystemTests: failed: %s\n", text); + std::abort(); + } +} + +class TempDirectory { +public: + TempDirectory() { + const auto unique = + std::chrono::steady_clock::now().time_since_epoch().count(); + m_path = std::filesystem::temp_directory_path() / + ("kyty_kernel_file_system_" + std::to_string(unique)); + Check(std::filesystem::create_directories(m_path), + "create temporary directory"); + } + + ~TempDirectory() { + std::error_code error; + std::filesystem::remove_all(m_path, error); + } + + [[nodiscard]] const std::filesystem::path &Path() const { return m_path; } + + KYTY_CLASS_NO_COPY(TempDirectory); + +private: + std::filesystem::path m_path; +}; + +void CheckSaveRename(const std::filesystem::path &root, + std::string_view payload) { + constexpr char Source[] = "/savedata0/STEMP000.DAT"; + constexpr char Target[] = "/savedata0/SDATA000.DAT"; + constexpr char Suffix[] = "-after-rename"; + + const int fd = FileSystem::KernelOpen(Source, 0x601, 0777); + Check(fd >= 3, "open temporary save file"); + Check(FileSystem::KernelWrite(fd, payload.data(), payload.size()) == + payload.size(), + "write save payload"); + Check(FileSystem::KernelRename(Source, Target) == OK, + "rename open save file"); + Check(FileSystem::KernelWrite(fd, Suffix, sizeof(Suffix) - 1) == + sizeof(Suffix) - 1, + "write through renamed descriptor"); + Check(FileSystem::KernelClose(fd) == OK, "close renamed descriptor"); + + Common::File result(root / "SDATA000.DAT", Common::File::Mode::Read); + Check(!result.IsInvalid(), "open renamed save file"); + const auto data = result.ReadWholeBuffer(); + result.Close(); + const std::string expected = std::string(payload) + Suffix; + Check(data.Size() == expected.size(), "renamed save size"); + Check(std::memcmp(data.GetData(), expected.data(), expected.size()) == 0, + "renamed save contents"); +} + +} // namespace + +int main() { + Common::InitializeThreads(); + Common::Subsystems subsystems; + subsystems.Initialize(); + Config::ConfigOptions options; + options.printf_direction = Config::OutputDirection::Silent; + Config::Load(options); + subsystems.Initialize(); + + TempDirectory temporary; + FileSystem::Initialize(); + FileSystem::Mount(temporary.Path(), "/savedata0"); + CheckSaveRename(temporary.Path(), "first-save"); + CheckSaveRename(temporary.Path(), "replacement-save"); + FileSystem::Shutdown(); + subsystems.Destroy(); + + std::printf("KernelFileSystemTests: all cases passed\n"); + return 0; +}