shadPS4/src/core/libraries/kernel/file_system.cpp
Stephen Miller 0ba9ea6a3b
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / linux-qt-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
Only perform early read-write open when truncating is needed (#2874)
Should stop some fs error spam when games open files from /app0, as this open call would fail from reduced permissions.
2025-05-02 11:22:05 -07:00

1095 lines
36 KiB
C++

// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <map>
#include <ranges>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/singleton.h"
#include "core/devices/console_device.h"
#include "core/devices/deci_tty6_device.h"
#include "core/devices/logger.h"
#include "core/devices/nop_device.h"
#include "core/devices/random_device.h"
#include "core/devices/srandom_device.h"
#include "core/devices/urandom_device.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/kernel/posix_error.h"
#include "core/libraries/libs.h"
#include "core/memory.h"
#include "kernel.h"
namespace D = Core::Devices;
using FactoryDevice = std::function<std::shared_ptr<D::BaseDevice>(u32, const char*, int, u16)>;
#define GET_DEVICE_FD(fd) \
[](u32, const char*, int, u16) { \
return Common::Singleton<Core::FileSys::HandleTable>::Instance()->GetFile(fd)->device; \
}
// prefix path, only dev devices
static std::map<std::string, FactoryDevice> available_device = {
// clang-format off
{"/dev/stdin", GET_DEVICE_FD(0)},
{"/dev/stdout", GET_DEVICE_FD(1)},
{"/dev/stderr", GET_DEVICE_FD(2)},
{"/dev/fd/0", GET_DEVICE_FD(0)},
{"/dev/fd/1", GET_DEVICE_FD(1)},
{"/dev/fd/2", GET_DEVICE_FD(2)},
{"/dev/deci_stdin", GET_DEVICE_FD(0)},
{"/dev/deci_stdout", GET_DEVICE_FD(1)},
{"/dev/deci_stderr", GET_DEVICE_FD(2)},
{"/dev/null", GET_DEVICE_FD(0)}, // fd0 (stdin) is a nop device
{"/dev/urandom", &D::URandomDevice::Create },
{"/dev/random", &D::RandomDevice::Create },
{"/dev/srandom", &D::SRandomDevice::Create },
{"/dev/console", &D::ConsoleDevice::Create },
{"/dev/deci_tty6",&D::DeciTty6Device::Create }
// clang-format on
};
namespace Libraries::Kernel {
s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {}", raw_path, flags, mode);
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool read = (flags & 0x3) == ORBIS_KERNEL_O_RDONLY;
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;
// 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;
bool dsync = (flags & ORBIS_KERNEL_O_DSYNC) != 0;
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);
if (path.starts_with("/dev/")) {
for (const auto& [prefix, factory] : available_device) {
if (path.starts_with(prefix)) {
file->is_opened = true;
file->type = Core::FileSys::FileType::Device;
file->m_guest_name = path;
file->device = factory(handle, path.data(), flags, mode);
return handle;
}
}
}
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_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 {
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_EROFS;
return -1;
} else if (truncate) {
// Open the file 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 (e == 0) {
// If the file was opened successfully, 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
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
} else if (rdwr) {
// Read and write
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;
}
s32 PS4_SYSV_ABI posix_open(const char* filename, s32 flags, u16 mode) {
return open(filename, flags, mode);
}
s32 PS4_SYSV_ABI sceKernelOpen(const char* path, s32 flags, /* SceKernelMode*/ u16 mode) {
s32 result = open(path, flags, mode);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s32 PS4_SYSV_ABI close(s32 fd) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
if (fd < 3) {
// This is technically possible, but it's usually caused by some stubbed function instead.
LOG_WARNING(Kernel_Fs, "called on an std handle, fd = {}", fd);
}
if (file->type == Core::FileSys::FileType::Regular) {
file->f.Close();
}
file->is_opened = false;
LOG_INFO(Kernel_Fs, "Closing {}", file->m_guest_name);
// FIXME: Lock file mutex before deleting it?
h->DeleteHandle(fd);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI posix_close(s32 fd) {
return close(fd);
}
s32 PS4_SYSV_ABI sceKernelClose(s32 fd) {
s32 result = close(fd);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s64 PS4_SYSV_ABI write(s32 fd, const void* buf, size_t nbytes) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
s64 result = file->device->write(buf, nbytes);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
return file->f.WriteRaw<u8>(buf, nbytes);
}
s64 PS4_SYSV_ABI posix_write(s32 fd, const void* buf, size_t nbytes) {
return write(fd, buf, nbytes);
}
s64 PS4_SYSV_ABI sceKernelWrite(s32 fd, const void* buf, size_t nbytes) {
s64 result = write(fd, buf, nbytes);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
size_t ReadFile(Common::FS::IOFile& file, void* buf, size_t nbytes) {
const auto* memory = Core::Memory::Instance();
// Invalidate up to the actual number of bytes that could be read.
const auto remaining = file.GetSize() - file.Tell();
memory->InvalidateMemory(reinterpret_cast<VAddr>(buf), std::min<u64>(nbytes, remaining));
return file.ReadRaw<u8>(buf, nbytes);
}
size_t PS4_SYSV_ABI readv(s32 fd, const SceKernelIovec* iov, s32 iovcnt) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
size_t result = file->device->readv(iov, iovcnt);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
size_t total_read = 0;
for (s32 i = 0; i < iovcnt; i++) {
total_read += ReadFile(file->f, iov[i].iov_base, iov[i].iov_len);
}
return total_read;
}
size_t PS4_SYSV_ABI posix_readv(s32 fd, const SceKernelIovec* iov, s32 iovcnt) {
return readv(fd, iov, iovcnt);
}
size_t PS4_SYSV_ABI sceKernelReadv(s32 fd, const SceKernelIovec* iov, s32 iovcnt) {
size_t result = readv(fd, iov, iovcnt);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
size_t PS4_SYSV_ABI writev(s32 fd, const SceKernelIovec* iov, s32 iovcnt) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
size_t result = file->device->writev(iov, iovcnt);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
size_t total_written = 0;
for (s32 i = 0; i < iovcnt; i++) {
total_written += file->f.WriteRaw<u8>(iov[i].iov_base, iov[i].iov_len);
}
return total_written;
}
size_t PS4_SYSV_ABI posix_writev(s32 fd, const SceKernelIovec* iov, s32 iovcnt) {
return writev(fd, iov, iovcnt);
}
size_t PS4_SYSV_ABI sceKernelWritev(s32 fd, const SceKernelIovec* iov, s32 iovcnt) {
size_t result = writev(fd, iov, iovcnt);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s64 PS4_SYSV_ABI posix_lseek(s32 fd, s64 offset, s32 whence) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
s64 result = file->device->lseek(offset, whence);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
Common::FS::SeekOrigin origin{};
if (whence == 0) {
origin = Common::FS::SeekOrigin::SetOrigin;
} else if (whence == 1) {
origin = Common::FS::SeekOrigin::CurrentPosition;
} else if (whence == 2) {
origin = Common::FS::SeekOrigin::End;
} 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;
return -1;
}
if (!file->f.Seek(offset, origin)) {
if (errno != 0) {
// Seek failed in platform-specific code, errno needs to be converted.
SetPosixErrno(errno);
return -1;
}
// Shouldn't be possible, but just in case.
return -1;
}
s64 result = file->f.Tell();
if (result < 0) {
// Tell failed in platform-specific code, errno needs to be converted.
SetPosixErrno(errno);
return -1;
}
return result;
}
s64 PS4_SYSV_ABI sceKernelLseek(s32 fd, s64 offset, s32 whence) {
s64 result = posix_lseek(fd, offset, whence);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s64 PS4_SYSV_ABI read(s32 fd, void* buf, size_t nbytes) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
s64 result = file->device->read(buf, nbytes);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
return ReadFile(file->f, buf, nbytes);
}
s64 PS4_SYSV_ABI posix_read(s32 fd, void* buf, size_t nbytes) {
return read(fd, buf, nbytes);
}
s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, size_t nbytes) {
s64 result = read(fd, buf, nbytes);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} mode = {}", path, mode);
if (path == nullptr) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool ro = false;
const auto dir_name = mnt->GetHostPath(path, &ro);
if (std::filesystem::exists(dir_name)) {
*__Error() = POSIX_EEXIST;
return -1;
}
if (ro) {
*__Error() = POSIX_EROFS;
return -1;
}
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
std::error_code ec;
if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
*__Error() = POSIX_EIO;
return -1;
}
if (!std::filesystem::exists(dir_name)) {
*__Error() = POSIX_ENOENT;
return -1;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
s32 result = posix_mkdir(path, mode);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s32 PS4_SYSV_ABI posix_rmdir(const char* path) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool ro = false;
const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
if (ro) {
*__Error() = POSIX_EROFS;
return -1;
}
if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
if (!std::filesystem::exists(dir_name)) {
*__Error() = POSIX_ENOENT;
return -1;
}
std::error_code ec;
s32 result = std::filesystem::remove_all(dir_name, ec);
if (ec) {
*__Error() = POSIX_EIO;
return -1;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelRmdir(const char* path) {
s32 result = posix_rmdir(path);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
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();
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);
if (!is_dir && !is_file) {
*__Error() = POSIX_ENOENT;
return -1;
}
if (std::filesystem::is_directory(path_name)) {
sb->st_mode = 0000777u | 0040000u;
sb->st_size = 0;
sb->st_blksize = 512;
sb->st_blocks = 0;
// TODO incomplete
} else {
sb->st_mode = 0000777u | 0100000u;
sb->st_size = static_cast<s64>(std::filesystem::file_size(path_name));
sb->st_blksize = 512;
sb->st_blocks = (sb->st_size + 511) / 512;
// TODO incomplete
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
s32 result = posix_stat(path, sb);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
std::string_view guest_path{path};
for (const auto& prefix : available_device | std::views::keys) {
if (guest_path.starts_with(prefix)) {
return ORBIS_OK;
}
}
const auto path_name = mnt->GetHostPath(guest_path);
if (!std::filesystem::exists(path_name)) {
return ORBIS_KERNEL_ERROR_ENOENT;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) {
LOG_INFO(Kernel_Fs, "(PARTIAL) fd = {}", fd);
if (sb == nullptr) {
*__Error() = POSIX_EFAULT;
return -1;
}
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
std::memset(sb, 0, sizeof(OrbisKernelStat));
switch (file->type) {
case Core::FileSys::FileType::Device: {
s32 result = file->device->fstat(sb);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
case Core::FileSys::FileType::Regular: {
sb->st_mode = 0000777u | 0100000u;
sb->st_size = file->f.GetSize();
sb->st_blksize = 512;
sb->st_blocks = (sb->st_size + 511) / 512;
// TODO incomplete
break;
}
case Core::FileSys::FileType::Directory: {
sb->st_mode = 0000777u | 0040000u;
sb->st_size = 0;
sb->st_blksize = 512;
sb->st_blocks = 0;
// TODO incomplete
break;
}
default:
UNREACHABLE();
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI posix_fstat(s32 fd, OrbisKernelStat* sb) {
return fstat(fd, sb);
}
s32 PS4_SYSV_ABI sceKernelFstat(s32 fd, OrbisKernelStat* sb) {
s32 result = fstat(fd, sb);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s32 PS4_SYSV_ABI posix_ftruncate(s32 fd, s64 length) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
if (file->type == Core::FileSys::FileType::Device) {
s32 result = file->device->ftruncate(length);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
if (file->m_host_name.empty()) {
*__Error() = POSIX_EACCES;
return -1;
}
file->f.SetSize(length);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelFtruncate(s32 fd, s64 length) {
s32 result = posix_ftruncate(fd, length);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool ro = false;
const auto src_path = mnt->GetHostPath(from, &ro);
if (!std::filesystem::exists(src_path)) {
*__Error() = POSIX_ENOENT;
return -1;
}
if (ro) {
*__Error() = POSIX_EROFS;
return -1;
}
const auto dst_path = mnt->GetHostPath(to, &ro);
if (ro) {
*__Error() = POSIX_EROFS;
return -1;
}
const bool src_is_dir = std::filesystem::is_directory(src_path);
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
if (src_is_dir && !dst_is_dir) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
if (!src_is_dir && dst_is_dir) {
*__Error() = POSIX_EISDIR;
return -1;
}
if (dst_is_dir && !std::filesystem::is_empty(dst_path)) {
*__Error() = POSIX_ENOTEMPTY;
return -1;
}
std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) {
s32 result = posix_rename(from, to);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s64 PS4_SYSV_ABI posix_preadv(s32 fd, SceKernelIovec* iov, s32 iovcnt, s64 offset) {
if (offset < 0) {
*__Error() = POSIX_EINVAL;
return -1;
}
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
s64 result = file->device->preadv(iov, iovcnt, offset);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
const s64 pos = file->f.Tell();
SCOPE_EXIT {
file->f.Seek(pos);
};
if (!file->f.Seek(offset)) {
*__Error() = POSIX_EIO;
return -1;
}
size_t total_read = 0;
for (int i = 0; i < iovcnt; i++) {
total_read += ReadFile(file->f, iov[i].iov_base, iov[i].iov_len);
}
return total_read;
}
s64 PS4_SYSV_ABI sceKernelPreadv(s32 fd, SceKernelIovec* iov, s32 iovcnt, s64 offset) {
s64 result = posix_preadv(fd, iov, iovcnt, offset);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s64 PS4_SYSV_ABI posix_pread(s32 fd, void* buf, size_t nbytes, s64 offset) {
SceKernelIovec iovec{buf, nbytes};
return posix_preadv(fd, &iovec, 1, offset);
}
s64 PS4_SYSV_ABI sceKernelPread(s32 fd, void* buf, size_t nbytes, s64 offset) {
SceKernelIovec iovec{buf, nbytes};
return sceKernelPreadv(fd, &iovec, 1, offset);
}
s32 PS4_SYSV_ABI posix_fsync(s32 fd) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
if (file->type == Core::FileSys::FileType::Device) {
s32 result = file->device->fsync();
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
file->f.Flush();
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelFsync(s32 fd) {
s32 result = posix_fsync(fd);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
static s32 GetDents(s32 fd, char* buf, s32 nbytes, s64* basep) {
if (buf == nullptr) {
*__Error() = POSIX_EFAULT;
return -1;
}
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
if (file->type == Core::FileSys::FileType::Device) {
s32 result = file->device->getdents(buf, nbytes, basep);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
if (file->dirents_index == file->dirents.size()) {
return ORBIS_OK;
}
if (file->type != Core::FileSys::FileType::Directory || nbytes < 512 ||
file->dirents_index > file->dirents.size()) {
*__Error() = POSIX_EINVAL;
return -1;
}
const auto& entry = file->dirents.at(file->dirents_index++);
auto str = entry.name;
static int fileno = 1000; // random
OrbisKernelDirent* sce_ent = (OrbisKernelDirent*)buf;
sce_ent->d_fileno = fileno++; // TODO this should be unique but atm it changes maybe switch to a
// hash or something?
sce_ent->d_reclen = sizeof(OrbisKernelDirent);
sce_ent->d_type = (entry.isFile ? 8 : 4);
sce_ent->d_namlen = str.size();
strncpy(sce_ent->d_name, str.c_str(), ORBIS_MAX_PATH);
sce_ent->d_name[ORBIS_MAX_PATH] = '\0';
if (basep != nullptr) {
*basep = file->dirents_index;
}
return sizeof(OrbisKernelDirent);
}
s32 PS4_SYSV_ABI posix_getdents(s32 fd, char* buf, s32 nbytes) {
return GetDents(fd, buf, nbytes, nullptr);
}
s32 PS4_SYSV_ABI sceKernelGetdents(s32 fd, char* buf, s32 nbytes) {
s32 result = GetDents(fd, buf, nbytes, nullptr);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s32 PS4_SYSV_ABI getdirentries(s32 fd, char* buf, s32 nbytes, s64* basep) {
return GetDents(fd, buf, nbytes, basep);
}
s32 PS4_SYSV_ABI posix_getdirentries(s32 fd, char* buf, s32 nbytes, s64* basep) {
return GetDents(fd, buf, nbytes, basep);
}
s32 PS4_SYSV_ABI sceKernelGetdirentries(s32 fd, char* buf, s32 nbytes, s64* basep) {
s32 result = GetDents(fd, buf, nbytes, basep);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s64 PS4_SYSV_ABI posix_pwritev(s32 fd, const SceKernelIovec* iov, s32 iovcnt, s64 offset) {
if (offset < 0) {
*__Error() = POSIX_EINVAL;
return -1;
}
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
*__Error() = POSIX_EBADF;
return -1;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
s64 result = file->device->pwritev(iov, iovcnt, offset);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
const s64 pos = file->f.Tell();
SCOPE_EXIT {
file->f.Seek(pos);
};
if (!file->f.Seek(offset)) {
*__Error() = POSIX_EIO;
return -1;
}
size_t total_written = 0;
for (int i = 0; i < iovcnt; i++) {
total_written += file->f.WriteRaw<u8>(iov[i].iov_base, iov[i].iov_len);
}
return total_written;
}
s64 PS4_SYSV_ABI posix_pwrite(s32 fd, void* buf, size_t nbytes, s64 offset) {
SceKernelIovec iovec{buf, nbytes};
return posix_pwritev(fd, &iovec, 1, offset);
}
s64 PS4_SYSV_ABI sceKernelPwrite(s32 fd, void* buf, size_t nbytes, s64 offset) {
s64 result = posix_pwrite(fd, buf, nbytes, offset);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s64 PS4_SYSV_ABI sceKernelPwritev(s32 fd, const SceKernelIovec* iov, s32 iovcnt, s64 offset) {
s64 result = posix_pwritev(fd, iov, iovcnt, offset);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
s32 PS4_SYSV_ABI posix_unlink(const char* path) {
if (path == nullptr) {
*__Error() = POSIX_EINVAL;
return -1;
}
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool ro = false;
const auto host_path = mnt->GetHostPath(path, &ro);
if (host_path.empty()) {
*__Error() = POSIX_ENOENT;
return -1;
}
if (ro) {
*__Error() = POSIX_EROFS;
return -1;
}
if (std::filesystem::is_directory(host_path)) {
*__Error() = POSIX_EPERM;
return -1;
}
auto* file = h->GetFile(host_path);
if (file == nullptr) {
// File to unlink hasn't been opened, manually open and unlink it.
Common::FS::IOFile file(host_path, Common::FS::FileAccessMode::ReadWrite);
file.Unlink();
} else {
file->f.Unlink();
}
LOG_INFO(Kernel_Fs, "Unlinked {}", path);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelUnlink(const char* path) {
s32 result = posix_unlink(path);
if (result < 0) {
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
return result;
}
void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("6c3rCVE-fTU", "libkernel", 1, "libkernel", 1, 1, open);
LIB_FUNCTION("wuCroIGjt2g", "libScePosix", 1, "libkernel", 1, 1, posix_open);
LIB_FUNCTION("wuCroIGjt2g", "libkernel", 1, "libkernel", 1, 1, posix_open);
LIB_FUNCTION("1G3lF1Gg1k8", "libkernel", 1, "libkernel", 1, 1, sceKernelOpen);
LIB_FUNCTION("NNtFaKJbPt0", "libkernel", 1, "libkernel", 1, 1, close);
LIB_FUNCTION("bY-PO6JhzhQ", "libScePosix", 1, "libkernel", 1, 1, posix_close);
LIB_FUNCTION("bY-PO6JhzhQ", "libkernel", 1, "libkernel", 1, 1, posix_close);
LIB_FUNCTION("UK2Tl2DWUns", "libkernel", 1, "libkernel", 1, 1, sceKernelClose);
LIB_FUNCTION("FxVZqBAA7ks", "libkernel", 1, "libkernel", 1, 1, write);
LIB_FUNCTION("FN4gaPmuFV8", "libScePosix", 1, "libkernel", 1, 1, posix_write);
LIB_FUNCTION("FN4gaPmuFV8", "libkernel", 1, "libkernel", 1, 1, posix_write);
LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite);
LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, readv);
LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, writev);
LIB_FUNCTION("Oy6IpwgtYOk", "libScePosix", 1, "libkernel", 1, 1, posix_lseek);
LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 1, "libkernel", 1, 1, posix_lseek);
LIB_FUNCTION("oib76F-12fk", "libkernel", 1, "libkernel", 1, 1, sceKernelLseek);
LIB_FUNCTION("DRuBt2pvICk", "libkernel", 1, "libkernel", 1, 1, read);
LIB_FUNCTION("AqBioC2vF3I", "libScePosix", 1, "libkernel", 1, 1, posix_read);
LIB_FUNCTION("AqBioC2vF3I", "libkernel", 1, "libkernel", 1, 1, posix_read);
LIB_FUNCTION("Cg4srZ6TKbU", "libkernel", 1, "libkernel", 1, 1, sceKernelRead);
LIB_FUNCTION("JGMio+21L4c", "libScePosix", 1, "libkernel", 1, 1, posix_mkdir);
LIB_FUNCTION("JGMio+21L4c", "libkernel", 1, "libkernel", 1, 1, posix_mkdir);
LIB_FUNCTION("1-LFLmRFxxM", "libkernel", 1, "libkernel", 1, 1, sceKernelMkdir);
LIB_FUNCTION("c7ZnT7V1B98", "libScePosix", 1, "libkernel", 1, 1, posix_rmdir);
LIB_FUNCTION("c7ZnT7V1B98", "libkernel", 1, "libkernel", 1, 1, posix_rmdir);
LIB_FUNCTION("naInUjYt3so", "libkernel", 1, "libkernel", 1, 1, sceKernelRmdir);
LIB_FUNCTION("E6ao34wPw+U", "libScePosix", 1, "libkernel", 1, 1, posix_stat);
LIB_FUNCTION("E6ao34wPw+U", "libkernel", 1, "libkernel", 1, 1, posix_stat);
LIB_FUNCTION("eV9wAD2riIA", "libkernel", 1, "libkernel", 1, 1, sceKernelStat);
LIB_FUNCTION("uWyW3v98sU4", "libkernel", 1, "libkernel", 1, 1, sceKernelCheckReachability);
LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat);
LIB_FUNCTION("mqQMh1zPPT8", "libkernel", 1, "libkernel", 1, 1, posix_fstat);
LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFstat);
LIB_FUNCTION("ih4CD9-gghM", "libkernel", 1, "libkernel", 1, 1, posix_ftruncate);
LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate);
LIB_FUNCTION("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename);
LIB_FUNCTION("yTj62I7kw4s", "libkernel", 1, "libkernel", 1, 1, sceKernelPreadv);
LIB_FUNCTION("ezv-RSBNKqI", "libScePosix", 1, "libkernel", 1, 1, posix_pread);
LIB_FUNCTION("ezv-RSBNKqI", "libkernel", 1, "libkernel", 1, 1, posix_pread);
LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread);
LIB_FUNCTION("juWbTNM+8hw", "libScePosix", 1, "libkernel", 1, 1, posix_fsync);
LIB_FUNCTION("juWbTNM+8hw", "libkernel", 1, "libkernel", 1, 1, posix_fsync);
LIB_FUNCTION("fTx66l5iWIA", "libkernel", 1, "libkernel", 1, 1, sceKernelFsync);
LIB_FUNCTION("j2AIqSqJP0w", "libkernel", 1, "libkernel", 1, 1, sceKernelGetdents);
LIB_FUNCTION("sfKygSjIbI8", "libkernel", 1, "libkernel", 1, 1, getdirentries);
LIB_FUNCTION("taRWhTJFTgE", "libkernel", 1, "libkernel", 1, 1, sceKernelGetdirentries);
LIB_FUNCTION("C2kJ-byS5rM", "libkernel", 1, "libkernel", 1, 1, posix_pwrite);
LIB_FUNCTION("FCcmRZhWtOk", "libScePosix", 1, "libkernel", 1, 1, posix_pwritev);
LIB_FUNCTION("FCcmRZhWtOk", "libkernel", 1, "libkernel", 1, 1, posix_pwritev);
LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPwrite);
LIB_FUNCTION("mBd4AfLP+u8", "libkernel", 1, "libkernel", 1, 1, sceKernelPwritev);
LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", 1, 1, sceKernelUnlink);
}
} // namespace Libraries::Kernel