forked from qt-creator/qt-creator
Fix saving of hardlinked files
Our atomic write involves writing a temp file and renaming that (which is the only way to achieve something atomic). This creates a new inode, and disconnects any hardlinks. Note that the existing implementation for file paths with needsDevice already keeps hardlinks intact, because even though it first writes into a local temporary file it then writes the content directly into the target with dd. Check the number of hard links via system API and fallback to unsafe writing if there are any, for desktop paths. Fixes: QTCREATORBUG-19651 Change-Id: I3ce1ee81f339f241f0a2c9aa6f2259cb118ebef6 Reviewed-by: Christian Kandeler <christian.kandeler@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -106,6 +106,13 @@ bool DeviceFileAccess::isSymLink(const FilePath &filePath) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeviceFileAccess::hasHardLinks(const FilePath &filePath) const
|
||||
{
|
||||
Q_UNUSED(filePath)
|
||||
QTC_CHECK(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeviceFileAccess::ensureWritableDirectory(const FilePath &filePath) const
|
||||
{
|
||||
if (isWritableDirectory(filePath))
|
||||
@@ -475,6 +482,21 @@ bool DesktopDeviceFileAccess::isSymLink(const FilePath &filePath) const
|
||||
return fi.isSymLink();
|
||||
}
|
||||
|
||||
bool DesktopDeviceFileAccess::hasHardLinks(const FilePath &filePath) const
|
||||
{
|
||||
#ifdef Q_OS_UNIX
|
||||
struct stat s
|
||||
{};
|
||||
const int r = stat(filePath.absoluteFilePath().toString().toLocal8Bit().constData(), &s);
|
||||
if (r == 0) {
|
||||
// check for hardlinks because these would break without the atomic write implementation
|
||||
if (s.st_nlink > 1)
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DesktopDeviceFileAccess::ensureWritableDirectory(const FilePath &filePath) const
|
||||
{
|
||||
const QFileInfo fi(filePath.path());
|
||||
@@ -849,6 +871,13 @@ bool UnixDeviceFileAccess::isSymLink(const FilePath &filePath) const
|
||||
return runInShellSuccess({"test", {"-h", path}, OsType::OsTypeLinux});
|
||||
}
|
||||
|
||||
bool UnixDeviceFileAccess::hasHardLinks(const FilePath &filePath) const
|
||||
{
|
||||
const QStringList args = statArgs(filePath, "%h", "%l");
|
||||
const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux});
|
||||
return result.stdOut.toLongLong() > 1;
|
||||
}
|
||||
|
||||
bool UnixDeviceFileAccess::ensureExistingFile(const FilePath &filePath) const
|
||||
{
|
||||
const QString path = filePath.path();
|
||||
|
||||
@@ -34,6 +34,7 @@ protected:
|
||||
virtual bool isFile(const FilePath &filePath) const;
|
||||
virtual bool isDirectory(const FilePath &filePath) const;
|
||||
virtual bool isSymLink(const FilePath &filePath) const;
|
||||
virtual bool hasHardLinks(const FilePath &filePath) const;
|
||||
virtual bool ensureWritableDirectory(const FilePath &filePath) const;
|
||||
virtual bool ensureExistingFile(const FilePath &filePath) const;
|
||||
virtual bool createDirectory(const FilePath &filePath) const;
|
||||
@@ -90,6 +91,7 @@ protected:
|
||||
bool isFile(const FilePath &filePath) const override;
|
||||
bool isDirectory(const FilePath &filePath) const override;
|
||||
bool isSymLink(const FilePath &filePath) const override;
|
||||
bool hasHardLinks(const FilePath &filePath) const override;
|
||||
bool ensureWritableDirectory(const FilePath &filePath) const override;
|
||||
bool ensureExistingFile(const FilePath &filePath) const override;
|
||||
bool createDirectory(const FilePath &filePath) const override;
|
||||
@@ -148,6 +150,7 @@ protected:
|
||||
bool isFile(const FilePath &filePath) const override;
|
||||
bool isDirectory(const FilePath &filePath) const override;
|
||||
bool isSymLink(const FilePath &filePath) const override;
|
||||
bool hasHardLinks(const FilePath &filePath) const override;
|
||||
bool ensureExistingFile(const FilePath &filePath) const override;
|
||||
bool createDirectory(const FilePath &filePath) const override;
|
||||
bool exists(const FilePath &filePath) const override;
|
||||
|
||||
@@ -564,6 +564,11 @@ bool FilePath::isSymLink() const
|
||||
return fileAccess()->isSymLink(*this);
|
||||
}
|
||||
|
||||
bool FilePath::hasHardLinks() const
|
||||
{
|
||||
return fileAccess()->hasHardLinks(*this);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Creates a directory in this location.
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ public:
|
||||
bool isFile() const;
|
||||
bool isDir() const;
|
||||
bool isSymLink() const;
|
||||
bool hasHardLinks() const;
|
||||
bool isRootPath() const;
|
||||
bool isNewerThan(const QDateTime &timeStamp) const;
|
||||
QDateTime lastModified() const;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "savefile.h"
|
||||
|
||||
#include "algorithm.h"
|
||||
#include "devicefileaccess.h"
|
||||
#include "qtcassert.h"
|
||||
#include "utilstr.h"
|
||||
|
||||
@@ -189,12 +190,13 @@ FileSaver::FileSaver(const FilePath &filePath, QIODevice::OpenMode mode)
|
||||
auto tf = new QTemporaryFile(QDir::tempPath() + "/remotefilesaver-XXXXXX");
|
||||
tf->setAutoRemove(false);
|
||||
m_file.reset(tf);
|
||||
} else if (mode & (QIODevice::ReadOnly | QIODevice::Append)) {
|
||||
m_file.reset(new QFile{filePath.path()});
|
||||
m_isSafe = false;
|
||||
} else {
|
||||
const bool readOnlyOrAppend = mode & (QIODevice::ReadOnly | QIODevice::Append);
|
||||
m_isSafe = !readOnlyOrAppend && !filePath.hasHardLinks();
|
||||
if (m_isSafe)
|
||||
m_file.reset(new SaveFile(filePath));
|
||||
m_isSafe = true;
|
||||
else
|
||||
m_file.reset(new QFile{filePath.path()});
|
||||
}
|
||||
if (!m_file->open(QIODevice::WriteOnly | mode)) {
|
||||
QString err = filePath.exists() ?
|
||||
|
||||
Reference in New Issue
Block a user