Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/libs/prx/libc/include/FilesystemError.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ inline int FilesystemError(const std::error_code& error) {
return 5;
}

std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination, bool replacesFile);
std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination, bool replacesExisting);

inline int ResolvePathError_nid_no_patch(const char* path, std::filesystem::path& resolved, bool module = false, bool archived = false) {
try {
Expand Down
16 changes: 4 additions & 12 deletions core/libs/prx/libc/src/Rename.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
#include <windows.h>
#endif

std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination, [[maybe_unused]] bool replacesFile) {
std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination, [[maybe_unused]] bool replacesExisting) {
namespace fs = std::filesystem;
std::error_code error;
#ifdef _WIN32
if (replacesFile) {
if (replacesExisting) {
const auto target = fs::absolute(destination, error);
if (error) return error;
struct RenameInfo { DWORD flags; HANDLE root; DWORD nameBytes; WCHAR name[1]; };
Expand All @@ -28,7 +28,7 @@ std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, c
info->flags = replaceExisting | posixSemantics | (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_READONLY) ? ignoreReadOnly : 0);
info->nameBytes = static_cast<DWORD>(nameBytes);
std::memcpy(info->name, target.c_str(), nameBytes);
const auto handle = CreateFileW(source.c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
const auto handle = CreateFileW(source.c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (handle == INVALID_HANDLE_VALUE) return std::error_code(static_cast<int>(GetLastError()), std::system_category());
constexpr auto renameInfoEx = static_cast<FILE_INFO_BY_HANDLE_CLASS>(22);
if (!SetFileInformationByHandle(handle, renameInfoEx, info, static_cast<DWORD>(buffer.size()))) error = std::error_code(static_cast<int>(GetLastError()), std::system_category());
Expand All @@ -42,7 +42,6 @@ std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, c

static int RenamePath_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination) {
constexpr int GuestEnoent = 2;
constexpr int GuestEio = 5;
constexpr int GuestEnotdir = 20;
constexpr int GuestEisdir = 21;
constexpr int GuestEinval = 22;
Expand Down Expand Up @@ -90,14 +89,7 @@ static int RenamePath_nid_no_patch(const std::filesystem::path& source, const st
return error ? FilesystemError(error) : GuestEnotempty;
}
}
#ifdef _WIN32
if (!sameFile && sourceIsDirectory && destinationIsDirectory) {
error.clear();
if (!fs::remove(destination, error)) return error ? FilesystemError(error) : GuestEio;
}
#endif

error = RenameNative_nid_no_patch(source, destination, destinationExists && !sourceIsDirectory && !destinationIsDirectory);
error = RenameNative_nid_no_patch(source, destination, destinationExists && (!sourceIsDirectory || !sameFile));
return error ? FilesystemError(error) : 0;
}

Expand Down
20 changes: 2 additions & 18 deletions core/libs/prx/libkernel/File/src/Stdio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1271,24 +1271,8 @@ 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 == '\0' || *to == '\0') return SceErrorFromErrno(GUEST_ENOENT);
std::filesystem::path source;
if (const int error = ResolvePathError_nid_no_patch(from, source)) return SceKernelError(error);
std::error_code error;
const auto sourceStatus = std::filesystem::status(source, error);
if (!std::filesystem::exists(sourceStatus)) return SceErrorFromErrno(GUEST_ENOENT);
std::filesystem::path destination;
if (const int result = ResolvePathError_nid_no_patch(to, destination)) return SceKernelError(result);
bool replacesFile = false;
#ifdef _WIN32
const auto destinationStatus = std::filesystem::symlink_status(destination, error);
replacesFile = !std::filesystem::is_directory(sourceStatus) && std::filesystem::exists(destinationStatus) && !std::filesystem::is_directory(destinationStatus);
#endif
error = RenameNative_nid_no_patch(source, destination, replacesFile);
if (error) return SceErrorFromErrno(GUEST_EIO);
RecordWrittenPath_nid_no_patch(source);
RecordWrittenPath_nid_no_patch(destination);
return 0;
const int result = RenameGuestPath_nid_no_patch(from, to);
return result == 0 ? 0 : SceKernelError(errno);
}

int APS5_VABI sceKernelRmdir(const char* path) {
Expand Down
57 changes: 57 additions & 0 deletions core/libs/tests/GuestFilesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,63 @@ int main() {
Require(rename_nid_postfix(name(targetFile).c_str(), name(targetFile).c_str()) == 0);
Require(rename_nid_postfix(name(sourceDirectory).c_str(), name(emptyDirectory).c_str()) == 0);
Require(!std::filesystem::exists(sourceDirectory) && std::filesystem::is_directory(emptyDirectory / "child"));
const auto kernelSource = area / "kernel_source";
const auto kernelTarget = area / "kernel_target";
Require(std::filesystem::create_directories(kernelSource / "child"));
Require(std::filesystem::create_directory(kernelTarget));
{ std::ofstream stream(kernelSource / "marker"); stream << "kernel directory contents"; }
const int directoryResult = sceKernelRename(name(kernelSource).c_str(), name(kernelTarget).c_str());
if (directoryResult != 0)
std::fprintf(stderr, "kernel empty-directory replacement result=0x%08x, expected=0\n", static_cast<unsigned>(directoryResult));
Require(directoryResult == 0);
Require(!std::filesystem::exists(kernelSource) && std::filesystem::is_directory(kernelTarget / "child"));
{ std::ifstream stream(kernelTarget / "marker"); std::string contents; std::getline(stream, contents);
Require(contents == "kernel directory contents"); }
const int parentResult = sceKernelRename(name(targetFile).c_str(), absentParent.c_str());
Require(parentResult == static_cast<int>(0x80020002u));
Require(std::filesystem::is_regular_file(targetFile) && !std::filesystem::exists(area / "absent"));
const int prefixResult = sceKernelRename(name(targetFile).c_str(), name(targetFile / "target").c_str());
Require(prefixResult == static_cast<int>(0x80020014u));
Require(std::filesystem::is_regular_file(targetFile));
const int fileDirectoryResult = sceKernelRename(name(targetFile).c_str(), name(fullDirectory).c_str());
Require(fileDirectoryResult == static_cast<int>(0x80020015u));
Require(std::filesystem::is_regular_file(targetFile) && std::filesystem::is_regular_file(fullDirectory / "entry"));
const int directoryFileResult = sceKernelRename(name(kernelTarget).c_str(), name(targetFile).c_str());
Require(directoryFileResult == static_cast<int>(0x80020014u));
Require(std::filesystem::is_regular_file(kernelTarget / "marker") && std::filesystem::is_regular_file(targetFile));
const int nonemptyResult = sceKernelRename(name(kernelTarget).c_str(), name(fullDirectory).c_str());
Require(nonemptyResult == static_cast<int>(0x80020042u));
Require(std::filesystem::is_regular_file(kernelTarget / "marker") && std::filesystem::is_regular_file(fullDirectory / "entry"));
const auto descendant = kernelTarget / "child" / "moved";
const int descendantResult = sceKernelRename(name(kernelTarget).c_str(), name(descendant).c_str());
Require(descendantResult == static_cast<int>(0x80020016u));
Require(std::filesystem::is_directory(kernelTarget / "child") && !std::filesystem::exists(descendant));
{ std::ifstream stream(targetFile); std::string contents; std::getline(stream, contents); Require(contents == "source"); }
{ std::ifstream stream(fullDirectory / "entry"); std::string contents; std::getline(stream, contents); Require(contents == "entry"); }
{ std::ifstream stream(kernelTarget / "marker"); std::string contents; std::getline(stream, contents);
Require(contents == "kernel directory contents"); }
#ifdef _WIN32
for (const bool kernel : {true, false}) {
const auto lockedSource = area / (kernel ? "locked_kernel_source" : "locked_libc_source");
const auto preservedTarget = area / (kernel ? "preserved_kernel_target" : "preserved_libc_target");
Require(std::filesystem::create_directory(lockedSource));
Require(std::filesystem::create_directory(preservedTarget));
const auto marker = lockedSource / "marker";
{ std::ofstream stream(marker); stream << "preserved source contents"; }
const HANDLE held = CreateFileW(marker.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
Require(held != INVALID_HANDLE_VALUE);
const int result = kernel ? sceKernelRename(name(lockedSource).c_str(), name(preservedTarget).c_str()) :
rename_nid_postfix(name(lockedSource).c_str(), name(preservedTarget).c_str());
Require(CloseHandle(held) != 0);
Require(result != 0 && std::filesystem::is_regular_file(marker));
if (!std::filesystem::is_directory(preservedTarget))
std::fprintf(stderr, "failed %s directory rename removed its destination\n", kernel ? "kernel" : "libc");
Require(std::filesystem::is_directory(preservedTarget) && std::filesystem::is_empty(preservedTarget));
{ std::ifstream stream(marker); std::string contents; std::getline(stream, contents);
Require(contents == "preserved source contents"); }
}
#endif
for (const bool kernel : {false, true}) {
const auto incoming = name(area / "incoming.txt");
const auto occupied = name(area / "occupied.txt");
Expand Down
16 changes: 13 additions & 3 deletions tools/tests/test_build_eligibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
WORKFLOW = Path(__file__).resolve().parents[2] / ".github/workflows/build.yml"

HARNESS = r"""
console.error("eligibility: harness-start", process.pid, process.version);
const fs = require('fs');
const scenario = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
const script = fs.readFileSync(process.argv[3], 'utf8');
console.error("eligibility: inputs-read", process.pid);
const calls = [];
const outputs = {};
const warnings = [];
Expand Down Expand Up @@ -56,7 +58,10 @@
};
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
new AsyncFunction('github', 'context', 'core', script)(github, context, core).then(
() => process.stdout.write(JSON.stringify({ outputs, warnings, calls, summary })),
() => {
console.error("eligibility: gate-complete", process.pid);
process.stdout.write(JSON.stringify({ outputs, warnings, calls, summary }));
},
(error) => { console.error(error); process.exit(1); }
);
"""
Expand Down Expand Up @@ -93,8 +98,13 @@ def run_gate(self, commits, search, files=("core/relinker/main.cpp",), manual=Fa
scenario.write_text(json.dumps({"commits": commits, "search": search, "files": list(files), "manual": manual}),
encoding="utf-8")
environment = dict(os.environ, PR_NUMBER="7", GITHUB_TRIGGERING_ACTOR="maintainer")
result = subprocess.run([self.node, "harness.js", "scenario.json", "gate.js"], cwd=self.root,
capture_output=True, text=True, env=environment, timeout=90)
try:
result = subprocess.run([self.node, "harness.js", "scenario.json", "gate.js"], cwd=self.root,
capture_output=True, text=True, env=environment, timeout=90)
except subprocess.TimeoutExpired as error:
stdout = error.stdout.decode(errors="replace") if isinstance(error.stdout, bytes) else error.stdout or ""
stderr = error.stderr.decode(errors="replace") if isinstance(error.stderr, bytes) else error.stderr or ""
self.fail(f"Node harness timed out after {error.timeout}s: {self.node}\nstdout:\n{stdout}\nstderr:\n{stderr}")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
return json.loads(result.stdout)

Expand Down
Loading