Files
AnyPS5/core/libs/tests/GuestAppendWrites.cpp
T
Ahmad e8c97bf926 fix(libkernel): append O_APPEND writes atomically on Windows
On Windows the C runtime appends by seeking to the end and then writing,
so concurrent appends through different descriptors overwrite each other
and lose data. Write and writev on an O_APPEND descriptor now go through
WriteFile at the end-of-file offset, which Windows appends atomically,
and writev gathers its buffers into one append as FreeBSD writes the
whole vector at the end.
2026-10-16 20:25:29 +02:00

111 lines
4.2 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iterator>
#include <set>
#include <string>
#include <thread>
#include <utility>
#include <vector>
struct GuestIovec {
const void* base;
std::size_t length;
};
extern "C" {
int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t);
int APS5_VABI sceKernelClose(int);
std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t);
std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int);
std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t);
std::int64_t APS5_VABI writev_nid_postfix(int, const GuestIovec*, int);
int APS5_VABI dup_nid_postfix(int);
}
static void Check(bool condition, int line) {
if (!condition) {
std::fprintf(stderr, "append check failed at line %d\n", line);
std::abort();
}
}
#define Require(value) Check((value), __LINE__)
static constexpr const char* Path = "guest_append_writes.txt";
static constexpr int CreateWriteAppend = 0x0209;
static constexpr int Truncate = 0x0400;
static constexpr int FromStart = 0;
static constexpr int FromCurrent = 1;
static constexpr int Writers = 4;
static constexpr int LinesPerWriter = 1500;
static constexpr std::size_t LineLength = 32;
static std::string Line(int writer, int index) {
std::string line = "w" + std::to_string(writer) + " " + std::to_string(index) + " ";
line.resize(LineLength - 1, '.');
return line + "\n";
}
static void WriteLines(int fd, int writer) {
for (int index = 0; index < LinesPerWriter; ++index) {
const std::string line = Line(writer, index);
std::int64_t written = -1;
if (writer == 0 || writer == 2) {
written = sceKernelWrite(fd, line.data(), line.size());
} else if (writer == 1) {
written = write_nid_postfix(fd, line.data(), static_cast<std::int64_t>(line.size()));
} else {
const GuestIovec parts[2]{{line.data(), 7}, {line.data() + 7, line.size() - 7}};
written = writev_nid_postfix(fd, parts, 2);
}
Require(written == static_cast<std::int64_t>(LineLength));
}
}
static std::string Contents() {
std::ifstream file(Path, std::ios::binary);
return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
int main() {
const int first = sceKernelOpen(Path, CreateWriteAppend | Truncate, 0644);
Require(first >= 0);
const int second = sceKernelOpen(Path, CreateWriteAppend, 0644);
const int duplicate = dup_nid_postfix(first);
const int vectored = sceKernelOpen(Path, CreateWriteAppend, 0644);
Require(second >= 0 && duplicate >= 0 && vectored >= 0);
const int descriptors[Writers]{first, second, duplicate, vectored};
std::vector<std::thread> threads;
for (int writer = 0; writer < Writers; ++writer) threads.emplace_back(WriteLines, descriptors[writer], writer);
for (auto& thread : threads) thread.join();
const std::string contents = Contents();
Require(contents.size() == static_cast<std::size_t>(Writers) * LinesPerWriter * LineLength);
std::set<std::pair<int, int>> seen;
for (std::size_t offset = 0; offset < contents.size(); offset += LineLength) {
const std::string line = contents.substr(offset, LineLength);
int writer = -1;
int index = -1;
Require(std::sscanf(line.c_str(), "w%d %d ", &writer, &index) == 2);
Require(writer >= 0 && writer < Writers && index >= 0 && index < LinesPerWriter);
Require(line == Line(writer, index));
Require(seen.emplace(writer, index).second);
}
Require(seen.size() == static_cast<std::size_t>(Writers) * LinesPerWriter);
const auto size = static_cast<std::int64_t>(contents.size());
Require(sceKernelLseek(first, 0, FromStart) == 0);
const std::string tail = Line(9, 0);
Require(sceKernelWrite(first, tail.data(), tail.size()) == static_cast<std::int64_t>(LineLength));
Require(sceKernelLseek(first, 0, FromCurrent) == size + static_cast<std::int64_t>(LineLength));
Require(Contents().substr(static_cast<std::size_t>(size)) == tail);
for (const int fd : descriptors) Require(sceKernelClose(fd) == 0);
std::remove(Path);
}