fix(libc): resolve the rename source before the destination argument

FreeBSD 11.4 kern_renameat looks up the source first (namei on fromnd),
so a missing or dot source fails with ENOENT or EINVAL before the new
name is touched. The guest call rejected a NULL, empty or dot destination
first, so rename(missing, NULL) returned EFAULT and rename(missing, ".")
returned EINVAL where FreeBSD returns ENOENT.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
xXPBSXxandClaude Sonnet 5.5 committed 2026-10-20 12:51:02 +02:00
1 parent 7602dde042
commit 308848e1a6
3 files changed
+25 -4

No files matched your search

+16 -3
View File
@@ -94,13 +94,26 @@ static int RenamePath_nid_no_patch(const std::filesystem::path& source, const st
return error ? FilesystemError(error) : 0;
}
static int SourceLookupError_nid_no_patch(const std::filesystem::path& source) {
std::error_code status;
const auto sourceStatus = std::filesystem::symlink_status(source, status);
if (HasNonDirectoryPrefix_nid_no_patch(source)) return 20;
if (status && status != std::errc::no_such_file_or_directory) return FilesystemError(status);
return sourceStatus.type() == std::filesystem::file_type::not_found ? 2 : 0;
}
extern "C" int RenameGuestPath_nid_no_patch(const char* from, const char* to) {
if (!from || !to) { errno = 14; return -1; }
if (!*from || !*to) { errno = 2; return -1; }
if (!from) { errno = 14; return -1; }
if (!*from) { errno = 2; return -1; }
if (const int dot = RenameDotError_nid_no_patch(from)) { errno = dot; return -1; }
if (const int dot = RenameDotError_nid_no_patch(to)) { errno = dot; return -1; }
const int destinationError = !to ? 14 : !*to ? 2 : RenameDotError_nid_no_patch(to);
try {
const auto source = ResolvePath_nid_no_patch(from);
if (destinationError) {
const int sourceError = SourceLookupError_nid_no_patch(source);
errno = sourceError ? sourceError : destinationError;
return -1;
}
const auto destination = ResolvePath_nid_no_patch(to);
int error = RenamePath_nid_no_patch(source, destination);
if (error == 2) {
+1 -1
View File
@@ -1379,7 +1379,7 @@ int64_t APS5_VABI pwritev_nid_postfix(int d, const KernelIovec* iov, int iovcnt,
}
int APS5_VABI sceKernelRename(const char* from, const char* to) {
if (!from || !to) return SceErrorFromErrno(GUEST_EFAULT);
if (!from) return SceErrorFromErrno(GUEST_EFAULT);
const int result = RenameGuestPath_nid_no_patch(from, to);
return result == 0 ? 0 : SceKernelError(errno);
if (const int dot = RenameDotError_nid_no_patch(from)) return SceKernelError(dot);
+8
View File
@@ -856,6 +856,14 @@ int main() {
Require(sceKernelCheckReachability("") == static_cast<int>(0x80020002u));
Require(sceKernelRename("", presentName.c_str()) == static_cast<int>(0x80020002u));
Require(sceKernelRename(presentName.c_str(), "") == static_cast<int>(0x80020002u));
Require(sceKernelRename(missingName.c_str(), nullptr) == static_cast<int>(0x80020002u));
Require(sceKernelRename("", nullptr) == static_cast<int>(0x80020002u));
Require(sceKernelRename(".", nullptr) == static_cast<int>(0x80020016u));
Require(sceKernelRename(missingName.c_str(), ".") == static_cast<int>(0x80020002u));
Require(sceKernelRename(presentName.c_str(), nullptr) == static_cast<int>(0x8002000Eu));
Require(sceKernelRename(nullptr, presentName.c_str()) == static_cast<int>(0x8002000Eu));
Require(rename_nid_postfix(missingName.c_str(), nullptr) == -1 && *__error_nid_postfix() == 2);
Require(rename_nid_postfix(presentName.c_str(), nullptr) == -1 && *__error_nid_postfix() == 14);
Require(sceKernelRmdir("") == static_cast<int>(0x80020002u));
Require(sceKernelMkdir("", 0700) == static_cast<int>(0x80020002u));
const auto cwdPermissions = std::filesystem::status(".").permissions();