mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 12:05:06 +00:00
On Windows the UCRT drops a write that directly follows a read on an r+, w+ or a+ stream: fputc and fprintf report success, fwrite returns 0 and fputs returns EOF, all without the error indicator, and the file is left unchanged, also after an fflush. On a stream open only for reading, fputc after a read reports success as well. Writes now first do what FreeBSD 11.4's __swsetup does: a stream open only for reading fails with EBADF and sets the error indicator, and an update stream that is reading drops its read buffer and ungetc data, clears end of file and writes at the descriptor's offset. This covers fwrite, fputs, fputc, putc_unlocked, fprintf, fprintf_s, fputwc, fputws, fwprintf, fwprintf_s, puts and wprintf, and printf, vprintf and printf_s, which write to standard output through PrintWindows and now return -1 there instead of throwing. Linux is unchanged: glibc writes at the stream position, which the libc technical debt now records.
395 lines
22 KiB
C++
395 lines
22 KiB
C++
#include "prx/libc/include/Api.hpp"
|
|
#include "prx/libc/include/FileStream.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <array>
|
|
#include <barrier>
|
|
#include <cerrno>
|
|
#include <cstdarg>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <thread>
|
|
#include <vector>
|
|
#include <fcntl.h>
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
static void Require(bool condition, const char* message = "File stream check failed") {
|
|
if (!condition) throw std::runtime_error(message);
|
|
}
|
|
|
|
template<typename TAction>
|
|
static void ExpectException(TAction action) {
|
|
try {
|
|
action();
|
|
} catch (const std::runtime_error&) {
|
|
return;
|
|
}
|
|
throw std::runtime_error("Expected a stream exception");
|
|
}
|
|
|
|
static void ReplaceWithReadOnlyDescriptor(FileStream* stream, const char* path) {
|
|
#ifdef _WIN32
|
|
const int descriptor = _open(path, _O_RDONLY | _O_BINARY);
|
|
Require(descriptor >= 0 && _dup2(descriptor, _fileno(GetNativeStream(stream))) == 0);
|
|
Require(_close(descriptor) == 0);
|
|
#else
|
|
const int descriptor = open(path, O_RDONLY);
|
|
Require(descriptor >= 0 && dup2(descriptor, fileno(GetNativeStream(stream))) >= 0);
|
|
Require(close(descriptor) == 0);
|
|
#endif
|
|
}
|
|
|
|
static void CheckExclusiveCppOpen(const std::string& base) {
|
|
constexpr int Out = 0x02, Binary = 0x80, Noreplace = 0x40;
|
|
const std::string path = base + ".exclusive";
|
|
std::remove(path.c_str());
|
|
auto* stream = _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), Out | Binary | Noreplace, 0);
|
|
Require(stream != nullptr);
|
|
constexpr char original[] = "keep existing contents";
|
|
Require(fwrite_nid_postfix(original, 1, sizeof(original), stream) == sizeof(original));
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
#ifdef _WIN32
|
|
const auto nativePath = std::filesystem::path(path).wstring();
|
|
const auto writer = CreateFileW(nativePath.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
Require(writer != INVALID_HANDLE_VALUE);
|
|
auto* refused = _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), Out | Binary | Noreplace, 0);
|
|
if (refused) fclose_nid_postfix(refused);
|
|
Require(CloseHandle(writer) != 0);
|
|
Require(refused == nullptr, "C++ noreplace truncated an existing write-shared file");
|
|
#endif
|
|
stream = fopen_nid_postfix(path.c_str(), "rb");
|
|
Require(stream != nullptr);
|
|
std::array<char, sizeof(original)> saved{};
|
|
Require(fread_nid_postfix(saved.data(), 1, saved.size(), stream) == saved.size());
|
|
Require(std::memcmp(saved.data(), original, saved.size()) == 0);
|
|
Require(fclose_nid_postfix(stream) == 0 && std::remove(path.c_str()) == 0);
|
|
for (const int mode : {0x42, 0x52, 0x53, 0xc2, 0xd2, 0xd3}) {
|
|
stream = _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), mode | 0x04, 0);
|
|
Require(stream != nullptr && ftell_nid_postfix(stream) == 0);
|
|
Require(fwrite_nid_postfix(original, 1, sizeof(original), stream) == sizeof(original));
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
errno = 0;
|
|
Require(_ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), mode, 0) == nullptr && errno == EEXIST);
|
|
stream = fopen_nid_postfix(path.c_str(), "rb");
|
|
Require(stream != nullptr && fread_nid_postfix(saved.data(), 1, saved.size(), stream) == saved.size());
|
|
Require(std::memcmp(saved.data(), original, saved.size()) == 0);
|
|
Require(fclose_nid_postfix(stream) == 0 && std::remove(path.c_str()) == 0);
|
|
}
|
|
for (int round = 0; round < 12; ++round) {
|
|
std::array<FileStream*, 2> created{};
|
|
std::barrier start(2);
|
|
const auto create = [&](int index) {
|
|
start.arrive_and_wait();
|
|
created[index] = _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), Out | Binary | Noreplace, 0);
|
|
};
|
|
std::thread first(create, 0), second(create, 1);
|
|
first.join();
|
|
second.join();
|
|
const auto winners = static_cast<int>(created[0] != nullptr) + static_cast<int>(created[1] != nullptr);
|
|
for (auto* opened : created) if (opened) Require(fclose_nid_postfix(opened) == 0);
|
|
Require(winners == 1 && std::remove(path.c_str()) == 0);
|
|
}
|
|
}
|
|
|
|
static int APS5_VABI ScanList(FileStream* stream, const char* format, ...) {
|
|
#ifdef _WIN32
|
|
__builtin_sysv_va_list args;
|
|
__builtin_sysv_va_start(args, format);
|
|
#else
|
|
std::va_list args;
|
|
va_start(args, format);
|
|
#endif
|
|
const int result = vfscanf_nid_postfix(stream, format, reinterpret_cast<VaList*>(args));
|
|
#ifdef _WIN32
|
|
__builtin_sysv_va_end(args);
|
|
#else
|
|
va_end(args);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
static std::string ReadWhole(const std::string& path) {
|
|
auto* stream = fopen_nid_postfix(path.c_str(), "rb");
|
|
Require(stream != nullptr);
|
|
std::array<char, 64> buffer{};
|
|
const auto size = fread_nid_postfix(buffer.data(), 1, buffer.size(), stream);
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
return std::string(buffer.data(), size);
|
|
}
|
|
|
|
template<typename TRead>
|
|
static void CheckReadAfterWrite(const std::string& path, const char* mode, const char* expected, TRead read) {
|
|
auto* stream = fopen_nid_postfix(path.c_str(), "wb");
|
|
Require(stream != nullptr && fputs_nid_postfix("0123456789", stream) >= 0 && fclose_nid_postfix(stream) == 0);
|
|
stream = fopen_nid_postfix(path.c_str(), mode);
|
|
Require(stream != nullptr && fwrite_nid_postfix("AB", 1, 2, stream) == 2);
|
|
read(stream);
|
|
Require(ferror_nid_postfix(stream) == 0, "Read after write on an update stream set the error indicator");
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
Require(ReadWhole(path) == expected, "Read after write on an update stream changed the file");
|
|
}
|
|
|
|
static void CheckUpdateStreamReadsAfterWrites(const std::string& base) {
|
|
const std::string path = base + ".update";
|
|
constexpr char overwritten[] = "AB23456789";
|
|
CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) {
|
|
std::array<char, 4> buffer{};
|
|
Require(fread_nid_postfix(buffer.data(), 1, 3, stream) == 3, "fread after fwrite on an r+ stream");
|
|
Require(std::strcmp(buffer.data(), "234") == 0, "fread after fwrite on an r+ stream read other bytes");
|
|
Require(ftell_nid_postfix(stream) == 5);
|
|
});
|
|
CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) {
|
|
std::vector<char> buffer(65536);
|
|
Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == 8, "fread of more than the stream buffer after fwrite on an r+ stream");
|
|
Require(std::string_view(buffer.data(), 8) == "23456789", "fread of more than the stream buffer after fwrite on an r+ stream read other bytes");
|
|
Require(ftell_nid_postfix(stream) == 10);
|
|
});
|
|
CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) {
|
|
Require(fgetc_nid_postfix(stream) == '2', "fgetc after fwrite on an r+ stream");
|
|
});
|
|
CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) {
|
|
Require(getc_unlocked_nid_postfix(stream) == '2', "getc_unlocked after fwrite on an r+ stream");
|
|
});
|
|
CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) {
|
|
std::array<char, 4> buffer{};
|
|
Require(fgets_nid_postfix(buffer.data(), static_cast<int>(buffer.size()), stream) == buffer.data(), "fgets after fwrite on an r+ stream");
|
|
Require(std::strcmp(buffer.data(), "234") == 0, "fgets after fwrite on an r+ stream read other bytes");
|
|
});
|
|
CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) {
|
|
Require(ungetc_nid_postfix('Z', stream) == 'Z', "ungetc after fwrite on an r+ stream");
|
|
Require(fgetc_nid_postfix(stream) == 'Z' && fgetc_nid_postfix(stream) == '2', "ungetc after fwrite on an r+ stream pushed back other bytes");
|
|
});
|
|
CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) {
|
|
std::array<char, 4> buffer{};
|
|
Require(fscanf_nid_postfix(stream, "%3s", buffer.data()) == 1, "fscanf after fwrite on an r+ stream");
|
|
Require(std::strcmp(buffer.data(), "234") == 0, "fscanf after fwrite on an r+ stream read other bytes");
|
|
});
|
|
CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) {
|
|
std::array<char, 4> buffer{};
|
|
Require(ScanList(stream, "%3s", buffer.data()) == 1, "vfscanf after fwrite on an r+ stream");
|
|
Require(std::strcmp(buffer.data(), "234") == 0, "vfscanf after fwrite on an r+ stream read other bytes");
|
|
});
|
|
CheckReadAfterWrite(path, "w+b", "AB", [](FileStream* stream) {
|
|
std::array<char, 4> buffer{};
|
|
Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == 0 && feof_nid_postfix(stream) != 0, "fread after fwrite on a w+ stream");
|
|
});
|
|
CheckReadAfterWrite(path, "a+b", "0123456789AB", [](FileStream* stream) {
|
|
Require(fgetc_nid_postfix(stream) == EOF && feof_nid_postfix(stream) != 0, "fgetc after fwrite on an a+ stream");
|
|
});
|
|
auto* stream = fopen_nid_postfix(path.c_str(), "r+b");
|
|
Require(stream != nullptr);
|
|
Require(ungetc_nid_postfix('Z', stream) == 'Z', "ungetc on a new r+ stream");
|
|
Require(fgetc_nid_postfix(stream) == 'Z' && fgetc_nid_postfix(stream) == '0', "ungetc on a new r+ stream pushed back other bytes");
|
|
Require(fseek_nid_postfix(stream, 2, SEEK_SET) == 0 && ungetc_nid_postfix('Y', stream) == 'Y', "ungetc after fseek on an r+ stream");
|
|
Require(fgetc_nid_postfix(stream) == 'Y' && fgetc_nid_postfix(stream) == '2', "ungetc after fseek on an r+ stream pushed back other bytes");
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
Require(std::remove(path.c_str()) == 0);
|
|
}
|
|
|
|
template<typename TAccess>
|
|
static void CheckWriteAfterRead(const std::string& path, const char* mode, const char* expected, TAccess access) {
|
|
auto* stream = fopen_nid_postfix(path.c_str(), "wb");
|
|
Require(stream != nullptr && fputs_nid_postfix("0123456789", stream) >= 0 && fclose_nid_postfix(stream) == 0, "Creating the written file");
|
|
stream = fopen_nid_postfix(path.c_str(), mode);
|
|
Require(stream != nullptr, "fopen of the written file");
|
|
access(stream);
|
|
Require(fclose_nid_postfix(stream) == 0, "fclose after a write after read");
|
|
Require(ReadWhole(path) == expected, "Write after read wrote other bytes");
|
|
}
|
|
|
|
static void CheckStreamWritesAfterReads(const std::string& base) {
|
|
const std::string path = base + ".written";
|
|
constexpr char appended[] = "0123456789XY";
|
|
#if defined(__GLIBC__)
|
|
constexpr char afterReadAhead[] = "0XY3456789";
|
|
constexpr char afterPushBack[] = "XY23456789";
|
|
#else
|
|
constexpr char afterReadAhead[] = "0123456789XY";
|
|
constexpr char afterPushBack[] = "0123456789XY";
|
|
#endif
|
|
using Write = void (*)(FileStream*);
|
|
for (const Write write : {
|
|
+[](FileStream* stream) { Require(fwrite_nid_postfix("XY", 1, 2, stream) == 2, "fwrite after fgetc on an update stream"); },
|
|
+[](FileStream* stream) { Require(fputs_nid_postfix("XY", stream) >= 0, "fputs after fgetc on an update stream"); },
|
|
+[](FileStream* stream) { Require(fputc_nid_postfix('X', stream) == 'X' && fputc_nid_postfix('Y', stream) == 'Y', "fputc after fgetc on an update stream"); },
|
|
+[](FileStream* stream) { Require(putc_unlocked_nid_postfix('X', stream) == 'X' && putc_unlocked_nid_postfix('Y', stream) == 'Y', "putc_unlocked after fgetc on an update stream"); },
|
|
+[](FileStream* stream) { Require(fprintf_nid_postfix(stream, "%s", "XY") == 2, "fprintf after fgetc on an update stream"); },
|
|
+[](FileStream* stream) { Require(fprintf_s_nid_postfix(stream, "%s", "XY") == 2, "fprintf_s after fgetc on an update stream"); },
|
|
+[](FileStream* stream) { Require(fputwc_nid_postfix(u'X', stream) == u'X' && fputwc_nid_postfix(u'Y', stream) == u'Y', "fputwc after fgetc on an update stream"); },
|
|
+[](FileStream* stream) { Require(fputws_nid_postfix(u"XY", stream) >= 0, "fputws after fgetc on an update stream"); },
|
|
+[](FileStream* stream) { Require(fwprintf_nid_postfix(stream, u"XY") == 2, "fwprintf after fgetc on an update stream"); },
|
|
}) {
|
|
for (const char* mode : {"r+b", "a+b"}) {
|
|
CheckWriteAfterRead(path, mode, std::string_view(mode) == "a+b" ? appended : afterReadAhead, [write](FileStream* stream) {
|
|
Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0 && fgetc_nid_postfix(stream) == '0', "fgetc on an update stream");
|
|
write(stream);
|
|
Require(ferror_nid_postfix(stream) == 0, "Write after fgetc on an update stream set the error indicator");
|
|
});
|
|
}
|
|
}
|
|
CheckWriteAfterRead(path, "r+b", appended, [](FileStream* stream) {
|
|
std::array<char, 10> buffer{};
|
|
Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == buffer.size() && feof_nid_postfix(stream) == 0, "fread of every byte of an r+ stream");
|
|
Require(fwrite_nid_postfix("XY", 1, 2, stream) == 2 && ferror_nid_postfix(stream) == 0, "fwrite after reading every byte of an r+ stream");
|
|
});
|
|
CheckWriteAfterRead(path, "r+b", afterReadAhead, [](FileStream* stream) {
|
|
Require(fgetc_nid_postfix(stream) == '0' && fflush_nid_postfix(stream) == 0, "fflush after fgetc on an r+ stream");
|
|
Require(fputs_nid_postfix("XY", stream) >= 0 && ferror_nid_postfix(stream) == 0, "fputs after fgetc and fflush on an r+ stream");
|
|
});
|
|
CheckWriteAfterRead(path, "r+b", afterPushBack, [](FileStream* stream) {
|
|
Require(fgetc_nid_postfix(stream) == '0' && ungetc_nid_postfix('Z', stream) == 'Z', "ungetc after fgetc on an r+ stream");
|
|
Require(fputs_nid_postfix("XY", stream) >= 0 && ferror_nid_postfix(stream) == 0, "fputs after ungetc on an r+ stream");
|
|
});
|
|
CheckWriteAfterRead(path, "rb", "0123456789", [](FileStream* stream) {
|
|
Require(fgetc_nid_postfix(stream) == '0', "fgetc on a read-only stream");
|
|
errno = 0;
|
|
Require(fputc_nid_postfix('X', stream) == EOF && errno == EBADF && ferror_nid_postfix(stream) != 0, "fputc after fgetc on a read-only stream");
|
|
clearerr_nid_postfix(stream);
|
|
errno = 0;
|
|
Require(fwrite_nid_postfix("XY", 1, 2, stream) == 0 && errno == EBADF && ferror_nid_postfix(stream) != 0, "fwrite after fgetc on a read-only stream");
|
|
clearerr_nid_postfix(stream);
|
|
errno = 0;
|
|
Require(fprintf_nid_postfix(stream, "%s", "XY") == -1 && errno == EBADF && ferror_nid_postfix(stream) != 0, "fprintf after fgetc on a read-only stream");
|
|
});
|
|
Require(std::remove(path.c_str()) == 0);
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
Require(argc == 2);
|
|
CheckExclusiveCppOpen(argv[1]);
|
|
CheckUpdateStreamReadsAfterWrites(argv[1]);
|
|
CheckStreamWritesAfterReads(argv[1]);
|
|
Require(_Stdout_nid_postfix.GetHandle() == stdout);
|
|
Require(_Stderr_nid_postfix.GetHandle() == stderr);
|
|
Require(fputs_nid_postfix("stdout object works\n", &_Stdout_nid_postfix) >= 0);
|
|
Require(fputs_nid_postfix("stderr object works\n", &_Stderr_nid_postfix) >= 0);
|
|
Require(fflush_nid_postfix(nullptr) == 0);
|
|
auto* stream = fopen_nid_postfix(argv[1], "w+b");
|
|
constexpr char payload[] = "stream round trip";
|
|
Require(fputs_nid_postfix(payload, stream) >= 0);
|
|
Require(fwrite_nid_postfix(payload, 1, sizeof(payload), stream) == sizeof(payload));
|
|
const auto expectedSize = sizeof(payload) * 2 - 1;
|
|
Require(ftell_nid_postfix(stream) == static_cast<long>(expectedSize));
|
|
Require(fflush_nid_postfix(stream) == 0);
|
|
Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0);
|
|
std::array<char, sizeof(payload)> buffer{};
|
|
Require(fread_nid_postfix(buffer.data(), 1, sizeof(payload) - 1, stream) == sizeof(payload) - 1);
|
|
Require(std::strcmp(buffer.data(), payload) == 0);
|
|
Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == buffer.size());
|
|
Require(std::strcmp(buffer.data(), payload) == 0);
|
|
Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == 0);
|
|
ExpectException([&] { fputs_nid_postfix(nullptr, stream); });
|
|
ExpectException([&] { fwrite_nid_postfix(nullptr, 1, sizeof(payload), stream); });
|
|
errno = 0;
|
|
Require(fread_nid_postfix(buffer.data(), 2, SIZE_MAX / 2 + 1, stream) == 0 && errno == EINVAL && ferror_nid_postfix(stream) == 0);
|
|
errno = 0;
|
|
Require(fread_nid_postfix(buffer.data(), 2, SIZE_MAX / 2 + 1, nullptr) == 0 && errno == EINVAL);
|
|
ExpectException([&] { fwrite_nid_postfix(buffer.data(), SIZE_MAX / 2 + 1, 2, stream); });
|
|
errno = 0;
|
|
Require(fseek_nid_postfix(stream, 0, -1) == -1);
|
|
Require(errno == EINVAL);
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
ExpectException([] { fputs_nid_postfix("invalid stream", nullptr); });
|
|
ExpectException([] { fopen_nid_postfix(nullptr, "r"); });
|
|
FileStream closed(std::tmpfile());
|
|
closed.Close();
|
|
Require(fflush_nid_postfix(&closed) == -1 && errno == 9);
|
|
Require(std::remove(argv[1]) == 0);
|
|
errno = 0;
|
|
Require(fopen_nid_postfix(argv[1], "rb") == nullptr);
|
|
Require(errno == ENOENT);
|
|
const std::string directoryName = std::string(argv[1]) + ".dir";
|
|
Require(std::filesystem::create_directory(directoryName));
|
|
errno = 0;
|
|
Require(fopen_nid_postfix(directoryName.c_str(), "wb") == nullptr);
|
|
Require(errno == EISDIR);
|
|
Require(std::filesystem::remove(directoryName));
|
|
const std::u8string unicodeName = u8"セーブé.tmp";
|
|
const std::string guestName(unicodeName.begin(), unicodeName.end());
|
|
const std::filesystem::path hostName(unicodeName);
|
|
constexpr char unicodePayload[] = "unicode";
|
|
stream = fopen_nid_postfix(guestName.c_str(), "wb");
|
|
Require(stream != nullptr);
|
|
Require(fwrite_nid_postfix(unicodePayload, 1, sizeof(unicodePayload), stream) == sizeof(unicodePayload));
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
Require(std::filesystem::exists(hostName));
|
|
Require(std::filesystem::file_size(hostName) == sizeof(unicodePayload));
|
|
stream = fopen_nid_postfix(guestName.c_str(), "rb");
|
|
Require(stream != nullptr);
|
|
std::array<char, sizeof(unicodePayload)> unicodeBuffer{};
|
|
Require(fread_nid_postfix(unicodeBuffer.data(), 1, unicodeBuffer.size(), stream) == unicodeBuffer.size());
|
|
Require(std::strcmp(unicodeBuffer.data(), unicodePayload) == 0);
|
|
stream = freopen_nid_postfix(guestName.c_str(), "wb", stream);
|
|
Require(stream != nullptr);
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
Require(std::filesystem::file_size(hostName) == 0);
|
|
std::filesystem::remove(hostName);
|
|
const std::string heldName = std::string(argv[1]) + ".held";
|
|
const std::string movedName = std::string(argv[1]) + ".moved";
|
|
stream = fopen_nid_postfix(heldName.c_str(), "w+b");
|
|
Require(stream != nullptr && fwrite_nid_postfix(payload, 1, sizeof(payload), stream) == sizeof(payload));
|
|
Require(fflush_nid_postfix(stream) == 0);
|
|
Require(rename_nid_postfix(heldName.c_str(), movedName.c_str()) == 0 && !std::filesystem::exists(heldName));
|
|
Require(remove_nid_postfix(movedName.c_str()) == 0 && !std::filesystem::exists(movedName));
|
|
Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0);
|
|
std::array<char, sizeof(payload)> heldBuffer{};
|
|
Require(fread_nid_postfix(heldBuffer.data(), 1, heldBuffer.size(), stream) == heldBuffer.size());
|
|
Require(std::strcmp(heldBuffer.data(), payload) == 0 && fclose_nid_postfix(stream) == 0);
|
|
stream = fopen_nid_postfix(heldName.c_str(), "ab");
|
|
Require(stream != nullptr && fputs_nid_postfix("ab", stream) >= 0 && fclose_nid_postfix(stream) == 0);
|
|
stream = fopen_nid_postfix(heldName.c_str(), "ab");
|
|
Require(stream != nullptr && fputs_nid_postfix("cd", stream) >= 0 && fclose_nid_postfix(stream) == 0);
|
|
errno = 0;
|
|
Require(fopen_nid_postfix(heldName.c_str(), "wbx") == nullptr && errno == EEXIST);
|
|
stream = fopen_nid_postfix(heldName.c_str(), "rb");
|
|
std::array<char, 5> appended{};
|
|
Require(stream != nullptr && fread_nid_postfix(appended.data(), 1, 4, stream) == 4 && std::strcmp(appended.data(), "abcd") == 0);
|
|
Require(fclose_nid_postfix(stream) == 0 && remove_nid_postfix(heldName.c_str()) == 0);
|
|
stream = fopen_nid_postfix(heldName.c_str(), "w+b");
|
|
Require(stream != nullptr);
|
|
stream = freopen_nid_postfix(movedName.c_str(), "w+b", stream);
|
|
Require(stream != nullptr && fwrite_nid_postfix(payload, 1, sizeof(payload), stream) == sizeof(payload));
|
|
Require(fflush_nid_postfix(stream) == 0);
|
|
Require(rename_nid_postfix(movedName.c_str(), heldName.c_str()) == 0 && !std::filesystem::exists(movedName));
|
|
Require(remove_nid_postfix(heldName.c_str()) == 0 && !std::filesystem::exists(heldName));
|
|
Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0);
|
|
std::array<char, sizeof(payload)> reopenedBuffer{};
|
|
Require(fread_nid_postfix(reopenedBuffer.data(), 1, reopenedBuffer.size(), stream) == reopenedBuffer.size());
|
|
Require(std::strcmp(reopenedBuffer.data(), payload) == 0 && fclose_nid_postfix(stream) == 0);
|
|
constexpr char marker[] = "marker";
|
|
stream = fopen_nid_postfix(argv[1], "wb");
|
|
Require(stream != nullptr);
|
|
std::array<char, sizeof(marker)> markerBuffer{};
|
|
Require(fread_nid_postfix(markerBuffer.data(), 1, markerBuffer.size(), stream) == 0);
|
|
Require(ferror_nid_postfix(stream) != 0);
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
stream = fopen_nid_postfix(argv[1], "rb");
|
|
Require(stream != nullptr);
|
|
Require(fwrite_nid_postfix(marker, 1, sizeof(marker), stream) == 0);
|
|
Require(ferror_nid_postfix(stream) != 0);
|
|
clearerr_nid_postfix(stream);
|
|
Require(fputs_nid_postfix(marker, stream) == EOF);
|
|
Require(ferror_nid_postfix(stream) != 0);
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
stream = fopen_nid_postfix(argv[1], "wb");
|
|
Require(stream != nullptr && fputs_nid_postfix(marker, stream) >= 0);
|
|
ReplaceWithReadOnlyDescriptor(stream, argv[1]);
|
|
Require(fflush_nid_postfix(stream) == EOF);
|
|
Require(ferror_nid_postfix(stream) != 0);
|
|
static_cast<void>(fclose_nid_postfix(stream));
|
|
Require(std::remove(argv[1]) == 0);
|
|
std::cout << "PASS: stream objects, file operations, EOF and error handling\n";
|
|
}
|