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