From 7e5e7eeaa1418d959906cdf9d717c984c9fc7a7e Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Wed, 28 May 2014 22:53:57 +0200 Subject: QSaveFile: follow symbolic links [ChangeLog][QtCore][QSaveFile] Now follows symbolic links while writing to a link instead of replacing the link with the contents. Change-Id: I5afd519cb9f96ae68fa4c23c33a18de75671a301 Reviewed-by: Oswald Buddenhagen Reviewed-by: David Faure --- src/corelib/io/qsavefile.cpp | 18 +++- src/corelib/io/qsavefile_p.h | 1 + tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp | 114 ++++++++++++++++++++++ 3 files changed, 130 insertions(+), 3 deletions(-) diff --git a/src/corelib/io/qsavefile.cpp b/src/corelib/io/qsavefile.cpp index 2b901c7ccd..05d7687007 100644 --- a/src/corelib/io/qsavefile.cpp +++ b/src/corelib/io/qsavefile.cpp @@ -205,14 +205,26 @@ bool QSaveFile::open(OpenMode mode) d->writeError = QFileDevice::WriteError; return false; } - d->fileEngine = new QTemporaryFileEngine(d->fileName); + + // Resolve symlinks. Don't use QFileInfo::canonicalFilePath so it still give the expected + // target even if the file does not exist + d->finalFileName = d->fileName; + if (existingFile.isSymLink()) { + int maxDepth = 128; + while (--maxDepth && existingFile.isSymLink()) + existingFile.setFile(existingFile.symLinkTarget()); + if (maxDepth > 0) + d->finalFileName = existingFile.filePath(); + } + + d->fileEngine = new QTemporaryFileEngine(d->finalFileName); // Same as in QFile: QIODevice provides the buffering, so there's no need to request it from the file engine. if (!d->fileEngine->open(mode | QIODevice::Unbuffered)) { QFileDevice::FileError err = d->fileEngine->error(); #ifdef Q_OS_UNIX if (d->directWriteFallback && err == QFileDevice::OpenError && errno == EACCES) { delete d->fileEngine; - d->fileEngine = QAbstractFileEngine::create(d->fileName); + d->fileEngine = QAbstractFileEngine::create(d->finalFileName); if (d->fileEngine->open(mode | QIODevice::Unbuffered)) { d->useTemporaryFile = false; QFileDevice::open(mode); @@ -285,7 +297,7 @@ bool QSaveFile::commit() // atomically replace old file with new file // Can't use QFile::rename for that, must use the file engine directly Q_ASSERT(d->fileEngine); - if (!d->fileEngine->renameOverwrite(d->fileName)) { + if (!d->fileEngine->renameOverwrite(d->finalFileName)) { d->setError(d->fileEngine->error(), d->fileEngine->errorString()); d->fileEngine->remove(); delete d->fileEngine; diff --git a/src/corelib/io/qsavefile_p.h b/src/corelib/io/qsavefile_p.h index b9efd1ee7c..2134c4c9bc 100644 --- a/src/corelib/io/qsavefile_p.h +++ b/src/corelib/io/qsavefile_p.h @@ -70,6 +70,7 @@ protected: ~QSaveFilePrivate(); QString fileName; + QString finalFileName; // fileName with symbolic links resolved QFileDevice::FileError writeError; diff --git a/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp b/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp index 87bcfe572d..63b9266ff6 100644 --- a/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp +++ b/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp @@ -93,6 +93,7 @@ private slots: void transactionalWriteNoPermissionsOnFile(); void transactionalWriteCanceled(); void transactionalWriteErrorRenaming(); + void symlink(); }; static inline QByteArray msgCannotOpen(const QFileDevice &f) @@ -340,5 +341,118 @@ void tst_QSaveFile::transactionalWriteErrorRenaming() QCOMPARE(file.error(), QFile::RenameError); } +void tst_QSaveFile::symlink() +{ +#ifdef Q_OS_UNIX + QByteArray someData = "some data"; + QTemporaryDir dir; + QVERIFY(dir.isValid()); + + const QString targetFile = dir.path() + QLatin1String("/outfile"); + const QString linkFile = dir.path() + QLatin1String("/linkfile"); + { + QFile file(targetFile); + QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); + QCOMPARE(file.write("Hello"), Q_INT64_C(5)); + file.close(); + } + + QVERIFY(QFile::link(targetFile, linkFile)); + + QString canonical = QFileInfo(linkFile).canonicalFilePath(); + QCOMPARE(canonical, QFileInfo(targetFile).canonicalFilePath()); + + // Try saving into it + { + QSaveFile saveFile(linkFile); + QVERIFY(saveFile.open(QIODevice::WriteOnly)); + QCOMPARE(saveFile.write(someData), someData.size()); + saveFile.commit(); + + //Check that the linkFile is still a link and still has the same canonical path + QFileInfo info(linkFile); + QVERIFY(info.isSymLink()); + QCOMPARE(QFileInfo(linkFile).canonicalFilePath(), canonical); + + QFile file(targetFile); + QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); + QCOMPARE(file.readAll(), someData); + file.remove(); + } + + // Save into a symbolic link that point to a removed file + someData = "more stuff"; + { + QSaveFile saveFile(linkFile); + QVERIFY(saveFile.open(QIODevice::WriteOnly)); + QCOMPARE(saveFile.write(someData), someData.size()); + saveFile.commit(); + + QFileInfo info(linkFile); + QVERIFY(info.isSymLink()); + QCOMPARE(QFileInfo(linkFile).canonicalFilePath(), canonical); + + QFile file(targetFile); + QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); + QCOMPARE(file.readAll(), someData); + } + + // link to a link in another directory + QTemporaryDir dir2; + QVERIFY(dir2.isValid()); + + const QString linkFile2 = dir2.path() + QLatin1String("/linkfile"); + QVERIFY(QFile::link(linkFile, linkFile2)); + QCOMPARE(QFileInfo(linkFile2).canonicalFilePath(), canonical); + + + someData = "hello everyone"; + + { + QSaveFile saveFile(linkFile2); + QVERIFY(saveFile.open(QIODevice::WriteOnly)); + QCOMPARE(saveFile.write(someData), someData.size()); + saveFile.commit(); + + QFile file(targetFile); + QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); + QCOMPARE(file.readAll(), someData); + } + + //cyclic link + const QString cyclicLink = dir.path() + QLatin1String("/cyclic"); + QVERIFY(QFile::link(cyclicLink, cyclicLink)); + { + QSaveFile saveFile(cyclicLink); + QVERIFY(saveFile.open(QIODevice::WriteOnly)); + QCOMPARE(saveFile.write(someData), someData.size()); + saveFile.commit(); + + QFile file(cyclicLink); + QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); + QCOMPARE(file.readAll(), someData); + } + + //cyclic link2 + QVERIFY(QFile::link(cyclicLink + QLatin1Char('1'), cyclicLink + QLatin1Char('2'))); + QVERIFY(QFile::link(cyclicLink + QLatin1Char('2'), cyclicLink + QLatin1Char('1'))); + + { + QSaveFile saveFile(cyclicLink + QLatin1Char('1')); + QVERIFY(saveFile.open(QIODevice::WriteOnly)); + QCOMPARE(saveFile.write(someData), someData.size()); + saveFile.commit(); + + // the explicit file becomes a file instead of a link + QVERIFY(!QFileInfo(cyclicLink + QLatin1Char('1')).isSymLink()); + QVERIFY(QFileInfo(cyclicLink + QLatin1Char('2')).isSymLink()); + + QFile file(cyclicLink + QLatin1Char('1')); + QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); + QCOMPARE(file.readAll(), someData); + } +#endif +} + QTEST_MAIN(tst_QSaveFile) #include "tst_qsavefile.moc" -- cgit v1.2.3