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 * Returns the file name of the lock file
155 */
156QString QLockFile::fileName() const
157{
158 return d_ptr->fileName;
159}
160
161/*!
162 Sets \a staleLockTime to be the time in milliseconds after which
163 a lock file is considered stale.
164 The default value is 30000, i.e. 30 seconds.
165 If your application typically keeps the file locked for more than 30 seconds
166 (for instance while saving megabytes of data for 2 minutes), you should set
167 a bigger value using setStaleLockTime().
168
169 The value of \a staleLockTime is used by lock() and tryLock() in order
170 to determine when an existing lock file is considered stale, i.e. left over
171 by a crashed process. This is useful for the case where the PID got reused
172 meanwhile, so one way to detect a stale lock file is by the fact that
173 it has been around for a long time.
174
175 \sa staleLockTime()
176*/
177void QLockFile::setStaleLockTime(int staleLockTime)
178{
179 Q_D(QLockFile);
180 d->staleLockTime = staleLockTime;
181}
182
183/*!
184 Returns the time in milliseconds after which
185 a lock file is considered stale.
186
187 \sa setStaleLockTime()
188*/
189int QLockFile::staleLockTime() const
190{
191 Q_D(const QLockFile);
192 return d->staleLockTime;
193}
194
195/*!
196 Returns \c true if the lock was acquired by this QLockFile instance,
197 otherwise returns \c false.
198
199 \sa lock(), unlock(), tryLock()
200*/
201bool QLockFile::isLocked() const
202{
203 Q_D(const QLockFile);
204 return d->isLocked;
205}
206
207/*!
208 Creates the lock file.
209
210 If another process (or another thread) has created the lock file already,
211 this function will block until that process (or thread) releases it.
212
213 Calling this function multiple times on the same lock from the same
214 thread without unlocking first is not allowed. This function will
215 \e dead-lock when the file is locked recursively.
216
217 Returns \c true if the lock was acquired, false if it could not be acquired
218 due to an unrecoverable error, such as no permissions in the parent directory.
219
220 \sa unlock(), tryLock()
221*/
222bool QLockFile::lock()
223{
224 return tryLock(-1);
225}
226
227/*!
228 Attempts to create the lock file. This function returns \c true if the
229 lock was obtained; otherwise it returns \c false. If another process (or
230 another thread) has created the lock file already, this function will
231 wait for at most \a timeout milliseconds for the lock file to become
232 available.
233
234 Note: Passing a negative number as the \a timeout is equivalent to
235 calling lock(), i.e. this function will wait forever until the lock
236 file can be locked if \a timeout is negative.
237
238 If the lock was obtained, it must be released with unlock()
239 before another process (or thread) can successfully lock it.
240
241 Calling this function multiple times on the same lock from the same
242 thread without unlocking first is not allowed, this function will
243 \e always return false when attempting to lock the file recursively.
244
245 \sa lock(), unlock()
246*/
247bool QLockFile::tryLock(int timeout)
248{
249 Q_D(QLockFile);
250 QDeadlineTimer timer(qMax(timeout, -1)); // QDT only takes -1 as "forever"
251 int sleepTime = 100;
252 forever {
253 d->lockError = d->tryLock_sys();
254 switch (d->lockError) {
255 case NoError:
256 d->isLocked = true;
257 return true;
258 case PermissionError:
259 case UnknownError:
260 return false;
261 case LockFailedError:
262 if (!d->isLocked && d->isApparentlyStale()) {
263 if (Q_UNLIKELY(QFileInfo(d->fileName).lastModified() > QDateTime::currentDateTime()))
264 qInfo("QLockFile: Lock file '%ls' has a modification time in the future", qUtf16Printable(d->fileName));
265 // Stale lock from another thread/process
266 // Ensure two processes don't remove it at the same time
267 QLockFile rmlock(d->fileName + QLatin1String(".rmlock"));
268 if (rmlock.tryLock()) {
269 if (d->isApparentlyStale() && d->removeStaleLock())
270 continue;
271 }
272 }
273 break;
274 }
275
276 int remainingTime = timer.remainingTime();
277 if (remainingTime == 0)
278 return false;
279 else if (uint(sleepTime) > uint(remainingTime))
280 sleepTime = remainingTime;
281
282 QThread::msleep(sleepTime);
283 if (sleepTime < 5 * 1000)
284 sleepTime *= 2;
285 }
286 // not reached
287 return false;
288}
289
290/*!
291 \fn void QLockFile::unlock()
292 Releases the lock, by deleting the lock file.
293
294 Calling unlock() without locking the file first, does nothing.
295
296 \sa lock(), tryLock()
297*/
298
299/*!
300 Retrieves information about the current owner of the lock file.
301
302 If tryLock() returns \c false, and error() returns LockFailedError,
303 this function can be called to find out more information about the existing
304 lock file:
305 \list
306 \li the PID of the application (returned in \a pid)
307 \li the \a hostname it's running on (useful in case of networked filesystems),
308 \li the name of the application which created it (returned in \a appname),
309 \endlist
310
311 Note that tryLock() automatically deleted the file if there is no
312 running application with this PID, so LockFailedError can only happen if there is
313 an application with this PID (it could be unrelated though).
314
315 This can be used to inform users about the existing lock file and give them
316 the choice to delete it. After removing the file using removeStaleLockFile(),
317 the application can call tryLock() again.
318
319 This function returns \c true if the information could be successfully retrieved, false
320 if the lock file doesn't exist or doesn't contain the expected data.
321 This can happen if the lock file was deleted between the time where tryLock() failed
322 and the call to this function. Simply call tryLock() again if this happens.
323*/
324bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
325{
326 Q_D(const QLockFile);
327 LockFileInfo info;
328 if (!getLockInfo_helper(d->fileName, &info))
329 return false;
330 if (pid)
331 *pid = info.pid;
332 if (hostname)
333 *hostname = info.hostname;
334 if (appname)
335 *appname = info.appname;
336 return true;
337}
338
339QByteArray QLockFilePrivate::lockFileContents() const
340{
341 // Use operator% from the fast builder to avoid multiple memory allocations.
342 return QByteArray::number(QCoreApplication::applicationPid()) % '\n'
343 % processNameByPid(QCoreApplication::applicationPid()).toUtf8() % '\n'
344 % machineName().toUtf8() % '\n'
345 % QSysInfo::machineUniqueId() % '\n'
346 % QSysInfo::bootUniqueId() % '\n';
347}
348
349static bool getLockInfo_helper(const QString &fileName, LockFileInfo *info)
350{
351 QFile reader(fileName);
352 if (!reader.open(QIODevice::ReadOnly | QIODevice::Text))
353 return false;
354
355 QByteArray pidLine = reader.readLine();
356 pidLine.chop(1);
357 if (pidLine.isEmpty())
358 return false;
359 QByteArray appNameLine = reader.readLine();
360 appNameLine.chop(1);
361 QByteArray hostNameLine = reader.readLine();
362 hostNameLine.chop(1);
363
364 // prior to Qt 5.10, only the lines above were recorded
365 QByteArray hostId = reader.readLine();
366 hostId.chop(1);
367 QByteArray bootId = reader.readLine();
368 bootId.chop(1);
369
370 bool ok;
371 info->appname = QString::fromUtf8(appNameLine);
372 info->hostname = QString::fromUtf8(hostNameLine);
373 info->hostid = hostId;
374 info->bootid = bootId;
375 info->pid = pidLine.toLongLong(&ok);
376 return ok && info->pid > 0;
377}
378
379bool QLockFilePrivate::isApparentlyStale() const
380{
381 LockFileInfo info;
382 if (getLockInfo_helper(fileName, &info)) {
383 bool sameHost = info.hostname.isEmpty() || info.hostname == machineName();
384 if (!info.hostid.isEmpty()) {
385 // Override with the host ID, if we know it.
386 QByteArray ourHostId = QSysInfo::machineUniqueId();
387 if (!ourHostId.isEmpty())
388 sameHost = (ourHostId == info.hostid);
389 }
390
391 if (sameHost) {
392 if (!info.bootid.isEmpty()) {
393 // If we've rebooted, then the lock is definitely stale.
394 if (info.bootid != QSysInfo::bootUniqueId())
395 return true;
396 }
397 if (!isProcessRunning(info.pid, info.appname))
398 return true;
399 }
400 }
401
402 const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTimeUtc());
403 return staleLockTime > 0 && qAbs(age) > staleLockTime;
404}
405
406/*!
407 Attempts to forcefully remove an existing lock file.
408
409 Calling this is not recommended when protecting a short-lived operation: QLockFile
410 already takes care of removing lock files after they are older than staleLockTime().
411
412 This method should only be called when protecting a resource for a long time, i.e.
413 with staleLockTime(0), and after tryLock() returned LockFailedError, and the user
414 agreed on removing the lock file.
415
416 Returns \c true on success, false if the lock file couldn't be removed. This happens
417 on Windows, when the application owning the lock is still running.
418*/
419bool QLockFile::removeStaleLockFile()
420{
421 Q_D(QLockFile);
422 if (d->isLocked) {
423 qWarning("removeStaleLockFile can only be called when not holding the lock");
424 return false;
425 }
426 return d->removeStaleLock();
427}
428
429/*!
430 Returns the lock file error status.
431
432 If tryLock() returns \c false, this function can be called to find out
433 the reason why the locking failed.
434*/
435QLockFile::LockError QLockFile::error() const
436{
437 Q_D(const QLockFile);
438 return d->lockError;
439}
440
441QT_END_NAMESPACE
442