mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
kernel: share directory streams across seeks and reads
Fix PS5 boot failure when an optional empty sound path opens /app0/ and the game seeks and reads that directory. Pack directory records once and share their bytes and position across raw reads, enumeration, seeking, and size reporting. Apply seek and read error semantics with checked offsets. Add multiblock directory regression coverage using the existing production Vulkan resource lifecycle. Existing CMake build and kernel_file_system test pass. Independent correctness, fixture, runtime, and final compactness audits passed; launch.json run reached Jurassic Park gameplay with 4560 sampled flips, no Vulkan validation errors, and normal window-close exit.
This commit is contained in:
+109
-132
@@ -28,7 +28,8 @@ namespace Libs::LibKernel::FileSystem {
|
|||||||
|
|
||||||
LIB_NAME("libkernel", "libkernel");
|
LIB_NAME("libkernel", "libkernel");
|
||||||
|
|
||||||
constexpr int DESCRIPTOR_MIN = 3;
|
constexpr int DESCRIPTOR_MIN = 3;
|
||||||
|
constexpr uint64_t DIR_BLOCK_SIZE = 512;
|
||||||
|
|
||||||
enum class SpecialFile {
|
enum class SpecialFile {
|
||||||
None,
|
None,
|
||||||
@@ -68,7 +69,7 @@ struct File {
|
|||||||
std::atomic_bool sync_writes;
|
std::atomic_bool sync_writes;
|
||||||
SpecialFile special;
|
SpecialFile special;
|
||||||
Common::Mutex mutex;
|
Common::Mutex mutex;
|
||||||
std::vector<Common::File::DirEntry> dents;
|
std::vector<uint8_t> dirents;
|
||||||
uint64_t dents_offset;
|
uint64_t dents_offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -119,6 +120,51 @@ static uint64_t AlignUp(uint64_t value, uint64_t alignment) {
|
|||||||
return (alignment != 0 ? (value + alignment - 1) & ~(alignment - 1) : value);
|
return (alignment != 0 ? (value + alignment - 1) & ~(alignment - 1) : value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<uint8_t> PackDirents(const std::vector<Common::File::DirEntry>& entries) {
|
||||||
|
std::vector<uint8_t> dirents;
|
||||||
|
uint64_t offset = 0;
|
||||||
|
uint64_t last_reclen_offset = 0;
|
||||||
|
for (const auto& entry: entries) {
|
||||||
|
const auto& name = entry.name;
|
||||||
|
EXIT_NOT_IMPLEMENTED(name.size() > 255);
|
||||||
|
const auto reclen = AlignUp(8 + name.size() + 1, 4);
|
||||||
|
if (offset + reclen > dirents.size()) {
|
||||||
|
if (!dirents.empty()) {
|
||||||
|
auto* last_reclen =
|
||||||
|
reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
|
||||||
|
*last_reclen += static_cast<uint16_t>(dirents.size() - offset);
|
||||||
|
}
|
||||||
|
offset = dirents.size();
|
||||||
|
dirents.resize(offset + DIR_BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
*reinterpret_cast<uint32_t*>(dirents.data() + offset) =
|
||||||
|
Common::hash(name.data(), static_cast<uint32_t>(name.size()));
|
||||||
|
*reinterpret_cast<uint16_t*>(dirents.data() + offset + 4) = static_cast<uint16_t>(reclen);
|
||||||
|
dirents[offset + 6] = entry.is_file ? 8 : 4;
|
||||||
|
dirents[offset + 7] = static_cast<uint8_t>(name.size());
|
||||||
|
memcpy(dirents.data() + offset + 8, name.c_str(), name.size() + 1);
|
||||||
|
last_reclen_offset = offset + 4;
|
||||||
|
offset += reclen;
|
||||||
|
}
|
||||||
|
if (!dirents.empty()) {
|
||||||
|
auto* last_reclen = reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
|
||||||
|
*last_reclen += static_cast<uint16_t>(dirents.size() - offset);
|
||||||
|
}
|
||||||
|
return dirents;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t ReadDirectory(File* file, void* buf, uint64_t count) {
|
||||||
|
const auto remaining =
|
||||||
|
file->dents_offset < file->dirents.size() ? file->dirents.size() - file->dents_offset : 0;
|
||||||
|
const auto bytes = std::min(count, remaining);
|
||||||
|
if (bytes != 0) {
|
||||||
|
Memory::InvalidateMemory(reinterpret_cast<uint64_t>(buf), bytes);
|
||||||
|
memcpy(buf, file->dirents.data() + file->dents_offset, bytes);
|
||||||
|
file->dents_offset += bytes;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
static int PosixToKernel(int posix_errno) {
|
static int PosixToKernel(int posix_errno) {
|
||||||
return KERNEL_ERROR_UNKNOWN + posix_errno;
|
return KERNEL_ERROR_UNKNOWN + posix_errno;
|
||||||
}
|
}
|
||||||
@@ -433,15 +479,16 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
|
|||||||
EXIT_NOT_IMPLEMENTED(!directory && rw_mode != Common::File::Mode::Read);
|
EXIT_NOT_IMPLEMENTED(!directory && rw_mode != Common::File::Mode::Read);
|
||||||
EXIT_NOT_IMPLEMENTED(!directory && (trunc || creat));
|
EXIT_NOT_IMPLEMENTED(!directory && (trunc || creat));
|
||||||
|
|
||||||
file->dents = Common::File::GetDirEntries(file->real_name);
|
const auto entries = Common::File::GetDirEntries(file->real_name);
|
||||||
|
file->dirents = PackDirents(entries);
|
||||||
file->dents_offset = 0;
|
file->dents_offset = 0;
|
||||||
file->directory = true;
|
file->directory = true;
|
||||||
|
|
||||||
LOGF_COLOR(Log::Color::Green, "\tOpen dir: %s, entries = %" PRIu32 ", [ok]\n",
|
LOGF_COLOR(Log::Color::Green, "\tOpen dir: %s, entries = %" PRIu32 ", [ok]\n",
|
||||||
Common::PathToString(file->real_name).c_str(),
|
Common::PathToString(file->real_name).c_str(),
|
||||||
static_cast<uint32_t>(file->dents.size()));
|
static_cast<uint32_t>(entries.size()));
|
||||||
|
|
||||||
for (const auto& f: file->dents) {
|
for (const auto& f: entries) {
|
||||||
LOGF("\t\t%s %s\n", f.is_file ? "[file]" : "[dir ]", f.name.c_str());
|
LOGF("\t\t%s %s\n", f.is_file ? "[file]" : "[dir ]", f.name.c_str());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -538,11 +585,18 @@ int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes) {
|
|||||||
return KERNEL_ERROR_EBADF;
|
return KERNEL_ERROR_EBADF;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(file->directory);
|
|
||||||
|
|
||||||
EXIT_IF(!file->opened);
|
EXIT_IF(!file->opened);
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(nbytes > UINT_MAX);
|
if (nbytes > INT_MAX) {
|
||||||
|
return KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
if (file->directory) {
|
||||||
|
Common::LockGuard lock(file->mutex);
|
||||||
|
const auto bytes = ReadDirectory(file, buf, nbytes);
|
||||||
|
LOGF("\tRead %" PRIu64 " directory bytes from: %s\n", bytes,
|
||||||
|
Common::PathToString(file->real_name).c_str());
|
||||||
|
return static_cast<int64_t>(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
if (file->special == SpecialFile::Random) {
|
if (file->special == SpecialFile::Random) {
|
||||||
FillRandomBuffer(buf, nbytes);
|
FillRandomBuffer(buf, nbytes);
|
||||||
@@ -761,7 +815,7 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
|
|||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
if (d < DESCRIPTOR_MIN) {
|
if (d < DESCRIPTOR_MIN) {
|
||||||
return KERNEL_ERROR_EPERM;
|
return KERNEL_ERROR_EBADF;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* file = g_files->GetFile(d);
|
auto* file = g_files->GetFile(d);
|
||||||
@@ -770,8 +824,6 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
|
|||||||
return KERNEL_ERROR_EBADF;
|
return KERNEL_ERROR_EBADF;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(file->directory);
|
|
||||||
|
|
||||||
EXIT_IF(!file->opened);
|
EXIT_IF(!file->opened);
|
||||||
|
|
||||||
if (file->special != SpecialFile::None) {
|
if (file->special != SpecialFile::None) {
|
||||||
@@ -780,38 +832,47 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
|
|||||||
|
|
||||||
Common::LockGuard lock(file->mutex);
|
Common::LockGuard lock(file->mutex);
|
||||||
|
|
||||||
bool is_invalid = file->f.IsInvalid();
|
if (!file->directory && file->f.IsInvalid()) {
|
||||||
|
|
||||||
if (whence == 1) {
|
|
||||||
offset = static_cast<int64_t>(file->f.Tell()) + offset;
|
|
||||||
whence = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (whence == 2) {
|
|
||||||
offset = static_cast<int64_t>(file->f.Size()) + offset;
|
|
||||||
whence = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(whence != 0);
|
|
||||||
|
|
||||||
if (offset < 0) {
|
|
||||||
return KERNEL_ERROR_EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
file->f.Seek(offset);
|
|
||||||
auto pos = static_cast<int64_t>(file->f.Tell());
|
|
||||||
|
|
||||||
EXIT_IF(pos != offset);
|
|
||||||
|
|
||||||
if (is_invalid) {
|
|
||||||
LOGF("\tfile is invalid\n");
|
LOGF("\tfile is invalid\n");
|
||||||
return KERNEL_ERROR_EIO;
|
return KERNEL_ERROR_EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGF("\tLseek (pos = %" PRId64 ") to: %s\n", offset,
|
uint64_t base = 0;
|
||||||
|
switch (whence) {
|
||||||
|
case 0: break;
|
||||||
|
case 1: base = file->directory ? file->dents_offset : file->f.Tell(); break;
|
||||||
|
case 2: base = file->directory ? file->dirents.size() : file->f.Size(); break;
|
||||||
|
default: return KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t position = 0;
|
||||||
|
if (offset >= 0) {
|
||||||
|
if (base > static_cast<uint64_t>(INT64_MAX - offset)) {
|
||||||
|
return KERNEL_ERROR_EOVERFLOW;
|
||||||
|
}
|
||||||
|
position = base + static_cast<uint64_t>(offset);
|
||||||
|
} else {
|
||||||
|
const auto distance = uint64_t {0} - static_cast<uint64_t>(offset);
|
||||||
|
if (base < distance) {
|
||||||
|
return KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
position = base - distance;
|
||||||
|
if (position > INT64_MAX) {
|
||||||
|
return KERNEL_ERROR_EOVERFLOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file->directory) {
|
||||||
|
file->dents_offset = position;
|
||||||
|
} else {
|
||||||
|
file->f.Seek(position);
|
||||||
|
EXIT_IF(file->f.Tell() != position);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGF("\tLseek (pos = %" PRIu64 ") to: %s\n", position,
|
||||||
Common::PathToString(file->real_name).c_str());
|
Common::PathToString(file->real_name).c_str());
|
||||||
|
|
||||||
return pos;
|
return static_cast<int64_t>(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
|
int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
|
||||||
@@ -842,9 +903,10 @@ int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
|
|||||||
auto wt = at;
|
auto wt = at;
|
||||||
|
|
||||||
if (is_dir) {
|
if (is_dir) {
|
||||||
stat.st_size = 0;
|
stat.st_size =
|
||||||
|
static_cast<int64_t>(PackDirents(Common::File::GetDirEntries(real_file_name)).size());
|
||||||
stat.st_blksize = 512;
|
stat.st_blksize = 512;
|
||||||
stat.st_blocks = 0;
|
stat.st_blocks = stat.st_size / 512;
|
||||||
} else {
|
} else {
|
||||||
stat.st_size = static_cast<int64_t>(Common::File::Size(real_file_name));
|
stat.st_size = static_cast<int64_t>(Common::File::Size(real_file_name));
|
||||||
stat.st_blksize = 512;
|
stat.st_blksize = 512;
|
||||||
@@ -920,9 +982,9 @@ int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb) {
|
|||||||
stat.st_blksize = 512;
|
stat.st_blksize = 512;
|
||||||
stat.st_blocks = (stat.st_size + 511) / 512;
|
stat.st_blocks = (stat.st_size + 511) / 512;
|
||||||
} else {
|
} else {
|
||||||
stat.st_size = 0;
|
stat.st_size = static_cast<int64_t>(file->dirents.size());
|
||||||
stat.st_blksize = 512;
|
stat.st_blksize = 512;
|
||||||
stat.st_blocks = 0;
|
stat.st_blocks = stat.st_size / 512;
|
||||||
}
|
}
|
||||||
|
|
||||||
SecToTimespec(&stat.st_atim, at.ToUnix());
|
SecToTimespec(&stat.st_atim, at.ToUnix());
|
||||||
@@ -1057,14 +1119,15 @@ int KYTY_SYSV_ABI KernelGetdirentries(int fd, char* buf, int nbytes, int64_t* ba
|
|||||||
return KERNEL_ERROR_EBADF;
|
return KERNEL_ERROR_EBADF;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr uint64_t DIR_BLOCK_SIZE = 512;
|
|
||||||
constexpr uint64_t DIRENT_META_SIZE = 8;
|
|
||||||
|
|
||||||
if (!file->directory || nbytes < static_cast<int>(DIR_BLOCK_SIZE)) {
|
if (!file->directory || nbytes < static_cast<int>(DIR_BLOCK_SIZE)) {
|
||||||
return KERNEL_ERROR_EINVAL;
|
return KERNEL_ERROR_EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXIT_IF(!file->opened);
|
EXIT_IF(!file->opened);
|
||||||
|
Common::LockGuard lock(file->mutex);
|
||||||
|
if (file->dents_offset > file->dirents.size() || file->dents_offset % DIR_BLOCK_SIZE != 0) {
|
||||||
|
return KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
LOGF("\t dir = %s\n"
|
LOGF("\t dir = %s\n"
|
||||||
"\t nbytes = %d\n"
|
"\t nbytes = %d\n"
|
||||||
@@ -1074,94 +1137,8 @@ int KYTY_SYSV_ABI KernelGetdirentries(int fd, char* buf, int nbytes, int64_t* ba
|
|||||||
if (basep != nullptr) {
|
if (basep != nullptr) {
|
||||||
*basep = static_cast<int64_t>(file->dents_offset);
|
*basep = static_cast<int64_t>(file->dents_offset);
|
||||||
}
|
}
|
||||||
|
return static_cast<int>(
|
||||||
std::vector<uint8_t> dirents;
|
ReadDirectory(file, buf, AlignDown(static_cast<uint64_t>(nbytes), DIR_BLOCK_SIZE)));
|
||||||
dirents.reserve(
|
|
||||||
std::max<size_t>(static_cast<size_t>(DIR_BLOCK_SIZE), file->dents.size() * 64u));
|
|
||||||
|
|
||||||
uint64_t next_ceiling = 0;
|
|
||||||
uint64_t dirent_offset = 0;
|
|
||||||
int64_t last_reclen_offset = -1;
|
|
||||||
|
|
||||||
for (const auto& entry: file->dents) {
|
|
||||||
const auto& str = entry.name;
|
|
||||||
auto str_size = str.size();
|
|
||||||
EXIT_NOT_IMPLEMENTED(str_size > 255);
|
|
||||||
|
|
||||||
uint64_t reclen = AlignUp(DIRENT_META_SIZE + str_size + 1, 4);
|
|
||||||
if (dirent_offset + reclen > next_ceiling) {
|
|
||||||
if (last_reclen_offset >= 0) {
|
|
||||||
auto* last_reclen =
|
|
||||||
reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
|
|
||||||
*last_reclen = static_cast<uint16_t>(*last_reclen + (next_ceiling - dirent_offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
dirent_offset = next_ceiling;
|
|
||||||
next_ceiling += DIR_BLOCK_SIZE;
|
|
||||||
dirents.resize(next_ceiling);
|
|
||||||
std::fill(dirents.begin() + static_cast<std::ptrdiff_t>(dirent_offset), dirents.end(),
|
|
||||||
0);
|
|
||||||
}
|
|
||||||
|
|
||||||
*reinterpret_cast<uint32_t*>(dirents.data() + dirent_offset + 0) =
|
|
||||||
Common::hash(str.data(), static_cast<uint32_t>(str.size()));
|
|
||||||
*reinterpret_cast<uint16_t*>(dirents.data() + dirent_offset + 4) =
|
|
||||||
static_cast<uint16_t>(reclen);
|
|
||||||
*reinterpret_cast<uint8_t*>(dirents.data() + dirent_offset + 6) = (entry.is_file ? 8 : 4);
|
|
||||||
*reinterpret_cast<uint8_t*>(dirents.data() + dirent_offset + 7) =
|
|
||||||
static_cast<uint8_t>(str_size);
|
|
||||||
memcpy(dirents.data() + dirent_offset + 8, str.data(), str_size);
|
|
||||||
dirents[dirent_offset + 8 + str_size] = '\0';
|
|
||||||
|
|
||||||
last_reclen_offset = static_cast<int64_t>(dirent_offset + 4);
|
|
||||||
dirent_offset += reclen;
|
|
||||||
|
|
||||||
LOGF("\t name = %s\n", str.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (last_reclen_offset >= 0) {
|
|
||||||
auto* last_reclen = reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
|
|
||||||
*last_reclen = static_cast<uint16_t>(*last_reclen + (next_ceiling - dirent_offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint64_t directory_size = next_ceiling;
|
|
||||||
if (file->dents_offset >= directory_size) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t bytes_written = 0;
|
|
||||||
uint64_t working_offset = file->dents_offset;
|
|
||||||
uint64_t dirent_buffer_offset = 0;
|
|
||||||
uint64_t aligned_count = AlignDown(static_cast<uint64_t>(nbytes), DIR_BLOCK_SIZE);
|
|
||||||
|
|
||||||
while (dirent_buffer_offset < dirents.size()) {
|
|
||||||
const auto* normal_dirent = dirents.data() + dirent_buffer_offset;
|
|
||||||
uint16_t reclen = *reinterpret_cast<const uint16_t*>(normal_dirent + 4);
|
|
||||||
uint8_t namlen = *(normal_dirent + 7);
|
|
||||||
|
|
||||||
if (namlen == 0 || reclen == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (working_offset >= reclen) {
|
|
||||||
dirent_buffer_offset += reclen;
|
|
||||||
working_offset -= reclen;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes_written + reclen > aligned_count) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto bytes_to_copy = reclen - working_offset;
|
|
||||||
memcpy(buf + bytes_written, normal_dirent + working_offset, bytes_to_copy);
|
|
||||||
bytes_written += bytes_to_copy;
|
|
||||||
dirent_buffer_offset += reclen;
|
|
||||||
working_offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
file->dents_offset += bytes_written;
|
|
||||||
return static_cast<int64_t>(bytes_written);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI KernelGetdents(int fd, char* buf, int nbytes) {
|
int KYTY_SYSV_ABI KernelGetdents(int fd, char* buf, int nbytes) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
#include "SDL.h"
|
||||||
#include "common/emulatorConfig.h"
|
#include "common/emulatorConfig.h"
|
||||||
#include "common/file.h"
|
#include "common/file.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/subsystems.h"
|
#include "common/subsystems.h"
|
||||||
#include "common/threads.h"
|
#include "common/threads.h"
|
||||||
|
#include "graphics/presentation/window/windowInternal.h"
|
||||||
#include "kernel/fileSystem.h"
|
#include "kernel/fileSystem.h"
|
||||||
#include "libs/errno.h"
|
#include "libs/errno.h"
|
||||||
#include "libs/network.h"
|
#include "libs/network.h"
|
||||||
@@ -15,8 +17,11 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Libs::LibKernelApr {
|
namespace Libs::LibKernelApr {
|
||||||
void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols);
|
void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols);
|
||||||
@@ -123,6 +128,109 @@ void CheckMountRoot(const std::filesystem::path &root) {
|
|||||||
FileSystem::Umount("/app0");
|
FileSystem::Umount("/app0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CheckDirectoryStream(const std::filesystem::path &root) {
|
||||||
|
const auto directory = root / "directory-stream";
|
||||||
|
Check(std::filesystem::create_directory(directory),
|
||||||
|
"create seek fixture directory");
|
||||||
|
for (int i = 0; i < 48; ++i) {
|
||||||
|
Common::File fixture;
|
||||||
|
Check(fixture.Create(directory / ("directory-entry-" + std::to_string(i))),
|
||||||
|
"create enough entries to cross directory blocks");
|
||||||
|
fixture.Close();
|
||||||
|
}
|
||||||
|
FileSystem::Mount(directory, "/app0");
|
||||||
|
const int fd = FileSystem::KernelOpen("/app0/", 0, 0);
|
||||||
|
Check(fd >= 3, "open directory as read-only asset");
|
||||||
|
const auto end = FileSystem::KernelLseek(fd, 0, 2);
|
||||||
|
Check(end > 512 && end % 512 == 0,
|
||||||
|
"directory SEEK_END uses padded stream size");
|
||||||
|
FileSystem::FileStat stat{};
|
||||||
|
Check(FileSystem::KernelFstat(fd, &stat) == OK && stat.st_size == end &&
|
||||||
|
stat.st_blksize == 512,
|
||||||
|
"directory stat agrees with seek and enumeration");
|
||||||
|
Check(FileSystem::KernelStat("/app0/", &stat) == OK && stat.st_size == end &&
|
||||||
|
stat.st_blocks == end / 512,
|
||||||
|
"path and descriptor directory stat agree");
|
||||||
|
Check(FileSystem::KernelLseek(fd, 0, 0) == 0,
|
||||||
|
"rewind directory for asset read");
|
||||||
|
std::vector<char> raw(static_cast<size_t>(end));
|
||||||
|
Check(FileSystem::KernelRead(fd, raw.data(), raw.size()) == end &&
|
||||||
|
FileSystem::KernelRead(fd, raw.data(), 1) == 0,
|
||||||
|
"raw directory read reaches EOF");
|
||||||
|
Check(FileSystem::KernelLseek(fd, -end, 1) == 0,
|
||||||
|
"directory SEEK_CUR uses the position advanced by read");
|
||||||
|
|
||||||
|
std::array<char, 512> block{};
|
||||||
|
int64_t base = -1;
|
||||||
|
for (int64_t offset = 0; offset < end; offset += block.size()) {
|
||||||
|
Check(FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
|
||||||
|
&base) == block.size() &&
|
||||||
|
base == offset &&
|
||||||
|
std::memcmp(block.data(), raw.data() + offset, block.size()) == 0,
|
||||||
|
"directory enumeration shares raw bytes and reports each block "
|
||||||
|
"position");
|
||||||
|
for (size_t pos = 0; pos < block.size();) {
|
||||||
|
Check(block.size() - pos >= 8, "directory record header fits");
|
||||||
|
uint16_t length = 0;
|
||||||
|
std::memcpy(&length, block.data() + pos + 4, sizeof(length));
|
||||||
|
const auto name_length = static_cast<uint8_t>(block[pos + 7]);
|
||||||
|
Check(length >= 9 + name_length && length <= block.size() - pos &&
|
||||||
|
block[pos + 8 + name_length] == '\0',
|
||||||
|
"directory entries remain complete within every block");
|
||||||
|
pos += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Check(FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
|
||||||
|
&base) == 0 &&
|
||||||
|
base == end,
|
||||||
|
"directory enumeration reports EOF position");
|
||||||
|
Check(FileSystem::KernelLseek(fd, 512, 0) == 512 &&
|
||||||
|
FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
|
||||||
|
&base) == block.size() &&
|
||||||
|
base == 512 &&
|
||||||
|
std::memcmp(block.data(), raw.data() + 512, block.size()) == 0,
|
||||||
|
"restore and reread a directory enumeration position");
|
||||||
|
|
||||||
|
const auto position = FileSystem::KernelLseek(fd, 0, 1);
|
||||||
|
Check(
|
||||||
|
FileSystem::KernelLseek(fd, 0, 9) ==
|
||||||
|
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
|
||||||
|
FileSystem::KernelLseek(fd, -1, 0) ==
|
||||||
|
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
|
||||||
|
FileSystem::KernelLseek(fd, std::numeric_limits<int64_t>::min(), 1) ==
|
||||||
|
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
|
||||||
|
FileSystem::KernelLseek(fd, std::numeric_limits<int64_t>::max(), 1) ==
|
||||||
|
Libs::LibKernel::KERNEL_ERROR_EOVERFLOW &&
|
||||||
|
FileSystem::KernelLseek(fd, 0, 1) == position,
|
||||||
|
"invalid and overflowing directory seeks preserve the position");
|
||||||
|
Check(FileSystem::KernelLseek(fd, -19, 2) == end - 19 &&
|
||||||
|
FileSystem::KernelRead(fd, block.data(), block.size()) == 19 &&
|
||||||
|
std::memcmp(block.data(), raw.data() + end - 19, 19) == 0,
|
||||||
|
"raw directory reads support byte positions and stop at EOF");
|
||||||
|
Check(FileSystem::KernelLseek(fd, 1, 0) == 1 &&
|
||||||
|
FileSystem::KernelGetdents(fd, block.data(), block.size()) ==
|
||||||
|
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
|
||||||
|
FileSystem::KernelLseek(fd, 0, 1) == 1,
|
||||||
|
"directory enumeration rejects an incomplete record position");
|
||||||
|
Check(FileSystem::KernelLseek(fd, 512, 2) == end + 512 &&
|
||||||
|
FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
|
||||||
|
&base) ==
|
||||||
|
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
|
||||||
|
FileSystem::KernelLseek(fd, 0, 1) == end + 512,
|
||||||
|
"directory enumeration rejects a position beyond EOF");
|
||||||
|
Check(FileSystem::KernelRead(
|
||||||
|
fd, block.data(),
|
||||||
|
static_cast<size_t>(std::numeric_limits<int>::max()) + 1) ==
|
||||||
|
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
|
||||||
|
FileSystem::KernelLseek(fd, 0, 1) == end + 512,
|
||||||
|
"oversized read fails without changing the directory position");
|
||||||
|
Check(FileSystem::KernelClose(fd) == OK, "close directory stream");
|
||||||
|
Check(FileSystem::KernelLseek(fd, 0, 0) ==
|
||||||
|
Libs::LibKernel::KERNEL_ERROR_EBADF,
|
||||||
|
"seek rejects a closed descriptor");
|
||||||
|
FileSystem::Umount("/app0");
|
||||||
|
}
|
||||||
|
|
||||||
void CheckAprPaths(const std::filesystem::path &root) {
|
void CheckAprPaths(const std::filesystem::path &root) {
|
||||||
Loader::SymbolDatabase symbols;
|
Loader::SymbolDatabase symbols;
|
||||||
Libs::LibKernelApr::InitLibKernel_1_Apr(&symbols);
|
Libs::LibKernelApr::InitLibKernel_1_Apr(&symbols);
|
||||||
@@ -267,15 +375,26 @@ int main() {
|
|||||||
Config::Load(options);
|
Config::Load(options);
|
||||||
subsystems.Initialize<Log::Lifecycle>();
|
subsystems.Initialize<Log::Lifecycle>();
|
||||||
|
|
||||||
|
Check(SDL_InitSubSystem(SDL_INIT_VIDEO) == 0, "initialize Vulkan test video");
|
||||||
|
auto graphics = std::make_unique<Libs::Graphics::WindowContext>();
|
||||||
|
graphics->graphic_ctx.screen_width = 64;
|
||||||
|
graphics->graphic_ctx.screen_height = 64;
|
||||||
|
graphics->window = SDL_CreateWindow("KernelFileSystemTests", 0, 0, 64, 64,
|
||||||
|
SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN);
|
||||||
|
Check(graphics->window != nullptr, "create hidden Vulkan test window");
|
||||||
|
graphics->CreateVulkan();
|
||||||
|
|
||||||
TempDirectory temporary;
|
TempDirectory temporary;
|
||||||
FileSystem::Initialize();
|
FileSystem::Initialize();
|
||||||
CheckMountRoot(temporary.Path());
|
CheckMountRoot(temporary.Path());
|
||||||
|
CheckDirectoryStream(temporary.Path());
|
||||||
CheckAprPaths(temporary.Path());
|
CheckAprPaths(temporary.Path());
|
||||||
FileSystem::Mount(temporary.Path(), "/savedata0");
|
FileSystem::Mount(temporary.Path(), "/savedata0");
|
||||||
CheckSaveRename(temporary.Path(), "first-save");
|
CheckSaveRename(temporary.Path(), "first-save");
|
||||||
CheckSaveRename(temporary.Path(), "replacement-save");
|
CheckSaveRename(temporary.Path(), "replacement-save");
|
||||||
FileSystem::Shutdown();
|
FileSystem::Shutdown();
|
||||||
CheckSocketWakeup();
|
CheckSocketWakeup();
|
||||||
|
graphics.reset();
|
||||||
subsystems.Destroy();
|
subsystems.Destroy();
|
||||||
|
|
||||||
std::printf("KernelFileSystemTests: all cases passed\n");
|
std::printf("KernelFileSystemTests: all cases passed\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user