1/****************************************************************************
2**
3** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
4** Copyright (C) 2016 The Qt Company Ltd.
5** Copyright (C) 2017 Intel Corporation.
6** Contact: https://www.qt.io/licensing/
7**
8** This file is part of the QtCore module of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:LGPL$
11** Commercial License Usage
12** Licensees holding valid commercial Qt licenses may use this file in
13** accordance with the commercial license agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and The Qt Company. For licensing terms
16** and conditions see https://www.qt.io/terms-conditions. For further
17** information use the contact form at https://www.qt.io/contact-us.
18**
19** GNU Lesser General Public License Usage
20** Alternatively, this file may be used under the terms of the GNU Lesser
21** General Public License version 3 as published by the Free Software
22** Foundation and appearing in the file LICENSE.LGPL3 included in the
23** packaging of this file. Please review the following information to
24** ensure the GNU Lesser General Public License version 3 requirements
25** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26**
27** GNU General Public License Usage
28** Alternatively, this file may be used under the terms of the GNU
29** General Public License version 2.0 or (at your option) the GNU General
30** Public license version 3 or any later version approved by the KDE Free
31** Qt Foundation. The licenses are as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33** included in the packaging of this file. Please review the following
34** information to ensure the GNU General Public License requirements will
35** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36** https://www.gnu.org/licenses/gpl-3.0.html.
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qlockfile.h"
43#include "qlockfile_p.h"
44
45#include <QtCore/qthread.h>
46#include <QtCore/qcoreapplication.h>
47#include <QtCore/qdeadlinetimer.h>
48#include <QtCore/qdatetime.h>
49#include <QtCore/qfileinfo.h>
50
51QT_BEGIN_NAMESPACE
52
53namespace {
54struct LockFileInfo
55{
56 qint64 pid;
57 QString appname;
58 QString hostname;
59 QByteArray hostid;
60 QByteArray bootid;
61};
62}
63
64static bool getLockInfo_helper(const QString &fileName, LockFileInfo *info);
65
66static QString machineName()
67{
68#ifdef Q_OS_WIN
69 // we don't use QSysInfo because it tries to do name resolution
70 return qEnvironmentVariable("COMPUTERNAME");
71#else
72 return QSysInfo::machineHostName();
73#endif
74}
75
76/*!
77 \class QLockFile
78 \inmodule QtCore
79 \brief The QLockFile class provides locking between processes using a file.
80 \since 5.1
81
82 A lock file can be used to prevent multiple processes from accessing concurrently
83 the same resource. For instance, a configuration file on disk, or a socket, a port,
84 a region of shared memory...
85
86 Serialization is only guaranteed if all processes that access the shared resource
87 use QLockFile, with the same file path.
88
89 QLockFile supports two use cases:
90 to protect a resource for a short-term operation (e.g. verifying if a configuration
91 file has changed before saving new settings), and for long-lived protection of a
92 resource (e.g. a document opened by a user in an editor) for an indefinite amount of time.
93
94 When protecting for a short-term operation, it is acceptable to call lock() and wait
95 until any running operation finishes.
96 When protecting a resource over a long time, however, the application should always
97 call setStaleLockTime(0) and then tryLock() with a short timeout, in order to
98 warn the user that the resource is locked.
99
100 If the process holding the lock crashes, the lock file stays on disk and can prevent
101 any other process from accessing the shared resource, ever. For this reason, QLockFile
102 tries to detect such a "stale" lock file, based on the process ID written into the file.
103 To cover the situation that the process ID got reused meanwhile, the current process name is
104 compared to the name of the process that corresponds to the process ID from the lock file.
105 If the process names differ, the lock file is considered stale.
106 Additionally, the last modification time of the lock file (30s by default, for the use case of a
107 short-lived operation) is taken into account.
108 If the lock file is found to be stale, it will be deleted.
109
110 For the use case of protecting a resource over a long time, you should therefore call
111 setStaleLockTime(0), and when tryLock() returns LockFailedError, inform the user
112 that the document is locked, possibly using getLockInfo() for more details.
113
114 \note On Windows, this class has problems detecting a stale lock if the
115 machine's hostname contains characters outside the US-ASCII character set.
116*/
117
118/*!
119 \enum QLockFile::LockError
120
121 This enum describes the result of the last call to lock() or tryLock().
122
123 \value NoError The lock was acquired successfully.
124 \value LockFailedError The lock could not be acquired because another process holds it.
125 \value PermissionError The lock file could not be created, for lack of permissions
126 in the parent directory.
127 \value UnknownError Another error happened, for instance a full partition
128 prevented writing out the lock file.
129*/
130
131/*!
132 Constructs a new lock file object.
133 The object is created in an unlocked state.
134 When calling lock() or tryLock(), a lock file named \a fileName will be created,
135 if it doesn't already exist.
136
137 \sa lock(), unlock()
138*/
139QLockFile::QLockFile(const QString &fileName)
140 : d_ptr(new QLockFilePrivate(fileName))
141{
142}
143
144/*!
145 Destroys the lock file object.
146 If the lock was acquired, this will release the lock, by deleting the lock file.
147*/
148QLockFile::~QLockFile()
149{
150 unlock();
151}
152
153/*!
154 Sets \a staleLockTime to be the time in milliseconds after which
155 a lock file is considered stale.
156 The default value is 30000, i.e. 30 seconds.
157 If your application typically keeps the file locked for more than 30 seconds
158 (for instance while saving megabytes of data for 2 minutes), you should set
159 a bigger value using setStaleLockTime().
160
161 The value of \a staleLockTime is used by lock() and tryLock() in order
162 to determine when an existing lock file is considered stale, i.e. left over
163 by a crashed process. This is useful for the case where the PID got reused
164 meanwhile, so one way to detect a stale lock file is by the fact that
165 it has been around for a long time.
166
167 \sa staleLockTime()
168*/
169void QLockFile::setStaleLockTime(int staleLockTime)
170{
171 Q_D(QLockFile);
172 d->staleLockTime = staleLockTime;
173}
174
175/*!
176 Returns the time in milliseconds after which
177 a lock file is considered stale.
178
179 \sa setStaleLockTime()
180*/
181int QLockFile::staleLockTime() const
182{
183 Q_D(const QLockFile);
184 return d->staleLockTime;
185}
186
187/*!
188 Returns \c true if the lock was acquired by this QLockFile instance,
189 otherwise returns \c false.
190
191 \sa lock(), unlock(), tryLock()
192*/
193bool QLockFile::isLocked() const
194{
195 Q_D(const QLockFile);
196 return d->isLocked;
197}
198
199/*!
200 Creates the lock file.
201
202 If another process (or another thread) has created the lock file already,
203 this function will block until that process (or thread) releases it.
204
205 Calling this function multiple times on the same lock from the same
206 thread without unlocking first is not allowed. This function will
207 \e dead-lock when the file is locked recursively.
208
209 Returns \c true if the lock was acquired, false if it could not be acquired
210 due to an unrecoverable error, such as no permissions in the parent directory.
211
212 \sa unlock(), tryLock()
213*/
214bool QLockFile::lock()
215{
216 return tryLock(timeout: -1);
217}
218
219/*!
220 Attempts to create the lock file. This function returns \c true if the
221 lock was obtained; otherwise it returns \c false. If another process (or
222 another thread) has created the lock file already, this function will
223 wait for at most \a timeout milliseconds for the lock file to become
224 available.
225
226 Note: Passing a negative number as the \a timeout is equivalent to
227 calling lock(), i.e. this function will wait forever until the lock
228 file can be locked if \a timeout is negative.
229
230 If the lock was obtained, it must be released with unlock()
231 before another process (or thread) can successfully lock it.
232
233 Calling this function multiple times on the same lock from the same
234 thread without unlocking first is not allowed, this function will
235 \e always return false when attempting to lock the file recursively.
236
237 \sa lock(), unlock()
238*/
239bool QLockFile::tryLock(int timeout)
240{
241 Q_D(QLockFile);
242 QDeadlineTimer timer(qMax(a: timeout, b: -1)); // QDT only takes -1 as "forever"
243 int sleepTime = 100;
244 forever {
245 d->lockError = d->tryLock_sys();
246 switch (d->lockError) {
247 case NoError:
248 d->isLocked = true;
249 return true;
250 case PermissionError:
251 case UnknownError:
252 return false;
253 case LockFailedError:
254 if (!d->isLocked && d->isApparentlyStale()) {
255 if (Q_UNLIKELY(QFileInfo(d->fileName).lastModified() > QDateTime::currentDateTime()))
256 qInfo(msg: "QLockFile: Lock file '%ls' has a modification time in the future", qUtf16Printable(d->fileName));
257 // Stale lock from another thread/process
258 // Ensure two processes don't remove it at the same time
259 QLockFile rmlock(d->fileName + QLatin1String(".rmlock"));
260 if (rmlock.tryLock()) {
261 if (d->isApparentlyStale() && d->removeStaleLock())
262 continue;
263 }
264 }
265 break;
266 }
267
268 int remainingTime = timer.remainingTime();
269 if (remainingTime == 0)
270 return false;
271 else if (uint(sleepTime) > uint(remainingTime))
272 sleepTime = remainingTime;
273
274 QThread::msleep(sleepTime);
275 if (sleepTime < 5 * 1000)
276 sleepTime *= 2;
277 }
278 // not reached
279 return false;
280}
281
282/*!
283 \fn void QLockFile::unlock()
284 Releases the lock, by deleting the lock file.
285
286 Calling unlock() without locking the file first, does nothing.
287
288 \sa lock(), tryLock()
289*/
290
291/*!
292 Retrieves information about the current owner of the lock file.
293
294 If tryLock() returns \c false, and error() returns LockFailedError,
295 this function can be called to find out more information about the existing
296 lock file:
297 \list
298 \li the PID of the application (returned in \a pid)
299 \li the \a hostname it's running on (useful in case of networked filesystems),
300 \li the name of the application which created it (returned in \a appname),
301 \endlist
302
303 Note that tryLock() automatically deleted the file if there is no
304 running application with this PID, so LockFailedError can only happen if there is
305 an application with this PID (it could be unrelated though).
306
307 This can be used to inform users about the existing lock file and give them
308 the choice to delete it. After removing the file using removeStaleLockFile(),
309 the application can call tryLock() again.
310
311 This function returns \c true if the information could be successfully retrieved, false
312 if the lock file doesn't exist or doesn't contain the expected data.
313 This can happen if the lock file was deleted between the time where tryLock() failed
314 and the call to this function. Simply call tryLock() again if this happens.
315*/
316bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
317{
318 Q_D(const QLockFile);
319 LockFileInfo info;
320 if (!getLockInfo_helper(fileName: d->fileName, info: &info))
321 return false;
322 if (pid)
323 *pid = info.pid;
324 if (hostname)
325 *hostname = info.hostname;
326 if (appname)
327 *appname = info.appname;
328 return true;
329}
330
331QByteArray QLockFilePrivate::lockFileContents() const
332{
333 // Use operator% from the fast builder to avoid multiple memory allocations.
334 return QByteArray::number(QCoreApplication::applicationPid()) % '\n'
335 % processNameByPid(pid: QCoreApplication::applicationPid()).toUtf8() % '\n'
336 % machineName().toUtf8() % '\n'
337 % QSysInfo::machineUniqueId() % '\n'
338 % QSysInfo::bootUniqueId() % '\n';
339}
340
341static bool getLockInfo_helper(const QString &fileName, LockFileInfo *info)
342{
343 QFile reader(fileName);
344 if (!reader.open(flags: QIODevice::ReadOnly | QIODevice::Text))
345 return false;
346
347 QByteArray pidLine = reader.readLine();
348 pidLine.chop(n: 1);
349 if (pidLine.isEmpty())
350 return false;
351 QByteArray appNameLine = reader.readLine();
352 appNameLine.chop(n: 1);
353 QByteArray hostNameLine = reader.readLine();
354 hostNameLine.chop(n: 1);
355
356 // prior to Qt 5.10, only the lines above were recorded
357 QByteArray hostId = reader.readLine();
358 hostId.chop(n: 1);
359 QByteArray bootId = reader.readLine();
360 bootId.chop(n: 1);
361
362 bool ok;
363 info->appname = QString::fromUtf8(str: appNameLine);
364 info->hostname = QString::fromUtf8(str: hostNameLine);
365 info->hostid = hostId;
366 info->bootid = bootId;
367 info->pid = pidLine.toLongLong(ok: &ok);
368 return ok && info->pid > 0;
369}
370
371bool QLockFilePrivate::isApparentlyStale() const
372{
373 LockFileInfo info;
374 if (getLockInfo_helper(fileName, info: &info)) {
375 bool sameHost = info.hostname.isEmpty() || info.hostname == machineName();
376 if (!info.hostid.isEmpty()) {
377 // Override with the host ID, if we know it.
378 QByteArray ourHostId = QSysInfo::machineUniqueId();
379 if (!ourHostId.isEmpty())
380 sameHost = (ourHostId == info.hostid);
381 }
382
383 if (sameHost) {
384 if (!info.bootid.isEmpty()) {
385 // If we've rebooted, then the lock is definitely stale.
386 if (info.bootid != QSysInfo::bootUniqueId())
387 return true;
388 }
389 if (!isProcessRunning(pid: info.pid, appname: info.appname))
390 return true;
391 }
392 }
393
394 const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTimeUtc());
395 return staleLockTime > 0 && qAbs(t: age) > staleLockTime;
396}
397
398/*!
399 Attempts to forcefully remove an existing lock file.
400
401 Calling this is not recommended when protecting a short-lived operation: QLockFile
402 already takes care of removing lock files after they are older than staleLockTime().
403
404 This method should only be called when protecting a resource for a long time, i.e.
405 with staleLockTime(0), and after tryLock() returned LockFailedError, and the user
406 agreed on removing the lock file.
407
408 Returns \c true on success, false if the lock file couldn't be removed. This happens
409 on Windows, when the application owning the lock is still running.
410*/
411bool QLockFile::removeStaleLockFile()
412{
413 Q_D(QLockFile);
414 if (d->isLocked) {
415 qWarning(msg: "removeStaleLockFile can only be called when not holding the lock");
416 return false;
417 }
418 return d->removeStaleLock();
419}
420
421/*!
422 Returns the lock file error status.
423
424 If tryLock() returns \c false, this function can be called to find out
425 the reason why the locking failed.
426*/
427QLockFile::LockError QLockFile::error() const
428{
429 Q_D(const QLockFile);
430 return d->lockError;
431}
432
433QT_END_NAMESPACE
434

source code of qtbase/src/corelib/io/qlockfile.cpp