Files
AnyPS5/core/libs/tests/GuestRenameDot.cpp
T
xXPBSXxandClaude Sonnet 5.5 9899ba0fa2 fix(libc): fail a rename of a path that ends in a dot component with EINVAL
rename and sceKernelRename resolved "dir/." to "dir" and renamed the directory. FreeBSD 11.4's lookup fails a last component of "." for DELETE and RENAME with EINVAL, for the source and the destination alike. Check the guest string before it is resolved: a last component of "." (also under trailing slashes) fails with EINVAL when its parent exists, ENOENT when it does not and ENOTDIR when it is not a directory.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 02:57:14 +02:00

81 lines
4.4 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <string>
extern "C" {
int APS5_VABI mkdir_nid_postfix(const char*, int);
int APS5_VABI rmdir_nid_postfix(const char*);
int APS5_VABI rename_nid_postfix(const char*, const char*);
int APS5_VABI sceKernelRename(const char*, const char*);
int APS5_VABI stat_nid_postfix(const char*, void*);
int* APS5_VABI __error_nid_postfix();
}
namespace {
constexpr int ErrorNoEntry = 2;
constexpr int ErrorInvalid = 22;
constexpr char Source[] = "anyps5_rename_dot_source";
constexpr char Target[] = "anyps5_rename_dot_target";
constexpr char Moved[] = "anyps5_rename_dot_moved";
void Cleanup() {
rmdir_nid_postfix(Source);
rmdir_nid_postfix(Target);
rmdir_nid_postfix(Moved);
}
void Require(bool condition, const char* what) {
if (condition) return;
std::fprintf(stderr, "%s (errno %d)\n", what, *__error_nid_postfix());
Cleanup();
std::abort();
}
bool Fails(long result, int error) { return result == -1 && *__error_nid_postfix() == error; }
bool Exists(const char* path) {
unsigned char status[256];
return stat_nid_postfix(path, status) == 0;
}
}
int main() {
Cleanup();
Require(mkdir_nid_postfix(Source, 0755) == 0 && mkdir_nid_postfix(Target, 0755) == 0, "mkdir failed");
const std::string dotted = std::string(Source) + "/.";
const std::string dottedSlash = std::string(Source) + "/./";
const std::string intoTarget = std::string(Target) + "/.";
Require(Fails(rename_nid_postfix(dotted.c_str(), Moved), ErrorInvalid), "rename from dir/. must fail with EINVAL (22)");
Require(Fails(rename_nid_postfix(dottedSlash.c_str(), Moved), ErrorInvalid), "rename from dir/./ must fail with EINVAL (22)");
Require(Exists(Source) && !Exists(Moved), "a failed rename from dir/. must leave the directory in place");
Require(Fails(rename_nid_postfix(Source, intoTarget.c_str()), ErrorInvalid), "rename to dir/. must fail with EINVAL (22)");
Require(Fails(rename_nid_postfix(".", Moved), ErrorInvalid), "rename of . must fail with EINVAL (22)");
Require(Fails(rename_nid_postfix(Source, "."), ErrorInvalid), "rename onto . must fail with EINVAL (22)");
Require(Fails(rename_nid_postfix("anyps5_rename_dot_missing/.", Moved), ErrorNoEntry), "rename from missing/. must fail with ENOENT (2)");
const std::string dotDot = std::string(Source) + "/..";
const std::string dotDotSlash = std::string(Source) + "/../";
Require(Fails(rename_nid_postfix(dotDot.c_str(), Moved), ErrorInvalid), "rename from dir/.. must fail with EINVAL (22)");
Require(Fails(rename_nid_postfix(dotDotSlash.c_str(), Moved), ErrorInvalid), "rename from dir/../ must fail with EINVAL (22)");
Require(Fails(rename_nid_postfix(Source, (std::string(Target) + "/..").c_str()), ErrorInvalid), "rename to dir/.. must fail with EINVAL (22)");
Require(Fails(rename_nid_postfix("..", Moved), ErrorInvalid), "rename of .. must fail with EINVAL (22)");
Require(Fails(rename_nid_postfix("anyps5_rename_dot_missing/..", Moved), ErrorNoEntry), "rename from missing/.. must fail with ENOENT (2)");
const std::string backslashDot = std::string(Source) + "\\.";
const std::string backslashDotDot = std::string(Source) + "\\..";
Require(Fails(rename_nid_postfix(backslashDot.c_str(), Moved), ErrorInvalid), "rename from dir\\. must fail with EINVAL (22), the guest resolver turns the backslash into a separator");
Require(Fails(rename_nid_postfix(backslashDotDot.c_str(), Moved), ErrorInvalid), "rename from dir\\.. must fail with EINVAL (22)");
Require(Exists(Source) && !Exists(Moved), "failed renames from dir\\. and dir\\.. must leave the directory in place");
Require(sceKernelRename(dotDot.c_str(), Moved) == static_cast<int>(0x80020016u), "sceKernelRename from dir/.. must return EINVAL");
Require(sceKernelRename(dotted.c_str(), Moved) == static_cast<int>(0x80020016u), "sceKernelRename from dir/. must return EINVAL");
Require(sceKernelRename(Source, intoTarget.c_str()) == static_cast<int>(0x80020016u), "sceKernelRename to dir/. must return EINVAL");
Require(Exists(Source) && Exists(Target) && !Exists(Moved), "failed renames must change nothing");
Require(rename_nid_postfix(Source, Moved) == 0 && Exists(Moved) && !Exists(Source), "an ordinary rename must still work");
Require(sceKernelRename(Moved, Source) == 0 && Exists(Source) && !Exists(Moved), "an ordinary sceKernelRename must still work");
Cleanup();
return 0;
}