Merge pull request #4532 from DavidSamir/fix/libc-rename-replace-open-destination-windows
fix(libc): replace an open or read-only destination in rename on Windows
This commit is contained in:
commit
9fa50827dd
|
|
@ -23,6 +23,8 @@ 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);
|
||||
|
||||
inline int ResolvePathError_nid_no_patch(const char* path, std::filesystem::path& resolved) {
|
||||
try {
|
||||
resolved = ResolvePath_nid_no_patch(path);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,42 @@
|
|||
#include <cerrno>
|
||||
#include <filesystem>
|
||||
#include <new>
|
||||
#ifdef _WIN32
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#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) {
|
||||
namespace fs = std::filesystem;
|
||||
std::error_code error;
|
||||
#ifdef _WIN32
|
||||
if (replacesFile) {
|
||||
const auto target = fs::absolute(destination, error);
|
||||
if (error) return error;
|
||||
struct RenameInfo { DWORD flags; HANDLE root; DWORD nameBytes; WCHAR name[1]; };
|
||||
static_assert(sizeof(RenameInfo) == sizeof(FILE_RENAME_INFO) && offsetof(RenameInfo, name) == offsetof(FILE_RENAME_INFO, FileName));
|
||||
if (target.native().size() > (MAXDWORD - sizeof(RenameInfo)) / sizeof(wchar_t)) return std::make_error_code(std::errc::filename_too_long);
|
||||
const auto nameBytes = target.native().size() * sizeof(wchar_t);
|
||||
std::vector<std::byte> buffer(sizeof(RenameInfo) + nameBytes);
|
||||
auto* info = reinterpret_cast<RenameInfo*>(buffer.data());
|
||||
constexpr DWORD replaceExisting = 0x00000001, posixSemantics = 0x00000002, ignoreReadOnly = 0x00000040;
|
||||
const auto attributes = GetFileAttributesW(destination.c_str());
|
||||
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);
|
||||
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());
|
||||
CloseHandle(handle);
|
||||
return error;
|
||||
}
|
||||
#endif
|
||||
fs::rename(source, destination, error);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int RenamePath_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination) {
|
||||
constexpr int GuestEnoent = 2;
|
||||
|
|
@ -61,8 +97,7 @@ static int RenamePath_nid_no_patch(const std::filesystem::path& source, const st
|
|||
}
|
||||
#endif
|
||||
|
||||
error.clear();
|
||||
fs::rename(source, destination, error);
|
||||
error = RenameNative_nid_no_patch(source, destination, destinationExists && !sourceIsDirectory && !destinationIsDirectory);
|
||||
return error ? FilesystemError(error) : 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1119,10 +1119,16 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) {
|
|||
std::filesystem::path source;
|
||||
if (const int error = ResolvePathError_nid_no_patch(from, source)) return SceKernelError(error);
|
||||
std::error_code error;
|
||||
if (!std::filesystem::exists(source, error)) return SceErrorFromErrno(GUEST_ENOENT);
|
||||
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);
|
||||
std::filesystem::rename(source, destination, error);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -384,6 +384,23 @@ 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"));
|
||||
for (const bool kernel : {false, true}) {
|
||||
const auto incoming = name(area / "incoming.txt");
|
||||
const auto occupied = name(area / "occupied.txt");
|
||||
const auto replace = [&] { return kernel ? sceKernelRename(incoming.c_str(), occupied.c_str()) : rename_nid_postfix(incoming.c_str(), occupied.c_str()); };
|
||||
const auto contents = [&] { std::ifstream stream(occupied); std::string text; std::getline(stream, text); return text; };
|
||||
{ std::ofstream stream(incoming); stream << "incoming"; }
|
||||
{ std::ofstream stream(occupied); stream << "occupied"; }
|
||||
const int occupiedDescriptor = sceKernelOpen(occupied.c_str(), 2, 0);
|
||||
Require(occupiedDescriptor >= 0 && replace() == 0 && !std::filesystem::exists(incoming) && contents() == "incoming");
|
||||
char occupiedBytes[8]{};
|
||||
Require(read_nid_postfix(occupiedDescriptor, occupiedBytes, sizeof(occupiedBytes)) == sizeof(occupiedBytes) && std::memcmp(occupiedBytes, "occupied", 8) == 0);
|
||||
Require(sceKernelClose(occupiedDescriptor) == 0 && contents() == "incoming");
|
||||
{ std::ofstream stream(incoming); stream << "replaced"; }
|
||||
Require(sceKernelChmod_nid_postfix(occupied.c_str(), 0400) == 0);
|
||||
Require(replace() == 0 && !std::filesystem::exists(incoming) && contents() == "replaced");
|
||||
Require(remove_nid_postfix(occupied.c_str()) == 0);
|
||||
}
|
||||
#ifndef _WIN32
|
||||
const auto dangling = area / "dangling";
|
||||
std::filesystem::create_symlink(area / "nonexistent", dangling);
|
||||
|
|
|
|||
Loading…
Reference in New Issue