1/****************************************************************************
2**
3** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite 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
30#include <QtTest/QtTest>
31#include <QtConcurrentRun>
32#include <qlockfile.h>
33#include <qtemporarydir.h>
34#include <qsysinfo.h>
35#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS)
36#include <unistd.h>
37#include <sys/time.h>
38#elif defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
39# include <qt_windows.h>
40#endif
41
42#include <private/qlockfile_p.h> // for getLockFileHandle()
43
44class tst_QLockFile : public QObject
45{
46 Q_OBJECT
47
48private slots:
49 void initTestCase();
50 void lockUnlock();
51 void lockOutOtherProcess();
52 void lockOutOtherThread();
53 void raceWithOtherThread();
54 void waitForLock_data();
55 void waitForLock();
56 void staleLockFromCrashedProcess_data();
57 void staleLockFromCrashedProcess();
58 void staleLockFromCrashedProcessReusedPid();
59 void staleShortLockFromBusyProcess();
60 void staleLongLockFromBusyProcess();
61 void staleLockRace();
62 void noPermissions();
63 void noPermissionsWindows();
64 void corruptedLockFile();
65 void corruptedLockFileInTheFuture();
66 void hostnameChange();
67 void differentMachines();
68 void reboot();
69
70private:
71 static bool overwriteLineInLockFile(QFile &f, int line, const QString &newLine);
72 static bool overwritePidInLockFile(const QString &filePath, qint64 pid);
73
74public:
75 QString m_helperApp;
76 QTemporaryDir dir;
77};
78
79void tst_QLockFile::initTestCase()
80{
81#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
82 QSKIP("This test requires deploying and running external console applications");
83#elif !QT_CONFIG(process)
84 QSKIP("This test requires QProcess support");
85#else
86 QVERIFY2(dir.isValid(), qPrintable(dir.errorString()));
87 // chdir to our testdata path and execute helper apps relative to that.
88 QString testdata_dir = QFileInfo(QFINDTESTDATA("qlockfiletesthelper")).absolutePath();
89 QVERIFY2(QDir::setCurrent(testdata_dir), qPrintable("Could not chdir to " + testdata_dir));
90 m_helperApp = "qlockfiletesthelper/qlockfile_test_helper";
91#endif // QT_CONFIG(process)
92}
93
94void tst_QLockFile::lockUnlock()
95{
96 const QString fileName = dir.path() + "/lock1";
97 QVERIFY(!QFile(fileName).exists());
98 QLockFile lockFile(fileName);
99 QVERIFY(lockFile.lock());
100 QVERIFY(lockFile.isLocked());
101 QCOMPARE(int(lockFile.error()), int(QLockFile::NoError));
102 QVERIFY(QFile::exists(fileName));
103
104 // Recursive locking is not allowed
105 // (can't test lock() here, it would wait forever)
106 QVERIFY(!lockFile.tryLock());
107 QCOMPARE(int(lockFile.error()), int(QLockFile::LockFailedError));
108 qint64 pid;
109 QString hostname, appname;
110 QVERIFY(lockFile.getLockInfo(&pid, &hostname, &appname));
111 QCOMPARE(pid, QCoreApplication::applicationPid());
112 QCOMPARE(appname, qAppName());
113 QVERIFY(!lockFile.tryLock(200));
114 QCOMPARE(int(lockFile.error()), int(QLockFile::LockFailedError));
115
116 // Unlock deletes the lock file
117 lockFile.unlock();
118 QCOMPARE(int(lockFile.error()), int(QLockFile::NoError));
119 QVERIFY(!lockFile.isLocked());
120 QVERIFY(!QFile::exists(fileName));
121}
122
123void tst_QLockFile::lockOutOtherProcess()
124{
125#if !QT_CONFIG(process)
126 QSKIP("This test requires QProcess support");
127#else
128 // Lock
129 const QString fileName = dir.path() + "/lockOtherProcess";
130 QLockFile lockFile(fileName);
131 QVERIFY(lockFile.lock());
132
133 // Other process can't acquire lock
134 QProcess proc;
135 proc.start(program: m_helperApp, arguments: QStringList() << fileName);
136 QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
137 QVERIFY(proc.waitForFinished());
138 QCOMPARE(proc.exitCode(), int(QLockFile::LockFailedError));
139
140 // Unlock
141 lockFile.unlock();
142 QVERIFY(!QFile::exists(fileName));
143
144 // Other process can now acquire lock
145 int ret = QProcess::execute(program: m_helperApp, arguments: QStringList() << fileName);
146 QCOMPARE(ret, int(QLockFile::NoError));
147 // Lock doesn't survive process though (on clean exit)
148 QVERIFY(!QFile::exists(fileName));
149#endif // QT_CONFIG(process)
150}
151
152static QLockFile::LockError tryLockFromThread(const QString &fileName)
153{
154 QLockFile lockInThread(fileName);
155 lockInThread.tryLock();
156 return lockInThread.error();
157}
158
159void tst_QLockFile::lockOutOtherThread()
160{
161 const QString fileName = dir.path() + "/lockOtherThread";
162 QLockFile lockFile(fileName);
163 QVERIFY(lockFile.lock());
164
165 // Other thread can't acquire lock
166 QFuture<QLockFile::LockError> ret = QtConcurrent::run<QLockFile::LockError>(functionPointer: tryLockFromThread, arg1: fileName);
167 QCOMPARE(ret.result(), QLockFile::LockFailedError);
168
169 lockFile.unlock();
170
171 // Now other thread can acquire lock
172 QFuture<QLockFile::LockError> ret2 = QtConcurrent::run<QLockFile::LockError>(functionPointer: tryLockFromThread, arg1: fileName);
173 QCOMPARE(ret2.result(), QLockFile::NoError);
174}
175
176static QLockFile::LockError lockFromThread(const QString &fileName)
177{
178 QLockFile lockInThread(fileName);
179 lockInThread.lock();
180 return lockInThread.error();
181}
182
183// QTBUG-38853, best way to trigger it was to add a QThread::sleep(1) in QLockFilePrivate::getLockInfo() after the first readLine.
184// Then (on Windows), the QFile::remove() in unlock() (called by the first thread who got the lock, in the destructor)
185// would fail due to the existing reader on the file. Fixed by checking the return value of QFile::remove() in unlock().
186void tst_QLockFile::raceWithOtherThread()
187{
188 const QString fileName = dir.path() + "/raceWithOtherThread";
189 QFuture<QLockFile::LockError> ret = QtConcurrent::run<QLockFile::LockError>(functionPointer: lockFromThread, arg1: fileName);
190 QFuture<QLockFile::LockError> ret2 = QtConcurrent::run<QLockFile::LockError>(functionPointer: lockFromThread, arg1: fileName);
191 QCOMPARE(ret.result(), QLockFile::NoError);
192 QCOMPARE(ret2.result(), QLockFile::NoError);
193}
194
195static bool lockFromThread(const QString &fileName, int sleepMs, QSemaphore *semThreadReady, QSemaphore *semMainThreadDone)
196{
197 QLockFile lockFile(fileName);
198 if (!lockFile.lock()) {
199 qWarning() << "Locking failed" << lockFile.error();
200 return false;
201 }
202 semThreadReady->release();
203 QThread::msleep(sleepMs);
204 semMainThreadDone->acquire();
205 lockFile.unlock();
206 return true;
207}
208
209void tst_QLockFile::waitForLock_data()
210{
211 QTest::addColumn<int>(name: "testNumber");
212 QTest::addColumn<int>(name: "threadSleepMs");
213 QTest::addColumn<bool>(name: "releaseEarly");
214 QTest::addColumn<int>(name: "tryLockTimeout");
215 QTest::addColumn<bool>(name: "expectedResult");
216
217 int tn = 0; // test number
218 QTest::newRow(dataTag: "wait_forever_succeeds") << ++tn << 500 << true << -1 << true;
219 QTest::newRow(dataTag: "wait_longer_succeeds") << ++tn << 500 << true << 1000 << true;
220 QTest::newRow(dataTag: "wait_zero_fails") << ++tn << 500 << false << 0 << false;
221 QTest::newRow(dataTag: "wait_not_enough_fails") << ++tn << 500 << false << 100 << false;
222}
223
224void tst_QLockFile::waitForLock()
225{
226 QFETCH(int, testNumber);
227 QFETCH(int, threadSleepMs);
228 QFETCH(bool, releaseEarly);
229 QFETCH(int, tryLockTimeout);
230 QFETCH(bool, expectedResult);
231
232 const QString fileName = dir.path() + "/waitForLock" + QString::number(testNumber);
233 QLockFile lockFile(fileName);
234 QSemaphore semThreadReady, semMainThreadDone;
235 // Lock file from a thread
236 QFuture<bool> ret = QtConcurrent::run<bool>(functionPointer: lockFromThread, arg1: fileName, arg2: threadSleepMs, arg3: &semThreadReady, arg4: &semMainThreadDone);
237 semThreadReady.acquire();
238
239 if (releaseEarly) // let the thread release the lock after threadSleepMs
240 semMainThreadDone.release();
241
242 QCOMPARE(lockFile.tryLock(tryLockTimeout), expectedResult);
243 if (expectedResult)
244 QCOMPARE(int(lockFile.error()), int(QLockFile::NoError));
245 else
246 QCOMPARE(int(lockFile.error()), int(QLockFile::LockFailedError));
247
248 if (!releaseEarly) // only let the thread release the lock now
249 semMainThreadDone.release();
250
251 QVERIFY(ret); // waits for the thread to finish
252}
253
254void tst_QLockFile::staleLockFromCrashedProcess_data()
255{
256 QTest::addColumn<int>(name: "staleLockTime");
257
258 // Test both use cases for QLockFile, should make no difference here.
259 QTest::newRow(dataTag: "short") << 30000;
260 QTest::newRow(dataTag: "long") << 0;
261}
262
263void tst_QLockFile::staleLockFromCrashedProcess()
264{
265#if !QT_CONFIG(process)
266 QSKIP("This test requires QProcess support");
267#else
268 QFETCH(int, staleLockTime);
269 const QString fileName = dir.path() + "/staleLockFromCrashedProcess";
270
271 int ret = QProcess::execute(program: m_helperApp, arguments: QStringList() << fileName << "-uncleanexit");
272 QCOMPARE(ret, int(QLockFile::NoError));
273 QTRY_VERIFY(QFile::exists(fileName));
274
275 QLockFile secondLock(fileName);
276 secondLock.setStaleLockTime(staleLockTime);
277 // tryLock detects and removes the stale lock (since the PID is dead)
278#ifdef Q_OS_WIN
279 // It can take a bit of time on Windows, though.
280 QVERIFY(secondLock.tryLock(30000));
281#else
282 QVERIFY(secondLock.tryLock());
283#endif
284 QCOMPARE(int(secondLock.error()), int(QLockFile::NoError));
285#endif // QT_CONFIG(process)
286}
287
288void tst_QLockFile::staleLockFromCrashedProcessReusedPid()
289{
290#if !QT_CONFIG(process)
291 QSKIP("This test requires QProcess support");
292#elif defined(Q_OS_WINRT) || defined(QT_PLATFORM_UIKIT)
293 QSKIP("We cannot retrieve information about other processes on this platform.");
294#else
295 const QString fileName = dir.path() + "/staleLockFromCrashedProcessReusedPid";
296
297 int ret = QProcess::execute(program: m_helperApp, arguments: QStringList() << fileName << "-uncleanexit");
298 QCOMPARE(ret, int(QLockFile::NoError));
299 QVERIFY(QFile::exists(fileName));
300 QVERIFY(overwritePidInLockFile(fileName, QCoreApplication::applicationPid()));
301
302 QLockFile secondLock(fileName);
303 qint64 pid = 0;
304 QVERIFY(secondLock.getLockInfo(&pid, 0, 0));
305 QCOMPARE(pid, QCoreApplication::applicationPid());
306 secondLock.setStaleLockTime(0);
307 QVERIFY(secondLock.tryLock());
308 QCOMPARE(int(secondLock.error()), int(QLockFile::NoError));
309#endif // QT_CONFIG(process)
310}
311
312void tst_QLockFile::staleShortLockFromBusyProcess()
313{
314#if !QT_CONFIG(process)
315 QSKIP("This test requires QProcess support");
316#else
317 const QString fileName = dir.path() + "/staleLockFromBusyProcess";
318
319 QProcess proc;
320 proc.start(program: m_helperApp, arguments: QStringList() << fileName << "-busy");
321 QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
322 QTRY_VERIFY(QFile::exists(fileName));
323
324 QLockFile secondLock(fileName);
325 QVERIFY(!secondLock.tryLock()); // held by other process
326 QCOMPARE(int(secondLock.error()), int(QLockFile::LockFailedError));
327 qint64 pid;
328 QString hostname, appname;
329 QTRY_VERIFY(secondLock.getLockInfo(&pid, &hostname, &appname));
330#ifdef Q_OS_UNIX
331 QCOMPARE(pid, proc.processId());
332#endif
333
334 secondLock.setStaleLockTime(100);
335 QTest::qSleep(ms: 100); // make the lock stale
336 // We can't "steal" (delete+recreate) a lock file from a running process
337 // until the file descriptor is closed.
338 QVERIFY(!secondLock.tryLock());
339
340 proc.waitForFinished();
341 QVERIFY(secondLock.tryLock());
342#endif // QT_CONFIG(process)
343}
344
345void tst_QLockFile::staleLongLockFromBusyProcess()
346{
347#if !QT_CONFIG(process)
348 QSKIP("This test requires QProcess support");
349#else
350 const QString fileName = dir.path() + "/staleLockFromBusyProcess";
351
352 QProcess proc;
353 proc.start(program: m_helperApp, arguments: QStringList() << fileName << "-busy");
354 QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
355 QTRY_VERIFY(QFile::exists(fileName));
356
357 QLockFile secondLock(fileName);
358 secondLock.setStaleLockTime(0);
359 QVERIFY(!secondLock.tryLock(100)); // never stale
360 QCOMPARE(int(secondLock.error()), int(QLockFile::LockFailedError));
361 qint64 pid;
362 QTRY_VERIFY(secondLock.getLockInfo(&pid, NULL, NULL));
363 QVERIFY(pid > 0);
364
365 // As long as the other process is running, we can't remove the lock file
366 QVERIFY(!secondLock.removeStaleLockFile());
367
368 proc.waitForFinished();
369#endif // QT_CONFIG(process)
370}
371
372static QString tryStaleLockFromThread(const QString &fileName)
373{
374 QLockFile lockInThread(fileName + ".lock");
375 lockInThread.setStaleLockTime(1000);
376 if (!lockInThread.lock())
377 return "Error locking: " + QString::number(lockInThread.error());
378
379 // The concurrent use of the file below (write, read, delete) is protected by the lock file above.
380 // (provided that it doesn't become stale due to this operation taking too long)
381 QFile theFile(fileName);
382 if (!theFile.open(flags: QIODevice::WriteOnly))
383 return "Couldn't open for write";
384 theFile.write(data: "Hello world");
385 theFile.flush();
386 theFile.close();
387 QFile reader(fileName);
388 if (!reader.open(flags: QIODevice::ReadOnly))
389 return "Couldn't open for read";
390 const QByteArray read = reader.readAll();
391 if (read != "Hello world")
392 return "File didn't have the expected contents:" + read;
393 reader.remove();
394 return QString();
395}
396
397void tst_QLockFile::staleLockRace()
398{
399#if !QT_CONFIG(process)
400 QSKIP("This test requires QProcess support");
401#else
402 // Multiple threads notice a stale lock at the same time
403 // Only one thread should delete it, otherwise a race will ensue
404 const QString fileName = dir.path() + "/sharedFile";
405 const QString lockName = fileName + ".lock";
406 int ret = QProcess::execute(program: m_helperApp, arguments: QStringList() << lockName << "-uncleanexit");
407 QCOMPARE(ret, int(QLockFile::NoError));
408 QTRY_VERIFY(QFile::exists(lockName));
409
410 QThreadPool::globalInstance()->setMaxThreadCount(10);
411 QFutureSynchronizer<QString> synchronizer;
412 for (int i = 0; i < 8; ++i)
413 synchronizer.addFuture(future: QtConcurrent::run<QString>(functionPointer: tryStaleLockFromThread, arg1: fileName));
414 synchronizer.waitForFinished();
415 foreach (const QFuture<QString> &future, synchronizer.futures())
416 QVERIFY2(future.result().isEmpty(), qPrintable(future.result()));
417#endif // QT_CONFIG(process)
418}
419
420void tst_QLockFile::noPermissions()
421{
422#if defined(Q_OS_WIN)
423 // A readonly directory still allows us to create files, on Windows.
424 QSKIP("No permission testing on Windows");
425#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS)
426 if (::geteuid() == 0)
427 QSKIP("Test is not applicable with root privileges");
428#endif
429 // Restore permissions so that the QTemporaryDir cleanup can happen
430 class PermissionRestorer
431 {
432 QString m_path;
433 public:
434 PermissionRestorer(const QString& path)
435 : m_path(path)
436 {}
437
438 ~PermissionRestorer()
439 {
440 QFile file(m_path);
441 file.setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner));
442 }
443 };
444
445 const QString fileName = dir.path() + "/staleLock";
446 QFile dirAsFile(dir.path()); // I have to use QFile to change a dir's permissions...
447 QVERIFY2(dirAsFile.setPermissions(QFile::Permissions{}), qPrintable(dir.path())); // no permissions
448 PermissionRestorer permissionRestorer(dir.path());
449
450 QLockFile lockFile(fileName);
451 QVERIFY(!lockFile.lock());
452 QCOMPARE(int(lockFile.error()), int(QLockFile::PermissionError));
453}
454
455enum ProcessProperty {
456 ElevatedProcess = 0x1,
457 VirtualStore = 0x2
458};
459
460Q_DECLARE_FLAGS(ProcessProperties, ProcessProperty)
461Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessProperties)
462
463static inline ProcessProperties processProperties()
464{
465 ProcessProperties result;
466#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
467 HANDLE processToken = NULL;
468 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &processToken)) {
469 DWORD elevation; // struct containing a DWORD, not present in some MinGW headers.
470 DWORD cbSize = sizeof(elevation);
471 if (GetTokenInformation(processToken, TokenElevation, &elevation, cbSize, &cbSize)
472 && elevation) {
473 result |= ElevatedProcess;
474 }
475 // Check for UAC virtualization (compatibility mode for old software
476 // allowing it to write to system folders by mirroring them under
477 // "\Users\...\AppData\Local\VirtualStore\", which is typically the case
478 // for MinGW).
479 DWORD virtualStoreEnabled = 0;
480 cbSize = sizeof(virtualStoreEnabled);
481 if (GetTokenInformation(processToken, TokenVirtualizationEnabled, &virtualStoreEnabled, cbSize, &cbSize)
482 && virtualStoreEnabled) {
483 result |= VirtualStore;
484 }
485 CloseHandle(processToken);
486 }
487#endif
488 return result;
489}
490
491void tst_QLockFile::noPermissionsWindows()
492{
493 // Windows: Do the permissions test in a system directory in which
494 // files cannot be created.
495#if !defined(Q_OS_WIN) || defined(Q_OS_WINRT)
496 QSKIP("This test is for desktop Windows only");
497#endif
498#ifdef Q_OS_WIN
499 if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows7)
500 QSKIP("This test requires at least Windows 7");
501#endif
502 if (const int p = processProperties()) {
503 const QByteArray message = "This test cannot be run (properties=0x"
504 + QByteArray::number(p, base: 16) + ')';
505 QSKIP(message.constData());
506 }
507
508 const QString fileName = QFile::decodeName(localFileName: qgetenv(varName: "ProgramFiles"))
509 + QLatin1Char('/') + QCoreApplication::applicationName()
510 + QDateTime::currentDateTime().toString(QStringLiteral("yyMMddhhmm"));
511 QLockFile lockFile(fileName);
512 QVERIFY(!lockFile.lock());
513 QCOMPARE(int(lockFile.error()), int(QLockFile::PermissionError));
514}
515
516void tst_QLockFile::corruptedLockFile()
517{
518 const QString fileName = dir.path() + "/corruptedLockFile";
519
520 {
521 // Create a empty file. Typically the result of a computer crash or hard disk full.
522 QFile file(fileName);
523 QVERIFY(file.open(QFile::WriteOnly));
524 }
525
526 QLockFile secondLock(fileName);
527 secondLock.setStaleLockTime(100);
528 QVERIFY(secondLock.tryLock(10000));
529 QCOMPARE(int(secondLock.error()), int(QLockFile::NoError));
530}
531
532void tst_QLockFile::corruptedLockFileInTheFuture()
533{
534#if !defined(Q_OS_UNIX)
535 QSKIP("This tests needs utimes");
536#else
537 // This test is the same as the previous one, but the corruption was so there is a corrupted
538 // .rmlock whose timestamp is in the future
539
540 const QString fileName = dir.path() + "/corruptedLockFile.rmlock";
541
542 {
543 QFile file(fileName);
544 QVERIFY(file.open(QFile::WriteOnly));
545 }
546
547 struct timeval times[2];
548 gettimeofday(tv: times, tz: 0);
549 times[1].tv_sec = (times[0].tv_sec += 600);
550 times[1].tv_usec = times[0].tv_usec;
551 utimes(file: fileName.toLocal8Bit(), tvp: times);
552
553 QTest::ignoreMessage(type: QtInfoMsg, message: "QLockFile: Lock file '" + fileName.toUtf8() + "' has a modification time in the future");
554 corruptedLockFile();
555#endif
556}
557
558void tst_QLockFile::hostnameChange()
559{
560 const QByteArray hostid = QSysInfo::machineUniqueId();
561 if (hostid.isEmpty())
562 QSKIP("Could not get a unique host ID on this machine");
563
564 QString lockFile = dir.path() + "/hostnameChangeLock";
565 QLockFile lock1(lockFile);
566 QVERIFY(lock1.lock());
567
568 {
569 // now modify it
570 QFile f;
571 QVERIFY(f.open(QLockFilePrivate::getLockFileHandle(&lock1),
572 QIODevice::ReadWrite | QIODevice::Text,
573 QFile::DontCloseHandle));
574 QVERIFY(overwriteLineInLockFile(f, 3, "this is not a hostname"));
575 }
576
577 {
578 // we should fail to lock
579 QLockFile lock2(lockFile);
580 QVERIFY(!lock2.tryLock(1000));
581 }
582}
583
584void tst_QLockFile::differentMachines()
585{
586 const QByteArray hostid = QSysInfo::machineUniqueId();
587 if (hostid.isEmpty())
588 QSKIP("Could not get a unique host ID on this machine");
589
590 QString lockFile = dir.path() + "/differentMachinesLock";
591 QLockFile lock1(lockFile);
592 QVERIFY(lock1.lock());
593
594 {
595 // now modify it
596 QFile f;
597 QVERIFY(f.open(QLockFilePrivate::getLockFileHandle(&lock1),
598 QIODevice::ReadWrite | QIODevice::Text,
599 QFile::DontCloseHandle));
600 QVERIFY(overwriteLineInLockFile(f, 1, QT_STRINGIFY(INT_MAX)));
601 QVERIFY(overwriteLineInLockFile(f, 4, "this is not a UUID"));
602 }
603
604 {
605 // we should fail to lock
606 QLockFile lock2(lockFile);
607 QVERIFY(!lock2.tryLock(1000));
608 }
609}
610
611void tst_QLockFile::reboot()
612{
613 const QByteArray bootid = QSysInfo::bootUniqueId();
614 if (bootid.isEmpty())
615 QSKIP("Could not get a unique boot ID on this machine");
616
617 // create a lock so we can get its contents
618 QString lockFile = dir.path() + "/rebootLock";
619 QLockFile lock1(lockFile);
620 QVERIFY(lock1.lock());
621
622 QFile f(lockFile);
623 QVERIFY(f.open(QFile::ReadOnly | QFile::Text));
624 auto lines = f.readAll().split(sep: '\n');
625 f.close();
626
627 lock1.unlock();
628
629 // now recreate the file simulating a reboot
630 QVERIFY(f.open(QFile::WriteOnly | QFile::Text));
631 lines[4] = "this is not a UUID";
632 f.write(data: lines.join(sep: '\n'));
633 f.close();
634
635 // we should succeed in locking
636 QVERIFY(lock1.tryLock(0));
637}
638
639bool tst_QLockFile::overwritePidInLockFile(const QString &filePath, qint64 pid)
640{
641 QFile f(filePath);
642 if (!f.open(flags: QFile::ReadWrite | QFile::Text)) {
643 qErrnoWarning(msg: "Cannot open %s", qPrintable(filePath));
644 return false;
645 }
646 return overwriteLineInLockFile(f, line: 1, newLine: QString::number(pid));
647}
648
649bool tst_QLockFile::overwriteLineInLockFile(QFile &f, int line, const QString &newLine)
650{
651 f.seek(offset: 0);
652 QByteArray buf = f.readAll();
653 QStringList lines = QString::fromUtf8(str: buf).split(sep: '\n');
654 if (lines.size() < 3 && lines.size() < line - 1) {
655 qWarning(msg: "Unexpected lockfile content.");
656 return false;
657 }
658 lines[line - 1] = newLine;
659 f.seek(offset: 0);
660 buf = lines.join(sep: '\n').toUtf8();
661 f.resize(sz: buf.size());
662 return f.write(data: buf) == buf.size();
663}
664
665struct LockFileUsageInGlobalDtor
666{
667 ~LockFileUsageInGlobalDtor() {
668 QLockFile lockFile(QDir::currentPath() + "/lastlock");
669 QVERIFY(lockFile.lock());
670 QVERIFY(lockFile.isLocked());
671 }
672};
673LockFileUsageInGlobalDtor s_instance;
674
675QTEST_MAIN(tst_QLockFile)
676#include "tst_qlockfile.moc"
677

source code of qtbase/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp