| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2012 David Faure <faure@kde.org> | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtCore module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 | ** included in the packaging of this file. Please review the following | 
| 22 | ** information to ensure the GNU General Public License requirements will | 
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 | ** | 
| 25 | ** $QT_END_LICENSE$ | 
| 26 | ** | 
| 27 | ****************************************************************************/ | 
| 28 |  | 
| 29 | #include <QtTest/QtTest> | 
| 30 | #include <qcoreapplication.h> | 
| 31 | #include <qstring.h> | 
| 32 | #include <qtemporaryfile.h> | 
| 33 | #include <qfile.h> | 
| 34 | #include <qdir.h> | 
| 35 | #include <qset.h> | 
| 36 |  | 
| 37 | #if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) | 
| 38 | #include <unistd.h> // for geteuid | 
| 39 | #endif | 
| 40 |  | 
| 41 | #if defined(Q_OS_WIN) | 
| 42 | # include <windows.h> | 
| 43 | #endif | 
| 44 |  | 
| 45 | // Restore permissions so that the QTemporaryDir cleanup can happen | 
| 46 | class PermissionRestorer | 
| 47 | { | 
| 48 |     Q_DISABLE_COPY(PermissionRestorer) | 
| 49 | public: | 
| 50 |     explicit PermissionRestorer(const QString& path) : m_path(path) {} | 
| 51 |     ~PermissionRestorer()  { restore(); } | 
| 52 |  | 
| 53 |     inline void restore() | 
| 54 |     { | 
| 55 |         QFile file(m_path); | 
| 56 | #ifdef Q_OS_UNIX | 
| 57 |         file.setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)); | 
| 58 | #else | 
| 59 |         file.setPermissions(QFile::WriteOwner); | 
| 60 |         file.remove(); | 
| 61 | #endif | 
| 62 |     } | 
| 63 |  | 
| 64 | private: | 
| 65 |     const QString m_path; | 
| 66 | }; | 
| 67 |  | 
| 68 | class tst_QSaveFile : public QObject | 
| 69 | { | 
| 70 |     Q_OBJECT | 
| 71 | public slots: | 
| 72 |  | 
| 73 | private slots: | 
| 74 |     void transactionalWrite(); | 
| 75 |     void retryTransactionalWrite(); | 
| 76 |     void textStreamManualFlush(); | 
| 77 |     void textStreamAutoFlush(); | 
| 78 |     void saveTwice(); | 
| 79 |     void transactionalWriteNoPermissionsOnDir_data(); | 
| 80 |     void transactionalWriteNoPermissionsOnDir(); | 
| 81 |     void transactionalWriteNoPermissionsOnFile(); | 
| 82 |     void transactionalWriteCanceled(); | 
| 83 |     void transactionalWriteErrorRenaming(); | 
| 84 |     void symlink(); | 
| 85 |     void directory(); | 
| 86 |  | 
| 87 | #ifdef Q_OS_WIN | 
| 88 |     void alternateDataStream_data(); | 
| 89 |     void alternateDataStream(); | 
| 90 | #endif | 
| 91 | }; | 
| 92 |  | 
| 93 | static inline QByteArray msgCannotOpen(const QFileDevice &f) | 
| 94 | { | 
| 95 |     QString result = QStringLiteral("Cannot open " ) + QDir::toNativeSeparators(pathName: f.fileName()) | 
| 96 |         + QStringLiteral(": " ) + f.errorString(); | 
| 97 |     return result.toLocal8Bit(); | 
| 98 | } | 
| 99 |  | 
| 100 | void tst_QSaveFile::transactionalWrite() | 
| 101 | { | 
| 102 |     QTemporaryDir dir; | 
| 103 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 104 |     const QString targetFile = dir.path() + QString::fromLatin1(str: "/outfile" ); | 
| 105 |     QFile::remove(fileName: targetFile); | 
| 106 |     QSaveFile file(targetFile); | 
| 107 |     QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 108 |     QVERIFY(file.isOpen()); | 
| 109 |     QCOMPARE(file.fileName(), targetFile); | 
| 110 |     QVERIFY(!QFile::exists(targetFile)); | 
| 111 |  | 
| 112 |     QCOMPARE(file.write("Hello" ), Q_INT64_C(5)); | 
| 113 |     QCOMPARE(file.error(), QFile::NoError); | 
| 114 |     QVERIFY(!QFile::exists(targetFile)); | 
| 115 |  | 
| 116 |     QVERIFY(file.commit()); | 
| 117 |     QVERIFY(QFile::exists(targetFile)); | 
| 118 |     QCOMPARE(file.fileName(), targetFile); | 
| 119 |  | 
| 120 |     QFile reader(targetFile); | 
| 121 |     QVERIFY(reader.open(QIODevice::ReadOnly)); | 
| 122 |     QCOMPARE(QString::fromLatin1(reader.readAll()), QString::fromLatin1("Hello" )); | 
| 123 |  | 
| 124 |     // check that permissions are the same as for QFile | 
| 125 |     const QString otherFile = dir.path() + QString::fromLatin1(str: "/otherfile" ); | 
| 126 |     QFile::remove(fileName: otherFile); | 
| 127 |     QFile other(otherFile); | 
| 128 |     other.open(flags: QIODevice::WriteOnly); | 
| 129 |     other.close(); | 
| 130 |     QCOMPARE(QFile::permissions(targetFile), QFile::permissions(otherFile)); | 
| 131 | } | 
| 132 |  | 
| 133 | // QTBUG-77007: Simulate the case of an application with a loop prompting | 
| 134 | // to retry saving on failure. Create a read-only file first (Unix only) | 
| 135 | void tst_QSaveFile::retryTransactionalWrite() | 
| 136 | { | 
| 137 | #ifndef Q_OS_UNIX | 
| 138 |     QSKIP("This test is Unix only" ); | 
| 139 | #else | 
| 140 |     // root can open the read-only file for writing... | 
| 141 |     if (geteuid() == 0) | 
| 142 |         QSKIP("This test does not work as the root user" ); | 
| 143 | #endif | 
| 144 |     QTemporaryDir dir; | 
| 145 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 146 |  | 
| 147 |     QString targetFile = dir.path() + QLatin1String("/outfile" ); | 
| 148 |     const QString readOnlyName = targetFile + QLatin1String(".ro" ); | 
| 149 |     { | 
| 150 |         QFile readOnlyFile(readOnlyName); | 
| 151 |         QVERIFY2(readOnlyFile.open(QIODevice::WriteOnly), msgCannotOpen(readOnlyFile).constData()); | 
| 152 |         readOnlyFile.write(data: "Hello" ); | 
| 153 |         readOnlyFile.close(); | 
| 154 |         auto permissions = readOnlyFile.permissions(); | 
| 155 |         permissions &= ~(QFileDevice::WriteOwner | QFileDevice::WriteGroup | QFileDevice::WriteUser); | 
| 156 |         QVERIFY(readOnlyFile.setPermissions(permissions)); | 
| 157 |     } | 
| 158 |  | 
| 159 |     QSaveFile file(readOnlyName); | 
| 160 |     QVERIFY(!file.open(QIODevice::WriteOnly)); | 
| 161 |  | 
| 162 |     file.setFileName(targetFile); | 
| 163 |     QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 164 |     QVERIFY(file.isOpen()); | 
| 165 |     QCOMPARE(file.write("Hello" ), Q_INT64_C(5)); | 
| 166 |     QCOMPARE(file.error(), QFile::NoError); | 
| 167 |     QVERIFY(file.commit()); | 
| 168 | } | 
| 169 |  | 
| 170 | void tst_QSaveFile::saveTwice() | 
| 171 | { | 
| 172 |     // Check that we can reuse a QSaveFile object | 
| 173 |     // (and test the case of an existing target file) | 
| 174 |     QTemporaryDir dir; | 
| 175 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 176 |     const QString targetFile = dir.path() + QString::fromLatin1(str: "/outfile" ); | 
| 177 |     QSaveFile file(targetFile); | 
| 178 |     QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 179 |     QCOMPARE(file.write("Hello" ), Q_INT64_C(5)); | 
| 180 |     QVERIFY2(file.commit(), qPrintable(file.errorString())); | 
| 181 |  | 
| 182 |     QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 183 |     QCOMPARE(file.write("World" ), Q_INT64_C(5)); | 
| 184 |     QVERIFY2(file.commit(), qPrintable(file.errorString())); | 
| 185 |  | 
| 186 |     QFile reader(targetFile); | 
| 187 |     QVERIFY2(reader.open(QIODevice::ReadOnly), msgCannotOpen(reader).constData()); | 
| 188 |     QCOMPARE(QString::fromLatin1(reader.readAll()), QString::fromLatin1("World" )); | 
| 189 | } | 
| 190 |  | 
| 191 | void tst_QSaveFile::textStreamManualFlush() | 
| 192 | { | 
| 193 |     QTemporaryDir dir; | 
| 194 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 195 |     const QString targetFile = dir.path() + QString::fromLatin1(str: "/outfile" ); | 
| 196 |     QSaveFile file(targetFile); | 
| 197 |     QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 198 |  | 
| 199 |     QTextStream ts(&file); | 
| 200 |     ts << "Manual flush" ; | 
| 201 |     ts.flush(); | 
| 202 |     QCOMPARE(file.error(), QFile::NoError); | 
| 203 |     QVERIFY(!QFile::exists(targetFile)); | 
| 204 |  | 
| 205 |     QVERIFY(file.commit()); | 
| 206 |     QFile reader(targetFile); | 
| 207 |     QVERIFY(reader.open(QIODevice::ReadOnly)); | 
| 208 |     QCOMPARE(QString::fromLatin1(reader.readAll().constData()), QString::fromLatin1("Manual flush" )); | 
| 209 |     QFile::remove(fileName: targetFile); | 
| 210 | } | 
| 211 |  | 
| 212 | void tst_QSaveFile::textStreamAutoFlush() | 
| 213 | { | 
| 214 |     QTemporaryDir dir; | 
| 215 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 216 |     const QString targetFile = dir.path() + QString::fromLatin1(str: "/outfile" ); | 
| 217 |     QSaveFile file(targetFile); | 
| 218 |     QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 219 |  | 
| 220 |     QTextStream ts(&file); | 
| 221 |     ts << "Auto-flush." ; | 
| 222 |     // no flush | 
| 223 |     QVERIFY(file.commit()); // QIODevice::close will emit aboutToClose, which will flush the stream | 
| 224 |     QFile reader(targetFile); | 
| 225 |     QVERIFY(reader.open(QIODevice::ReadOnly)); | 
| 226 |     QCOMPARE(QString::fromLatin1(reader.readAll().constData()), QString::fromLatin1("Auto-flush." )); | 
| 227 |     QFile::remove(fileName: targetFile); | 
| 228 | } | 
| 229 |  | 
| 230 | void tst_QSaveFile::transactionalWriteNoPermissionsOnDir_data() | 
| 231 | { | 
| 232 |     QTest::addColumn<bool>(name: "directWriteFallback" ); | 
| 233 |  | 
| 234 |     QTest::newRow(dataTag: "default" ) << false; | 
| 235 |     QTest::newRow(dataTag: "directWriteFallback" ) << true; | 
| 236 | } | 
| 237 |  | 
| 238 | void tst_QSaveFile::transactionalWriteNoPermissionsOnDir() | 
| 239 | { | 
| 240 | #ifdef Q_OS_UNIX | 
| 241 | #if !defined(Q_OS_VXWORKS) | 
| 242 |     if (::geteuid() == 0) | 
| 243 |         QSKIP("Test is not applicable with root privileges" ); | 
| 244 | #endif | 
| 245 |     QFETCH(bool, directWriteFallback); | 
| 246 |     QTemporaryDir dir; | 
| 247 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 248 |     QVERIFY(QFile(dir.path()).setPermissions(QFile::ReadOwner | QFile::ExeOwner)); | 
| 249 |     PermissionRestorer permissionRestorer(dir.path()); | 
| 250 |  | 
| 251 |     const QString targetFile = dir.path() + QString::fromLatin1(str: "/outfile" ); | 
| 252 |     QSaveFile firstTry(targetFile); | 
| 253 |     QVERIFY(!firstTry.open(QIODevice::WriteOnly)); | 
| 254 |     QCOMPARE((int)firstTry.error(), (int)QFile::OpenError); | 
| 255 |     QVERIFY(!firstTry.commit()); | 
| 256 |  | 
| 257 |     // Now make an existing writable file | 
| 258 |     permissionRestorer.restore(); | 
| 259 |     QFile f(targetFile); | 
| 260 |     QVERIFY(f.open(QIODevice::WriteOnly)); | 
| 261 |     QCOMPARE(f.write("Hello" ), Q_INT64_C(5)); | 
| 262 |     f.close(); | 
| 263 |  | 
| 264 |     // Make the directory non-writable again | 
| 265 |     QVERIFY(QFile(dir.path()).setPermissions(QFile::ReadOwner | QFile::ExeOwner)); | 
| 266 |  | 
| 267 |     // And write to it again using QSaveFile; only works if directWriteFallback is enabled | 
| 268 |     QSaveFile file(targetFile); | 
| 269 |     file.setDirectWriteFallback(directWriteFallback); | 
| 270 |     QCOMPARE(file.directWriteFallback(), directWriteFallback); | 
| 271 |     if (directWriteFallback) { | 
| 272 |         QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 273 |         QCOMPARE((int)file.error(), (int)QFile::NoError); | 
| 274 |         QCOMPARE(file.write("World" ), Q_INT64_C(5)); | 
| 275 |         QVERIFY(file.commit()); | 
| 276 |  | 
| 277 |         QFile reader(targetFile); | 
| 278 |         QVERIFY(reader.open(QIODevice::ReadOnly)); | 
| 279 |         QCOMPARE(QString::fromLatin1(reader.readAll()), QString::fromLatin1("World" )); | 
| 280 |         reader.close(); | 
| 281 |  | 
| 282 |         QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 283 |         QCOMPARE((int)file.error(), (int)QFile::NoError); | 
| 284 |         QCOMPARE(file.write("W" ), Q_INT64_C(1)); | 
| 285 |         file.cancelWriting(); // no effect, as per the documentation | 
| 286 |         QVERIFY(file.commit()); | 
| 287 |  | 
| 288 |         QVERIFY(reader.open(QIODevice::ReadOnly)); | 
| 289 |         QCOMPARE(QString::fromLatin1(reader.readAll()), QString::fromLatin1("W" )); | 
| 290 |     } else { | 
| 291 |         QVERIFY(!file.open(QIODevice::WriteOnly)); | 
| 292 |         QCOMPARE((int)file.error(), (int)QFile::OpenError); | 
| 293 |     } | 
| 294 | #endif | 
| 295 | } | 
| 296 |  | 
| 297 | void tst_QSaveFile::transactionalWriteNoPermissionsOnFile() | 
| 298 | { | 
| 299 | #if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) | 
| 300 |     if (::geteuid() == 0) | 
| 301 |         QSKIP("Test is not applicable with root privileges" ); | 
| 302 | #endif | 
| 303 |     // Setup an existing but readonly file | 
| 304 |     QTemporaryDir dir; | 
| 305 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 306 |     const QString targetFile = dir.path() + QString::fromLatin1(str: "/outfile" ); | 
| 307 |     QFile file(targetFile); | 
| 308 |     PermissionRestorer permissionRestorer(targetFile); | 
| 309 |     QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 310 |     QCOMPARE(file.write("Hello" ), Q_INT64_C(5)); | 
| 311 |     file.close(); | 
| 312 |     file.setPermissions(QFile::ReadOwner); | 
| 313 |     QVERIFY(!file.open(QIODevice::WriteOnly)); | 
| 314 |  | 
| 315 |     // Try saving into it | 
| 316 |     { | 
| 317 |         QSaveFile saveFile(targetFile); | 
| 318 |         QVERIFY(!saveFile.open(QIODevice::WriteOnly)); // just like QFile | 
| 319 |     } | 
| 320 |     QVERIFY(file.exists()); | 
| 321 | } | 
| 322 |  | 
| 323 | void tst_QSaveFile::transactionalWriteCanceled() | 
| 324 | { | 
| 325 |     QTemporaryDir dir; | 
| 326 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 327 |     const QString targetFile = dir.path() + QString::fromLatin1(str: "/outfile" ); | 
| 328 |     QFile::remove(fileName: targetFile); | 
| 329 |     QSaveFile file(targetFile); | 
| 330 |     QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 331 |  | 
| 332 |     QTextStream ts(&file); | 
| 333 |     ts << "This writing operation will soon be canceled.\n" ; | 
| 334 |     ts.flush(); | 
| 335 |     QCOMPARE(file.error(), QFile::NoError); | 
| 336 |     QVERIFY(!QFile::exists(targetFile)); | 
| 337 |  | 
| 338 |     // We change our mind, let's abort writing | 
| 339 |     file.cancelWriting(); | 
| 340 |  | 
| 341 |     QVERIFY(!file.commit()); | 
| 342 |  | 
| 343 |     QVERIFY(!QFile::exists(targetFile)); // temp file was discarded | 
| 344 |     QCOMPARE(file.fileName(), targetFile); | 
| 345 | } | 
| 346 |  | 
| 347 | void tst_QSaveFile::transactionalWriteErrorRenaming() | 
| 348 | { | 
| 349 | #if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) | 
| 350 |     if (::geteuid() == 0) | 
| 351 |         QSKIP("Test is not applicable with root privileges" ); | 
| 352 | #endif | 
| 353 |     QTemporaryDir dir; | 
| 354 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 355 |     const QString targetFile = dir.path() + QString::fromLatin1(str: "/outfile" ); | 
| 356 |     QSaveFile file(targetFile); | 
| 357 |     QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 358 |     QCOMPARE(file.write("Hello" ), qint64(5)); | 
| 359 |     QVERIFY(!QFile::exists(targetFile)); | 
| 360 | #ifdef Q_OS_UNIX | 
| 361 |     // Make rename() fail for lack of permissions in the directory | 
| 362 |     QFile dirAsFile(dir.path()); // yay, I have to use QFile to change a dir's permissions... | 
| 363 |     QVERIFY(dirAsFile.setPermissions(QFile::Permissions{})); // no permissions | 
| 364 |     PermissionRestorer permissionRestorer(dir.path()); | 
| 365 | #else | 
| 366 |     // Windows: Make rename() fail for lack of permissions on an existing target file | 
| 367 |     QFile existingTargetFile(targetFile); | 
| 368 |     QVERIFY2(existingTargetFile.open(QIODevice::WriteOnly), msgCannotOpen(existingTargetFile).constData()); | 
| 369 |     QCOMPARE(file.write("Target" ), qint64(6)); | 
| 370 |     existingTargetFile.close(); | 
| 371 |     QVERIFY(existingTargetFile.setPermissions(QFile::ReadOwner)); | 
| 372 |     PermissionRestorer permissionRestorer(targetFile); | 
| 373 | #endif | 
| 374 |  | 
| 375 |     // The saving should fail. | 
| 376 |     QVERIFY(!file.commit()); | 
| 377 | #ifdef Q_OS_UNIX | 
| 378 |     QVERIFY(!QFile::exists(targetFile)); // renaming failed | 
| 379 | #endif | 
| 380 |     QCOMPARE(file.error(), QFile::RenameError); | 
| 381 | } | 
| 382 |  | 
| 383 | void tst_QSaveFile::symlink() | 
| 384 | { | 
| 385 | #ifdef Q_OS_UNIX | 
| 386 |     QByteArray someData = "some data" ; | 
| 387 |     QTemporaryDir dir; | 
| 388 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 389 |  | 
| 390 |     const QString targetFile = dir.path() + QLatin1String("/outfile" ); | 
| 391 |     const QString linkFile = dir.path() + QLatin1String("/linkfile" ); | 
| 392 |     { | 
| 393 |         QFile file(targetFile); | 
| 394 |         QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData()); | 
| 395 |         QCOMPARE(file.write("Hello" ), Q_INT64_C(5)); | 
| 396 |         file.close(); | 
| 397 |     } | 
| 398 |  | 
| 399 |     QVERIFY(QFile::link(targetFile, linkFile)); | 
| 400 |  | 
| 401 |     QString canonical = QFileInfo(linkFile).canonicalFilePath(); | 
| 402 |     QCOMPARE(canonical, QFileInfo(targetFile).canonicalFilePath()); | 
| 403 |  | 
| 404 |     // Try saving into it | 
| 405 |     { | 
| 406 |         QSaveFile saveFile(linkFile); | 
| 407 |         QVERIFY(saveFile.open(QIODevice::WriteOnly)); | 
| 408 |         QCOMPARE(saveFile.write(someData), someData.size()); | 
| 409 |         saveFile.commit(); | 
| 410 |  | 
| 411 |         //Check that the linkFile is still a link and still has the same canonical path | 
| 412 |         QFileInfo info(linkFile); | 
| 413 |         QVERIFY(info.isSymLink()); | 
| 414 |         QCOMPARE(QFileInfo(linkFile).canonicalFilePath(), canonical); | 
| 415 |  | 
| 416 |         QFile file(targetFile); | 
| 417 |         QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); | 
| 418 |         QCOMPARE(file.readAll(), someData); | 
| 419 |         file.remove(); | 
| 420 |     } | 
| 421 |  | 
| 422 |     // Save into a symbolic link that point to a removed file | 
| 423 |     someData = "more stuff" ; | 
| 424 |     { | 
| 425 |         QSaveFile saveFile(linkFile); | 
| 426 |         QVERIFY(saveFile.open(QIODevice::WriteOnly)); | 
| 427 |         QCOMPARE(saveFile.write(someData), someData.size()); | 
| 428 |         saveFile.commit(); | 
| 429 |  | 
| 430 |         QFileInfo info(linkFile); | 
| 431 |         QVERIFY(info.isSymLink()); | 
| 432 |         QCOMPARE(QFileInfo(linkFile).canonicalFilePath(), canonical); | 
| 433 |  | 
| 434 |         QFile file(targetFile); | 
| 435 |         QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); | 
| 436 |         QCOMPARE(file.readAll(), someData); | 
| 437 |     } | 
| 438 |  | 
| 439 |     // link to a link in another directory | 
| 440 |     QTemporaryDir dir2; | 
| 441 |     QVERIFY2(dir2.isValid(), qPrintable(dir2.errorString())); | 
| 442 |  | 
| 443 |     const QString linkFile2 = dir2.path() + QLatin1String("/linkfile" ); | 
| 444 |     QVERIFY(QFile::link(linkFile, linkFile2)); | 
| 445 |     QCOMPARE(QFileInfo(linkFile2).canonicalFilePath(), canonical); | 
| 446 |  | 
| 447 |  | 
| 448 |     someData = "hello everyone" ; | 
| 449 |  | 
| 450 |     { | 
| 451 |         QSaveFile saveFile(linkFile2); | 
| 452 |         QVERIFY(saveFile.open(QIODevice::WriteOnly)); | 
| 453 |         QCOMPARE(saveFile.write(someData), someData.size()); | 
| 454 |         saveFile.commit(); | 
| 455 |  | 
| 456 |         QFile file(targetFile); | 
| 457 |         QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); | 
| 458 |         QCOMPARE(file.readAll(), someData); | 
| 459 |     } | 
| 460 |  | 
| 461 |     //cyclic link | 
| 462 |     const QString cyclicLink = dir.path() + QLatin1String("/cyclic" ); | 
| 463 |     QVERIFY(QFile::link(cyclicLink, cyclicLink)); | 
| 464 |     { | 
| 465 |         QSaveFile saveFile(cyclicLink); | 
| 466 |         QVERIFY(saveFile.open(QIODevice::WriteOnly)); | 
| 467 |         QCOMPARE(saveFile.write(someData), someData.size()); | 
| 468 |         saveFile.commit(); | 
| 469 |  | 
| 470 |         QFile file(cyclicLink); | 
| 471 |         QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); | 
| 472 |         QCOMPARE(file.readAll(), someData); | 
| 473 |     } | 
| 474 |  | 
| 475 |     //cyclic link2 | 
| 476 |     QVERIFY(QFile::link(cyclicLink + QLatin1Char('1'), cyclicLink + QLatin1Char('2'))); | 
| 477 |     QVERIFY(QFile::link(cyclicLink + QLatin1Char('2'), cyclicLink + QLatin1Char('1'))); | 
| 478 |  | 
| 479 |     { | 
| 480 |         QSaveFile saveFile(cyclicLink + QLatin1Char('1')); | 
| 481 |         QVERIFY(saveFile.open(QIODevice::WriteOnly)); | 
| 482 |         QCOMPARE(saveFile.write(someData), someData.size()); | 
| 483 |         saveFile.commit(); | 
| 484 |  | 
| 485 |         // the explicit file becomes a file instead of a link | 
| 486 |         QVERIFY(!QFileInfo(cyclicLink + QLatin1Char('1')).isSymLink()); | 
| 487 |         QVERIFY(QFileInfo(cyclicLink + QLatin1Char('2')).isSymLink()); | 
| 488 |  | 
| 489 |         QFile file(cyclicLink + QLatin1Char('1')); | 
| 490 |         QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData()); | 
| 491 |         QCOMPARE(file.readAll(), someData); | 
| 492 |     } | 
| 493 | #endif | 
| 494 | } | 
| 495 |  | 
| 496 | void tst_QSaveFile::directory() | 
| 497 | { | 
| 498 |     QTemporaryDir dir; | 
| 499 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 500 |  | 
| 501 |     const QString subdir = dir.path() + QLatin1String("/subdir" ); | 
| 502 |     QVERIFY(QDir(dir.path()).mkdir(QStringLiteral("subdir" ))); | 
| 503 |     { | 
| 504 |         QFile sf(subdir); | 
| 505 |         QVERIFY(!sf.open(QIODevice::WriteOnly)); | 
| 506 |     } | 
| 507 |  | 
| 508 | #ifdef Q_OS_UNIX | 
| 509 |     //link to a directory | 
| 510 |     const QString linkToDir = dir.path() + QLatin1String("/linkToDir" ); | 
| 511 |     QVERIFY(QFile::link(subdir, linkToDir)); | 
| 512 |  | 
| 513 |     { | 
| 514 |         QFile sf(linkToDir); | 
| 515 |         QVERIFY(!sf.open(QIODevice::WriteOnly)); | 
| 516 |     } | 
| 517 | #endif | 
| 518 | } | 
| 519 |  | 
| 520 | #ifdef Q_OS_WIN | 
| 521 | void tst_QSaveFile::alternateDataStream_data() | 
| 522 | { | 
| 523 |     QTest::addColumn<bool>("directWriteFallback" ); | 
| 524 |     QTest::addColumn<bool>("success" ); | 
| 525 |  | 
| 526 |     QTest::newRow("default" ) << false << false; | 
| 527 |     QTest::newRow("directWriteFallback" ) << true << true; | 
| 528 | } | 
| 529 |  | 
| 530 | void tst_QSaveFile::alternateDataStream() | 
| 531 | { | 
| 532 |     QFETCH(bool, directWriteFallback); | 
| 533 |     QFETCH(bool, success); | 
| 534 |     static const char newContent[] = "New content\r\n" ; | 
| 535 |  | 
| 536 |     QTemporaryDir dir; | 
| 537 |     QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); | 
| 538 |     QString baseName = dir.path() + QLatin1String("/base" ); | 
| 539 |     { | 
| 540 |         QFile baseFile(baseName); | 
| 541 |         QVERIFY2(baseFile.open(QIODevice::ReadWrite), qPrintable(baseFile.errorString())); | 
| 542 |     } | 
| 543 |  | 
| 544 |     // First, create a file with old content | 
| 545 |     QString adsName = baseName + QLatin1String(":outfile" ); | 
| 546 |     { | 
| 547 |         QFile targetFile(adsName); | 
| 548 |         if (!targetFile.open(QIODevice::ReadWrite)) | 
| 549 |             QSKIP("Failed to ceate ADS file ("  + targetFile.errorString().toUtf8() | 
| 550 |                   + "). Temp dir is FAT?" ); | 
| 551 |         targetFile.write("Old content\r\n" ); | 
| 552 |     } | 
| 553 |  | 
| 554 |     // And write to it again using QSaveFile; only works if directWriteFallback is enabled | 
| 555 |     QSaveFile file(adsName); | 
| 556 |     file.setDirectWriteFallback(directWriteFallback); | 
| 557 |     QCOMPARE(file.directWriteFallback(), directWriteFallback); | 
| 558 |  | 
| 559 |     if (success) { | 
| 560 |         QVERIFY2(file.open(QIODevice::WriteOnly), qPrintable(file.errorString())); | 
| 561 |         file.write(newContent); | 
| 562 |         QVERIFY2(file.commit(), qPrintable(file.errorString())); | 
| 563 |  | 
| 564 |         // check the contents | 
| 565 |         QFile targetFile(adsName); | 
| 566 |         QVERIFY2(targetFile.open(QIODevice::ReadOnly), qPrintable(targetFile.errorString())); | 
| 567 |         QByteArray contents = targetFile.readAll(); | 
| 568 |         QCOMPARE(contents, QByteArray(newContent)); | 
| 569 |     } else { | 
| 570 |         QVERIFY(!file.open(QIODevice::WriteOnly)); | 
| 571 |     } | 
| 572 | } | 
| 573 | #endif | 
| 574 |  | 
| 575 | QTEST_MAIN(tst_QSaveFile) | 
| 576 | #include "tst_qsavefile.moc" | 
| 577 |  |