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?
qt_write_loop(int fd,const char * data,qint64 len)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 + pos, 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 
setNativeLocks(int fd)142 static bool setNativeLocks(int fd)
143 {
144 #if defined(LOCK_EX) && defined(LOCK_NB)
145     if (flock(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 
tryLock_sys()153 QLockFile::LockError QLockFilePrivate::tryLock_sys()
154 {
155     const QByteArray lockFileName = QFile::encodeName(fileName);
156     const int fd = qt_safe_open(lockFileName.constData(), O_RDWR | O_CREAT | O_EXCL, 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(errnoSaved);
172     }
173 
174     QByteArray fileData = lockFileContents();
175     if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) {
176         qt_safe_close(fd);
177         if (!QFile::remove(fileName))
178             qWarning("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(fileHandle);
188 #else
189     fsync(fileHandle);
190 #endif
191 
192     return QLockFile::NoError;
193 }
194 
removeStaleLock()195 bool QLockFilePrivate::removeStaleLock()
196 {
197     const QByteArray lockFileName = QFile::encodeName(fileName);
198     const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0666);
199     if (fd < 0) // gone already?
200         return false;
201     bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0);
202     close(fd);
203     return success;
204 }
205 
isProcessRunning(qint64 pid,const QString & appname)206 bool QLockFilePrivate::isProcessRunning(qint64 pid, const QString &appname)
207 {
208     if (::kill(pid, 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 
processNameByPid(qint64 pid)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(exePath, "/proc/%lld/exe", pid);
235 
236     QByteArray buf = qt_readlink(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(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 
unlock()281 void QLockFile::unlock()
282 {
283     Q_D(QLockFile);
284     if (!d->isLocked)
285         return;
286     close(d->fileHandle);
287     d->fileHandle = -1;
288     if (!QFile::remove(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