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#if defined(Q_OS_OHOS)
18#include "QtCore/qcoreapplication.h"
19#endif
20
21#include "private/qcore_unix_p.h" // qt_safe_open
22#include "private/qabstractfileengine_p.h"
23#include "private/qfilesystementry_p.h"
24#include "private/qtemporaryfile_p.h"
25
26#if !defined(Q_OS_INTEGRITY)
27#include <sys/file.h> // flock
28#endif
29
30#if defined(Q_OS_RTEMS)
31// flock() does not work in these OSes and produce warnings when we try to use
32# undef LOCK_EX
33# undef LOCK_NB
34#endif
35
36#include <sys/types.h> // kill
37#include <signal.h> // kill
38#include <unistd.h> // gethostname
39
40#if defined(Q_OS_MACOS)
41# include <libproc.h>
42#elif defined(Q_OS_LINUX)
43# include <unistd.h>
44# include <cstdio>
45#elif defined(Q_OS_HAIKU)
46# include <kernel/OS.h>
47#elif defined(Q_OS_BSD4) && !defined(QT_PLATFORM_UIKIT)
48# include <sys/cdefs.h>
49# include <sys/param.h>
50# include <sys/sysctl.h>
51# if !defined(Q_OS_NETBSD)
52# include <sys/user.h>
53# endif
54#endif
55
56QT_BEGIN_NAMESPACE
57
58// ### merge into qt_safe_write?
59static qint64 qt_write_loop(int fd, const char *data, qint64 len)
60{
61 qint64 pos = 0;
62 while (pos < len) {
63 const qint64 ret = qt_safe_write(fd, data + pos, len - pos);
64 if (ret == -1) // e.g. partition full
65 return pos;
66 pos += ret;
67 }
68 return pos;
69}
70
71/*
72 * Details about file locking on Unix.
73 *
74 * There are three types of advisory locks on Unix systems:
75 * 1) POSIX process-wide locks using fcntl(F_SETLK)
76 * 2) BSD flock(2) system call
77 * 3) Linux-specific file descriptor locks using fcntl(F_OFD_SETLK)
78 * There's also a mandatory locking feature by POSIX, which is deprecated on
79 * Linux and users are advised not to use it.
80 *
81 * The first problem is that the POSIX API is braindead. POSIX.1-2008 says:
82 *
83 * All locks associated with a file for a given process shall be removed when
84 * a file descriptor for that file is closed by that process or the process
85 * holding that file descriptor terminates.
86 *
87 * The Linux manpage is clearer:
88 *
89 * * If a process closes _any_ file descriptor referring to a file, then all
90 * of the process's locks on that file are released, regardless of the file
91 * descriptor(s) on which the locks were obtained. This is bad: [...]
92 *
93 * * The threads in a process share locks. In other words, a multithreaded
94 * program can't use record locking to ensure that threads don't
95 * simultaneously access the same region of a file.
96 *
97 * So in order to use POSIX locks, we'd need a global mutex that stays locked
98 * while the QLockFile is locked. For that reason, Qt does not use POSIX
99 * advisory locks anymore.
100 *
101 * The next problem is that POSIX leaves undefined the relationship between
102 * locks with fcntl(), flock() and lockf(). In some systems (like the BSDs),
103 * all three use the same record set, while on others (like Linux) the locks
104 * are independent, except if locking over NFS mounts, in which case they're
105 * actually the same. Therefore, it's a very bad idea to mix them in the same
106 * process.
107 *
108 * We therefore use only flock(2), except on Android.
109 *
110 * Android Compatibility:
111 * Some versions of Android have known issues where flock does not function correctly.
112 * As a result, on Android, we use POSIX fcntl(F_SETLK) to handle file locking.
113 * fcntl is better integrated with Android’s underlying system, avoiding
114 * the limitations of flock.
115 */
116
117static bool setNativeLocks(int fd)
118{
119#if defined(Q_OS_ANDROID)
120 struct flock fl;
121 fl.l_type = F_WRLCK;
122 fl.l_whence = SEEK_SET;
123 fl.l_start = 0;
124 fl.l_len = 0;
125 if (fcntl(fd, F_SETLK, &fl) == -1)
126 return false;
127#elif defined(LOCK_EX) && defined(LOCK_NB)
128 if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
129 return false;
130#else
131 Q_UNUSED(fd);
132#endif
133 return true;
134}
135
136QLockFile::LockError QLockFilePrivate::tryLock_sys()
137{
138 const QByteArray lockFileName = QFile::encodeName(fileName);
139 const int fd = qt_safe_open(lockFileName.constData(), O_RDWR | O_CREAT | O_EXCL, 0666);
140 if (fd < 0) {
141 switch (errno) {
142 case EEXIST:
143 return QLockFile::LockFailedError;
144 case EACCES:
145 case EROFS:
146 return QLockFile::PermissionError;
147 default:
148 return QLockFile::UnknownError;
149 }
150 }
151 // Ensure nobody else can delete the file while we have it
152 if (!setNativeLocks(fd)) {
153 const int errnoSaved = errno;
154 qWarning() << "setNativeLocks failed:" << qt_error_string(errnoSaved);
155 }
156
157 QByteArray fileData = lockFileContents();
158 if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) {
159 qt_safe_close(fd);
160 if (unlink(lockFileName) != 0)
161 qWarning("QLockFile: Could not remove our own lock file %s: %ls.",
162 lockFileName.constBegin(), qUtf16Printable(qt_error_string()));
163 return QLockFile::UnknownError; // partition full
164 }
165
166 // We hold the lock, continue.
167 fileHandle = fd;
168
169 // Sync to disk if possible. Ignore errors (e.g. not supported).
170#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
171 fdatasync(fileHandle);
172#else
173 fsync(fileHandle);
174#endif
175
176 return QLockFile::NoError;
177}
178
179bool QLockFilePrivate::removeStaleLock()
180{
181 const QByteArray lockFileName = QFile::encodeName(fileName);
182 const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0666);
183 if (fd < 0) // gone already?
184 return false;
185 bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0);
186 close(fd);
187 return success;
188}
189
190bool QLockFilePrivate::isProcessRunning(qint64 pid, const QString &appname)
191{
192 if (::kill(pid_t(pid), 0) == -1 && errno == ESRCH)
193 return false; // PID doesn't exist anymore
194
195 const QString processName = processNameByPid(pid);
196 if (!processName.isEmpty()) {
197 QFileInfo fi(appname);
198 if (fi.isSymLink())
199 fi.setFile(fi.symLinkTarget());
200 if (processName != fi.fileName())
201 return false; // PID got reused by a different application.
202 }
203
204 return true;
205}
206
207QString QLockFilePrivate::processNameByPid(qint64 pid)
208{
209#if defined(Q_OS_MACOS)
210 char name[1024];
211 proc_name(pid, name, sizeof(name) / sizeof(char));
212 return QFile::decodeName(name);
213#elif defined(Q_OS_OHOS)
214 // On OHOS, all Qt applications are started by the appspawn daemon via fork() + dlopen(),
215 // so /proc/{pid}/exe always resolves to "appspawn" rather than the actual application.
216 // For the current process, qAppName() is used instead, which is set by the OHOS QPA plugin
217 // to the loaded .so path (e.g. libMyApp.so). For external processes, /proc/{pid}/... is
218 // inaccessible due to sandboxing, so an empty string is returned; this causes
219 // isProcessRunning() to skip the name comparison and rely solely on PID existence.
220 // Note: this means PID reuse by an external process is not detected.
221 return pid == ::getpid() ? qAppName() : QString();
222#elif defined(Q_OS_LINUX)
223 if (!qt_haveLinuxProcfs())
224 return QString();
225
226 char exePath[64];
227 sprintf(exePath, "/proc/%lld/exe", pid);
228
229 QByteArray buf = qt_readlink(exePath);
230 if (buf.isEmpty()) {
231 // The pid is gone. Return some invalid process name to fail the test.
232 return QStringLiteral("/ERROR/");
233 }
234
235 // remove the " (deleted)" suffix, if any
236 static const char deleted[] = " (deleted)";
237 if (buf.endsWith(deleted))
238 buf.chop(strlen(deleted));
239
240 return QFileSystemEntry(buf, QFileSystemEntry::FromNativePath()).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#elif defined(Q_OS_QNX)
274 char exePath[PATH_MAX];
275 sprintf(exePath, "/proc/%lld/exefile", pid);
276
277 int fd = qt_safe_open(exePath, O_RDONLY);
278 if (fd == -1)
279 return QString();
280
281 QT_STATBUF sbuf;
282 if (QT_FSTAT(fd, &sbuf) == -1) {
283 qt_safe_close(fd);
284 return QString();
285 }
286
287 QByteArray buffer(sbuf.st_size, Qt::Uninitialized);
288 buffer.resize(qt_safe_read(fd, buffer.data(), sbuf.st_size - 1));
289 if (buffer.isEmpty()) {
290 // The pid is gone. Return some invalid process name to fail the test.
291 return QStringLiteral("/ERROR/");
292 }
293 return QFileSystemEntry(buffer, QFileSystemEntry::FromNativePath()).fileName();
294#else
295 Q_UNUSED(pid);
296 return QString();
297#endif
298}
299
300int QLockFilePrivate::openNewFileDescriptor(const QString &fileName)
301{
302 return QT_OPEN(fileName.toLocal8Bit().constData(), QT_OPEN_RDONLY);
303}
304
305void QLockFile::unlock()
306{
307 Q_D(QLockFile);
308 if (!d->isLocked)
309 return;
310
311 const QByteArray lockFileName = QFile::encodeName(d->fileName);
312 if (unlink(lockFileName) != 0) {
313 qWarning("Could not remove our own lock file %s: %ls (maybe permissions changed meanwhile?)",
314 lockFileName.constBegin(), qUtf16Printable(qt_error_string()));
315 // This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
316 }
317
318 close(d->fileHandle);
319 d->fileHandle = -1;
320 d->lockError = QLockFile::NoError;
321 d->isLocked = false;
322}
323
324QT_END_NAMESPACE
static bool setNativeLocks(int fd)