mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
Resolves a guest path the way FreeBSD 11's realpath does: a relative path starts from the guest working directory, "." and empty components are skipped, ".." drops the last component and never climbs above "/", every other component must exist, and one that is not a directory with anything after it is ENOTDIR. Backslashes separate components as in the rest of the path layer. The result is the guest path; the host is consulted only for existence and type, so host symlinks are followed but not expanded, and ".." is lexical, as in the rest of the path layer. A null buffer is PATH_MAX bytes from the application heap, a null path is EINVAL, an empty one ENOENT, and 1024 bytes or more in the input or the result is ENAMETOOLONG. On failure the caller's buffer holds the path that failed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
107 lines
4.6 KiB
C++
107 lines
4.6 KiB
C++
#include "prx/libc/include/ApplicationHeap.hpp"
|
|
#include "prx/libc/include/General.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <array>
|
|
#include <cctype>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <string>
|
|
|
|
extern "C" {
|
|
char* APS5_VABI realpath_nid_postfix(const char*, char*);
|
|
int APS5_VABI chdir_nid_postfix(const char*);
|
|
void APS5_VABI free_nid_postfix(void*);
|
|
int* APS5_VABI __error_nid_postfix();
|
|
}
|
|
|
|
static void Require(bool value) { if (!value) std::abort(); }
|
|
|
|
static bool Resolves(const std::string& path, const std::string& expected) {
|
|
char out[1024];
|
|
std::memset(out, 'x', sizeof(out));
|
|
return realpath_nid_postfix(path.c_str(), out) == out && expected == out;
|
|
}
|
|
|
|
static bool Fails(const char* path, int error, const std::string& trouble = {}) {
|
|
char out[1024];
|
|
std::memset(out, 'x', sizeof(out));
|
|
*__error_nid_postfix() = 0;
|
|
if (realpath_nid_postfix(path, out) != nullptr || *__error_nid_postfix() != error) return false;
|
|
return trouble.empty() || trouble == out;
|
|
}
|
|
|
|
int main() {
|
|
std::array<void*, 10> api{};
|
|
ApplicationHeapRegister_nid_no_patch(api.data());
|
|
const auto host = std::filesystem::canonical(std::filesystem::current_path());
|
|
const auto name = "anyps5-realpath-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
|
|
const auto directory = host / name;
|
|
Require(std::filesystem::create_directories(directory / "sub"));
|
|
{ std::ofstream file(directory / "file.txt"); file << "file"; }
|
|
{ std::ofstream file(directory / "sub" / "inner.txt"); file << "inner"; }
|
|
const std::string guest = "/" + name;
|
|
|
|
Require(Resolves("/", "/"));
|
|
Require(Resolves(guest, guest));
|
|
Require(Resolves(guest + "/", guest));
|
|
Require(Resolves(guest + "/sub/../file.txt", guest + "/file.txt"));
|
|
Require(Resolves(guest + "/./sub//inner.txt", guest + "/sub/inner.txt"));
|
|
Require(Resolves("/../.." + guest + "/sub/", guest + "/sub"));
|
|
Require(Resolves(guest + "/sub/..", guest));
|
|
Require(Resolves("/..", "/"));
|
|
Require(Resolves(guest + "\\sub\\..\\file.txt", guest + "/file.txt"));
|
|
Require(Resolves("\\" + name + "\\sub\\", guest + "/sub"));
|
|
|
|
std::string upper = guest;
|
|
for (auto& character : upper) character = static_cast<char>(std::toupper(static_cast<unsigned char>(character)));
|
|
Require(Resolves(upper + "/file.txt", upper + "/file.txt"));
|
|
|
|
Require(chdir_nid_postfix(guest.c_str()) == 0);
|
|
Require(Resolves("sub/inner.txt", guest + "/sub/inner.txt"));
|
|
Require(Resolves("../" + name + "/file.txt", guest + "/file.txt"));
|
|
Require(Resolves(".", guest));
|
|
Require(Resolves("sub/./../sub", guest + "/sub"));
|
|
Require(Resolves("..", "/"));
|
|
Require(chdir_nid_postfix("/") == 0);
|
|
|
|
AddPathAlias_nid_no_patch("realpath-alias", directory.string().c_str());
|
|
Require(Resolves("/realpath-alias/sub/../file.txt", "/realpath-alias/file.txt"));
|
|
Require(Fails("/realpath-alias/missing", 2, "/realpath-alias/missing"));
|
|
RemovePathAlias_nid_no_patch("realpath-alias");
|
|
BlockPathAlias_nid_no_patch("realpath-blocked");
|
|
Require(Fails("/realpath-blocked/x", 2, "/realpath-blocked"));
|
|
|
|
char* allocated = realpath_nid_postfix((guest + "/file.txt").c_str(), nullptr);
|
|
Require(allocated != nullptr && guest + "/file.txt" == allocated);
|
|
free_nid_postfix(allocated);
|
|
|
|
Require(Fails(nullptr, 22));
|
|
Require(Fails("", 2));
|
|
Require(Fails((guest + "/missing").c_str(), 2, guest + "/missing"));
|
|
Require(Fails((guest + "/missing/../file.txt").c_str(), 2, guest + "/missing"));
|
|
Require(Fails((guest + "/file.txt/").c_str(), 20, guest + "/file.txt"));
|
|
Require(Fails((guest + "/file.txt/x").c_str(), 20, guest + "/file.txt"));
|
|
Require(Fails((guest + "/file.txt/.").c_str(), 20));
|
|
Require(Fails((guest + "/file.txt/..").c_str(), 20));
|
|
|
|
const std::string overflowing = "/" + std::string(1023, 'a');
|
|
Require(Fails(overflowing.c_str(), 63, overflowing.substr(0, 1023)));
|
|
const std::string longInput = guest + "/sub/" + std::string(1024, '.') + "/inner.txt";
|
|
Require(Fails(longInput.c_str(), 63));
|
|
std::string boundary = guest;
|
|
while (boundary.size() + 8 <= 1023) boundary += "/missing";
|
|
boundary += "/" + std::string(1023 - boundary.size() - 1, 'm');
|
|
Require(boundary.size() == 1023);
|
|
Require(Fails(boundary.c_str(), 2, guest + "/missing"));
|
|
|
|
*__error_nid_postfix() = 0;
|
|
Require(realpath_nid_postfix((guest + "/missing").c_str(), nullptr) == nullptr && *__error_nid_postfix() == 2);
|
|
|
|
std::filesystem::remove_all(directory);
|
|
return 0;
|
|
}
|