mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 16:43:05 -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
146 lines
4.7 KiB
C++
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;
|
|
}
|
|
}
|