diff --git a/src/core/qlockfile_p.h b/src/core/qlockfile_p.h index 93092683f..15fa37be6 100644 --- a/src/core/qlockfile_p.h +++ b/src/core/qlockfile_p.h @@ -83,10 +83,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; - -#ifdef Q_OS_UNIX - static int checkFcntlWorksAfterFlock(); -#endif + static QString processNameByPid(qint64 pid); QString fileName; #ifdef Q_OS_WIN diff --git a/src/core/qlockfile_unix.cpp b/src/core/qlockfile_unix.cpp index b1b808bc9..d716028bd 100644 --- a/src/core/qlockfile_unix.cpp +++ b/src/core/qlockfile_unix.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 David Faure +** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -42,10 +43,17 @@ #include // flock #include // kill #include // kill -#include +#include // gethostname #include +#if defined(Q_OS_MAC) +# include +#elif defined(Q_OS_LINUX) +# include +# include +#endif + QT_BEGIN_NAMESPACE #define EINTR_LOOP(var, cmd) \ @@ -77,13 +85,13 @@ static inline qint64 qt_safe_write(int fd, const void *data, qint64 len) return ret; } -static QString localHostName() // from QHostInfo::localHostName() +static QByteArray localHostName() // from QHostInfo::localHostName(), modified to return a QByteArray { - char hostName[512]; - if (gethostname(hostName, sizeof(hostName)) == -1) - return QString(); - hostName[sizeof(hostName) - 1] = '\0'; - return QString::fromLocal8Bit(hostName); + QByteArray hostName(512, Qt::Uninitialized); + if (gethostname(hostName.data(), hostName.size()) == -1) + return QByteArray(); + hostName.truncate(strlen(hostName.data())); + return hostName; } // ### merge into qt_safe_write? @@ -122,8 +130,8 @@ QLockFile::LockError QLockFilePrivate::tryLock_sys() // (otherwise we'd have to check every write call) // Use operator% from the fast builder to avoid multiple memory allocations. QByteArray fileData = QByteArray::number(QCoreApplication::applicationPid()) + '\n' - + qAppName().toUtf8() + '\n' - + localHostName().toUtf8() + '\n'; + + QCoreApplication::applicationName().toUtf8() + '\n' + + localHostName() + '\n'; const QByteArray lockFileName = QFile::encodeName(fileName); const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644); @@ -170,16 +178,48 @@ bool QLockFilePrivate::isApparentlyStale() const { qint64 pid; QString hostname, appname; - if (!getLockInfo(&pid, &hostname, &appname)) - return false; - if (hostname.isEmpty() || hostname == localHostName()) { - if (::kill(pid, 0) == -1 && errno == ESRCH) - return true; // PID doesn't exist anymore + if (getLockInfo(&pid, &hostname, &appname)) { + 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.toLower() != fi.fileName().toLower()) + return true; // PID got reused by a different application. + } + } } const qint64 age = QFileInfo(fileName).lastModified().secsTo(QDateTime::currentDateTime()) * 1000; return staleLockTime > 0 && age > staleLockTime; } +QString QLockFilePrivate::processNameByPid(qint64 pid) +{ +#if defined(Q_OS_MAC) + char name[1024]; + proc_name(pid, name, sizeof(name) / sizeof(char)); + return QFile::decodeName(name); +#elif defined(Q_OS_LINUX) + if (!QFile::exists(QString("/proc/version"))) + return QString(); + char exePath[64]; + char buf[PATH_MAX + 1]; + sprintf(exePath, "/proc/%lld/exe", pid); + size_t len = static_cast(readlink(exePath, buf, sizeof(buf))); + if (len >= sizeof(buf)) { + // The pid is gone. Return some invalid process name to fail the test. + return QString("/ERROR/"); + } + buf[len] = 0; + return QFileInfo(QFile::decodeName(buf)).fileName(); +#else + return QString(); +#endif +} + void QLockFile::unlock() { Q_D(QLockFile); @@ -191,7 +231,6 @@ void QLockFile::unlock() qWarning() << "Could not remove our own lock file" << d->fileName << "maybe permissions changed meanwhile?"; // This is bad because other users of this lock file will now have to wait for the stale-lock-timeout... } - QFile::remove(d->fileName); d->lockError = QLockFile::NoError; d->isLocked = false; } diff --git a/src/core/qlockfile_win.cpp b/src/core/qlockfile_win.cpp index 4ca07eb7e..5fc332792 100644 --- a/src/core/qlockfile_win.cpp +++ b/src/core/qlockfile_win.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 David Faure +** 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. @@ -134,27 +135,75 @@ bool QLockFilePrivate::isApparentlyStale() const { qint64 pid; QString hostname, appname; - if (!getLockInfo(&pid, &hostname, &appname)) - return false; // On WinRT there seems to be no way of obtaining information about other // processes due to sandboxing #ifndef Q_OS_WINRT - if (hostname == QString::fromLocal8Bit(localHostName())) { - HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); - if (!procHandle) - return true; - // We got a handle but check if process is still alive - DWORD dwR = ::WaitForSingleObject(procHandle, 0); - ::CloseHandle(procHandle); - if (dwR == WAIT_TIMEOUT) - return true; + if (getLockInfo(&pid, &hostname, &appname)) { + if (hostname.isEmpty() || hostname == QString::fromLocal8Bit(localHostName())) { + HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); + if (!procHandle) + return true; + // We got a handle but check if process is still alive + DWORD dwR = ::WaitForSingleObject(procHandle, 0); + ::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. + } } -#endif // !Q_OS_WINRT +#else // !Q_OS_WINRT + Q_UNUSED(pid); + Q_UNUSED(hostname); + Q_UNUSED(appname); +#endif // Q_OS_WINRT const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime()); 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);