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

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