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 | |