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

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