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 | |
87 | QT_BEGIN_NAMESPACE |
88 | |
89 | // ### merge into qt_safe_write? |
90 | static 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 | |
142 | static 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 | |
153 | QLockFile::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 | |
195 | bool 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 | |
206 | bool 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 | |
223 | QString 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 | |
281 | void 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 | |
296 | QT_END_NAMESPACE |
297 | |