Files
pilotclient/src/misc/atomicfile.cpp

182 lines
5.6 KiB
C++

// SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
#include "misc/atomicfile.h"
#include <type_traits>
#include <QDir>
#include <QFileInfo>
#include <QFlags>
#include <QStringBuilder>
#include "misc/algorithm.h"
#ifdef Q_OS_POSIX
# include <errno.h>
# include <stdio.h>
#elif defined(Q_OS_WIN32)
# include <Windows.h>
# include <io.h>
# include <QNtfsPermissionCheckGuard>
#endif
namespace swift::misc
{
//! \private
bool checkPermissions(CAtomicFile::OpenMode mode, const QFileInfo &fileInfo)
{
bool ok = true;
{
#ifdef Q_OS_WIN32
QNtfsPermissionCheckGuard permissionGuard;
#endif
if ((mode & CAtomicFile::ReadOnly) && !fileInfo.isReadable()) { ok = false; }
if ((mode & CAtomicFile::WriteOnly) && !fileInfo.isWritable()) { ok = false; }
}
return ok;
}
bool CAtomicFile::open(CAtomicFile::OpenMode mode)
{
m_originalFilename = fileName();
QFileInfo fileInfo(fileName());
if (exists() && !checkPermissions(mode, fileInfo))
{
m_permissionError = true;
setErrorString("Wrong permissions");
return false;
}
setFileName(QFileInfo(fileInfo.dir(), ".tmp." + fileInfo.fileName() + "." + randomSuffix()).filePath());
if (exists()) { remove(); }
bool ok = true;
if (mode & ReadOnly)
{
if (exists(m_originalFilename) && !copy(m_originalFilename, fileName())) { ok = false; }
}
if (ok && !QFile::open(mode)) { ok = false; }
if (!ok) { setFileName(m_originalFilename); }
return ok;
}
CAtomicFile::~CAtomicFile()
{
if (std::uncaught_exceptions() > 0) { QFile::close(); }
}
void CAtomicFile::close()
{
if (!isOpen()) { return; }
#ifdef Q_OS_WIN32
FlushFileBuffers(reinterpret_cast<HANDLE>(_get_osfhandle(handle())));
#endif
QFile::close();
if (error() == NoError) { replaceOriginal(); }
setFileName(m_originalFilename);
}
bool CAtomicFile::checkedClose()
{
close();
return error() == NoError;
}
void CAtomicFile::abandon()
{
if (!isOpen()) { return; }
QFile::close();
remove();
setFileName(m_originalFilename);
}
CAtomicFile::FileError CAtomicFile::error() const
{
if (m_renameError) { return RenameError; }
if (m_permissionError) { return PermissionsError; }
return QFile::error();
}
void CAtomicFile::unsetError()
{
m_renameError = false;
m_permissionError = false;
QFile::unsetError();
}
QString CAtomicFile::randomSuffix()
{
constexpr auto max = 2176782335;
return QStringLiteral("%1").arg(
std::uniform_int_distribution<std::decay_t<decltype(max)>>(0, max)(private_ns::defaultRandomGenerator()), 6,
36, QChar('0'));
}
#ifdef Q_OS_POSIX
void CAtomicFile::replaceOriginal()
{
auto result = ::rename(qPrintable(fileName()), qPrintable(m_originalFilename));
if (result < 0)
{
m_renameError = true;
char s[1024] {};
auto x = strerror_r(errno, s, sizeof(s));
setErrorString(QString::fromLocal8Bit(s));
static_assert(std::is_same_v<decltype(x), int>,
"Non-standard signature of POSIX function strerror_r, check documentation.");
}
}
#elif defined(Q_OS_WIN32)
void CAtomicFile::replaceOriginal()
{
auto encode = [](const QString &s) {
const auto prefix =
"\\\\?\\"; // support long paths:
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#maxpath
return (prefix + QDir::toNativeSeparators(QDir::cleanPath(QFileInfo(s).absoluteFilePath()))).toStdWString();
};
auto replace = exists(m_originalFilename);
auto result = replace ? ReplaceFile(encode(m_originalFilename).c_str(), encode(fileName()).c_str(), nullptr,
REPLACEFILE_IGNORE_MERGE_ERRORS, nullptr, nullptr) :
MoveFileEx(encode(fileName()).c_str(), encode(m_originalFilename).c_str(),
MOVEFILE_WRITE_THROUGH);
if (!result)
{
wchar_t *s = nullptr;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0,
reinterpret_cast<LPWSTR>(&s), 0, nullptr);
const QString windowsError =
(replace ? u"ReplaceFile: " : u"MoveFileEx: ") % QString::fromWCharArray(s).simplified();
LocalFree(reinterpret_cast<HLOCAL>(s));
// fall back to non-atomic remove-and-rename
if (exists(m_originalFilename))
{
QFile old(m_originalFilename);
if (!old.remove())
{
// fall back failed, so report the reasons for the original failure AND the fall back failure
m_renameError = true;
setErrorString(windowsError % u" QFile::remove: " % old.errorString());
return;
}
}
rename(m_originalFilename);
}
}
#else
void CAtomicFile::replaceOriginal()
{
if (exists(m_originalFilename)) { remove(m_originalFilename); }
rename(m_originalFilename);
}
#endif
} // namespace swift::misc