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:
Eike Ziller
2023-03-28 16:45:16 +02:00
parent 59c1fae60b
commit 3ba769fb46
5 changed files with 45 additions and 5 deletions

View File

@@ -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();