summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier Goffart <ogoffart@woboq.com>2014-05-28 22:53:57 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2014-05-30 19:36:38 +0200
commit7e5e7eeaa1418d959906cdf9d717c984c9fc7a7e (patch)
treecb7cc93009370ab9c703c1e54f9a9963a9df31fb
parente747324f68df827dcea15bd0e3ee6b023542fe01 (diff)
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 <oswald.buddenhagen@digia.com> Reviewed-by: David Faure <david.faure@kdab.com>
-rw-r--r--src/corelib/io/qsavefile.cpp18
-rw-r--r--src/corelib/io/qsavefile_p.h1
-rw-r--r--tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp114
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"