summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/io/qlockfile.cpp12
-rw-r--r--src/corelib/io/qlockfile_p.h1
-rw-r--r--src/corelib/io/qlockfile_unix.cpp48
-rw-r--r--src/corelib/io/qlockfile_win.cpp45
-rw-r--r--tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp48
5 files changed, 150 insertions, 4 deletions
diff --git a/src/corelib/io/qlockfile.cpp b/src/corelib/io/qlockfile.cpp
index 4f5aeff395..2bd996d213 100644
--- a/src/corelib/io/qlockfile.cpp
+++ b/src/corelib/io/qlockfile.cpp
@@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
+** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
@@ -66,9 +67,12 @@ QT_BEGIN_NAMESPACE
If the process holding the lock crashes, the lock file stays on disk and can prevent
any other process from accessing the shared resource, ever. For this reason, QLockFile
- tries to detect such a "stale" lock file, based on the process ID written into the file,
- and (in case that process ID got reused meanwhile), on the last modification time of
- the lock file (30s by default, for the use case of a short-lived operation).
+ tries to detect such a "stale" lock file, based on the process ID written into the file.
+ To cover the situation that the process ID got reused meanwhile, the current process name is
+ compared to the name of the process that corresponds to the process ID from the lock file.
+ If the process names differ, the lock file is considered stale.
+ Additionally, the last modification time of the lock file (30s by default, for the use case of a
+ short-lived operation) is taken into account.
If the lock file is found to be stale, it will be deleted.
For the use case of protecting a resource over a long time, you should therefore call
@@ -122,7 +126,7 @@ QLockFile::~QLockFile()
The value of \a staleLockTime is used by lock() and tryLock() in order
to determine when an existing lock file is considered stale, i.e. left over
by a crashed process. This is useful for the case where the PID got reused
- meanwhile, so the only way to detect a stale lock file is by the fact that
+ meanwhile, so one way to detect a stale lock file is by the fact that
it has been around for a long time.
\sa staleLockTime()
diff --git a/src/corelib/io/qlockfile_p.h b/src/corelib/io/qlockfile_p.h
index 0cfaa42849..168062f467 100644
--- a/src/corelib/io/qlockfile_p.h
+++ b/src/corelib/io/qlockfile_p.h
@@ -75,6 +75,7 @@ public:
// Returns \c true if the lock belongs to dead PID, or is old.
// The attempt to delete it will tell us if it was really stale or not, though.
bool isApparentlyStale() const;
+ static QString processNameByPid(qint64 pid);
#ifdef Q_OS_UNIX
static int checkFcntlWorksAfterFlock();
diff --git a/src/corelib/io/qlockfile_unix.cpp b/src/corelib/io/qlockfile_unix.cpp
index d1804f2cb6..d6ea2f1f2d 100644
--- a/src/corelib/io/qlockfile_unix.cpp
+++ b/src/corelib/io/qlockfile_unix.cpp
@@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
+** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
@@ -48,6 +49,15 @@
#include <signal.h> // kill
#include <unistd.h> // gethostname
+#if defined(Q_OS_OSX)
+# include <libproc.h>
+#elif defined(Q_OS_LINUX)
+# include <unistd.h>
+# include <cstdio>
+#elif defined(Q_OS_BSD4) && !defined(Q_OS_IOS)
+# include <sys/user.h>
+#endif
+
QT_BEGIN_NAMESPACE
static QByteArray localHostName() // from QHostInfo::localHostName(), modified to return a QByteArray
@@ -189,12 +199,50 @@ bool QLockFilePrivate::isApparentlyStale() const
if (hostname.isEmpty() || hostname == QString::fromLocal8Bit(localHostName())) {
if (::kill(pid, 0) == -1 && errno == ESRCH)
return true; // PID doesn't exist anymore
+ const QString processName = processNameByPid(pid);
+ if (!processName.isEmpty()) {
+ QFileInfo fi(appname);
+ if (fi.isSymLink())
+ fi.setFile(fi.symLinkTarget());
+ if (processName != fi.fileName())
+ return true; // PID got reused by a different application.
+ }
}
}
const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
return staleLockTime > 0 && age > staleLockTime;
}
+QString QLockFilePrivate::processNameByPid(qint64 pid)
+{
+#if defined(Q_OS_OSX)
+ char name[1024];
+ proc_name(pid, name, sizeof(name) / sizeof(char));
+ return QString::fromUtf8(name);
+#elif defined(Q_OS_LINUX)
+ if (!QFile::exists(QStringLiteral("/proc/version")))
+ return QString();
+ char exePath[64];
+ char buf[PATH_MAX];
+ memset(buf, 0, sizeof(buf));
+ sprintf(exePath, "/proc/%lld/exe", pid);
+ if (readlink(exePath, buf, sizeof(buf)) < 0) {
+ // The pid is gone. Return some invalid process name to fail the test.
+ return QStringLiteral("/ERROR/");
+ }
+ return QFileInfo(QString::fromUtf8(buf)).fileName();
+#elif defined(Q_OS_BSD4) && !defined(Q_OS_IOS)
+ kinfo_proc *proc = kinfo_getproc(pid);
+ if (!proc)
+ return QString();
+ QString name = QString::fromUtf8(proc->ki_comm);
+ free(proc);
+ return name;
+#else
+ return QString();
+#endif
+}
+
void QLockFile::unlock()
{
Q_D(QLockFile);
diff --git a/src/corelib/io/qlockfile_win.cpp b/src/corelib/io/qlockfile_win.cpp
index 4e0d8134ec..8cbe0a9dfd 100644
--- a/src/corelib/io/qlockfile_win.cpp
+++ b/src/corelib/io/qlockfile_win.cpp
@@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
+** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
@@ -140,6 +141,9 @@ bool QLockFilePrivate::isApparentlyStale() const
::CloseHandle(procHandle);
if (dwR == WAIT_TIMEOUT)
return true;
+ const QString processName = processNameByPid(pid);
+ if (!processName.isEmpty() && processName != appname)
+ return true; // PID got reused by a different application.
}
}
#else // !Q_OS_WINRT
@@ -151,6 +155,47 @@ bool QLockFilePrivate::isApparentlyStale() const
return staleLockTime > 0 && age > staleLockTime;
}
+QString QLockFilePrivate::processNameByPid(qint64 pid)
+{
+#if !defined(Q_OS_WINRT) && !defined(Q_OS_WINCE)
+ typedef DWORD (WINAPI *GetModuleFileNameExFunc)(HANDLE, HMODULE, LPTSTR, DWORD);
+
+ HMODULE hPsapi = LoadLibraryA("psapi");
+ if (!hPsapi)
+ return QString();
+
+ GetModuleFileNameExFunc qGetModuleFileNameEx
+ = (GetModuleFileNameExFunc)GetProcAddress(hPsapi, "GetModuleFileNameExW");
+ if (!qGetModuleFileNameEx) {
+ FreeLibrary(hPsapi);
+ return QString();
+ }
+
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, DWORD(pid));
+ if (!hProcess) {
+ FreeLibrary(hPsapi);
+ return QString();
+ }
+ wchar_t buf[MAX_PATH];
+ const DWORD length = qGetModuleFileNameEx(hProcess, NULL, buf, sizeof(buf) / sizeof(wchar_t));
+ CloseHandle(hProcess);
+ FreeLibrary(hPsapi);
+ if (!length)
+ return QString();
+ QString name = QString::fromWCharArray(buf, length);
+ int i = name.lastIndexOf(QLatin1Char('\\'));
+ if (i >= 0)
+ name.remove(0, i + 1);
+ i = name.lastIndexOf(QLatin1Char('.'));
+ if (i >= 0)
+ name.truncate(i);
+ return name;
+#else
+ Q_UNUSED(pid);
+ return QString();
+#endif
+}
+
void QLockFile::unlock()
{
Q_D(QLockFile);
diff --git a/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp b/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp
index 8d890e81fa..27614e0eb8 100644
--- a/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp
+++ b/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp
@@ -57,6 +57,7 @@ private slots:
void waitForLock();
void staleLockFromCrashedProcess_data();
void staleLockFromCrashedProcess();
+ void staleLockFromCrashedProcessReusedPid();
void staleShortLockFromBusyProcess();
void staleLongLockFromBusyProcess();
void staleLockRace();
@@ -64,6 +65,9 @@ private slots:
void noPermissionsWindows();
void corruptedLockFile();
+private:
+ static bool overwritePidInLockFile(const QString &filePath, qint64 pid);
+
public:
QString m_helperApp;
QTemporaryDir dir;
@@ -277,6 +281,30 @@ void tst_QLockFile::staleLockFromCrashedProcess()
#endif // !QT_NO_PROCESS
}
+void tst_QLockFile::staleLockFromCrashedProcessReusedPid()
+{
+#if defined(QT_NO_PROCESS)
+ QSKIP("This test requires QProcess support");
+#elif defined(Q_OS_WINRT) || defined(Q_OS_WINCE) || defined(Q_OS_IOS)
+ QSKIP("We cannot retrieve information about other processes on this platform.");
+#else
+ const QString fileName = dir.path() + "/staleLockFromCrashedProcessReusedPid";
+
+ int ret = QProcess::execute(m_helperApp, QStringList() << fileName << "-crash");
+ QCOMPARE(ret, int(QLockFile::NoError));
+ QVERIFY(QFile::exists(fileName));
+ QVERIFY(overwritePidInLockFile(fileName, QCoreApplication::applicationPid()));
+
+ QLockFile secondLock(fileName);
+ qint64 pid = 0;
+ secondLock.getLockInfo(&pid, 0, 0);
+ QCOMPARE(pid, QCoreApplication::applicationPid());
+ secondLock.setStaleLockTime(0);
+ QVERIFY(secondLock.tryLock());
+ QCOMPARE(int(secondLock.error()), int(QLockFile::NoError));
+#endif // !QT_NO_PROCESS
+}
+
void tst_QLockFile::staleShortLockFromBusyProcess()
{
#ifdef QT_NO_PROCESS
@@ -497,5 +525,25 @@ void tst_QLockFile::corruptedLockFile()
QCOMPARE(int(secondLock.error()), int(QLockFile::NoError));
}
+bool tst_QLockFile::overwritePidInLockFile(const QString &filePath, qint64 pid)
+{
+ QFile f(filePath);
+ if (!f.open(QFile::ReadWrite)) {
+ qWarning("Cannot open %s.", qPrintable(filePath));
+ return false;
+ }
+ QByteArray buf = f.readAll();
+ int i = buf.indexOf('\n');
+ if (i < 0) {
+ qWarning("Unexpected lockfile content.");
+ return false;
+ }
+ buf.remove(0, i);
+ buf.prepend(QByteArray::number(pid));
+ f.seek(0);
+ f.resize(buf.size());
+ return f.write(buf) == buf.size();
+}
+
QTEST_MAIN(tst_QLockFile)
#include "tst_qlockfile.moc"