mirror of
https://github.com/stenzek/duckstation.git
synced 2026-07-08 17:46:19 +00:00
FileSystem: Make CopyFilePath() atomic
This commit is contained in:
@@ -1830,20 +1830,39 @@ bool FileSystem::RecursiveDeleteDirectory(const char* path, Error* error)
|
|||||||
|
|
||||||
bool FileSystem::CopyFilePath(const char* source, const char* destination, bool replace, Error* error)
|
bool FileSystem::CopyFilePath(const char* source, const char* destination, bool replace, Error* error)
|
||||||
{
|
{
|
||||||
#ifndef _WIN32
|
#ifdef _WIN32
|
||||||
// TODO: There's technically a race here between checking and opening the file..
|
const std::wstring source_wpath = GetWin32Path(source);
|
||||||
// But fopen doesn't specify any way to say "don't create if it exists"...
|
const std::wstring destination_wpath = GetWin32Path(destination);
|
||||||
if (!replace && FileExists(destination))
|
std::string temp_path;
|
||||||
|
auto temp_fp = OpenTemporaryManagedCFile(destination, &temp_path, error);
|
||||||
|
if (!temp_fp)
|
||||||
|
return false;
|
||||||
|
temp_fp.reset();
|
||||||
|
|
||||||
|
const std::wstring temp_wpath = GetWin32Path(temp_path);
|
||||||
|
if (!CopyFileW(source_wpath.c_str(), temp_wpath.c_str(), FALSE))
|
||||||
{
|
{
|
||||||
Error::SetStringView(error, "File already exists.");
|
Error::SetWin32(error, "CopyFileW() failed: ", GetLastError());
|
||||||
|
DeleteFileW(temp_wpath.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DWORD move_flags = replace ? MOVEFILE_REPLACE_EXISTING : 0;
|
||||||
|
if (!MoveFileExW(temp_wpath.c_str(), destination_wpath.c_str(), move_flags))
|
||||||
|
{
|
||||||
|
Error::SetWin32(error, "MoveFileExW() failed: ", GetLastError());
|
||||||
|
DeleteFileW(temp_wpath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
auto in_fp = OpenManagedCFile(source, "rb", error);
|
auto in_fp = OpenManagedCFile(source, "rb", error);
|
||||||
if (!in_fp)
|
if (!in_fp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto out_fp = OpenManagedCFile(destination, "wb", error);
|
std::string temp_path;
|
||||||
|
auto out_fp = OpenTemporaryManagedCFile(destination, &temp_path, error);
|
||||||
if (!out_fp)
|
if (!out_fp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -1856,26 +1875,42 @@ bool FileSystem::CopyFilePath(const char* source, const char* destination, bool
|
|||||||
{
|
{
|
||||||
Error::SetErrno(error, "fread() or fwrite() failed: ", errno);
|
Error::SetErrno(error, "fread() or fwrite() failed: ", errno);
|
||||||
out_fp.reset();
|
out_fp.reset();
|
||||||
DeleteFile(destination);
|
DeleteFile(temp_path.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::fflush(out_fp.get()) != 0)
|
if (std::fclose(out_fp.release()) != 0)
|
||||||
{
|
{
|
||||||
Error::SetErrno(error, "fflush() failed: ", errno);
|
Error::SetErrno(error, "fclose() failed: ", errno);
|
||||||
out_fp.reset();
|
DeleteFile(temp_path.c_str());
|
||||||
DeleteFile(destination);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
if (replace)
|
||||||
#else
|
{
|
||||||
if (CopyFileW(GetWin32Path(source).c_str(), GetWin32Path(destination).c_str(), !replace))
|
if (!RenamePath(temp_path.c_str(), destination, error))
|
||||||
return true;
|
{
|
||||||
|
DeleteFile(temp_path.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// link() atomically publishes the completed temporary file without replacing an existing file.
|
||||||
|
// Removing the temporary name afterwards leaves the destination referring to the copied file.
|
||||||
|
if (link(temp_path.c_str(), destination) != 0)
|
||||||
|
{
|
||||||
|
Error::SetErrno(error, "link() failed: ", errno);
|
||||||
|
DeleteFile(temp_path.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Error::SetWin32(error, "CopyFileW() failed(): ", GetLastError());
|
if (unlink(temp_path.c_str()) != 0)
|
||||||
return false;
|
WARNING_LOG("Failed to remove temporary copy '{}': {}", temp_path, std::strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user