summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2017-07-25 18:08:30 -0700
committerThiago Macieira <thiago.macieira@intel.com>2017-08-04 17:30:13 +0000
commit66500b9d75cb895accfc385fd323f48c41b61a7a (patch)
treec1e077c2bad0defb11a9623bb01d9da8fe071388
parent9a3ce25f983b7ef842bbc055081474f044efb738 (diff)
Support Alternate Data Streams in QSaveFile
We can't use MoveFile to do atomic commits on an ADS, so QSaveFile needs to detect when the target name is ADS and then use the direct fallback mode. [ChangeLog][QtCore][QSaveFile] Saving to Alternate Data Streams on NTFS on Windows is now possible, but requires setDirectWriteFallback(true). Task-number: QTBUG-47379 Change-Id: I81480fdb578d4d43b3fcfffd14d4bc062ae1750d Reviewed-by: Kai Koehne <kai.koehne@qt.io> Reviewed-by: David Faure <david.faure@kdab.com> Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@qt.io> Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
-rw-r--r--src/corelib/io/qsavefile.cpp37
-rw-r--r--tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp60
2 files changed, 92 insertions, 5 deletions
diff --git a/src/corelib/io/qsavefile.cpp b/src/corelib/io/qsavefile.cpp
index 3f7ce8d9e9..63f2284ef5 100644
--- a/src/corelib/io/qsavefile.cpp
+++ b/src/corelib/io/qsavefile.cpp
@@ -231,6 +231,37 @@ bool QSaveFile::open(OpenMode mode)
d->finalFileName = existingFile.filePath();
}
+ auto openDirectly = [&]() {
+ d->fileEngine = QAbstractFileEngine::create(d->finalFileName);
+ if (d->fileEngine->open(mode | QIODevice::Unbuffered)) {
+ d->useTemporaryFile = false;
+ QFileDevice::open(mode);
+ return true;
+ }
+ return false;
+ };
+
+#ifdef Q_OS_WIN
+ // check if it is an Alternate Data Stream
+ if (d->finalFileName == d->fileName && d->fileName.indexOf(QLatin1Char(':'), 2) > 1) {
+ // yes, we can't rename onto it...
+ if (d->directWriteFallback) {
+ if (openDirectly())
+ return true;
+ d->setError(d->fileEngine->error(), d->fileEngine->errorString());
+ delete d->fileEngine;
+ d->fileEngine = 0;
+ } else {
+ QString msg =
+ QSaveFile::tr("QSaveFile cannot open '%1' without direct write fallback "
+ "enabled: path contains an Alternate Data Stream specifier")
+ .arg(QDir::toNativeSeparators(d->fileName));
+ d->setError(QFileDevice::OpenError, msg);
+ }
+ return false;
+ }
+#endif
+
d->fileEngine = new QTemporaryFileEngine(&d->finalFileName);
// if the target file exists, we'll copy its permissions below,
// but until then, let's ensure the temporary file is not accessible
@@ -243,12 +274,8 @@ bool QSaveFile::open(OpenMode mode)
#ifdef Q_OS_UNIX
if (d->directWriteFallback && err == QFileDevice::OpenError && errno == EACCES) {
delete d->fileEngine;
- d->fileEngine = QAbstractFileEngine::create(d->finalFileName);
- if (d->fileEngine->open(mode | QIODevice::Unbuffered)) {
- d->useTemporaryFile = false;
- QFileDevice::open(mode);
+ if (openDirectly())
return true;
- }
err = d->fileEngine->error();
}
#endif
diff --git a/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp b/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp
index 9573f3078b..96970421d3 100644
--- a/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp
+++ b/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp
@@ -82,6 +82,11 @@ private slots:
void transactionalWriteErrorRenaming();
void symlink();
void directory();
+
+#ifdef Q_OS_WIN
+ void alternateDataStream_data();
+ void alternateDataStream();
+#endif
};
static inline QByteArray msgCannotOpen(const QFileDevice &f)
@@ -474,5 +479,60 @@ void tst_QSaveFile::directory()
#endif
}
+#ifdef Q_OS_WIN
+void tst_QSaveFile::alternateDataStream_data()
+{
+ QTest::addColumn<bool>("directWriteFallback");
+ QTest::addColumn<bool>("success");
+
+ QTest::newRow("default") << false << false;
+ QTest::newRow("directWriteFallback") << true << true;
+}
+
+void tst_QSaveFile::alternateDataStream()
+{
+ QFETCH(bool, directWriteFallback);
+ QFETCH(bool, success);
+ static const char newContent[] = "New content\r\n";
+
+ QTemporaryDir dir;
+ QVERIFY2(dir.isValid(), qPrintable(dir.errorString()));
+ QString baseName = dir.path() + QLatin1String("/base");
+ {
+ QFile baseFile(baseName);
+ QVERIFY2(baseFile.open(QIODevice::ReadWrite), qPrintable(baseFile.errorString()));
+ }
+
+ // First, create a file with old content
+ QString adsName = baseName + QLatin1String(":outfile");
+ {
+ QFile targetFile(adsName);
+ if (!targetFile.open(QIODevice::ReadWrite))
+ QSKIP("Failed to ceate ADS file (" + targetFile.errorString().toUtf8()
+ + "). Temp dir is FAT?");
+ targetFile.write("Old content\r\n");
+ }
+
+ // And write to it again using QSaveFile; only works if directWriteFallback is enabled
+ QSaveFile file(adsName);
+ file.setDirectWriteFallback(directWriteFallback);
+ QCOMPARE(file.directWriteFallback(), directWriteFallback);
+
+ if (success) {
+ QVERIFY2(file.open(QIODevice::WriteOnly), qPrintable(file.errorString()));
+ file.write(newContent);
+ QVERIFY2(file.commit(), qPrintable(file.errorString()));
+
+ // check the contents
+ QFile targetFile(adsName);
+ QVERIFY2(targetFile.open(QIODevice::ReadOnly), qPrintable(targetFile.errorString()));
+ QByteArray contents = targetFile.readAll();
+ QCOMPARE(contents, QByteArray(newContent));
+ } else {
+ QVERIFY(!file.open(QIODevice::WriteOnly));
+ }
+}
+#endif
+
QTEST_MAIN(tst_QSaveFile)
#include "tst_qsavefile.moc"