From 54abbf48f29e872be7c52a87c26159235f1d58a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hector=20M=C3=B6dinger=20S=2E?= Date: Fri, 16 Oct 2026 01:27:57 +0200 Subject: [PATCH] kernel: implement vectored writes for profile index saves --- src/kernel/fileSystem.cpp | 32 ++++++++++---- src/kernel/fileSystem.h | 1 + src/libs/libKernel.cpp | 1 + tests/KernelFileSystemTests.cpp | 77 +++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 9 deletions(-) diff --git a/src/kernel/fileSystem.cpp b/src/kernel/fileSystem.cpp index 6f73b53b8..17e9f0d6b 100644 --- a/src/kernel/fileSystem.cpp +++ b/src/kernel/fileSystem.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -956,12 +957,10 @@ int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_ return bytes_written; } -int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset) { - PRINT_NAME(); - +static int64_t Writev(int d, const KernelIovec* iov, int iovcnt, std::optional offset) { std::vector buffers; size_t total = 0; - const int error = ValidateIovecs(iov, iovcnt, offset, &buffers, &total); + const int error = ValidateIovecs(iov, iovcnt, offset.value_or(0), &buffers, &total); if (error != OK) { return error; } @@ -991,11 +990,16 @@ int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, i } const auto position = file->f.Tell(); - const auto target = (file->append ? file->f.Size() : static_cast(offset)); + auto target = position; + if (file->append) { + target = file->f.Size(); + } else if (offset) { + target = static_cast(*offset); + } if (target > static_cast(INT64_MAX) - total) { return KERNEL_ERROR_EFBIG; } - if (!file->f.Seek(target)) { + if ((offset || file->append) && !file->f.Seek(target)) { return KERNEL_ERROR_EIO; } @@ -1012,16 +1016,26 @@ int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, i } } const bool flushed = !file->sync_writes || file->f.Flush(); - const bool restored = file->f.Seek(position); + const bool restored = !offset || file->f.Seek(position); if (!flushed || !restored) { return KERNEL_ERROR_EIO; } - LOGF("\tWritev %" PRId64 " bytes (pos = %" PRId64 ", iovcnt = %d) to: %s\n", bytes_written, - offset, iovcnt, Common::PathToString(file->real_name).c_str()); + LOGF("\tWritev %" PRId64 " bytes (pos = %" PRIu64 ", iovcnt = %d) to: %s\n", bytes_written, + target, iovcnt, Common::PathToString(file->real_name).c_str()); return bytes_written; } +int64_t KYTY_SYSV_ABI KernelWritev(int d, const KernelIovec* iov, int iovcnt) { + PRINT_NAME(); + return Writev(d, iov, iovcnt, std::nullopt); +} + +int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset) { + PRINT_NAME(); + return Writev(d, iov, iovcnt, offset); +} + int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) { PRINT_NAME(); diff --git a/src/kernel/fileSystem.h b/src/kernel/fileSystem.h index 4875197fd..351669840 100644 --- a/src/kernel/fileSystem.h +++ b/src/kernel/fileSystem.h @@ -64,6 +64,7 @@ int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes); int64_t KYTY_SYSV_ABI KernelPread(int d, void* buf, size_t nbytes, int64_t offset); int64_t KYTY_SYSV_ABI KernelPreadv(int d, const KernelIovec* iov, int iovcnt, int64_t offset); int64_t KYTY_SYSV_ABI KernelWrite(int d, const void* buf, size_t nbytes); +int64_t KYTY_SYSV_ABI KernelWritev(int d, const KernelIovec* iov, int iovcnt); int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset); int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset); int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence); diff --git a/src/libs/libKernel.cpp b/src/libs/libKernel.cpp index 1ba48fb88..dcdd5d2e1 100644 --- a/src/libs/libKernel.cpp +++ b/src/libs/libKernel.cpp @@ -3111,6 +3111,7 @@ LIB_DEFINE(InitLibKernel_1_FS) { LIB_FUNC("UK2Tl2DWUns", FileSystem::KernelClose); LIB_FUNC("Cg4srZ6TKbU", FileSystem::KernelRead); LIB_FUNC("4wSze92BhLI", FileSystem::KernelWrite); + LIB_FUNC("kAt6VDbHmro", FileSystem::KernelWritev); LIB_FUNC("+r3rMFwItV4", FileSystem::KernelPread); LIB_FUNC("yTj62I7kw4s", FileSystem::KernelPreadv); LIB_FUNC("nKWi-N2HBV4", FileSystem::KernelPwrite); diff --git a/tests/KernelFileSystemTests.cpp b/tests/KernelFileSystemTests.cpp index 5262a25d0..2d5c10863 100644 --- a/tests/KernelFileSystemTests.cpp +++ b/tests/KernelFileSystemTests.cpp @@ -386,6 +386,82 @@ void TestSaveTruncate() { Check(FileSystem::KernelClose(fd) == OK, "close path truncation fixture"); } +void TestSaveWritev() { + namespace Kernel = Libs::LibKernel; + Loader::SymbolDatabase symbols; + Libs::InitLibKernel_1(&symbols); + const auto resolve = [&](const char* nid) { + const auto* symbol = symbols.Find( + {nid, "libkernel", 1, "libkernel", 1, 1, Loader::SymbolType::Func}); + Check(symbol != nullptr, "vectored write export resolves"); + return symbol->vaddr; + }; + const auto writev = reinterpret_cast(resolve("kAt6VDbHmro")); + const auto pwritev = reinterpret_cast(resolve("mBd4AfLP+u8")); + constexpr char Path[] = "/savedata0/vector-save.dat"; + char first[] = "abc"; + char second[] = "DE"; + const std::array buffers {{ + {first, 3}, {nullptr, 0}, {second, 2}}}; + const int fd = FileSystem::KernelOpen(Path, 0x602, 0777); + Check(fd >= 3 && FileSystem::KernelWrite(fd, "_", 1) == 1 && + writev(fd, buffers.data(), buffers.size()) == 5 && + FileSystem::KernelLseek(fd, 0, 1) == 6, + "writev writes consecutive segments and advances the descriptor position"); + std::array bytes {}; + Check(FileSystem::KernelPread(fd, bytes.data(), bytes.size(), 0) == bytes.size() && + std::memcmp(bytes.data(), "_abcDE", bytes.size()) == 0, + "writev preserves the prefix and writes every nonempty segment in order"); + char replacement[] = "12"; + const std::array patch {{ + {replacement, 1}, {replacement + 1, 1}}}; + Check(pwritev(fd, patch.data(), patch.size(), 1) == 2 && + FileSystem::KernelLseek(fd, 0, 1) == 6 && + FileSystem::KernelPread(fd, bytes.data(), bytes.size(), 0) == bytes.size() && + std::memcmp(bytes.data(), "_12cDE", bytes.size()) == 0, + "pwritev shares segment ordering while preserving the descriptor position"); + + const std::array invalid {{ + {first, 1}, {nullptr, 1}}}; + const std::array oversized {{ + {first, 1}, {second, static_cast(std::numeric_limits::max())}}}; + Check(writev(fd, nullptr, 1) == Kernel::KERNEL_ERROR_EFAULT && + writev(fd, invalid.data(), invalid.size()) == Kernel::KERNEL_ERROR_EFAULT && + writev(fd, buffers.data(), -1) == Kernel::KERNEL_ERROR_EINVAL && + writev(fd, buffers.data(), 1025) == Kernel::KERNEL_ERROR_EINVAL && + writev(fd, oversized.data(), oversized.size()) == Kernel::KERNEL_ERROR_EINVAL && + pwritev(fd, patch.data(), patch.size(), -1) == Kernel::KERNEL_ERROR_EINVAL && + writev(-1, buffers.data(), buffers.size()) == Kernel::KERNEL_ERROR_EBADF && + writev(fd, nullptr, 0) == 0 && + FileSystem::KernelLseek(fd, 0, 1) == 6 && + FileSystem::KernelPread(fd, bytes.data(), bytes.size(), 0) == bytes.size() && + std::memcmp(bytes.data(), "_12cDE", bytes.size()) == 0, + "invalid vectors fail before writing any segment or moving the descriptor"); + const int reader = FileSystem::KernelOpen(Path, 0, 0); + Check(reader >= 3 && writev(reader, buffers.data(), buffers.size()) == + Kernel::KERNEL_ERROR_EBADF && + FileSystem::KernelLseek(reader, 0, 1) == 0 && + FileSystem::KernelClose(reader) == OK, + "writev rejects a read-only descriptor without moving it"); + + const int append = FileSystem::KernelOpen(Path, 0xa, 0); + Check(append >= 3 && FileSystem::KernelLseek(append, 1, 0) == 1 && + writev(append, buffers.data(), buffers.size()) == 5 && + FileSystem::KernelLseek(append, 0, 1) == 11 && + FileSystem::KernelLseek(append, 1, 0) == 1 && + pwritev(append, patch.data(), patch.size(), 0) == 2 && + FileSystem::KernelLseek(append, 0, 1) == 1, + "append vectored writes target EOF while positional writes preserve the cursor"); + std::array appended {}; + Check(FileSystem::KernelPread(fd, appended.data(), appended.size(), 0) == appended.size() && + std::memcmp(appended.data(), "_12cDEabcDE12", appended.size()) == 0, + "append writes retain the original contents and ordered segments"); + Check(FileSystem::KernelClose(append) == OK && FileSystem::KernelClose(fd) == OK, + "close vectored write fixtures"); +} + void TestAioBatches() { namespace Kernel = Libs::LibKernel; Loader::SymbolDatabase symbols; @@ -1969,6 +2045,7 @@ int main(int, char**) { FileSystem::Mount(temporary.Path(), "/savedata0"); TestSaveOpenVisibility(); TestSaveTruncate(); + TestSaveWritev(); TestAioBatches(); CheckSaveRename(temporary.Path(), "first-save"); CheckSaveRename(temporary.Path(), "replacement-save");