Dirents backport (#3876)

* lseek for directories behaves correctly when final index is smaller than 0 (EINVAL)
Backported and improved dirents from QFS
Normal directory dirents update on change

* PFS moves pointer to end when last dirent is returned

* Correct entry type in PFS directory
This commit is contained in:
marecl
2026-01-09 23:35:43 +01:00
committed by GitHub
parent be99cb4d5b
commit f42a566cef
6 changed files with 260 additions and 287 deletions

View File

@@ -5,6 +5,7 @@
#include "common/singleton.h"
#include "core/file_sys/directories/base_directory.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/orbis_error.h"
namespace Core::Directories {
@@ -12,4 +13,35 @@ BaseDirectory::BaseDirectory() = default;
BaseDirectory::~BaseDirectory() = default;
s64 BaseDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
s64 bytes_read = 0;
for (s32 i = 0; i < iovcnt; i++) {
const s64 result = read(iov[i].iov_base, iov[i].iov_len);
if (result < 0) {
return result;
}
bytes_read += result;
}
return bytes_read;
}
s64 BaseDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
const u64 old_file_pointer = file_offset;
file_offset = offset;
const s64 bytes_read = readv(iov, iovcnt);
file_offset = old_file_pointer;
return bytes_read;
}
s64 BaseDirectory::lseek(s64 offset, s32 whence) {
s64 file_offset_new = ((0 == whence) * offset) + ((1 == whence) * (file_offset + offset)) +
((2 == whence) * (directory_size + offset));
if (file_offset_new < 0)
return ORBIS_KERNEL_ERROR_EINVAL;
file_offset = file_offset_new;
return file_offset;
}
} // namespace Core::Directories

View File

@@ -19,6 +19,17 @@ struct OrbisKernelDirent;
namespace Core::Directories {
class BaseDirectory {
protected:
static inline u32 fileno_pool{10};
static u32 next_fileno() {
return ++fileno_pool;
}
s64 file_offset = 0;
u64 directory_size = 0;
std::vector<u8> dirent_cache_bin{};
public:
explicit BaseDirectory();
@@ -28,13 +39,8 @@ public:
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt);
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset);
virtual s64 write(const void* buf, u64 nbytes) {
return ORBIS_KERNEL_ERROR_EBADF;
@@ -48,9 +54,7 @@ public:
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 lseek(s64 offset, s32 whence) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 lseek(s64 offset, s32 whence);
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) {
return ORBIS_KERNEL_ERROR_EBADF;

View File

@@ -15,111 +15,30 @@ std::shared_ptr<BaseDirectory> NormalDirectory::Create(std::string_view guest_di
std::make_shared<NormalDirectory>(guest_directory));
}
NormalDirectory::NormalDirectory(std::string_view guest_directory) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
static s32 fileno = 0;
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
auto& dirent = dirents.emplace_back();
dirent.d_fileno = ++fileno;
dirent.d_type = (ent_is_file ? 8 : 4);
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
dirent.d_namlen = ent_path.filename().string().size();
// Calculate the appropriate length for this dirent.
// Account for the null terminator in d_name too.
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
(dirent.d_namlen + 1),
4);
directory_size += dirent.d_reclen;
});
// The last entry of a normal directory should have d_reclen covering the remaining data.
// Since the dirents of a folder are constant by this point, we can modify the last dirent
// before creating the emulated file buffer.
const u64 filler_count = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT) - directory_size;
dirents[dirents.size() - 1].d_reclen += filler_count;
// Reading from standard directories seems to be based around file pointer logic.
// Keep an internal buffer representing the raw contents of this file descriptor,
// then emulate the various read functions with that.
directory_size = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT);
data_buffer.reserve(directory_size);
memset(data_buffer.data(), 0, directory_size);
u8* current_dirent = data_buffer.data();
for (const NormalDirectoryDirent& dirent : dirents) {
NormalDirectoryDirent* dirent_to_write =
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
dirent_to_write->d_fileno = dirent.d_fileno;
// Using size d_namlen + 1 to account for null terminator.
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
dirent_to_write->d_namlen = dirent.d_namlen;
dirent_to_write->d_reclen = dirent.d_reclen;
dirent_to_write->d_type = dirent.d_type;
current_dirent += dirent.d_reclen;
}
NormalDirectory::NormalDirectory(std::string_view guest_directory)
: guest_directory(guest_directory) {
RebuildDirents();
}
s64 NormalDirectory::read(void* buf, u64 nbytes) {
// Nothing left to read.
if (file_offset >= directory_size) {
return ORBIS_OK;
}
RebuildDirents();
const s64 remaining_data = directory_size - file_offset;
const s64 bytes = nbytes > remaining_data ? remaining_data : nbytes;
// data is contiguous. read goes like any regular file would: start at offset, read n bytes
// output is always aligned up to 512 bytes with 0s
// offset - classic. however at the end of read any unused (exceeding dirent buffer size) buffer
// space will be left untouched
// reclen always sums up to end of current alignment
std::memcpy(buf, data_buffer.data() + file_offset, bytes);
s64 bytes_available = this->dirent_cache_bin.size() - file_offset;
if (bytes_available <= 0)
return 0;
bytes_available = std::min<s64>(bytes_available, static_cast<s64>(nbytes));
file_offset += bytes;
return bytes;
}
// data
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
s64 NormalDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
s64 bytes_read = 0;
for (s32 i = 0; i < iovcnt; i++) {
const s64 result = read(iov[i].iov_base, iov[i].iov_len);
if (result < 0) {
return result;
}
bytes_read += result;
}
return bytes_read;
}
s64 NormalDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
s64 offset) {
const u64 old_file_pointer = file_offset;
file_offset = offset;
const s64 bytes_read = readv(iov, iovcnt);
file_offset = old_file_pointer;
return bytes_read;
}
s64 NormalDirectory::lseek(s64 offset, s32 whence) {
switch (whence) {
case 0: {
file_offset = offset;
break;
}
case 1: {
file_offset += offset;
break;
}
case 2: {
file_offset = directory_size + offset;
break;
}
default: {
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
}
}
return file_offset;
file_offset += bytes_available;
return bytes_available;
}
s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
@@ -131,10 +50,110 @@ s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
}
s64 NormalDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
if (basep != nullptr) {
RebuildDirents();
if (basep)
*basep = file_offset;
// same as others, we just don't need a variable
if (file_offset >= directory_size)
return 0;
s64 bytes_written = 0;
s64 working_offset = file_offset;
s64 dirent_buffer_offset = 0;
s64 aligned_count = Common::AlignDown(nbytes, 512);
const u8* dirent_buffer = this->dirent_cache_bin.data();
while (dirent_buffer_offset < this->dirent_cache_bin.size()) {
const u8* normal_dirent_ptr = dirent_buffer + dirent_buffer_offset;
const NormalDirectoryDirent* normal_dirent =
reinterpret_cast<const NormalDirectoryDirent*>(normal_dirent_ptr);
auto d_reclen = normal_dirent->d_reclen;
// bad, incomplete or OOB entry
if (normal_dirent->d_namlen == 0)
break;
if (working_offset >= d_reclen) {
dirent_buffer_offset += d_reclen;
working_offset -= d_reclen;
continue;
}
// read behaves identically to getdents for normal directories.
return read(buf, nbytes);
if ((bytes_written + d_reclen) > aligned_count)
// dirents are aligned to the last full one
break;
memcpy(static_cast<u8*>(buf) + bytes_written, normal_dirent_ptr + working_offset,
d_reclen - working_offset);
bytes_written += d_reclen - working_offset;
dirent_buffer_offset += d_reclen;
working_offset = 0;
}
file_offset += bytes_written;
return bytes_written;
}
void NormalDirectory::RebuildDirents() {
// regenerate only when target wants to read contents again
// no reason for testing - read is always raw and dirents get processed on the go
if (previous_file_offset == file_offset)
return;
previous_file_offset = file_offset;
constexpr u32 dirent_meta_size =
sizeof(NormalDirectoryDirent::d_fileno) + sizeof(NormalDirectoryDirent::d_type) +
sizeof(NormalDirectoryDirent::d_namlen) + sizeof(NormalDirectoryDirent::d_reclen);
u64 next_ceiling = 0;
u64 dirent_offset = 0;
u64 last_reclen_offset = 4;
dirent_cache_bin.clear();
dirent_cache_bin.reserve(512);
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
mnt->IterateDirectory(
guest_directory, [this, &next_ceiling, &dirent_offset, &last_reclen_offset](
const std::filesystem::path& ent_path, const bool ent_is_file) {
NormalDirectoryDirent tmp{};
std::string leaf(ent_path.filename().string());
// prepare dirent
tmp.d_fileno = BaseDirectory::next_fileno();
tmp.d_namlen = leaf.size();
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
tmp.d_type = (ent_is_file ? 0100000 : 0040000) >> 12;
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 4);
// next element may break 512 byte alignment
if (tmp.d_reclen + dirent_offset > next_ceiling) {
// align previous dirent's size to the current ceiling
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) +
last_reclen_offset) += next_ceiling - dirent_offset;
// set writing pointer to the aligned start position (current ceiling)
dirent_offset = next_ceiling;
// move the ceiling up and zero-out the buffer
next_ceiling += 512;
dirent_cache_bin.resize(next_ceiling);
std::fill(dirent_cache_bin.begin() + dirent_offset,
dirent_cache_bin.begin() + next_ceiling, 0);
}
// current dirent's reclen position
last_reclen_offset = dirent_offset + 4;
memcpy(dirent_cache_bin.data() + dirent_offset, &tmp, tmp.d_reclen);
dirent_offset += tmp.d_reclen;
});
// last reclen, as before
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) + last_reclen_offset) +=
next_ceiling - dirent_offset;
// i have no idea if this is the case, but lseek returns size aligned to 512
directory_size = next_ceiling;
}
} // namespace Core::Directories

View File

@@ -19,27 +19,23 @@ public:
~NormalDirectory() override = default;
virtual s64 read(void* buf, u64 nbytes) override;
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
s64 offset) override;
virtual s64 lseek(s64 offset, s32 whence) override;
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
private:
static constexpr s32 MAX_LENGTH = 255;
static constexpr s64 DIRECTORY_ALIGNMENT = 0x200;
#pragma pack(push, 1)
struct NormalDirectoryDirent {
u32 d_fileno;
u16 d_reclen;
u8 d_type;
u8 d_namlen;
char d_name[MAX_LENGTH + 1];
char d_name[256];
};
#pragma pack(pop)
u64 directory_size = 0;
s64 file_offset = 0;
std::vector<u8> data_buffer;
std::vector<NormalDirectoryDirent> dirents;
std::string_view guest_directory{};
s64 previous_file_offset = -1;
void RebuildDirents(void);
};
} // namespace Core::Directories

View File

@@ -15,77 +15,49 @@ std::shared_ptr<BaseDirectory> PfsDirectory::Create(std::string_view guest_direc
}
PfsDirectory::PfsDirectory(std::string_view guest_directory) {
constexpr u32 dirent_meta_size =
sizeof(PfsDirectoryDirent::d_fileno) + sizeof(PfsDirectoryDirent::d_type) +
sizeof(PfsDirectoryDirent::d_namlen) + sizeof(PfsDirectoryDirent::d_reclen);
dirent_cache_bin.reserve(512);
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
static s32 fileno = 0;
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
auto& dirent = dirents.emplace_back();
dirent.d_fileno = ++fileno;
dirent.d_type = (ent_is_file ? 8 : 4);
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
dirent.d_namlen = ent_path.filename().string().size();
mnt->IterateDirectory(
guest_directory, [this](const std::filesystem::path& ent_path, const bool ent_is_file) {
PfsDirectoryDirent tmp{};
std::string leaf(ent_path.filename().string());
// Calculate the appropriate length for this dirent.
// Account for the null terminator in d_name too.
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
(dirent.d_namlen + 1),
8);
tmp.d_fileno = BaseDirectory::next_fileno();
tmp.d_namlen = leaf.size();
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
tmp.d_type = ent_is_file ? 2 : 4;
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 8);
auto dirent_ptr = reinterpret_cast<const u8*>(&tmp);
// To handle some obscure dirents_index behavior,
// keep track of the "actual" length of this directory.
directory_content_size += dirent.d_reclen;
dirent_cache_bin.insert(dirent_cache_bin.end(), dirent_ptr, dirent_ptr + tmp.d_reclen);
});
directory_size = Common::AlignUp(directory_content_size, DIRECTORY_ALIGNMENT);
directory_size = Common::AlignUp(dirent_cache_bin.size(), 0x10000);
}
s64 PfsDirectory::read(void* buf, u64 nbytes) {
if (dirents_index >= dirents.size()) {
if (dirents_index < directory_content_size) {
// We need to find the appropriate dirents_index to start from.
s64 data_to_skip = dirents_index;
u64 corrected_index = 0;
while (data_to_skip > 0) {
const auto dirent = dirents[corrected_index++];
data_to_skip -= dirent.d_reclen;
s64 bytes_available = this->dirent_cache_bin.size() - file_offset;
if (bytes_available <= 0)
return 0;
bytes_available = std::min<s64>(bytes_available, static_cast<s64>(nbytes));
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
s64 to_fill =
(std::min<s64>(directory_size, static_cast<s64>(nbytes))) - bytes_available - file_offset;
if (to_fill < 0) {
LOG_ERROR(Kernel_Fs, "Dirent may have leaked {} bytes", -to_fill);
return -to_fill + bytes_available;
}
dirents_index = corrected_index;
} else {
// Nothing left to read.
return ORBIS_OK;
}
}
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
// read on PfsDirectories will always return the maximum possible value.
const u64 bytes_written = bytes_remaining;
memset(buf, 0, bytes_remaining);
char* current_dirent = static_cast<char*>(buf);
PfsDirectoryDirent dirent = dirents[dirents_index];
while (bytes_remaining > dirent.d_reclen) {
PfsDirectoryDirent* dirent_to_write = reinterpret_cast<PfsDirectoryDirent*>(current_dirent);
dirent_to_write->d_fileno = dirent.d_fileno;
// Using size d_namlen + 1 to account for null terminator.
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
dirent_to_write->d_namlen = dirent.d_namlen;
dirent_to_write->d_reclen = dirent.d_reclen;
dirent_to_write->d_type = dirent.d_type;
current_dirent += dirent.d_reclen;
bytes_remaining -= dirent.d_reclen;
if (dirents_index == dirents.size() - 1) {
// Currently at the last dirent, so break out of the loop.
dirents_index++;
break;
}
dirent = dirents[++dirents_index];
}
return bytes_written;
memset(static_cast<u8*>(buf) + bytes_available, 0, to_fill);
file_offset += to_fill + bytes_available;
return to_fill + bytes_available;
}
s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
@@ -101,62 +73,13 @@ s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovc
}
s64 PfsDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
const u64 old_dirent_index = dirents_index;
dirents_index = 0;
s64 data_to_skip = offset;
// If offset is part-way through one dirent, that dirent is skipped.
while (data_to_skip > 0) {
const auto dirent = dirents[dirents_index++];
data_to_skip -= dirent.d_reclen;
if (dirents_index == dirents.size()) {
// We've reached the end of the dirents, nothing more can be skipped.
break;
}
}
const u64 old_file_pointer = file_offset;
file_offset = offset;
const s64 bytes_read = readv(iov, iovcnt);
dirents_index = old_dirent_index;
file_offset = old_file_pointer;
return bytes_read;
}
s64 PfsDirectory::lseek(s64 offset, s32 whence) {
switch (whence) {
// Seek start
case 0: {
dirents_index = 0;
}
case 1: {
// There aren't any dirents left to pass through.
if (dirents_index >= dirents.size()) {
dirents_index = dirents_index + offset;
break;
}
s64 data_to_skip = offset;
while (data_to_skip > 0) {
const auto dirent = dirents[dirents_index++];
data_to_skip -= dirent.d_reclen;
if (dirents_index == dirents.size()) {
// We've passed through all file dirents.
// Set dirents_index to directory_size + remaining_offset instead.
dirents_index = directory_content_size + data_to_skip;
break;
}
}
break;
}
case 2: {
// Seems like real hardware gives up on tracking dirents_index if you go this route.
dirents_index = directory_size + offset;
break;
}
default: {
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
}
}
return dirents_index;
}
s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
stat->st_mode = 0000777u | 0040000u;
stat->st_size = directory_size;
@@ -166,55 +89,58 @@ s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
}
s64 PfsDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
// basep is set at the start of the function.
if (basep != nullptr) {
*basep = dirents_index;
}
if (basep)
*basep = file_offset;
if (dirents_index >= dirents.size()) {
if (dirents_index < directory_content_size) {
// We need to find the appropriate dirents_index to start from.
s64 data_to_skip = dirents_index;
u64 corrected_index = 0;
while (data_to_skip > 0) {
const auto dirent = dirents[corrected_index++];
data_to_skip -= dirent.d_reclen;
}
dirents_index = corrected_index;
} else {
// Nothing left to read.
return ORBIS_OK;
}
}
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
memset(buf, 0, bytes_remaining);
// same as others, we just don't need a variable
if (file_offset >= directory_size)
return 0;
u64 bytes_written = 0;
char* current_dirent = static_cast<char*>(buf);
// getdents has to convert pfs dirents to normal dirents
PfsDirectoryDirent dirent = dirents[dirents_index];
while (bytes_remaining > dirent.d_reclen) {
NormalDirectoryDirent* dirent_to_write =
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
dirent_to_write->d_fileno = dirent.d_fileno;
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
dirent_to_write->d_namlen = dirent.d_namlen;
dirent_to_write->d_reclen = dirent.d_reclen;
dirent_to_write->d_type = dirent.d_type;
u64 starting_offset = 0;
u64 buffer_position = 0;
while (buffer_position < this->dirent_cache_bin.size()) {
const PfsDirectoryDirent* pfs_dirent =
reinterpret_cast<PfsDirectoryDirent*>(this->dirent_cache_bin.data() + buffer_position);
current_dirent += dirent.d_reclen;
bytes_remaining -= dirent.d_reclen;
bytes_written += dirent.d_reclen;
if (dirents_index == dirents.size() - 1) {
// Currently at the last dirent, so set dirents_index appropriately and break.
dirents_index = directory_size;
// bad, incomplete or OOB entry
if (pfs_dirent->d_namlen == 0)
break;
}
dirent = dirents[++dirents_index];
if (starting_offset < file_offset) {
// reading starts from the nearest full dirent
starting_offset += pfs_dirent->d_reclen;
buffer_position = bytes_written + starting_offset;
continue;
}
if ((bytes_written + pfs_dirent->d_reclen) > nbytes)
// dirents are aligned to the last full one
break;
// if this dirent breaks alignment, skip
// dirents are count-aligned here, excess data is simply not written
// if (Common::AlignUp(buffer_position, count) !=
// Common::AlignUp(buffer_position + pfs_dirent->d_reclen, count))
// break;
// reclen for both is the same despite difference in var sizes, extra 0s are padded after
// the name
NormalDirectoryDirent normal_dirent{};
normal_dirent.d_fileno = pfs_dirent->d_fileno;
normal_dirent.d_reclen = pfs_dirent->d_reclen;
normal_dirent.d_type = (pfs_dirent->d_type == 2) ? 8 : 4;
normal_dirent.d_namlen = pfs_dirent->d_namlen;
memcpy(normal_dirent.d_name, pfs_dirent->d_name, pfs_dirent->d_namlen);
memcpy(static_cast<u8*>(buf) + bytes_written, &normal_dirent, normal_dirent.d_reclen);
bytes_written += normal_dirent.d_reclen;
buffer_position = bytes_written + starting_offset;
}
file_offset = (buffer_position >= this->dirent_cache_bin.size())
? directory_size
: (file_offset + bytes_written);
return bytes_written;
}
} // namespace Core::Directories

View File

@@ -22,32 +22,28 @@ public:
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
s64 offset) override;
virtual s64 lseek(s64 offset, s32 whence) override;
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
private:
static constexpr s32 MAX_LENGTH = 255;
static constexpr s32 DIRECTORY_ALIGNMENT = 0x10000;
#pragma pack(push, 1)
struct PfsDirectoryDirent {
u32 d_fileno;
u32 d_type;
u32 d_namlen;
u32 d_reclen;
char d_name[MAX_LENGTH + 1];
char d_name[256];
};
#pragma pack(pop)
#pragma pack(push, 1)
struct NormalDirectoryDirent {
u32 d_fileno;
u16 d_reclen;
u8 d_type;
u8 d_namlen;
char d_name[MAX_LENGTH + 1];
char d_name[256];
};
u64 directory_size = 0;
u64 directory_content_size = 0;
s64 dirents_index = 0;
std::vector<PfsDirectoryDirent> dirents;
#pragma pack(pop)
};
} // namespace Core::Directories