mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-21 16:12:47 -05:00
305 lines
11 KiB
C++
305 lines
11 KiB
C++
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_FILESTREAM_HPP
|
|
#define CORE_LIBS_PRX_LIBC_INCLUDE_FILESTREAM_HPP
|
|
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
#include <cstdio>
|
|
#include <filesystem>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <type_traits>
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include "prx/libc/include/FileLock.hpp"
|
|
extern "C" void* HostStandardHandle_nid_no_patch(int descriptor);
|
|
extern "C" void KeepStandardHandle_nid_no_patch(int descriptor, void* handle);
|
|
struct UcrtStreamState {
|
|
char* pointer;
|
|
char* base;
|
|
int count;
|
|
long flags;
|
|
};
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
#if defined(__GLIBC__)
|
|
#include <stdio_ext.h>
|
|
extern "C" int _IO_switch_to_get_mode(std::FILE* stream);
|
|
#endif
|
|
extern "C" int DuplicateHostDescriptor_nid_no_patch(int descriptor);
|
|
|
|
struct GuestFilePrefix {
|
|
unsigned char* position = nullptr;
|
|
std::int32_t readRemaining = 0;
|
|
std::int32_t writeRemaining = 0;
|
|
std::int16_t flags = 0;
|
|
std::int16_t descriptor = -1;
|
|
unsigned char* buffer = nullptr;
|
|
std::int32_t bufferSize = 0;
|
|
std::int32_t bufferPadding = 0;
|
|
std::int32_t lineBufferSize = 0;
|
|
};
|
|
static_assert(offsetof(GuestFilePrefix, flags) == 16);
|
|
static_assert(offsetof(GuestFilePrefix, descriptor) == 18);
|
|
static_assert(offsetof(GuestFilePrefix, buffer) == 24);
|
|
static_assert(offsetof(GuestFilePrefix, lineBufferSize) == 40);
|
|
|
|
static constexpr const char* FOPEN_EXT_VERT = ".vert";
|
|
static constexpr const char* FOPEN_MSG_NULL_ARG = "null argument";
|
|
static constexpr const char* FOPEN_MSG_NOT_FOUND = "file not found";
|
|
static constexpr const char* FOPEN_MSG_OPEN_FAILED = "open failed";
|
|
|
|
#ifndef _WIN32
|
|
extern "C" int KeepHostStandardError_nid_no_patch();
|
|
#endif
|
|
|
|
class FileStream {
|
|
// Only the macro-accessed FreeBSD prefix is exposed. The remaining guest
|
|
// FILE area is reserved; host state lives beyond it and is never a guest FILE*.
|
|
GuestFilePrefix _guest{};
|
|
std::byte _reserved[256 - sizeof(GuestFilePrefix)]{};
|
|
std::FILE* _handle;
|
|
bool _dynamic;
|
|
bool guestError = false;
|
|
bool randomDevice = false;
|
|
|
|
public:
|
|
explicit FileStream(std::FILE* handle, bool dynamic = false) : _handle(handle), _dynamic(dynamic) {
|
|
if (!_handle) throw std::runtime_error("FileStream: null handle");
|
|
_guest.flags = handle == stdin ? 4 : handle == stdout || handle == stderr ? 8 : 0x10;
|
|
#ifdef _WIN32
|
|
const int descriptor = _fileno(handle);
|
|
#else
|
|
const int descriptor = ::fileno(handle);
|
|
#endif
|
|
_guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast<std::int16_t>(descriptor) : -1;
|
|
}
|
|
|
|
FileStream(const FileStream&) = delete;
|
|
FileStream& operator=(const FileStream&) = delete;
|
|
|
|
std::FILE* GetHandle() const {
|
|
if (!_handle) throw std::runtime_error("FileStream: closed stream");
|
|
return _handle;
|
|
}
|
|
|
|
bool IsOpen() const {
|
|
return _handle != nullptr;
|
|
}
|
|
|
|
bool IsDynamic() const {
|
|
return _dynamic;
|
|
}
|
|
|
|
GuestFilePrefix& GuestState() { return _guest; }
|
|
bool IsHostStandard() const {
|
|
return _handle == stdin || _handle == stdout || _handle == stderr;
|
|
}
|
|
|
|
void Detach() {
|
|
_handle = nullptr;
|
|
_guest.flags = 0;
|
|
_guest.descriptor = -1;
|
|
}
|
|
|
|
void RestoreHostStandard(std::FILE* stream, int kept) {
|
|
const int error = errno;
|
|
#ifdef _WIN32
|
|
const bool restored = _handle || (::_wfreopen(L"NUL", stream == stdin ? L"r" : L"w", stream) && ::_dup2(kept, ::_fileno(stream)) == 0);
|
|
::_unlock_file(stream);
|
|
::_close(kept);
|
|
#else
|
|
const bool restored = _handle || (std::freopen("/dev/null", stream == stdin ? "r" : "w", stream) && ::dup2(kept, ::fileno(stream)) >= 0);
|
|
::funlockfile(stream);
|
|
::close(kept);
|
|
#endif
|
|
if (!restored) throw std::runtime_error("FileStream: cannot restore the host standard stream");
|
|
errno = error;
|
|
}
|
|
|
|
void MarkRandomDevice() {
|
|
randomDevice = true;
|
|
_guest.descriptor = -1;
|
|
}
|
|
|
|
bool IsRandomDevice() const {
|
|
return randomDevice;
|
|
}
|
|
|
|
bool Reopen(const std::filesystem::path& filename, const char* mode,
|
|
[[maybe_unused]] std::FILE* (*open)(const std::filesystem::path&, const std::string&) = nullptr) {
|
|
auto* previous = GetHandle();
|
|
_guest = {};
|
|
guestError = false;
|
|
randomDevice = false;
|
|
#ifdef _WIN32
|
|
std::wstring wideMode;
|
|
for (const char character : std::string_view(mode)) wideMode.push_back(static_cast<unsigned char>(character));
|
|
const int replaced = previous ? _fileno(previous) : -1;
|
|
auto* const standard = HostStandardHandle_nid_no_patch(replaced);
|
|
GuestFileLockForget_nid_no_patch(replaced);
|
|
if (open && !filename.empty()) {
|
|
if (!IsHostStandard()) {
|
|
_handle = nullptr;
|
|
std::fclose(previous);
|
|
_handle = open(filename, mode);
|
|
} else {
|
|
std::wstring resetMode;
|
|
for (const char character : std::string_view(mode))
|
|
if (character != 'x') resetMode.push_back(static_cast<unsigned char>(character));
|
|
const int kept = DuplicateHostDescriptor_nid_no_patch(replaced);
|
|
if (kept >= 0) ::_lock_file(previous);
|
|
_handle = nullptr;
|
|
try {
|
|
std::FILE* const replacement = open(filename, mode);
|
|
if (replacement) {
|
|
if (::_wfreopen(L"NUL", resetMode.c_str(), previous) == previous) {
|
|
const int moved = ::_dup2(_fileno(replacement), replaced);
|
|
const int error = errno;
|
|
std::fclose(replacement);
|
|
if (moved == 0) _handle = previous;
|
|
else errno = error;
|
|
} else {
|
|
const int error = errno;
|
|
std::fclose(replacement);
|
|
errno = error;
|
|
}
|
|
}
|
|
} catch (...) {
|
|
if (kept >= 0) RestoreHostStandard(previous, kept);
|
|
KeepStandardHandle_nid_no_patch(replaced, standard);
|
|
throw;
|
|
}
|
|
if (kept >= 0) RestoreHostStandard(previous, kept);
|
|
}
|
|
} else {
|
|
const int kept = IsHostStandard() ? DuplicateHostDescriptor_nid_no_patch(replaced) : -1;
|
|
if (kept >= 0) ::_lock_file(previous);
|
|
_handle = ::_wfreopen(filename.c_str(), wideMode.c_str(), previous);
|
|
if (kept >= 0) RestoreHostStandard(previous, kept);
|
|
}
|
|
KeepStandardHandle_nid_no_patch(replaced, standard);
|
|
#else
|
|
if (::fileno(previous) == 2) KeepHostStandardError_nid_no_patch();
|
|
const int kept = IsHostStandard() ? DuplicateHostDescriptor_nid_no_patch(::fileno(previous)) : -1;
|
|
if (kept >= 0) ::flockfile(previous);
|
|
_handle = std::freopen(filename.c_str(), mode, previous);
|
|
if (kept >= 0) RestoreHostStandard(previous, kept);
|
|
#endif
|
|
if (!_handle) return false;
|
|
_guest.flags = 0x10;
|
|
#ifdef _WIN32
|
|
const int descriptor = _fileno(_handle);
|
|
#else
|
|
const int descriptor = ::fileno(_handle);
|
|
#endif
|
|
_guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast<std::int16_t>(descriptor) : -1;
|
|
return true;
|
|
}
|
|
void SyncStatus() {
|
|
if (!_handle) return;
|
|
_guest.readRemaining = 0;
|
|
_guest.writeRemaining = 0;
|
|
_guest.flags = static_cast<std::int16_t>((_guest.flags & ~0x60) |
|
|
(std::feof(GetHandle()) ? 0x20 : 0) | (std::ferror(GetHandle()) || guestError ? 0x40 : 0));
|
|
}
|
|
|
|
void SyncUnlockedStatus() {
|
|
if (!_handle) return;
|
|
_guest.readRemaining = 0;
|
|
_guest.writeRemaining = 0;
|
|
#ifdef _WIN32
|
|
const bool endOfFile = std::feof(GetHandle()) != 0;
|
|
const bool error = std::ferror(GetHandle()) != 0;
|
|
#else
|
|
const bool endOfFile = ::feof_unlocked(GetHandle()) != 0;
|
|
const bool error = ::ferror_unlocked(GetHandle()) != 0;
|
|
#endif
|
|
_guest.flags = static_cast<std::int16_t>((_guest.flags & ~0x60) |
|
|
(endOfFile ? 0x20 : 0) | (error || guestError ? 0x40 : 0));
|
|
}
|
|
|
|
bool SwitchToReading() {
|
|
#ifdef _WIN32
|
|
::_lock_file(GetHandle());
|
|
const bool switched = SwitchToReadingUnlocked();
|
|
::_unlock_file(GetHandle());
|
|
return switched;
|
|
#else
|
|
::flockfile(GetHandle());
|
|
const bool switched = SwitchToReadingUnlocked();
|
|
::funlockfile(GetHandle());
|
|
return switched;
|
|
#endif
|
|
}
|
|
|
|
bool SwitchToReadingUnlocked() {
|
|
#ifdef _WIN32
|
|
constexpr long UcrtReading = 0x0001;
|
|
constexpr long UcrtWriting = 0x0002;
|
|
constexpr long UcrtUpdate = 0x0004;
|
|
auto* native = GetHandle();
|
|
std::atomic_ref<long> state(reinterpret_cast<UcrtStreamState*>(native)->flags);
|
|
if ((state.load() & (UcrtReading | UcrtUpdate)) != UcrtUpdate) return true;
|
|
const bool switched = (state.load() & UcrtWriting) == 0 || ::_fflush_nolock(native) == 0;
|
|
if (switched) state.fetch_or(UcrtReading);
|
|
SyncUnlockedStatus();
|
|
return switched;
|
|
#elif defined(__GLIBC__)
|
|
auto* native = GetHandle();
|
|
if (!::__freadable(native) || !::__fwriting(native)) return true;
|
|
const bool switched = ::_IO_switch_to_get_mode(native) == 0;
|
|
SyncUnlockedStatus();
|
|
return switched;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void SetError() {
|
|
guestError = true;
|
|
SyncStatus();
|
|
}
|
|
|
|
void ClearError() {
|
|
if (!_handle) return;
|
|
std::clearerr(GetHandle());
|
|
guestError = false;
|
|
SyncStatus();
|
|
}
|
|
|
|
int Close() {
|
|
GetHandle();
|
|
#ifdef _WIN32
|
|
GuestFileLockForget_nid_no_patch(::_fileno(_handle));
|
|
#endif
|
|
const int result = std::fclose(std::exchange(_handle, nullptr));
|
|
_guest.flags = 0;
|
|
_guest.descriptor = -1;
|
|
return result;
|
|
}
|
|
};
|
|
static_assert(std::is_standard_layout_v<FileStream>);
|
|
|
|
inline std::FILE* GetStreamHandle(FileStream* stream) {
|
|
if (!stream) throw std::runtime_error("FileStream: null stream");
|
|
return stream->GetHandle();
|
|
}
|
|
|
|
inline std::FILE* GetNativeStream(FileStream* stream) {
|
|
auto* handle = GetStreamHandle(stream);
|
|
if (stream->IsRandomDevice()) throw std::runtime_error("FileStream: this function is not implemented on a /dev/random or /dev/urandom stream");
|
|
return handle;
|
|
}
|
|
|
|
extern "C" {
|
|
extern FileStream _Stdout_nid_postfix;
|
|
extern FileStream _Stderr_nid_postfix;
|
|
}
|
|
|
|
#endif
|