mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-13 05:05:57 +00:00
* sceKernelOpen: Clean up flag handling
* sceKernelOpen: fix params
Based on decompilation, the second parameter of _open should be flags.
Additionally swaps the return and parameter types to align with our current standards.
* sceKernelOpen: Fix errors
Based on POSIX spec, if part of the path is missing, ENOENT is the correct return.
Additionally, decompilation suggests that open sets errno too.
* Fix exports
Fixes function exports to align with what they should be, based on what I've seen from decompilation and our module generator.
* Proper errno behavior on open
Since sceKernelOpen calls posix_open, errno should be set during this process.
Simplest way to handle that is to move the actual open code to posix_open and adjust error cases accordingly.
* Reorganize open calls, add error log
* Improve close
Removes the EPERM return, as it makes no sense, and swaps sceKernelClose with posix_close to properly emulate errno behavior.
* Fix log on close
* posix_open fixups
* Readd hack in posix_close
It's either this, or removing LLE DiscMap.
Or shadow implements posix sockets.
* Missing exports
Commented out while I gradually work through them all
* Remaining placeholder exports
* Swap some stuff around
I see nothing that suggests "open" only takes two parameters, so this should be completely safe.
It's also more accurate to how these are handled in libkernel, and means I won't need to reorganize anything for readv and writev.
* Update file_system.cpp
* Implement write and posix_write
* Oops
* Implement posix_readv and sceKernelReadv
Also fixes error behavior on readv, as that function shouldn't be returning any kernel error codes.
* Move sceKernelUnlink
Will deal with this one later, was just annoyed by how it's location doesn't align with the export order.
* Cleanup readv
* Implement posix_writev and sceKernelWritev
Also fixes error behavior on writev, since it shouldn't ever return kernel errors (since our device files return those)
* More cleanup on older functions
* Swap around sceKernelLseek and posix_lseek
This ensures that these have the correct error behavior, and makes their behavior align with the updated implementations for earlier functions.
* Update file_system.cpp
* Implement read
Also fixes error behavior
* Swap sceKernelMkdir and posix_mkdir
Fixes errno behavior on kernel calls, also fixed some incorrect error returns.
* Fix errno behavior on sceKernelRmdir
Also reduces function logging to bring it closer to the level of logging seen in other filesystem functions.
* Slight clean up of sceKernelStat
Fixes error behavior and changes some of the old data types.
* Refactor fstat
Fixes errno behavior, implements fstat, and shifts exports around based on file position.
Might reorganize function locations later though.
* Implement posix_ftruncate
Implements posix_ftruncate and fixes errno behavior for sceKernelFtruncate
* Add missing error conversions for more device functions
* Implement posix_rename, fix sceKernelRename errno behavior
* Add posix_preadv and posix_pread
Also fixes some incorrect error returns, fixes errno behavior, and removes an unnecessary hack.
* Fix compile
* Implement posix_getdents, getdirentries, and posix_getdirentries
Also fixes errno behavior for the kernel variants of these functions.
* Fix errno behavior of sceKernelFsync
* Implement posix_pwrite and posix_unlink
Also fixes errno behavior in related functions.
* Update file_system.cpp
* Remove SetPosixErrno
Ideally, we've handled all possible error conditions before calling these functions, so treat errors in platform-specific code as IO errors and return POSIX_EIO instead.
* Update header exports
Not sure where these get used, but might as well keep them consistent with the rest of this.
* Check if file exists before calling platform-specific code
Bloodborne checks if a file doesn't exist using open, checking if it specifically failed with error code ENOENT. To avoid working with platform-specific errnos, add a proper error return for if the file doesn't exist.
Fixes a regression in Bloodborne.
* Clang
Out of all the changes, this is apparently the only thing Clang-Format doesn't like.
I'm honestly surprised.
* Improve error checks on posix_unlink
Just because a file isn't opened doesn't mean the file doesn't exist.
Fixes the error returned if host_path.empty(), and removes the error return for when GetFile fails.
* Fix the Bloodborne fix
* Limit exports to tested functions
* More confirmed working exports
* Remaining stuff my games can test
* FS exports from firmware tests
* Bring back missing exports from main
I don't have any bootable things that call these, but since they were working well enough on main, they should be fine to readd.
* Add export for posix_pread
Spotted in Dreams a while back, might as well add it.
* Revert "Remove SetPosixErrno"
This reverts commit bdfc0c246c
.
* Revert SetPosixErrno changes
shadow's using it for posix sockets, so significant modifications would introduce unnecessary merge conflicts.
* Update comment
* Add EACCES errno to SetPosixErrno
Seen in Gravity Rush.
Also reorganizes the switch case based on the posix errno value, since ordering by errno makes no sense on some OSes.
* More export fixups
Missed these during my initial pass through FS stuff because they were in kernel.cpp for some reason.
* Symbols from FS tests
Tested by messing around with firmware elfs, these atleast don't cause any crashes.
* Remove inaccurate error behavior
Seek can have offsets past the end of a file.
Also add logging for two valid whence values that are unsupported on Windows.
I'll need to verify that SEEK_HOLE and SEEK_DATA correspond to 3 and 4 respectively, I've yet to check source to verify.
* Fix error log
Oops
* Clang
Clang
* Remove close hack
Since LLE libSceDiscMap is no longer a concern, this hack shouldn't be needed.
Since sockets are still stubbed, and close can be used on sockets, I've added a warning log just in case this still occurs in some titles.
* Change SetPosixErrno unreachable to warning
I changed it to an unreachable in an earlier commit to make testing easier.
At this point, having an unreachable for this seems unnecessary, so change it to a warning instead.
* Remove Bloodborne hack
Games should be able to unlink files that aren't opened file descriptors. As far as I've tested, this doesn't break Bloodborne.
421 lines
10 KiB
C++
421 lines
10 KiB
C++
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <vector>
|
|
|
|
#include "common/alignment.h"
|
|
#include "common/assert.h"
|
|
#include "common/error.h"
|
|
#include "common/io_file.h"
|
|
#include "common/logging/log.h"
|
|
#include "common/path_util.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "common/ntapi.h"
|
|
|
|
#include <io.h>
|
|
#include <share.h>
|
|
#include <windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef _MSC_VER
|
|
#define fileno _fileno
|
|
#define fseeko _fseeki64
|
|
#define ftello _ftelli64
|
|
#endif
|
|
|
|
namespace Common::FS {
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace {
|
|
|
|
#ifdef _WIN32
|
|
|
|
[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) {
|
|
switch (type) {
|
|
case FileType::BinaryFile:
|
|
switch (mode) {
|
|
case FileAccessMode::Read:
|
|
return L"rb";
|
|
case FileAccessMode::Write:
|
|
return L"wb";
|
|
case FileAccessMode::Append:
|
|
return L"ab";
|
|
case FileAccessMode::ReadWrite:
|
|
return L"r+b";
|
|
case FileAccessMode::ReadAppend:
|
|
return L"a+b";
|
|
}
|
|
break;
|
|
case FileType::TextFile:
|
|
switch (mode) {
|
|
case FileAccessMode::Read:
|
|
return L"r";
|
|
case FileAccessMode::Write:
|
|
return L"w";
|
|
case FileAccessMode::Append:
|
|
return L"a";
|
|
case FileAccessMode::ReadWrite:
|
|
return L"r+";
|
|
case FileAccessMode::ReadAppend:
|
|
return L"a+";
|
|
}
|
|
break;
|
|
}
|
|
|
|
return L"";
|
|
}
|
|
|
|
[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) {
|
|
switch (flag) {
|
|
case FileShareFlag::ShareNone:
|
|
default:
|
|
return _SH_DENYRW;
|
|
case FileShareFlag::ShareReadOnly:
|
|
return _SH_DENYWR;
|
|
case FileShareFlag::ShareWriteOnly:
|
|
return _SH_DENYRD;
|
|
case FileShareFlag::ShareReadWrite:
|
|
return _SH_DENYNO;
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) {
|
|
switch (type) {
|
|
case FileType::BinaryFile:
|
|
switch (mode) {
|
|
case FileAccessMode::Read:
|
|
return "rb";
|
|
case FileAccessMode::Write:
|
|
return "wb";
|
|
case FileAccessMode::Append:
|
|
return "ab";
|
|
case FileAccessMode::ReadWrite:
|
|
return "r+b";
|
|
case FileAccessMode::ReadAppend:
|
|
return "a+b";
|
|
}
|
|
break;
|
|
case FileType::TextFile:
|
|
switch (mode) {
|
|
case FileAccessMode::Read:
|
|
return "r";
|
|
case FileAccessMode::Write:
|
|
return "w";
|
|
case FileAccessMode::Append:
|
|
return "a";
|
|
case FileAccessMode::ReadWrite:
|
|
return "r+";
|
|
case FileAccessMode::ReadAppend:
|
|
return "a+";
|
|
}
|
|
break;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
#endif
|
|
|
|
[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) {
|
|
switch (origin) {
|
|
case SeekOrigin::SetOrigin:
|
|
return SEEK_SET;
|
|
case SeekOrigin::CurrentPosition:
|
|
return SEEK_CUR;
|
|
case SeekOrigin::End:
|
|
return SEEK_END;
|
|
default:
|
|
LOG_ERROR(Common_Filesystem, "Unsupported origin {}, defaulting to SEEK_SET",
|
|
static_cast<u32>(origin));
|
|
return SEEK_SET;
|
|
}
|
|
}
|
|
|
|
} // Anonymous namespace
|
|
|
|
IOFile::IOFile() = default;
|
|
|
|
IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
|
|
Open(path, mode, type, flag);
|
|
}
|
|
|
|
IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) {
|
|
Open(path, mode, type, flag);
|
|
}
|
|
|
|
IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
|
|
Open(path, mode, type, flag);
|
|
}
|
|
|
|
IOFile::~IOFile() {
|
|
Close();
|
|
}
|
|
|
|
IOFile::IOFile(IOFile&& other) noexcept {
|
|
std::swap(file_path, other.file_path);
|
|
std::swap(file_access_mode, other.file_access_mode);
|
|
std::swap(file_type, other.file_type);
|
|
std::swap(file, other.file);
|
|
}
|
|
|
|
IOFile& IOFile::operator=(IOFile&& other) noexcept {
|
|
std::swap(file_path, other.file_path);
|
|
std::swap(file_access_mode, other.file_access_mode);
|
|
std::swap(file_type, other.file_type);
|
|
std::swap(file, other.file);
|
|
return *this;
|
|
}
|
|
|
|
int IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
|
|
Close();
|
|
|
|
file_path = path;
|
|
file_access_mode = mode;
|
|
file_type = type;
|
|
|
|
errno = 0;
|
|
int result = 0;
|
|
|
|
#ifdef _WIN32
|
|
if (flag != FileShareFlag::ShareNone) {
|
|
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
|
|
result = errno;
|
|
} else {
|
|
result = _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
|
|
}
|
|
#else
|
|
file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
|
|
result = errno;
|
|
#endif
|
|
|
|
if (!IsOpen()) {
|
|
const auto ec = std::error_code{result, std::generic_category()};
|
|
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, error_message={}",
|
|
PathToUTF8String(file_path), ec.message());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void IOFile::Close() {
|
|
if (!IsOpen()) {
|
|
return;
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
const auto close_result = std::fclose(file) == 0;
|
|
|
|
if (!close_result) {
|
|
const auto ec = std::error_code{errno, std::generic_category()};
|
|
LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}",
|
|
PathToUTF8String(file_path), ec.message());
|
|
}
|
|
|
|
file = nullptr;
|
|
|
|
#ifdef _WIN64
|
|
if (file_mapping && file_access_mode == FileAccessMode::ReadWrite) {
|
|
CloseHandle(std::bit_cast<HANDLE>(file_mapping));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void IOFile::Unlink() {
|
|
if (!IsOpen()) {
|
|
return;
|
|
}
|
|
|
|
// Mark the file for deletion
|
|
// TODO: Also remove the file path?
|
|
#ifdef _WIN64
|
|
FILE_DISPOSITION_INFORMATION disposition;
|
|
IO_STATUS_BLOCK iosb;
|
|
|
|
const int fd = fileno(file);
|
|
HANDLE hfile = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
|
|
|
disposition.DeleteFile = TRUE;
|
|
NtSetInformationFile(hfile, &iosb, &disposition, sizeof(disposition),
|
|
FileDispositionInformation);
|
|
#else
|
|
if (unlink(file_path.c_str()) != 0) {
|
|
const auto ec = std::error_code{errno, std::generic_category()};
|
|
LOG_ERROR(Common_Filesystem, "Failed to unlink the file at path={}, ec_message={}",
|
|
PathToUTF8String(file_path), ec.message());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
uintptr_t IOFile::GetFileMapping() {
|
|
if (file_mapping) {
|
|
return file_mapping;
|
|
}
|
|
#ifdef _WIN64
|
|
const int fd = fileno(file);
|
|
|
|
HANDLE hfile = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
|
HANDLE mapping = nullptr;
|
|
|
|
if (file_access_mode == FileAccessMode::ReadWrite) {
|
|
mapping = CreateFileMapping2(hfile, NULL, FILE_MAP_WRITE, PAGE_READWRITE, SEC_COMMIT, 0,
|
|
NULL, NULL, 0);
|
|
} else {
|
|
mapping = hfile;
|
|
}
|
|
|
|
file_mapping = std::bit_cast<uintptr_t>(mapping);
|
|
ASSERT_MSG(file_mapping, "{}", Common::GetLastErrorMsg());
|
|
return file_mapping;
|
|
#else
|
|
file_mapping = fileno(file);
|
|
return file_mapping;
|
|
#endif
|
|
}
|
|
|
|
std::string IOFile::ReadString(size_t length) const {
|
|
std::vector<char> string_buffer(length);
|
|
|
|
const auto chars_read = ReadSpan<char>(string_buffer);
|
|
const auto string_size = chars_read != length ? chars_read : length;
|
|
|
|
return std::string{string_buffer.data(), string_size};
|
|
}
|
|
|
|
bool IOFile::Flush() const {
|
|
if (!IsOpen()) {
|
|
return false;
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
#ifdef _WIN32
|
|
const auto flush_result = std::fflush(file) == 0;
|
|
#else
|
|
const auto flush_result = std::fflush(file) == 0;
|
|
#endif
|
|
|
|
if (!flush_result) {
|
|
const auto ec = std::error_code{errno, std::generic_category()};
|
|
LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}",
|
|
PathToUTF8String(file_path), ec.message());
|
|
}
|
|
|
|
return flush_result;
|
|
}
|
|
|
|
bool IOFile::Commit() const {
|
|
if (!IsOpen()) {
|
|
return false;
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
#ifdef _WIN32
|
|
const auto commit_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0;
|
|
#else
|
|
const auto commit_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0;
|
|
#endif
|
|
|
|
if (!commit_result) {
|
|
const auto ec = std::error_code{errno, std::generic_category()};
|
|
LOG_ERROR(Common_Filesystem, "Failed to commit the file at path={}, ec_message={}",
|
|
PathToUTF8String(file_path), ec.message());
|
|
}
|
|
|
|
return commit_result;
|
|
}
|
|
|
|
bool IOFile::SetSize(u64 size) const {
|
|
if (!IsOpen()) {
|
|
return false;
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
#ifdef _WIN32
|
|
const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0;
|
|
#else
|
|
const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0;
|
|
#endif
|
|
|
|
if (!set_size_result) {
|
|
const auto ec = std::error_code{errno, std::generic_category()};
|
|
LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
|
|
PathToUTF8String(file_path), size, ec.message());
|
|
}
|
|
|
|
return set_size_result;
|
|
}
|
|
|
|
u64 IOFile::GetSize() const {
|
|
if (!IsOpen()) {
|
|
return 0;
|
|
}
|
|
|
|
// Flush any unwritten buffered data into the file prior to retrieving the file size.
|
|
std::fflush(file);
|
|
|
|
std::error_code ec;
|
|
|
|
const auto file_size = fs::file_size(file_path, ec);
|
|
|
|
if (ec) {
|
|
LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
|
|
PathToUTF8String(file_path), ec.message());
|
|
return 0;
|
|
}
|
|
|
|
return file_size;
|
|
}
|
|
|
|
bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
|
|
if (!IsOpen()) {
|
|
return false;
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
|
|
|
|
if (!seek_result) {
|
|
const auto ec = std::error_code{errno, std::generic_category()};
|
|
LOG_ERROR(Common_Filesystem,
|
|
"Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
|
|
PathToUTF8String(file_path), offset, static_cast<u32>(origin), ec.message());
|
|
}
|
|
|
|
return seek_result;
|
|
}
|
|
|
|
s64 IOFile::Tell() const {
|
|
if (!IsOpen()) {
|
|
return 0;
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
return ftello(file);
|
|
}
|
|
|
|
u64 GetDirectorySize(const std::filesystem::path& path) {
|
|
if (!fs::exists(path)) {
|
|
return 0;
|
|
}
|
|
|
|
u64 total = 0;
|
|
for (const auto& entry : fs::recursive_directory_iterator(path)) {
|
|
if (fs::is_regular_file(entry.path())) {
|
|
total += fs::file_size(entry.path());
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
} // namespace Common::FS
|