mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-05-27 22:03:20 +00:00
libkernel: Various filesystem fixes (#2868)
* Proper handling of whence 3 & 4 * Accurate directory handling in open Directories can be opened, and can be created in open, these changes should handle that more accurately. * Mount /app0 as read only On real hardware, it's read only. * Proper directory flag handling. Even when directory is specified, it will still succeed to open non-directories. * Check for read only directories * Earlier ro check in posix_rmdir Hardware tests suggest these checks are in a different order * Clear temp folder on boot My tests rely on this, and some games do too. Two birds with one stone * Clang * Add missing DeleteHandle calls Whoops * Final flags adjustment in sceKernelOpen All my current tests are now hardware accurate. * Fix truncates Host ftruncate consistently fails on EINVAL, I'll need to test if this issue affected Windows too. * Windows hacks Windows is more limiting about how folders are opened and things like that. For now, pretend these calls didn't error. Also fixes compilation for Windows * Final touch-ups After expanding my test suite further, I found a couple more edge cases that needed addressing. Bloodborne audio is still broken, I'll look into that soon. * Remove hacky read-only behavior in posix_stat Bloodborne apparently uses the mode parameter here when querying it's audio files, and the mode we returned led to it disabling audio entirely. * Clang * Cleaner code * Combine fsync and sync flags According to FreeBSD docs, the "sync" flag is synonymous with the fsync flag, and is only included to meet the POSIX spec. * Log if any currently unhandled flags are encountered. These are rare and probably not too important, but log a warning when they're seen. * Update file_system.cpp * Update file_system.cpp * Clang * Revert truncate fix Using ftruncate works fine after moving the call to before the proper file opening code. * Truncate before open Open the file as read-write, then try truncating. This fixes read | truncate flag behavior on Windows. * Slightly adjust check for invalid flags Any open call with invalid flags should return EINVAL, regardless of other errors parameters might cause.
This commit is contained in:
parent
b0e4e87ff3
commit
6c39bf229c
4 changed files with 133 additions and 85 deletions
|
@ -131,9 +131,7 @@ namespace {
|
|||
case SeekOrigin::End:
|
||||
return SEEK_END;
|
||||
default:
|
||||
LOG_ERROR(Common_Filesystem, "Unsupported origin {}, defaulting to SEEK_SET",
|
||||
static_cast<u32>(origin));
|
||||
return SEEK_SET;
|
||||
UNREACHABLE_MSG("Impossible SeekOrigin {}", static_cast<u32>(origin));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,8 +61,6 @@ enum class SeekOrigin : u32 {
|
|||
SetOrigin, // Seeks from the start of the file.
|
||||
CurrentPosition, // Seeks from the current file pointer position.
|
||||
End, // Seeks from the end of the file.
|
||||
SeekHole, // Seeks from the start of the next hole in the file.
|
||||
SeekData, // Seeks from the start of the next non-hole region in the file.
|
||||
};
|
||||
|
||||
class IOFile final {
|
||||
|
|
|
@ -67,10 +67,16 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
|||
bool write = (flags & 0x3) == ORBIS_KERNEL_O_WRONLY;
|
||||
bool rdwr = (flags & 0x3) == ORBIS_KERNEL_O_RDWR;
|
||||
|
||||
if (!read && !write && !rdwr) {
|
||||
// Start by checking for invalid flags.
|
||||
*__Error() = POSIX_EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool nonblock = (flags & ORBIS_KERNEL_O_NONBLOCK) != 0;
|
||||
bool append = (flags & ORBIS_KERNEL_O_APPEND) != 0;
|
||||
bool fsync = (flags & ORBIS_KERNEL_O_FSYNC) != 0;
|
||||
bool sync = (flags & ORBIS_KERNEL_O_SYNC) != 0;
|
||||
// Flags fsync and sync behave the same
|
||||
bool sync = (flags & ORBIS_KERNEL_O_SYNC) != 0 || (flags & ORBIS_KERNEL_O_FSYNC) != 0;
|
||||
bool create = (flags & ORBIS_KERNEL_O_CREAT) != 0;
|
||||
bool truncate = (flags & ORBIS_KERNEL_O_TRUNC) != 0;
|
||||
bool excl = (flags & ORBIS_KERNEL_O_EXCL) != 0;
|
||||
|
@ -78,6 +84,10 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
|||
bool direct = (flags & ORBIS_KERNEL_O_DIRECT) != 0;
|
||||
bool directory = (flags & ORBIS_KERNEL_O_DIRECTORY) != 0;
|
||||
|
||||
if (sync || direct || dsync || nonblock) {
|
||||
LOG_WARNING(Kernel_Fs, "flags {:#x} not fully handled", flags);
|
||||
}
|
||||
|
||||
std::string_view path{raw_path};
|
||||
u32 handle = h->CreateHandle();
|
||||
auto* file = h->GetFile(handle);
|
||||
|
@ -94,84 +104,126 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
|||
}
|
||||
}
|
||||
|
||||
if (directory) {
|
||||
file->type = Core::FileSys::FileType::Directory;
|
||||
file->m_guest_name = path;
|
||||
file->m_host_name = mnt->GetHostPath(file->m_guest_name);
|
||||
if (!std::filesystem::is_directory(file->m_host_name)) { // directory doesn't exist
|
||||
bool read_only = false;
|
||||
file->m_guest_name = path;
|
||||
file->m_host_name = mnt->GetHostPath(file->m_guest_name, &read_only);
|
||||
bool exists = std::filesystem::exists(file->m_host_name);
|
||||
s32 e = 0;
|
||||
|
||||
if (create) {
|
||||
if (excl && exists) {
|
||||
// Error if file exists
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_ENOENT;
|
||||
*__Error() = POSIX_EEXIST;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (read_only) {
|
||||
// Can't create files in a read only directory
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EROFS;
|
||||
return -1;
|
||||
}
|
||||
// Create a file if it doesn't exist
|
||||
Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Write);
|
||||
} else if (!exists) {
|
||||
// If we're not creating a file, and it doesn't exist, return ENOENT
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (std::filesystem::is_directory(file->m_host_name) || directory) {
|
||||
// Directories can be opened even if the directory flag isn't set.
|
||||
// In these cases, error behavior is identical to the directory code path.
|
||||
directory = true;
|
||||
}
|
||||
|
||||
if (directory) {
|
||||
if (!std::filesystem::is_directory(file->m_host_name)) {
|
||||
// If the opened file is not a directory, return ENOTDIR.
|
||||
// This will trigger when create & directory is specified, this is expected.
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
file->type = Core::FileSys::FileType::Directory;
|
||||
|
||||
// Populate directory contents
|
||||
mnt->IterateDirectory(file->m_guest_name,
|
||||
[&file](const auto& ent_path, const auto ent_is_file) {
|
||||
auto& dir_entry = file->dirents.emplace_back();
|
||||
dir_entry.name = ent_path.filename().string();
|
||||
dir_entry.isFile = ent_is_file;
|
||||
});
|
||||
file->dirents_index = 0;
|
||||
|
||||
if (read) {
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Read);
|
||||
} else if (write || rdwr) {
|
||||
// Cannot open directories with any type of write access
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EISDIR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (e == EACCES) {
|
||||
// Hack to bypass some platform limitations, ignore the error and continue as normal.
|
||||
LOG_WARNING(Kernel_Fs, "Opening directories is not fully supported on this platform");
|
||||
e = 0;
|
||||
}
|
||||
|
||||
if (truncate) {
|
||||
// Cannot open directories with truncate
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EISDIR;
|
||||
return -1;
|
||||
} else {
|
||||
if (create) {
|
||||
return handle; // dir already exists
|
||||
} else {
|
||||
mnt->IterateDirectory(file->m_guest_name,
|
||||
[&file](const auto& ent_path, const auto ent_is_file) {
|
||||
auto& dir_entry = file->dirents.emplace_back();
|
||||
dir_entry.name = ent_path.filename().string();
|
||||
dir_entry.isFile = ent_is_file;
|
||||
});
|
||||
file->dirents_index = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
file->m_guest_name = path;
|
||||
file->m_host_name = mnt->GetHostPath(file->m_guest_name);
|
||||
bool exists = std::filesystem::exists(file->m_host_name);
|
||||
int e = 0;
|
||||
// Start by opening as read-write so we can truncate regardless of flags.
|
||||
// Since open starts by closing the file, this won't interfere with later open calls.
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
|
||||
|
||||
if (create) {
|
||||
if (excl && exists) {
|
||||
// Error if file exists
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EEXIST;
|
||||
return -1;
|
||||
}
|
||||
// Create file if it doesn't exist
|
||||
Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Write);
|
||||
} else if (!exists) {
|
||||
// File to open doesn't exist, return ENOENT
|
||||
file->type = Core::FileSys::FileType::Regular;
|
||||
|
||||
if (truncate && read_only) {
|
||||
// Can't open files with truncate flag in a read only directory
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_ENOENT;
|
||||
*__Error() = POSIX_EROFS;
|
||||
return -1;
|
||||
} else if (truncate && e == 0) {
|
||||
// If the file was opened successfully and truncate was enabled, reduce size to 0
|
||||
file->f.SetSize(0);
|
||||
}
|
||||
|
||||
if (read) {
|
||||
// Read only
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Read);
|
||||
} else if (read_only) {
|
||||
// Can't open files with write/read-write access in a read only directory
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EROFS;
|
||||
return -1;
|
||||
} else if (append) {
|
||||
// Append can be specified with rdwr or write, but we treat it as a separate mode.
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append);
|
||||
} else if (write) {
|
||||
// Write only
|
||||
if (append) {
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append);
|
||||
} else {
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
|
||||
}
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
|
||||
} else if (rdwr) {
|
||||
// Read and write
|
||||
if (append) {
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append);
|
||||
} else {
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
|
||||
}
|
||||
} else {
|
||||
// Invalid flags
|
||||
*__Error() = POSIX_EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (truncate && e == 0) {
|
||||
// If the file was opened successfully and truncate was enabled, reduce size to 0
|
||||
file->f.SetSize(0);
|
||||
}
|
||||
|
||||
if (e != 0) {
|
||||
// Open failed in platform-specific code, errno needs to be converted.
|
||||
h->DeleteHandle(handle);
|
||||
SetPosixErrno(e);
|
||||
return -1;
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
|
||||
}
|
||||
}
|
||||
|
||||
if (e != 0) {
|
||||
// Open failed in platform-specific code, errno needs to be converted.
|
||||
h->DeleteHandle(handle);
|
||||
SetPosixErrno(e);
|
||||
return -1;
|
||||
}
|
||||
|
||||
file->is_opened = true;
|
||||
return handle;
|
||||
}
|
||||
|
@ -365,10 +417,10 @@ s64 PS4_SYSV_ABI posix_lseek(s32 fd, s64 offset, s32 whence) {
|
|||
origin = Common::FS::SeekOrigin::CurrentPosition;
|
||||
} else if (whence == 2) {
|
||||
origin = Common::FS::SeekOrigin::End;
|
||||
} else if (whence == 3) {
|
||||
origin = Common::FS::SeekOrigin::SeekHole;
|
||||
} else if (whence == 4) {
|
||||
origin = Common::FS::SeekOrigin::SeekData;
|
||||
} else if (whence == 3 || whence == 4) {
|
||||
// whence parameter belongs to an unsupported POSIX extension
|
||||
*__Error() = POSIX_ENOTTY;
|
||||
return -1;
|
||||
} else {
|
||||
// whence parameter is invalid
|
||||
*__Error() = POSIX_EINVAL;
|
||||
|
@ -486,13 +538,13 @@ s32 PS4_SYSV_ABI posix_rmdir(const char* path) {
|
|||
|
||||
const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
|
||||
|
||||
if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
if (ro) {
|
||||
*__Error() = POSIX_EROFS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ro) {
|
||||
*__Error() = POSIX_EROFS;
|
||||
if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -523,8 +575,7 @@ s32 PS4_SYSV_ABI sceKernelRmdir(const char* path) {
|
|||
s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
|
||||
LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path);
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
bool ro = false;
|
||||
const auto path_name = mnt->GetHostPath(path, &ro);
|
||||
const auto path_name = mnt->GetHostPath(path);
|
||||
std::memset(sb, 0, sizeof(OrbisKernelStat));
|
||||
const bool is_dir = std::filesystem::is_directory(path_name);
|
||||
const bool is_file = std::filesystem::is_regular_file(path_name);
|
||||
|
@ -545,9 +596,6 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
|
|||
sb->st_blocks = (sb->st_size + 511) / 512;
|
||||
// TODO incomplete
|
||||
}
|
||||
if (ro) {
|
||||
sb->st_mode &= ~0000555u;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
|
|
@ -98,9 +98,9 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
|
|||
|
||||
// Applications expect to be run from /app0 so mount the file's parent path as app0.
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
mnt->Mount(game_folder, "/app0");
|
||||
mnt->Mount(game_folder, "/app0", true);
|
||||
// Certain games may use /hostapp as well such as CUSA001100
|
||||
mnt->Mount(game_folder, "/hostapp");
|
||||
mnt->Mount(game_folder, "/hostapp", true);
|
||||
|
||||
auto& game_info = Common::ElfInfo::Instance();
|
||||
|
||||
|
@ -231,11 +231,15 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
|
|||
std::filesystem::create_directory(mount_data_dir);
|
||||
}
|
||||
mnt->Mount(mount_data_dir, "/data"); // should just exist, manually create with game serial
|
||||
|
||||
// Mounting temp folders
|
||||
const auto& mount_temp_dir = Common::FS::GetUserPath(Common::FS::PathType::TempDataDir) / id;
|
||||
if (!std::filesystem::exists(mount_temp_dir)) {
|
||||
std::filesystem::create_directory(mount_temp_dir);
|
||||
if (std::filesystem::exists(mount_temp_dir)) {
|
||||
// Temp folder should be cleared on each boot.
|
||||
std::filesystem::remove_all(mount_temp_dir);
|
||||
}
|
||||
mnt->Mount(mount_temp_dir, "/temp0"); // called in app_content ==> stat/mkdir
|
||||
std::filesystem::create_directory(mount_temp_dir);
|
||||
mnt->Mount(mount_temp_dir, "/temp0");
|
||||
mnt->Mount(mount_temp_dir, "/temp");
|
||||
|
||||
const auto& mount_download_dir =
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue