Files
AnyPS5/core/libs/prx/libc/src/RealPath.cpp
T
Jan ŠléglandClaude Fable 5.1 e1f168a24d feat(libc): implement realpath
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
2026-10-11 01:11:33 +02:00

146 lines
4.7 KiB
C++

#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cerrno>
#include <cstddef>
#include <cstring>
#include <filesystem>
#include <new>
#include <string>
#include <string_view>
#include <system_error>
extern "C" char* APS5_VABI getcwd_nid_postfix(char* buffer, std::size_t size);
namespace {
constexpr std::size_t PathMax = 1024;
constexpr std::string_view Separators = "/\\";
constexpr int NoEntry = 2;
constexpr int InputOutput = 5;
constexpr int NoMemory = 12;
constexpr int PermissionDenied = 13;
constexpr int NotDirectory = 20;
constexpr int InvalidArgument = 22;
constexpr int TooManyLinks = 62;
constexpr int NameTooLong = 63;
int StatusError(const std::error_code& error) {
if (error == std::errc::no_such_file_or_directory) return NoEntry;
if (error == std::errc::not_a_directory) return NotDirectory;
if (error == std::errc::permission_denied) return PermissionDenied;
if (error == std::errc::filename_too_long) return NameTooLong;
if (error == std::errc::too_many_symbolic_link_levels) return TooManyLinks;
if (error == std::errc::not_enough_memory) return NoMemory;
return InputOutput;
}
void LeaveTrouble(char* resolved, const std::string& trouble) {
if (resolved == nullptr) return;
const auto length = trouble.size() < PathMax ? trouble.size() : PathMax - 1;
std::memcpy(resolved, trouble.data(), length);
resolved[length] = '\0';
}
void DropLastComponent(std::string& result) {
if (result.size() <= 1) return;
const auto slash = result.rfind('/');
result.erase(slash == 0 ? 1 : slash);
}
bool IsSeparator(char character) { return Separators.find(character) != std::string_view::npos; }
bool ResolveGuest(const char* path, std::string& result, int& error) {
std::string_view left(path);
if (IsSeparator(left.front())) {
result = "/";
left.remove_prefix(1);
} else {
char current[PathMax];
if (getcwd_nid_postfix(current, sizeof(current)) == nullptr) {
result = ".";
error = errno;
return false;
}
result = current;
}
if (left.size() >= PathMax || result.size() >= PathMax) {
error = NameTooLong;
return false;
}
while (!left.empty()) {
const auto separator = left.find_first_of(Separators);
const auto token = left.substr(0, separator);
const bool moreFollows = separator != std::string_view::npos;
left = moreFollows ? left.substr(separator + 1) : std::string_view();
if (result.size() > 1 && result.size() + 1 >= PathMax) {
error = NameTooLong;
return false;
}
if (token.empty() || token == ".") continue;
if (token == "..") {
DropLastComponent(result);
continue;
}
std::string candidate = result;
if (candidate.size() > 1) candidate += '/';
candidate += token;
result = candidate;
if (candidate.size() >= PathMax) {
error = NameTooLong;
return false;
}
std::error_code code;
const auto status = std::filesystem::status(ResolvePath_nid_no_patch(candidate.c_str()), code);
if (code && code != std::errc::no_such_file_or_directory && code != std::errc::not_a_directory) {
error = StatusError(code);
return false;
}
if (!std::filesystem::exists(status)) {
error = NoEntry;
return false;
}
if (moreFollows && !std::filesystem::is_directory(status)) {
error = NotDirectory;
return false;
}
}
return true;
}
}
extern "C" char* APS5_VABI realpath_nid_postfix(const char* path, char* resolved) {
if (path == nullptr) {
errno = InvalidArgument;
return nullptr;
}
if (*path == '\0') {
errno = NoEntry;
return nullptr;
}
std::string result;
int error = 0;
try {
if (!ResolveGuest(path, result, error)) {
LeaveTrouble(resolved, result);
errno = error;
return nullptr;
}
if (resolved == nullptr) {
resolved = static_cast<char*>(ApplicationHeapAllocate_nid_no_patch(PathMax));
if (resolved == nullptr) {
errno = NoMemory;
return nullptr;
}
}
std::memcpy(resolved, result.c_str(), result.size() + 1);
return resolved;
} catch (const std::bad_alloc&) {
errno = NoMemory;
return nullptr;
} catch (const std::filesystem::filesystem_error& failure) {
LeaveTrouble(resolved, result);
errno = StatusError(failure.code());
return nullptr;
}
}