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