Virtual device abstraction (#1577)

* IOFile: removes seek limit checks when file is writable

* add virtual devices scaffold

* add stdin/out/err as virtual devices

* fixed some merging issues

* clang-fix

---------

Co-authored-by: georgemoralis <giorgosmrls@gmail.com>
This commit is contained in:
Vinicius Rangel 2024-12-05 13:00:17 -03:00 committed by GitHub
parent c019b54fec
commit 2380f2f9c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 687 additions and 77 deletions

View file

@ -11,6 +11,39 @@
#include "core/libraries/libs.h"
#include "kernel.h"
#include <map>
#include <ranges>
#include "core/devices/logger.h"
#include "core/devices/nop_device.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
// clang-format on
};
namespace Libraries::Kernel {
auto GetDirectoryEntries(const std::filesystem::path& path) {
@ -24,8 +57,8 @@ auto GetDirectoryEntries(const std::filesystem::path& path) {
return files;
}
int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {}", path, flags, mode);
int PS4_SYSV_ABI sceKernelOpen(const char* raw_path, int 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();
@ -44,22 +77,35 @@ int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) {
bool direct = (flags & ORBIS_KERNEL_O_DIRECT) != 0;
bool directory = (flags & ORBIS_KERNEL_O_DIRECTORY) != 0;
if (std::string_view{path} == "/dev/console") {
std::string_view path{raw_path};
if (path == "/dev/console") {
return 2000;
}
if (std::string_view{path} == "/dev/deci_tty6") {
if (path == "/dev/deci_tty6") {
return 2001;
}
if (std::string_view{path} == "/dev/stdout") {
return 2002;
}
if (std::string_view{path} == "/dev/urandom") {
if (path == "/dev/urandom") {
return 2003;
}
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;
}
}
}
if (directory) {
file->is_directory = true;
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
@ -135,11 +181,12 @@ int PS4_SYSV_ABI sceKernelClose(int d) {
if (file == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
if (!file->is_directory) {
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(d);
return ORBIS_OK;
}
@ -155,14 +202,6 @@ int PS4_SYSV_ABI posix_close(int d) {
}
size_t PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes) {
if (d <= 2) { // stdin,stdout,stderr
char* str = strdup((const char*)buf);
if (str[nbytes - 1] == '\n')
str[nbytes - 1] = 0;
LOG_INFO(Tty, "{}", str);
free(str);
return nbytes;
}
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(d);
if (file == nullptr) {
@ -170,6 +209,9 @@ size_t PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes) {
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
return file->device->write(buf, nbytes);
}
return file->f.WriteRaw<u8>(buf, nbytes);
}
@ -207,17 +249,63 @@ int PS4_SYSV_ABI sceKernelUnlink(const char* path) {
size_t PS4_SYSV_ABI _readv(int d, const SceKernelIovec* iov, int iovcnt) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(d);
size_t total_read = 0;
if (file == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
int r = file->device->readv(iov, iovcnt);
if (r < 0) {
ErrSceToPosix(r);
return -1;
}
return r;
}
size_t total_read = 0;
for (int i = 0; i < iovcnt; i++) {
total_read += file->f.ReadRaw<u8>(iov[i].iov_base, iov[i].iov_len);
}
return total_read;
}
size_t PS4_SYSV_ABI _writev(int fd, const SceKernelIovec* iov, int iovcn) {
if (fd == 1) {
size_t total_written = 0;
for (int i = 0; i < iovcn; i++) {
total_written += ::fwrite(iov[i].iov_base, 1, iov[i].iov_len, stdout);
}
return total_written;
}
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
return file->device->writev(iov, iovcn);
}
size_t total_written = 0;
for (int i = 0; i < iovcn; i++) {
total_written += file->f.WriteRaw<u8>(iov[i].iov_base, iov[i].iov_len);
}
return total_written;
}
s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(d);
if (file == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
return file->device->lseek(offset, whence);
}
Common::FS::SeekOrigin origin{};
if (whence == 0) {
@ -228,7 +316,6 @@ s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) {
origin = Common::FS::SeekOrigin::End;
}
std::scoped_lock lk{file->m_mutex};
if (!file->f.Seek(offset, origin)) {
LOG_CRITICAL(Kernel_Fs, "sceKernelLseek: failed to seek");
return ORBIS_KERNEL_ERROR_EINVAL;
@ -261,6 +348,9 @@ s64 PS4_SYSV_ABI sceKernelRead(int d, void* buf, size_t nbytes) {
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
return file->device->read(buf, nbytes);
}
return file->f.ReadRaw<u8>(buf, nbytes);
}
@ -409,7 +499,13 @@ int PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
int PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
const auto path_name = mnt->GetHostPath(path);
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;
}
@ -431,6 +527,10 @@ s64 PS4_SYSV_ABI sceKernelPreadv(int d, SceKernelIovec* iov, int iovcnt, s64 off
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
return file->device->preadv(iov, iovcnt, offset);
}
const s64 pos = file->f.Tell();
SCOPE_EXIT {
file->f.Seek(pos);
@ -466,18 +566,25 @@ int PS4_SYSV_ABI sceKernelFStat(int fd, OrbisKernelStat* sb) {
}
std::memset(sb, 0, sizeof(OrbisKernelStat));
if (file->is_directory) {
sb->st_mode = 0000777u | 0040000u;
sb->st_size = 0;
sb->st_blksize = 512;
sb->st_blocks = 0;
// TODO incomplete
} else {
switch (file->type) {
case Core::FileSys::FileType::Device:
return file->device->fstat(sb);
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;
}
@ -495,6 +602,13 @@ int PS4_SYSV_ABI posix_fstat(int fd, OrbisKernelStat* sb) {
s32 PS4_SYSV_ABI sceKernelFsync(int fd) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
if (file->type == Core::FileSys::FileType::Device) {
return file->device->fsync();
}
file->f.Flush();
return ORBIS_OK;
}
@ -517,6 +631,10 @@ int PS4_SYSV_ABI sceKernelFtruncate(int fd, s64 length) {
return ORBIS_KERNEL_ERROR_EBADF;
}
if (file->type == Core::FileSys::FileType::Device) {
return file->device->ftruncate(length);
}
if (file->m_host_name.empty()) {
return ORBIS_KERNEL_ERROR_EACCES;
}
@ -538,10 +656,15 @@ static int GetDents(int fd, char* buf, int nbytes, s64* basep) {
if (file == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
if (file->type != Core::FileSys::FileType::Device) {
return file->device->getdents(buf, nbytes, basep);
}
if (file->dirents_index == file->dirents.size()) {
return ORBIS_OK;
}
if (!file->is_directory || nbytes < 512 || file->dirents_index > file->dirents.size()) {
if (file->type != Core::FileSys::FileType::Directory || nbytes < 512 ||
file->dirents_index > file->dirents.size()) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
const auto& entry = file->dirents.at(file->dirents_index++);
@ -586,6 +709,10 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) {
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
return file->device->pwrite(buf, nbytes, offset);
}
const s64 pos = file->f.Tell();
SCOPE_EXIT {
file->f.Seek(pos);
@ -637,6 +764,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
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", "libkernel", 1, "libkernel", 1, 1, posix_lseek);
LIB_FUNCTION("Oy6IpwgtYOk", "libScePosix", 1, "libkernel", 1, 1, posix_lseek);
LIB_FUNCTION("oib76F-12fk", "libkernel", 1, "libkernel", 1, 1, sceKernelLseek);

View file

@ -9,6 +9,9 @@
#include "common/logging/log.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "common/va_ctx.h"
#include "core/file_sys/fs.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/equeue.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/kernel.h"
@ -24,6 +27,7 @@
#ifdef _WIN64
#include <Rpc.h>
#endif
#include <common/singleton.h>
namespace Libraries::Kernel {
@ -65,19 +69,6 @@ static PS4_SYSV_ABI void stack_chk_fail() {
UNREACHABLE();
}
struct iovec {
void* iov_base; /* Base address. */
size_t iov_len; /* Length. */
};
size_t PS4_SYSV_ABI _writev(int fd, const struct iovec* iov, int iovcn) {
size_t total_written = 0;
for (int i = 0; i < iovcn; i++) {
total_written += ::fwrite(iov[i].iov_base, 1, iov[i].iov_len, stdout);
}
return total_written;
}
static thread_local int g_posix_errno = 0;
int* PS4_SYSV_ABI __Error() {
@ -142,24 +133,33 @@ void PS4_SYSV_ABI sceLibcHeapGetTraceInfo(HeapInfoInfo* info) {
}
s64 PS4_SYSV_ABI ps4__write(int d, const char* buf, std::size_t nbytes) {
if (d <= 2) { // stdin,stdout,stderr
std::string_view str{buf};
if (str[nbytes - 1] == '\n') {
str = str.substr(0, nbytes - 1);
}
LOG_INFO(Tty, "{}", str);
return nbytes;
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(d);
if (file == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
LOG_ERROR(Kernel, "(STUBBED) called d = {} nbytes = {} ", d, nbytes);
UNREACHABLE();
return ORBIS_OK;
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
return file->device->write(buf, nbytes);
}
return file->f.WriteRaw<u8>(buf, nbytes);
}
s64 PS4_SYSV_ABI ps4__read(int d, void* buf, u64 nbytes) {
ASSERT_MSG(d == 0, "d is not 0!");
return static_cast<s64>(
strlen(std::fgets(static_cast<char*>(buf), static_cast<int>(nbytes), stdin)));
if (d == 0) {
return static_cast<s64>(
strlen(std::fgets(static_cast<char*>(buf), static_cast<int>(nbytes), stdin)));
}
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(d);
if (file == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
return file->device->read(buf, nbytes);
}
return file->f.ReadRaw<u8>(buf, nbytes);
}
struct OrbisKernelUuid {
@ -189,6 +189,29 @@ int PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) {
return 0;
}
int PS4_SYSV_ABI kernel_ioctl(int fd, u64 cmd, VA_ARGS) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd);
if (file == nullptr) {
LOG_INFO(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} file == nullptr", fd, cmd);
g_posix_errno = POSIX_EBADF;
return -1;
}
if (file->type != Core::FileSys::FileType::Device) {
LOG_WARNING(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} file->type != Device", fd, cmd);
g_posix_errno = ENOTTY;
return -1;
}
VA_CTX(ctx);
int result = file->device->ioctl(cmd, &ctx);
LOG_TRACE(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} result = {}", fd, cmd, result);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
const char* PS4_SYSV_ABI sceKernelGetFsSandboxRandomWord() {
const char* path = "sys";
return path;
@ -219,13 +242,13 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) {
Libraries::Kernel::RegisterException(sym);
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard);
LIB_FUNCTION("PfccT7qURYE", "libkernel", 1, "libkernel", 1, 1, kernel_ioctl);
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetFsSandboxRandomWord);
LIB_FUNCTION("XVL8So3QJUk", "libkernel", 1, "libkernel", 1, 1, posix_connect);
LIB_FUNCTION("6xVpy0Fdq+I", "libkernel", 1, "libkernel", 1, 1, _sigprocmask);
LIB_FUNCTION("Xjoosiw+XPI", "libkernel", 1, "libkernel", 1, 1, sceKernelUuidCreate);
LIB_FUNCTION("Ou3iL1abvng", "libkernel", 1, "libkernel", 1, 1, stack_chk_fail);
LIB_FUNCTION("9BcDykPmo1I", "libkernel", 1, "libkernel", 1, 1, __Error);
LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, _writev);
LIB_FUNCTION("DRuBt2pvICk", "libkernel", 1, "libkernel", 1, 1, ps4__read);
LIB_FUNCTION("k+AXqu2-eBc", "libkernel", 1, "libkernel", 1, 1, posix_getpagesize);
LIB_FUNCTION("k+AXqu2-eBc", "libScePosix", 1, "libkernel", 1, 1, posix_getpagesize);

View file

@ -20,7 +20,7 @@ int PS4_SYSV_ABI sceKernelIsNeoMode() {
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) {
int version = Common::ElfInfo::Instance().RawFirmwareVer();
*ver = version;
return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
}
int PS4_SYSV_ABI sceKernelGetCpumode() {