1/****************************************************************************
2**
3** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
4** Copyright (C) 2017 Intel Corporation.
5** Copyright (C) 2016 The Qt Company Ltd.
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 "private/qlockfile_p.h"
43
44#include "QtCore/qtemporaryfile.h"
45#include "QtCore/qfileinfo.h"
46#include "QtCore/qdebug.h"
47#include "QtCore/qdatetime.h"
48#include "QtCore/qfileinfo.h"
49#include "QtCore/qcache.h"
50#include "QtCore/qglobalstatic.h"
51#include "QtCore/qmutex.h"
52
53#include "private/qcore_unix_p.h" // qt_safe_open
54#include "private/qabstractfileengine_p.h"
55#include "private/qtemporaryfile_p.h"
56
57#if !defined(Q_OS_INTEGRITY)
58#include <sys/file.h> // flock
59#endif
60
61#if defined(Q_OS_RTEMS)
62# undef LOCK_EX
63# undef LOCK_NB
64#endif
65
66#include <sys/types.h> // kill
67#include <signal.h> // kill
68#include <unistd.h> // gethostname
69
70#if defined(Q_OS_OSX)
71# include <libproc.h>
72#elif defined(Q_OS_LINUX)
73# include <unistd.h>
74# include <cstdio>
75#elif defined(Q_OS_HAIKU)
76# include <kernel/OS.h>
77#elif defined(Q_OS_BSD4) && !defined(QT_PLATFORM_UIKIT)
78# include <sys/cdefs.h>
79# include <sys/param.h>
80# include <sys/sysctl.h>
81# if !defined(Q_OS_NETBSD)
82# include <sys/user.h>
83# endif
84#endif
85
86QT_BEGIN_NAMESPACE
87
88// ### merge into qt_safe_write?
89static qint64 qt_write_loop(int fd, const char *data, qint64 len)
90{
91 qint64 pos = 0;
92 while (pos < len) {
93 const qint64 ret = qt_safe_write(fd, data + pos, len - pos);
94 if (ret == -1) // e.g. partition full
95 return pos;
96 pos += ret;
97 }
98 return pos;
99}
100
101/*
102 * Details about file locking on Unix.
103 *
104 * There are three types of advisory locks on Unix systems:
105 * 1) POSIX process-wide locks using fcntl(F_SETLK)
106 * 2) BSD flock(2) system call
107 * 3) Linux-specific file descriptor locks using fcntl(F_OFD_SETLK)
108 * There's also a mandatory locking feature by POSIX, which is deprecated on
109 * Linux and users are advised not to use it.
110 *
111 * The first problem is that the POSIX API is braindead. POSIX.1-2008 says:
112 *
113 * All locks associated with a file for a given process shall be removed when
114 * a file descriptor for that file is closed by that process or the process
115 * holding that file descriptor terminates.
116 *
117 * The Linux manpage is clearer:
118 *
119 * * If a process closes _any_ file descriptor referring to a file, then all
120 * of the process's locks on that file are released, regardless of the file
121 * descriptor(s) on which the locks were obtained. This is bad: [...]
122 *
123 * * The threads in a process share locks. In other words, a multithreaded
124 * program can't use record locking to ensure that threads don't
125 * simultaneously access the same region of a file.
126 *
127 * So in order to use POSIX locks, we'd need a global mutex that stays locked
128 * while the QLockFile is locked. For that reason, Qt does not use POSIX
129 * advisory locks anymore.
130 *
131 * The next problem is that POSIX leaves undefined the relationship between
132 * locks with fcntl(), flock() and lockf(). In some systems (like the BSDs),
133 * all three use the same record set, while on others (like Linux) the locks
134 * are independent, except if locking over NFS mounts, in which case they're
135 * actually the same. Therefore, it's a very bad idea to mix them in the same
136 * process.
137 *
138 * We therefore use only flock(2).
139 */
140
141static bool setNativeLocks(int fd)
142{
143#if defined(LOCK_EX) && defined(LOCK_NB)
144 if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
145 return false;
146#else
147 Q_UNUSED(fd);
148#endif
149 return true;
150}
151
152QLockFile::LockError QLockFilePrivate::tryLock_sys()
153{
154 const QByteArray lockFileName = QFile::encodeName(fileName);
155 const int fd = qt_safe_open(lockFileName.constData(), O_RDWR | O_CREAT | O_EXCL, 0666);
156 if (fd < 0) {
157 switch (errno) {
158 case EEXIST:
159 return QLockFile::LockFailedError;
160 case EACCES:
161 case EROFS:
162 return QLockFile::PermissionError;
163 default:
164 return QLockFile::UnknownError;
165 }
166 }
167 // Ensure nobody else can delete the file while we have it
168 if (!setNativeLocks(fd)) {
169 const int errnoSaved = errno;
170 qWarning() << "setNativeLocks failed:" << qt_error_string(errnoSaved);
171 }
172
173 QByteArray fileData = lockFileContents();
174 if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) {
175 qt_safe_close(fd);
176 if (!QFile::remove(fileName))
177 qWarning("QLockFile: Could not remove our own lock file %ls.", qUtf16Printable(fileName));
178 return QLockFile::UnknownError; // partition full
179 }
180
181 // We hold the lock, continue.
182 fileHandle = fd;
183
184 // Sync to disk if possible. Ignore errors (e.g. not supported).
185#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
186 fdatasync(fileHandle);
187#else
188 fsync(fileHandle);
189#endif
190
191 return QLockFile::NoError;
192}
193
194bool QLockFilePrivate::removeStaleLock()
195{
196 const QByteArray lockFileName = QFile::encodeName(fileName);
197 const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0666);
198 if (fd < 0) // gone already?
199 return false;
200 bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0);
201 close(fd);
202 return success;
203}
204
205bool QLockFilePrivate::isProcessRunning(qint64 pid, const QString &appname)
206{
207 if (::kill(pid, 0) == -1 && errno == ESRCH)
208 return false; // PID doesn't exist anymore
209
210 const QString processName = processNameByPid(pid);
211 if (!processName.isEmpty()) {
212 QFileInfo fi(appname);
213 if (fi.isSymLink())
214 fi.setFile(fi.symLinkTarget());
215 if (processName != fi.fileName())
216 return false; // PID got reused by a different application.
217 }
218
219 return true;
220}
221
222QString QLockFilePrivate::processNameByPid(qint64 pid)
223{
224#if defined(Q_OS_OSX)
225 char name[1024];
226 proc_name(pid, name, sizeof(name) / sizeof(char));
227 return QFile::decodeName(name);
228#elif defined(Q_OS_LINUX)
229 if (!qt_haveLinuxProcfs())
230 return QString();
231
232 char exePath[64];
233 sprintf(exePath, "/proc/%lld/exe", pid);
234
235 QByteArray buf = qt_readlink(exePath);
236 if (buf.isEmpty()) {
237 // The pid is gone. Return some invalid process name to fail the test.
238 return QStringLiteral("/ERROR/");
239 }
240 return QFileInfo(QFile::decodeName(buf)).fileName();
241#elif defined(Q_OS_HAIKU)
242 thread_info info;
243 if (get_thread_info(pid, &info) != B_OK)
244 return QString();
245 return QFile::decodeName(info.name);
246#elif defined(Q_OS_BSD4) && !defined(QT_PLATFORM_UIKIT)
247# if defined(Q_OS_NETBSD)
248 struct kinfo_proc2 kp;
249 int mib[6] = { CTL_KERN, KERN_PROC2, KERN_PROC_PID, (int)pid, sizeof(struct kinfo_proc2), 1 };
250# elif defined(Q_OS_OPENBSD)
251 struct kinfo_proc kp;
252 int mib[6] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid, sizeof(struct kinfo_proc), 1 };
253# else
254 struct kinfo_proc kp;
255 int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid };
256# endif
257 size_t len = sizeof(kp);
258 u_int mib_len = sizeof(mib)/sizeof(u_int);
259
260 if (sysctl(mib, mib_len, &kp, &len, NULL, 0) < 0)
261 return QString();
262
263# if defined(Q_OS_OPENBSD) || defined(Q_OS_NETBSD)
264 if (kp.p_pid != pid)
265 return QString();
266 QString name = QFile::decodeName(kp.p_comm);
267# else
268 if (kp.ki_pid != pid)
269 return QString();
270 QString name = QFile::decodeName(kp.ki_comm);
271# endif
272 return name;
273
274#else
275 Q_UNUSED(pid);
276 return QString();
277#endif
278}
279
280void QLockFile::unlock()
281{
282 Q_D(QLockFile);
283 if (!d->isLocked)
284 return;
285 close(d->fileHandle);
286 d->fileHandle = -1;
287 if (!QFile::remove(d->fileName)) {
288 qWarning() << "Could not remove our own lock file" << d->fileName << "maybe permissions changed meanwhile?";
289 // This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
290 }
291 d->lockError = QLockFile::NoError;
292 d->isLocked = false;
293}
294
295QT_END_NAMESPACE
296