kernel: implement path-based truncate for replay files

This commit is contained in:
overlordxrz-source
2026-10-16 01:27:53 +02:00
committed by nmzik
parent cbe5b62947
commit 56d3ce98f0
4 changed files with 124 additions and 0 deletions
+61
View File
@@ -1197,6 +1197,67 @@ int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb) {
return OK; return OK;
} }
int KYTY_SYSV_ABI KernelTruncate(const char* path, int64_t length) {
PRINT_NAME();
if (path == nullptr) {
return KERNEL_ERROR_EFAULT;
}
if (path[0] != '/' || length < 0) {
return KERNEL_ERROR_EINVAL;
}
size_t component_length = 0;
for (size_t i = 0; path[i] != '\0'; ++i) {
if (i >= 1023) {
return KERNEL_ERROR_ENAMETOOLONG;
}
if (path[i] == '/') {
component_length = 0;
} else if (++component_length > 255) {
return KERNEL_ERROR_ENAMETOOLONG;
}
}
if (IsRandomDevice(path)) {
return OK;
}
const auto real_name = g_mount_points->ResolvePath(path);
if (real_name.empty()) {
return KERNEL_ERROR_ENOENT;
}
if (Common::IsArchivePath(real_name)) {
return KERNEL_ERROR_EROFS;
}
std::error_code error;
const auto status = std::filesystem::status(real_name, error);
if (!error) {
if (!std::filesystem::exists(status)) {
return KERNEL_ERROR_ENOENT;
}
if (std::filesystem::is_directory(status)) {
return KERNEL_ERROR_EISDIR;
}
if (std::filesystem::is_regular_file(status)) {
std::filesystem::resize_file(real_name, static_cast<uint64_t>(length), error);
}
}
if (error) {
switch (static_cast<std::errc>(error.default_error_condition().value())) {
case std::errc::no_such_file_or_directory: return KERNEL_ERROR_ENOENT;
case std::errc::not_a_directory: return KERNEL_ERROR_ENOTDIR;
case std::errc::filename_too_long: return KERNEL_ERROR_ENAMETOOLONG;
case std::errc::permission_denied: return KERNEL_ERROR_EACCES;
case std::errc::read_only_file_system: return KERNEL_ERROR_EROFS;
case std::errc::file_too_large: return KERNEL_ERROR_EFBIG;
case std::errc::invalid_argument: return KERNEL_ERROR_EINVAL;
case std::errc::no_space_on_device: return KERNEL_ERROR_ENOSPC;
default: return KERNEL_ERROR_EIO;
}
}
LOGF("\tTruncate (size = %" PRId64 ") file: %s\n", length,
Common::PathToString(real_name).c_str());
return OK;
}
int KYTY_SYSV_ABI KernelFtruncate(int d, int64_t length) { int KYTY_SYSV_ABI KernelFtruncate(int d, int64_t length) {
PRINT_NAME(); PRINT_NAME();
+1
View File
@@ -69,6 +69,7 @@ int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, i
int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence); int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence);
int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb); int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb);
int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb); int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb);
int KYTY_SYSV_ABI KernelTruncate(const char* path, int64_t length);
int KYTY_SYSV_ABI KernelFtruncate(int d, int64_t length); int KYTY_SYSV_ABI KernelFtruncate(int d, int64_t length);
int KYTY_SYSV_ABI KernelUnlink(const char* path); int KYTY_SYSV_ABI KernelUnlink(const char* path);
int KYTY_SYSV_ABI KernelRename(const char* from, const char* to); int KYTY_SYSV_ABI KernelRename(const char* from, const char* to);
+1
View File
@@ -3118,6 +3118,7 @@ LIB_DEFINE(InitLibKernel_1_FS) {
LIB_FUNC("eV9wAD2riIA", FileSystem::KernelStat); LIB_FUNC("eV9wAD2riIA", FileSystem::KernelStat);
LIB_FUNC("kBwCPsYX-m4", FileSystem::KernelFstat); LIB_FUNC("kBwCPsYX-m4", FileSystem::KernelFstat);
LIB_FUNC("VW3TVZiM4-E", FileSystem::KernelFtruncate); LIB_FUNC("VW3TVZiM4-E", FileSystem::KernelFtruncate);
LIB_FUNC("WlyEA-sLDf0", FileSystem::KernelTruncate);
LIB_FUNC("AUXVxWeJU-A", FileSystem::KernelUnlink); LIB_FUNC("AUXVxWeJU-A", FileSystem::KernelUnlink);
LIB_FUNC("52NcYU9+lEo", FileSystem::KernelRename); LIB_FUNC("52NcYU9+lEo", FileSystem::KernelRename);
LIB_FUNC("taRWhTJFTgE", FileSystem::KernelGetdirentries); LIB_FUNC("taRWhTJFTgE", FileSystem::KernelGetdirentries);
+61
View File
@@ -332,6 +332,60 @@ void TestSaveOpenVisibility() {
Check(FileSystem::KernelClose(truncated) == OK, "close truncated save file"); Check(FileSystem::KernelClose(truncated) == OK, "close truncated save file");
} }
void TestSaveTruncate() {
namespace Kernel = Libs::LibKernel;
Loader::SymbolDatabase symbols;
Libs::InitLibKernel_1(&symbols);
const auto* symbol = symbols.Find(
{"WlyEA-sLDf0", "libkernel", 1, "libkernel", 1, 1, Loader::SymbolType::Func});
Check(symbol != nullptr, "sceKernelTruncate export resolves");
const auto truncate =
reinterpret_cast<int (KYTY_SYSV_ABI *)(const char*, int64_t)>(symbol->vaddr);
constexpr char Path[] = "/savedata0/truncate-save.dat";
constexpr char Payload[] = "profile data";
const int fd = FileSystem::KernelOpen(Path, 0x602, 0777);
Check(fd >= 3 && FileSystem::KernelWrite(fd, Payload, sizeof(Payload) - 1) ==
sizeof(Payload) - 1,
"populate path truncation fixture");
FileSystem::FileStat stat {};
std::array<char, 8> bytes {};
Check(truncate(Path, 3) == OK &&
FileSystem::KernelFstat(fd, &stat) == OK && stat.st_size == 3 &&
FileSystem::KernelLseek(fd, 0, 1) == sizeof(Payload) - 1 &&
FileSystem::KernelPread(fd, bytes.data(), bytes.size(), 0) == 3 &&
std::memcmp(bytes.data(), Payload, 3) == 0,
"path truncate discards the tail without moving an open descriptor");
bytes.fill('x');
Check(truncate(Path, bytes.size()) == OK &&
FileSystem::KernelFstat(fd, &stat) == OK && stat.st_size == bytes.size() &&
FileSystem::KernelPread(fd, bytes.data(), bytes.size(), 0) == bytes.size() &&
std::memcmp(bytes.data(), Payload, 3) == 0 &&
std::all_of(bytes.begin() + 3, bytes.end(), [](char byte) { return byte == 0; }) &&
FileSystem::KernelLseek(fd, 0, 1) == sizeof(Payload) - 1,
"path truncate extends with zero bytes and preserves the descriptor position");
Check(truncate(Path, -1) == Kernel::KERNEL_ERROR_EINVAL &&
truncate(nullptr, 0) == Kernel::KERNEL_ERROR_EFAULT &&
truncate("truncate-save.dat", 0) == Kernel::KERNEL_ERROR_EINVAL &&
truncate("/savedata0/missing-save.dat", 0) == Kernel::KERNEL_ERROR_ENOENT &&
truncate("/savedata0", 0) == Kernel::KERNEL_ERROR_EISDIR &&
truncate("/savedata0/truncate-save.dat/child", 0) == Kernel::KERNEL_ERROR_ENOTDIR &&
FileSystem::KernelFstat(fd, &stat) == OK && stat.st_size == bytes.size() &&
FileSystem::KernelLseek(fd, 0, 1) == sizeof(Payload) - 1,
"path truncate errors preserve file size and descriptor position");
Check(truncate("/dev/urandom", 0) == OK,
"path truncate is a no-op for a nonregular nondirectory file");
std::string long_path = "/savedata0";
long_path.resize(1024, '/');
Check(truncate(long_path.c_str(), 0) == Kernel::KERNEL_ERROR_ENAMETOOLONG,
"path truncate rejects a pathname beyond the guest length limit");
long_path.pop_back();
Check(truncate(long_path.c_str(), 0) == Kernel::KERNEL_ERROR_EISDIR &&
truncate((std::string("/savedata0/") + std::string(256, 'a')).c_str(), 0) ==
Kernel::KERNEL_ERROR_ENAMETOOLONG,
"path truncate permits the pathname length limit and bounds each component");
Check(FileSystem::KernelClose(fd) == OK, "close path truncation fixture");
}
void TestAioBatches() { void TestAioBatches() {
namespace Kernel = Libs::LibKernel; namespace Kernel = Libs::LibKernel;
Loader::SymbolDatabase symbols; Loader::SymbolDatabase symbols;
@@ -699,6 +753,12 @@ void CheckArchiveMount(const std::filesystem::path &root) {
FileSystem::KernelFtruncate(fd, 0) == FileSystem::KernelFtruncate(fd, 0) ==
Libs::LibKernel::KERNEL_ERROR_EBADF, Libs::LibKernel::KERNEL_ERROR_EBADF,
"archive read-only descriptors reject write and truncate"); "archive read-only descriptors reject write and truncate");
Check(FileSystem::KernelTruncate(GuestMember, 0) ==
Libs::LibKernel::KERNEL_ERROR_EROFS &&
FileSystem::KernelFstat(fd, &descriptor_stat) == OK &&
descriptor_stat.st_size == payload.size() &&
FileSystem::KernelLseek(fd, 0, 1) == payload.size(),
"path truncate rejects a read-only archive and preserves its member");
const auto unicode_guest = std::string("/app0/") + std::string(ArchiveTests::UnicodeFilename); const auto unicode_guest = std::string("/app0/") + std::string(ArchiveTests::UnicodeFilename);
const int unicode = FileSystem::KernelOpen(unicode_guest.c_str(), 0, 0); const int unicode = FileSystem::KernelOpen(unicode_guest.c_str(), 0, 0);
@@ -1908,6 +1968,7 @@ int main(int, char**) {
CheckAprPaths(temporary.Path()); CheckAprPaths(temporary.Path());
FileSystem::Mount(temporary.Path(), "/savedata0"); FileSystem::Mount(temporary.Path(), "/savedata0");
TestSaveOpenVisibility(); TestSaveOpenVisibility();
TestSaveTruncate();
TestAioBatches(); TestAioBatches();
CheckSaveRename(temporary.Path(), "first-save"); CheckSaveRename(temporary.Path(), "first-save");
CheckSaveRename(temporary.Path(), "replacement-save"); CheckSaveRename(temporary.Path(), "replacement-save");