QLockInfo: Centralize the management of the lock file's contents

We duplicated both the creation of the contents and the check if the
file was stale. Centralize everything in qlockfile.cpp.

Change-Id: I0b48fc8e90304e0dacc3fffd14e91174af79841f
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@qt.io>
Reviewed-by: Olivier Goffart (Woboq GmbH) <ogoffart@woboq.com>
This commit is contained in:
Thiago Macieira 2017-09-29 23:59:03 -07:00
parent 3399d79773
commit 4dca0cba30
4 changed files with 100 additions and 77 deletions

View File

@ -2,6 +2,7 @@
** **
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org> ** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2017 Intel Corporation.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of the QtCore module of the Qt Toolkit. ** This file is part of the QtCore module of the Qt Toolkit.
@ -42,12 +43,34 @@
#include "qlockfile_p.h" #include "qlockfile_p.h"
#include <QtCore/qthread.h> #include <QtCore/qthread.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdeadlinetimer.h> #include <QtCore/qdeadlinetimer.h>
#include <QtCore/qdatetime.h> #include <QtCore/qdatetime.h>
#include <QtCore/qfileinfo.h> #include <QtCore/qfileinfo.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
namespace {
struct LockFileInfo
{
qint64 pid;
QString appname;
QString hostname;
};
}
static bool getLockInfo_helper(const QString &fileName, LockFileInfo *info);
static QString machineName()
{
#ifdef Q_OS_WIN
// we don't use QSysInfo because it tries to do name resolution
return qEnvironmentVariable("COMPUTERNAME");
#else
return QSysInfo::machineHostName();
#endif
}
/*! /*!
\class QLockFile \class QLockFile
\inmodule QtCore \inmodule QtCore
@ -291,10 +314,27 @@ bool QLockFile::tryLock(int timeout)
bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
{ {
Q_D(const QLockFile); Q_D(const QLockFile);
return d->getLockInfo(pid, hostname, appname); LockFileInfo info;
if (!getLockInfo_helper(d->fileName, &info))
return false;
if (pid)
*pid = info.pid;
if (hostname)
*hostname = info.hostname;
if (appname)
*appname = info.appname;
return true;
} }
bool QLockFilePrivate::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const QByteArray QLockFilePrivate::lockFileContents() const
{
// Use operator% from the fast builder to avoid multiple memory allocations.
return QByteArray::number(QCoreApplication::applicationPid()) % '\n'
% QCoreApplication::applicationName().toUtf8() % '\n'
% machineName().toUtf8() % '\n';
}
static bool getLockInfo_helper(const QString &fileName, LockFileInfo *info)
{ {
QFile reader(fileName); QFile reader(fileName);
if (!reader.open(QIODevice::ReadOnly)) if (!reader.open(QIODevice::ReadOnly))
@ -309,14 +349,25 @@ bool QLockFilePrivate::getLockInfo(qint64 *pid, QString *hostname, QString *appn
QByteArray hostNameLine = reader.readLine(); QByteArray hostNameLine = reader.readLine();
hostNameLine.chop(1); hostNameLine.chop(1);
qint64 thePid = pidLine.toLongLong(); bool ok;
if (pid) info->appname = QString::fromUtf8(appNameLine);
*pid = thePid; info->hostname = QString::fromUtf8(hostNameLine);
if (appname) info->pid = pidLine.toLongLong(&ok);
*appname = QString::fromUtf8(appNameLine); return ok && info->pid > 0;
if (hostname) }
*hostname = QString::fromUtf8(hostNameLine);
return thePid > 0; bool QLockFilePrivate::isApparentlyStale() const
{
LockFileInfo info;
if (getLockInfo_helper(fileName, &info)) {
if (info.hostname.isEmpty() || info.hostname == machineName()) {
if (!isProcessRunning(info.pid, info.appname))
return true;
}
}
const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTimeUtc());
return staleLockTime > 0 && qAbs(age) > staleLockTime;
} }
/*! /*!

View File

@ -78,12 +78,14 @@ public:
} }
QLockFile::LockError tryLock_sys(); QLockFile::LockError tryLock_sys();
bool removeStaleLock(); bool removeStaleLock();
bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const; QByteArray lockFileContents() const;
// Returns \c true if the lock belongs to dead PID, or is old. // 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. // The attempt to delete it will tell us if it was really stale or not, though.
bool isApparentlyStale() const; bool isApparentlyStale() const;
// used in dbusmenu // used in dbusmenu
Q_CORE_EXPORT static QString processNameByPid(qint64 pid); Q_CORE_EXPORT static QString processNameByPid(qint64 pid);
static bool isProcessRunning(qint64 pid, const QString &appname);
QString fileName; QString fileName;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN

View File

@ -1,7 +1,7 @@
/**************************************************************************** /****************************************************************************
** **
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org> ** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
** Copyright (C) 2016 Intel Corporation. ** Copyright (C) 2017 Intel Corporation.
** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
@ -42,7 +42,6 @@
#include "private/qlockfile_p.h" #include "private/qlockfile_p.h"
#include "QtCore/qtemporaryfile.h" #include "QtCore/qtemporaryfile.h"
#include "QtCore/qcoreapplication.h"
#include "QtCore/qfileinfo.h" #include "QtCore/qfileinfo.h"
#include "QtCore/qdebug.h" #include "QtCore/qdebug.h"
#include "QtCore/qdatetime.h" #include "QtCore/qdatetime.h"
@ -147,13 +146,6 @@ static bool setNativeLocks(int fd)
QLockFile::LockError QLockFilePrivate::tryLock_sys() QLockFile::LockError QLockFilePrivate::tryLock_sys()
{ {
// Assemble data, to write in a single call to write
// (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'
% QCoreApplication::applicationName().toUtf8() % '\n'
% QSysInfo::machineHostName().toUtf8() % '\n';
const QByteArray lockFileName = QFile::encodeName(fileName); const QByteArray lockFileName = QFile::encodeName(fileName);
const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0666); const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd < 0) { if (fd < 0) {
@ -173,6 +165,7 @@ QLockFile::LockError QLockFilePrivate::tryLock_sys()
qWarning() << "setNativeLocks failed:" << qt_error_string(errnoSaved); qWarning() << "setNativeLocks failed:" << qt_error_string(errnoSaved);
} }
QByteArray fileData = lockFileContents();
if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) { if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) {
qt_safe_close(fd); qt_safe_close(fd);
if (!QFile::remove(fileName)) if (!QFile::remove(fileName))
@ -204,26 +197,21 @@ bool QLockFilePrivate::removeStaleLock()
return success; return success;
} }
bool QLockFilePrivate::isApparentlyStale() const bool QLockFilePrivate::isProcessRunning(qint64 pid, const QString &appname)
{ {
qint64 pid;
QString hostname, appname;
if (getLockInfo(&pid, &hostname, &appname)) {
if (hostname.isEmpty() || hostname == QSysInfo::machineHostName()) {
if (::kill(pid, 0) == -1 && errno == ESRCH) if (::kill(pid, 0) == -1 && errno == ESRCH)
return true; // PID doesn't exist anymore return false; // PID doesn't exist anymore
const QString processName = processNameByPid(pid); const QString processName = processNameByPid(pid);
if (!processName.isEmpty()) { if (!processName.isEmpty()) {
QFileInfo fi(appname); QFileInfo fi(appname);
if (fi.isSymLink()) if (fi.isSymLink())
fi.setFile(fi.symLinkTarget()); fi.setFile(fi.symLinkTarget());
if (processName != fi.fileName()) if (processName != fi.fileName())
return true; // PID got reused by a different application. return false; // PID got reused by a different application.
} }
}
} return true;
const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
return staleLockTime > 0 && qAbs(age) > staleLockTime;
} }
QString QLockFilePrivate::processNameByPid(qint64 pid) QString QLockFilePrivate::processNameByPid(qint64 pid)

View File

@ -2,6 +2,7 @@
** **
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org> ** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2017 Intel Corporation.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of the QtCore module of the Qt Toolkit. ** This file is part of the QtCore module of the Qt Toolkit.
@ -42,7 +43,6 @@
#include "private/qfilesystementry_p.h" #include "private/qfilesystementry_p.h"
#include <qt_windows.h> #include <qt_windows.h>
#include "QtCore/qcoreapplication.h"
#include "QtCore/qfileinfo.h" #include "QtCore/qfileinfo.h"
#include "QtCore/qdatetime.h" #include "QtCore/qdatetime.h"
#include "QtCore/qdebug.h" #include "QtCore/qdebug.h"
@ -50,11 +50,6 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
static inline QByteArray localHostName()
{
return qgetenv("COMPUTERNAME");
}
static inline bool fileExists(const wchar_t *fileName) static inline bool fileExists(const wchar_t *fileName)
{ {
WIN32_FILE_ATTRIBUTE_DATA data; WIN32_FILE_ATTRIBUTE_DATA data;
@ -107,15 +102,7 @@ QLockFile::LockError QLockFilePrivate::tryLock_sys()
// We hold the lock, continue. // We hold the lock, continue.
fileHandle = fh; fileHandle = fh;
// Assemble data, to write in a single call to write QByteArray fileData = lockFileContents();
// (otherwise we'd have to check every write call)
QByteArray fileData;
fileData += QByteArray::number(QCoreApplication::applicationPid());
fileData += '\n';
fileData += QCoreApplication::applicationName().toUtf8();
fileData += '\n';
fileData += localHostName();
fileData += '\n';
DWORD bytesWritten = 0; DWORD bytesWritten = 0;
QLockFile::LockError error = QLockFile::NoError; QLockFile::LockError error = QLockFile::NoError;
if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh)) if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh))
@ -129,38 +116,33 @@ bool QLockFilePrivate::removeStaleLock()
return QFile::remove(fileName); return QFile::remove(fileName);
} }
bool QLockFilePrivate::isApparentlyStale() const bool QLockFilePrivate::isProcessRunning(qint64 pid, const QString &appname)
{ {
qint64 pid;
QString hostname, appname;
// On WinRT there seems to be no way of obtaining information about other // On WinRT there seems to be no way of obtaining information about other
// processes due to sandboxing // processes due to sandboxing
#ifndef Q_OS_WINRT #ifndef Q_OS_WINRT
if (getLockInfo(&pid, &hostname, &appname)) {
if (hostname.isEmpty() || hostname == QString::fromLocal8Bit(localHostName())) {
HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!procHandle) if (!procHandle)
return true; return false;
// We got a handle but check if process is still alive // We got a handle but check if process is still alive
DWORD exitCode = 0; DWORD exitCode = 0;
if (!::GetExitCodeProcess(procHandle, &exitCode)) if (!::GetExitCodeProcess(procHandle, &exitCode))
exitCode = 0; exitCode = 0;
::CloseHandle(procHandle); ::CloseHandle(procHandle);
if (exitCode != STILL_ACTIVE) if (exitCode != STILL_ACTIVE)
return true; return false;
const QString processName = processNameByPid(pid); const QString processName = processNameByPid(pid);
if (!processName.isEmpty() && processName != appname) if (!processName.isEmpty() && processName != appname)
return true; // PID got reused by a different application. return false; // PID got reused by a different application.
}
}
#else // !Q_OS_WINRT #else // !Q_OS_WINRT
Q_UNUSED(pid); Q_UNUSED(pid);
Q_UNUSED(hostname);
Q_UNUSED(appname); Q_UNUSED(appname);
#endif // Q_OS_WINRT #endif // Q_OS_WINRT
const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
return staleLockTime > 0 && qAbs(age) > staleLockTime; return true;
} }
QString QLockFilePrivate::processNameByPid(qint64 pid) QString QLockFilePrivate::processNameByPid(qint64 pid)