feat(libkernel_sys): implement renameat

This commit is contained in:
jagrat Singh 2026-10-13 06:52:33 +05:30 committed by Adrià Franch
parent db3d379c87
commit 2a8e745267
3 changed files with 168 additions and 0 deletions

View File

@ -42,6 +42,7 @@ constexpr int GuestEperm = 1;
constexpr int GuestEio = 5;
constexpr int GuestEfault = 14;
constexpr int GuestEnotdir = 20;
constexpr int GuestEisdir = 21;
constexpr int GuestEinval = 22;
constexpr int GuestEexist = 17;
constexpr int GuestExdev = 18;
@ -194,6 +195,64 @@ bool IsDotOrDotDot(std::string_view path) {
const auto component = path.substr(separator == std::string_view::npos ? 0 : separator + 1);
return component == "." || component == "..";
}
int RenamePath(const std::filesystem::path& source, const std::filesystem::path& destination) {
namespace fs = std::filesystem;
std::error_code error;
const auto sourceStatus = fs::symlink_status(source, error);
if (error && error != std::errc::no_such_file_or_directory) return FilesystemError(error);
if (sourceStatus.type() == fs::file_type::not_found)
return HasNonDirectoryPrefix(source) ? GuestEnotdir : GuestEnoent;
const auto parent = destination.parent_path().empty() ? fs::path(".") : destination.parent_path();
error.clear();
const auto parentStatus = fs::status(parent, error);
if (error) return FilesystemError(error);
if (!fs::exists(parentStatus))
return HasNonDirectoryPrefix(destination) ? GuestEnotdir : GuestEnoent;
if (!fs::is_directory(parentStatus)) return GuestEnotdir;
error.clear();
const auto destinationStatus = fs::symlink_status(destination, error);
if (error && error != std::errc::no_such_file_or_directory) return FilesystemError(error);
const bool destinationExists = destinationStatus.type() != fs::file_type::not_found;
const bool sourceIsDirectory = fs::is_directory(sourceStatus);
const bool destinationIsDirectory = destinationExists && fs::is_directory(destinationStatus);
bool sameFile = false;
if (destinationExists) {
error.clear();
sameFile = fs::equivalent(source, destination, error);
if (error == std::errc::no_such_file_or_directory) error.clear();
if (error) return FilesystemError(error);
if (!sameFile && sourceIsDirectory && !destinationIsDirectory) return GuestEnotdir;
if (!sameFile && !sourceIsDirectory && destinationIsDirectory) return GuestEisdir;
}
if (sourceIsDirectory && !sameFile) {
error.clear();
const auto canonicalSource = fs::weakly_canonical(source, error);
if (error) return FilesystemError(error);
const auto canonicalDestination = fs::weakly_canonical(destination, error);
if (error) return FilesystemError(error);
const auto relative = canonicalDestination.lexically_relative(canonicalSource);
if (!relative.empty() && *relative.begin() != "..") return GuestEinval;
}
if (destinationExists && !sameFile && destinationIsDirectory) {
error.clear();
if (!fs::is_empty(destination, error)) {
return error ? FilesystemError(error) : GuestEnotempty;
}
}
#ifdef _WIN32
if (!sameFile && sourceIsDirectory && destinationIsDirectory) {
error.clear();
if (!fs::remove(destination, error)) return error ? FilesystemError(error) : GuestEio;
}
#endif
error.clear();
fs::rename(source, destination, error);
return error ? FilesystemError(error) : 0;
}
}
extern "C" {
@ -485,4 +544,64 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) {
return 0;
}
int APS5_VABI rename_nid_postfix(const char* from, const char* to);
int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const char* to) {
if (from == nullptr || to == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*from == '\0' || *to == '\0') return static_cast<int>(Fail(GuestEnoent));
if ((oldfd == GuestAtFdcwd || *from == '/') && (newfd == GuestAtFdcwd || *to == '/'))
return rename_nid_postfix(from, to);
if (std::strlen(from) >= GuestPathMax || HasLongComponent(from) ||
std::strlen(to) >= GuestPathMax || HasLongComponent(to))
return static_cast<int>(Fail(GuestEnametoolong));
if (IsDotOrDotDot(from) || IsDotOrDotDot(to)) return static_cast<int>(Fail(GuestEinval));
std::filesystem::path source;
if (oldfd == GuestAtFdcwd || *from == '/') {
if (const int error = ResolvePathError_nid_no_patch(from, source)) return static_cast<int>(Fail(error));
} else {
int error = 0;
const char* base = GetDirectoryDescriptorPath_nid_no_patch(oldfd, &error);
if (base == nullptr) return static_cast<int>(Fail(error));
source = std::filesystem::path(reinterpret_cast<const char8_t*>(base)) /
std::filesystem::path(reinterpret_cast<const char8_t*>(from));
}
std::filesystem::path destination;
if (newfd == GuestAtFdcwd || *to == '/') {
if (const int error = ResolvePathError_nid_no_patch(to, destination)) return static_cast<int>(Fail(error));
} else {
int error = 0;
const char* base = GetDirectoryDescriptorPath_nid_no_patch(newfd, &error);
if (base == nullptr) return static_cast<int>(Fail(error));
destination = std::filesystem::path(reinterpret_cast<const char8_t*>(base)) /
std::filesystem::path(reinterpret_cast<const char8_t*>(to));
}
const std::string_view fromView(from);
const std::string_view toView(to);
const bool oldTrailingSlash = !fromView.empty() && fromView.back() == '/';
const bool newTrailingSlash = !toView.empty() && toView.back() == '/';
while (!source.has_filename() && source.has_relative_path()) source = source.parent_path();
while (!destination.has_filename() && destination.has_relative_path()) destination = destination.parent_path();
std::error_code statusError;
const auto sourceStatus = std::filesystem::symlink_status(source, statusError);
if (oldTrailingSlash) {
if (sourceStatus.type() == std::filesystem::file_type::not_found)
return static_cast<int>(Fail(HasNonDirectoryPrefix(source) ? GuestEnotdir : GuestEnoent));
if (!std::filesystem::is_directory(sourceStatus)) return static_cast<int>(Fail(GuestEnotdir));
}
if (newTrailingSlash && sourceStatus.type() != std::filesystem::file_type::not_found &&
!std::filesystem::is_directory(sourceStatus))
return static_cast<int>(Fail(GuestEnotdir));
const int error = RenamePath(source, destination);
if (error) return static_cast<int>(Fail(error));
RecordWrittenPath_nid_no_patch(source);
RecordWrittenPath_nid_no_patch(destination);
return 0;
}
}

View File

@ -40,6 +40,7 @@ int APS5_VABI openat_nid_postfix(int, const char*, int, ...);
int APS5_VABI unlinkat_nid_postfix(int, const char*, int);
std::int64_t APS5_VABI readlinkat_nid_postfix(int, const char*, char*, std::size_t);
int APS5_VABI mkdirat_nid_postfix(int, const char*, std::uint16_t);
int APS5_VABI renameat_nid_postfix(int, const char*, int, const char*);
std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t);
std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t);
int* APS5_VABI __error_nid_postfix();
@ -356,6 +357,53 @@ int main() {
Require(openat_nid_postfix(-1, "file.txt", SCE_KERNEL_O_RDONLY) == -1 && Error() == 9);
Require(openat_nid_postfix(base, nullptr, SCE_KERNEL_O_RDONLY) == -1 && Error() == 14);
Require(openat_nid_postfix(base, "", SCE_KERNEL_O_RDONLY) == -1 && Error() == 2);
Require(renameat_nid_postfix(base, nullptr, base, "renamed.txt") == -1 && Error() == 14);
Require(renameat_nid_postfix(base, "made.bin", base, nullptr) == -1 && Error() == 14);
Require(renameat_nid_postfix(base, "", base, "renamed.txt") == -1 && Error() == 2);
Require(renameat_nid_postfix(base, "made.bin", base, "") == -1 && Error() == 2);
const int bad = sceKernelOpen(file.c_str(), SCE_KERNEL_O_RDONLY, 0);
Require(bad >= 0);
Require(renameat_nid_postfix(-1, "made.bin", base, "renamed.txt") == -1 && Error() == 9);
Require(renameat_nid_postfix(base, "made.bin", -1, "renamed.txt") == -1 && Error() == 9);
Require(renameat_nid_postfix(bad, "made.bin", base, "renamed.txt") == -1 && Error() == 20);
Require(renameat_nid_postfix(base, "made.bin", bad, "renamed.txt") == -1 && Error() == 20);
Require(sceKernelClose(bad) == 0);
const auto longPart = std::string(256, 'y');
Require(renameat_nid_postfix(base, longPart.c_str(), base, "renamed.txt") == -1 && Error() == 63);
Require(renameat_nid_postfix(base, "made.bin", base, longPart.c_str()) == -1 && Error() == 63);
Require(renameat_nid_postfix(base, ".", base, "renamed.txt") == -1 && Error() == 22);
Require(renameat_nid_postfix(base, "..", base, "renamed.txt") == -1 && Error() == 22);
Require(renameat_nid_postfix(base, "made.bin", base, ".") == -1 && Error() == 22);
Require(renameat_nid_postfix(base, "made.bin", base, "..") == -1 && Error() == 22);
Require(renameat_nid_postfix(base, "missing.bin", base, "renamed.txt") == -1 && Error() == 2);
Require(renameat_nid_postfix(base, "made.bin", base, "renamed.bin") == 0);
Require(!std::filesystem::exists(root / "made.bin"));
Require(std::filesystem::is_regular_file(root / "renamed.bin"));
Require(renameat_nid_postfix(base, "renamed.bin", base, "renamed.bin") == 0);
const auto subPath = root / "sub_dir";
std::filesystem::create_directory(subPath);
Require(renameat_nid_postfix(base, "renamed.bin", base, "sub_dir") == -1 && Error() == 21);
Require(renameat_nid_postfix(base, "sub_dir", base, "renamed.bin") == -1 && Error() == 20);
Require(renameat_nid_postfix(base, "sub_dir", base, "sub_renamed") == 0);
Require(!std::filesystem::exists(subPath));
Require(std::filesystem::is_directory(root / "sub_renamed"));
const int subFd = sceKernelOpen((root / "sub_renamed").string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
Require(subFd >= 0);
Require(renameat_nid_postfix(base, "renamed.bin", subFd, "nested.bin") == 0);
Require(!std::filesystem::exists(root / "renamed.bin"));
Require(std::filesystem::is_regular_file(root / "sub_renamed" / "nested.bin"));
Require(renameat_nid_postfix(base, "sub_renamed", subFd, "child") == -1 && Error() == 22);
const auto otherDir = root / "other_dir";
std::filesystem::create_directory(otherDir);
{ std::ofstream stream(otherDir / "item.txt"); }
Require(renameat_nid_postfix(base, "sub_renamed", base, "other_dir") == -1 && Error() == 66);
Require(renameat_nid_postfix(subFd, "nested.bin/", base, "back.bin") == -1 && Error() == 20);
Require(renameat_nid_postfix(subFd, "nested.bin", base, "nonexistent/") == -1 && Error() == 20);
Require(renameat_nid_postfix(subFd, "nested.bin", base, "made.bin") == 0);
Require(std::filesystem::is_regular_file(root / "made.bin"));
Require(sceKernelClose(subFd) == 0);
Require(renameat_nid_postfix(-100, (root / "made.bin").string().c_str(), -100, (root / "final.bin").string().c_str()) == 0);
Require(std::filesystem::is_regular_file(root / "final.bin"));
Require(sceKernelClose(base) == 0);
std::filesystem::remove_all(root);
return 0;

View File

@ -9,4 +9,5 @@
- [readlink](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows reads a symbolic link's target from its reparse data and returns it with `/` separators, only when the target is relative, as `symlink` creates them. A symbolic link with an absolute target and a junction throw, as a host path has no guest form. Any other reparse point throws too, where FreeBSD reports `EINVAL` for a file that is not a link
- [readlinkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `readlink`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Console behaviour is unverified
- [unlinkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rmdir` (with `AT_REMOVEDIR`, 0x800) or `unlink`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Removing a directory without `AT_REMOVEDIR` fails with `EPERM`, removing `.` or `..` with `AT_REMOVEDIR` fails with `EINVAL`, and flags other than `AT_REMOVEDIR` fail with `EINVAL`. Console behaviour is unverified
- [renameat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rename`. Any other relative path is joined to its descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to relative descriptors, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Console behaviour is unverified
- [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask