feat(libkernel_sys): implement renameat
This commit is contained in:
parent
db3d379c87
commit
2a8e745267
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue