diff options
-rw-r--r-- | dist/changes-5.2.0 | 4 | ||||
-rw-r--r-- | src/serialport/qserialport_unix.cpp | 64 | ||||
-rw-r--r-- | src/serialport/qserialport_unix_p.h | 9 | ||||
-rw-r--r-- | src/serialport/qserialportinfo_unix.cpp | 11 | ||||
-rw-r--r-- | src/serialport/qt4support/include/QtCore/qlockfile.h (renamed from src/serialport/qttylocker_unix_p.h) | 48 | ||||
-rw-r--r-- | src/serialport/qt4support/include/private/qlockfile_p.h | 104 | ||||
-rw-r--r-- | src/serialport/qt4support/install-helper.pri | 11 | ||||
-rw-r--r-- | src/serialport/qt4support/src/qlockfile.cpp | 352 | ||||
-rw-r--r-- | src/serialport/qt4support/src/qlockfile_unix.cpp | 199 | ||||
-rw-r--r-- | src/serialport/qttylocker_unix.cpp | 188 | ||||
-rw-r--r-- | src/serialport/serialport-lib.pri | 2 |
11 files changed, 775 insertions, 217 deletions
diff --git a/dist/changes-5.2.0 b/dist/changes-5.2.0 index 239a9a3..76360f8 100644 --- a/dist/changes-5.2.0 +++ b/dist/changes-5.2.0 @@ -107,3 +107,7 @@ warning now if it is used. - Support has been added for the hard-coded device enumeration backend to get information. Android uarts such as /dev/ttyHS* (High speed UART) and /dev/ttyHSL* (Low speed UART) are supported by that backend. + +- [QTBUG-34474] Replace the internal QTtyLocker with QLockFile from QtCore and a +small convenience on top of it to comply with the locking directories lockdev +also uses. diff --git a/src/serialport/qserialport_unix.cpp b/src/serialport/qserialport_unix.cpp index 6799814..81aafe7 100644 --- a/src/serialport/qserialport_unix.cpp +++ b/src/serialport/qserialport_unix.cpp @@ -42,7 +42,6 @@ ****************************************************************************/ #include "qserialport_unix_p.h" -#include "qttylocker_unix_p.h" #include <errno.h> #include <sys/time.h> @@ -62,6 +61,40 @@ QT_BEGIN_NAMESPACE +QString serialPortLockFilePath(const QString &portName) +{ + static const QStringList lockDirectoryPaths = QStringList() + << QLatin1String("/var/lock") + << QLatin1String("/etc/locks") + << QLatin1String("/var/spool/locks") + << QLatin1String("/var/spool/uucp") + << QLatin1String("/tmp"); + + QString lockFilePath; + + foreach (const QString &lockDirectoryPath, lockDirectoryPaths) { + QFileInfo lockDirectoryInfo(lockDirectoryPath); + if (lockDirectoryInfo.isReadable() && lockDirectoryInfo.isWritable()) { + lockFilePath = lockDirectoryPath; + break; + } + } + + if (lockFilePath.isEmpty()) { + qWarning("The following directories are not readable or writable for detaling with lock files\n"); + foreach (const QString &lockDirectoryPath, lockDirectoryPaths) + qWarning("\t%s\n", qPrintable(lockDirectoryPath)); + return QString(); + } + + QString replacedPortName = portName; + + lockFilePath.append(QLatin1String("/LCK..")); + lockFilePath.append(replacedPortName.replace(QLatin1Char('/'), QLatin1Char('_'))); + + return lockFilePath; +} + class ReadNotifier : public QSocketNotifier { Q_OBJECT @@ -146,11 +179,20 @@ bool QSerialPortPrivate::open(QIODevice::OpenMode mode) { Q_Q(QSerialPort); - QByteArray portName = portNameFromSystemLocation(systemLocation).toLocal8Bit(); - const char *ptr = portName.constData(); + QString lockFilePath = serialPortLockFilePath(portNameFromSystemLocation(systemLocation)); + bool isLockFileEmpty = lockFilePath.isEmpty(); + if (isLockFileEmpty) { + qWarning("Failed to create a lock file for opening the device"); + q->setError(QSerialPort::PermissionError); + return false; + } + + if (!lockFileScopedPointer.isNull()) { + QScopedPointer<QLockFile> newLockFileScopedPointer(new QLockFile(lockFilePath)); + lockFileScopedPointer.swap(newLockFileScopedPointer); + } - bool byCurrPid = false; - if (QTtyLocker::isLocked(ptr, &byCurrPid)) { + if (lockFileScopedPointer->isLocked()) { q->setError(QSerialPort::PermissionError); return false; } @@ -176,8 +218,8 @@ bool QSerialPortPrivate::open(QIODevice::OpenMode mode) return false; } - QTtyLocker::lock(ptr); - if (!QTtyLocker::isLocked(ptr, &byCurrPid)) { + lockFileScopedPointer->lock(); + if (!lockFileScopedPointer->isLocked()) { q->setError(QSerialPort::PermissionError); return false; } @@ -246,12 +288,8 @@ void QSerialPortPrivate::close() ::close(descriptor); - QByteArray portName = portNameFromSystemLocation(systemLocation).toLocal8Bit(); - const char *ptr = portName.constData(); - - bool byCurrPid = false; - if (QTtyLocker::isLocked(ptr, &byCurrPid) && byCurrPid) - QTtyLocker::unlock(ptr); + if (lockFileScopedPointer->isLocked()) + lockFileScopedPointer->unlock(); descriptor = -1; isCustomBaudRateSupported = false; diff --git a/src/serialport/qserialport_unix_p.h b/src/serialport/qserialport_unix_p.h index 2d09ee4..15bb5f8 100644 --- a/src/serialport/qserialport_unix_p.h +++ b/src/serialport/qserialport_unix_p.h @@ -45,6 +45,11 @@ #include "qserialport_p.h" +#include <QtCore/qlockfile.h> +#include <QtCore/qscopedpointer.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qstringlist.h> + #include <limits.h> #include <termios.h> #ifndef Q_OS_ANDROID @@ -78,6 +83,8 @@ struct serial_struct { QT_BEGIN_NAMESPACE +QString serialPortLockFilePath(const QString &portName); + class QSocketNotifier; class QSerialPortPrivate : public QSerialPortPrivateData @@ -151,6 +158,8 @@ public: bool emittedReadyRead; bool emittedBytesWritten; + QScopedPointer<QLockFile> lockFileScopedPointer; + private: bool updateTermios(); diff --git a/src/serialport/qserialportinfo_unix.cpp b/src/serialport/qserialportinfo_unix.cpp index e475e9b..38f9124 100644 --- a/src/serialport/qserialportinfo_unix.cpp +++ b/src/serialport/qserialportinfo_unix.cpp @@ -43,8 +43,9 @@ #include "qserialportinfo.h" #include "qserialportinfo_p.h" -#include "qttylocker_unix_p.h" #include "qserialport_unix_p.h" + +#include <QtCore/qlockfile.h> #include <QtCore/qfile.h> #ifndef Q_OS_MAC @@ -344,8 +345,12 @@ QList<qint32> QSerialPortInfo::standardBaudRates() bool QSerialPortInfo::isBusy() const { - bool currentPid = false; - return QTtyLocker::isLocked(portName().toLocal8Bit().constData(), ¤tPid); + QString lockFilePath = serialPortLockFilePath(portName()); + if (lockFilePath.isEmpty()) + return false; + + QLockFile lockFile(lockFilePath); + return lockFile.isLocked(); } bool QSerialPortInfo::isValid() const diff --git a/src/serialport/qttylocker_unix_p.h b/src/serialport/qt4support/include/QtCore/qlockfile.h index 9dce5d0..d46f07a 100644 --- a/src/serialport/qttylocker_unix_p.h +++ b/src/serialport/qt4support/include/QtCore/qlockfile.h @@ -1,9 +1,9 @@ /**************************************************************************** ** -** Copyright (C) 2012 Denis Shienkov <denis.shienkov@gmail.com> +** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org> ** Contact: http://www.qt-project.org/legal ** -** This file is part of the QtSerialPort module of the Qt Toolkit. +** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage @@ -39,21 +39,49 @@ ** ****************************************************************************/ -#include <QtCore/qglobal.h> +#ifndef QLOCKFILE_H +#define QLOCKFILE_H -#ifndef TTYLOCKER_UNIX_P_H -#define TTYLOCKER_UNIX_P_H +#include <QtCore/qstring.h> +#include <QtCore/qscopedpointer.h> QT_BEGIN_NAMESPACE -class QTtyLocker +class QLockFilePrivate; + +class Q_CORE_EXPORT QLockFile { public: - static bool lock(const char *portName); - static bool unlock(const char *portName); - static bool isLocked(const char *portName, bool *currentPid); + QLockFile(const QString &fileName); + ~QLockFile(); + + bool lock(); + bool tryLock(int timeout = 0); + void unlock(); + + void setStaleLockTime(int); + int staleLockTime() const; + + bool isLocked() const; + bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const; + bool removeStaleLockFile(); + + enum LockError { + NoError = 0, + LockFailedError = 1, + PermissionError = 2, + UnknownError = 3 + }; + LockError error() const; + +protected: + QScopedPointer<QLockFilePrivate> d_ptr; + +private: + Q_DECLARE_PRIVATE(QLockFile) + Q_DISABLE_COPY(QLockFile) }; QT_END_NAMESPACE -#endif // TTYLOCKER_UNIX_P_H +#endif // QLOCKFILE_H diff --git a/src/serialport/qt4support/include/private/qlockfile_p.h b/src/serialport/qt4support/include/private/qlockfile_p.h new file mode 100644 index 0000000..e08f86c --- /dev/null +++ b/src/serialport/qt4support/include/private/qlockfile_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCKFILE_P_H +#define QLOCKFILE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qlockfile.h> +#include <QtCore/qfile.h> + +#ifdef Q_OS_WIN +#include <qt_windows.h> +#endif + +QT_BEGIN_NAMESPACE + +class QLockFilePrivate +{ +public: + QLockFilePrivate(const QString &fn) + : fileName(fn), +#ifdef Q_OS_WIN + fileHandle(INVALID_HANDLE_VALUE), +#else + fileHandle(-1), +#endif + staleLockTime(30 * 1000), // 30 seconds + lockError(QLockFile::NoError), + isLocked(false) + { + } + QLockFile::LockError tryLock_sys(); + bool removeStaleLock(); + bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const; + // 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 + + QString fileName; +#ifdef Q_OS_WIN + Qt::HANDLE fileHandle; +#else + int fileHandle; +#endif + int staleLockTime; // "int milliseconds" is big enough for 24 days + QLockFile::LockError lockError; + bool isLocked; +}; + +QT_END_NAMESPACE + +#endif /* QLOCKFILE_P_H */ diff --git a/src/serialport/qt4support/install-helper.pri b/src/serialport/qt4support/install-helper.pri index 6037caf..a6b579f 100644 --- a/src/serialport/qt4support/install-helper.pri +++ b/src/serialport/qt4support/install-helper.pri @@ -8,6 +8,9 @@ for(header_file, PUBLIC_HEADERS) { system("$$QMAKE_COPY \"$${header_file}\" \"$$QTSERIALPORT_PROJECT_INCLUDEDIR\"") } +SOURCES += $$PWD/src/qlockfile.cpp +unix:!symbian: SOURCES += $$PWD/src/qlockfile_unix.cpp + # This is a quick workaround for generating forward header with Qt4. !equals(QMAKE_HOST.os, Windows): maybe_quote = "\'" @@ -36,5 +39,11 @@ target.path = $$[QT_INSTALL_LIBS] INSTALLS += target INCLUDEPATH += $$QTSERIALPORT_BUILD_ROOT/include $$QTSERIALPORT_BUILD_ROOT/include/QtSerialPort -lessThan(QT_MAJOR_VERSION, 5): INCLUDEPATH += $$QTSERIALPORT_PROJECT_ROOT/src/serialport/qt4support/include +lessThan(QT_MAJOR_VERSION, 5) { + QTSERIALPORT_PROJECT_QT4SUPPORT_INCLUDEDIR = $$QTSERIALPORT_PROJECT_ROOT/src/serialport/qt4support/include + INCLUDEPATH += \ + $$QTSERIALPORT_PROJECT_QT4SUPPORT_INCLUDEDIR \ + $$QTSERIALPORT_PROJECT_QT4SUPPORT_INCLUDEDIR/QtCore \ + $$QTSERIALPORT_PROJECT_QT4SUPPORT_INCLUDEDIR/private +} DEFINES += QT_BUILD_SERIALPORT_LIB diff --git a/src/serialport/qt4support/src/qlockfile.cpp b/src/serialport/qt4support/src/qlockfile.cpp new file mode 100644 index 0000000..b861a62 --- /dev/null +++ b/src/serialport/qt4support/src/qlockfile.cpp @@ -0,0 +1,352 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlockfile.h" +#include "qlockfile_p.h" + +#include <QtCore/qthread.h> +#include <QtCore/qelapsedtimer.h> +#include <QtCore/qdatetime.h> + +QT_BEGIN_NAMESPACE + +class QLockFileThread : public QThread +{ +public: + static void msleep(unsigned long msecs) { QThread::msleep(msecs); } +}; + +/*! + \class QLockFile + \inmodule QtCore + \brief The QLockFile class provides locking between processes using a file. + \since 5.1 + + A lock file can be used to prevent multiple processes from accessing concurrently + the same resource. For instance, a configuration file on disk, or a socket, a port, + a region of shared memory... + + Serialization is only guaranteed if all processes that access the shared resource + use QLockFile, with the same file path. + + QLockFile supports two use cases: + to protect a resource for a short-term operation (e.g. verifying if a configuration + file has changed before saving new settings), and for long-lived protection of a + resource (e.g. a document opened by a user in an editor) for an indefinite amount of time. + + When protecting for a short-term operation, it is acceptable to call lock() and wait + until any running operation finishes. + When protecting a resource over a long time, however, the application should always + call setStaleLockTime(0) and then tryLock() with a short timeout, in order to + warn the user that the resource is locked. + + 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). + 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 + setStaleLockTime(0), and when tryLock() returns LockFailedError, inform the user + that the document is locked, possibly using getLockInfo() for more details. +*/ + +/*! + \enum QLockFile::LockError + + This enum describes the result of the last call to lock() or tryLock(). + + \value NoError The lock was acquired successfully. + \value LockFailedError The lock could not be acquired because another process holds it. + \value PermissionError The lock file could not be created, for lack of permissions + in the parent directory. + \value UnknownError Another error happened, for instance a full partition + prevented writing out the lock file. +*/ + +/*! + Constructs a new lock file object. + The object is created in an unlocked state. + When calling lock() or tryLock(), a lock file named \a fileName will be created, + if it doesn't already exist. + + \sa lock(), unlock() +*/ +QLockFile::QLockFile(const QString &fileName) + : d_ptr(new QLockFilePrivate(fileName)) +{ +} + +/*! + Destroys the lock file object. + If the lock was acquired, this will release the lock, by deleting the lock file. +*/ +QLockFile::~QLockFile() +{ + unlock(); +} + +/*! + Sets \a staleLockTime to be the time in milliseconds after which + a lock file is considered stale. + The default value is 30000, i.e. 30 seconds. + If your application typically keeps the file locked for more than 30 seconds + (for instance while saving megabytes of data for 2 minutes), you should set + a bigger value using setStaleLockTime(). + + 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 + it has been around for a long time. + + \sa staleLockTime() +*/ +void QLockFile::setStaleLockTime(int staleLockTime) +{ + Q_D(QLockFile); + d->staleLockTime = staleLockTime; +} + +/*! + Returns the time in milliseconds after which + a lock file is considered stale. + + \sa setStaleLockTime() +*/ +int QLockFile::staleLockTime() const +{ + Q_D(const QLockFile); + return d->staleLockTime; +} + +/*! + Returns \c true if the lock was acquired by this QLockFile instance, + otherwise returns \c false. + + \sa lock(), unlock(), tryLock() +*/ +bool QLockFile::isLocked() const +{ + Q_D(const QLockFile); + return d->isLocked; +} + +/*! + Creates the lock file. + + If another process (or another thread) has created the lock file already, + this function will block until that process (or thread) releases it. + + Calling this function multiple times on the same lock from the same + thread without unlocking first is not allowed. This function will + \e dead-lock when the file is locked recursively. + + Returns \c true if the lock was acquired, false if it could not be acquired + due to an unrecoverable error, such as no permissions in the parent directory. + + \sa unlock(), tryLock() +*/ +bool QLockFile::lock() +{ + return tryLock(-1); +} + +/*! + Attempts to create the lock file. This function returns \c true if the + lock was obtained; otherwise it returns \c false. If another process (or + another thread) has created the lock file already, this function will + wait for at most \a timeout milliseconds for the lock file to become + available. + + Note: Passing a negative number as the \a timeout is equivalent to + calling lock(), i.e. this function will wait forever until the lock + file can be locked if \a timeout is negative. + + If the lock was obtained, it must be released with unlock() + before another process (or thread) can successfully lock it. + + Calling this function multiple times on the same lock from the same + thread without unlocking first is not allowed, this function will + \e always return false when attempting to lock the file recursively. + + \sa lock(), unlock() +*/ +bool QLockFile::tryLock(int timeout) +{ + Q_D(QLockFile); + QElapsedTimer timer; + if (timeout > 0) + timer.start(); + int sleepTime = 100; + forever { + d->lockError = d->tryLock_sys(); + switch (d->lockError) { + case NoError: + d->isLocked = true; + return true; + case PermissionError: + case UnknownError: + return false; + case LockFailedError: + if (!d->isLocked && d->isApparentlyStale()) { + // Stale lock from another thread/process + // Ensure two processes don't remove it at the same time + QLockFile rmlock(d->fileName + QLatin1String(".rmlock")); + if (rmlock.tryLock()) { + if (d->isApparentlyStale() && d->removeStaleLock()) + continue; + } + } + break; + } + if (timeout == 0 || (timeout > 0 && timer.hasExpired(timeout))) + return false; + QLockFileThread::msleep(sleepTime); + if (sleepTime < 5 * 1000) + sleepTime *= 2; + } + // not reached + return false; +} + +/*! + \fn void QLockFile::unlock() + Releases the lock, by deleting the lock file. + + Calling unlock() without locking the file first, does nothing. + + \sa lock(), tryLock() +*/ + +/*! + Retrieves information about the current owner of the lock file. + + If tryLock() returns \c false, and error() returns LockFailedError, + this function can be called to find out more information about the existing + lock file: + \list + \li the PID of the application (returned in \a pid) + \li the \a hostname it's running on (useful in case of networked filesystems), + \li the name of the application which created it (returned in \a appname), + \endlist + + Note that tryLock() automatically deleted the file if there is no + running application with this PID, so LockFailedError can only happen if there is + an application with this PID (it could be unrelated though). + + This can be used to inform users about the existing lock file and give them + the choice to delete it. After removing the file using removeStaleLockFile(), + the application can call tryLock() again. + + This function returns \c true if the information could be successfully retrieved, false + if the lock file doesn't exist or doesn't contain the expected data. + This can happen if the lock file was deleted between the time where tryLock() failed + and the call to this function. Simply call tryLock() again if this happens. +*/ +bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const +{ + Q_D(const QLockFile); + return d->getLockInfo(pid, hostname, appname); +} + +bool QLockFilePrivate::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const +{ + QFile reader(fileName); + if (!reader.open(QIODevice::ReadOnly)) + return false; + + QByteArray pidLine = reader.readLine(); + pidLine.chop(1); + QByteArray appNameLine = reader.readLine(); + appNameLine.chop(1); + QByteArray hostNameLine = reader.readLine(); + hostNameLine.chop(1); + if (pidLine.isEmpty() || appNameLine.isEmpty()) + return false; + + qint64 thePid = pidLine.toLongLong(); + if (pid) + *pid = thePid; + if (appname) + *appname = QString::fromUtf8(appNameLine); + if (hostname) + *hostname = QString::fromUtf8(hostNameLine); + return thePid > 0; +} + +/*! + Attempts to forcefully remove an existing lock file. + + Calling this is not recommended when protecting a short-lived operation: QLockFile + already takes care of removing lock files after they are older than staleLockTime(). + + This method should only be called when protecting a resource for a long time, i.e. + with staleLockTime(0), and after tryLock() returned LockFailedError, and the user + agreed on removing the lock file. + + Returns \c true on success, false if the lock file couldn't be removed. This happens + on Windows, when the application owning the lock is still running. +*/ +bool QLockFile::removeStaleLockFile() +{ + Q_D(QLockFile); + if (d->isLocked) { + qWarning("removeStaleLockFile can only be called when not holding the lock"); + return false; + } + return d->removeStaleLock(); +} + +/*! + Returns the lock file error status. + + If tryLock() returns \c false, this function can be called to find out + the reason why the locking failed. +*/ +QLockFile::LockError QLockFile::error() const +{ + Q_D(const QLockFile); + return d->lockError; +} + +QT_END_NAMESPACE diff --git a/src/serialport/qt4support/src/qlockfile_unix.cpp b/src/serialport/qt4support/src/qlockfile_unix.cpp new file mode 100644 index 0000000..04e0a3b --- /dev/null +++ b/src/serialport/qt4support/src/qlockfile_unix.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qlockfile_p.h" + +#include "QtCore/qtemporaryfile.h" +#include "QtCore/qcoreapplication.h" +#include "QtCore/qfileinfo.h" +#include "QtCore/qdebug.h" +#include "QtCore/qdatetime.h" + +#include <sys/file.h> // flock +#include <sys/types.h> // kill +#include <signal.h> // kill +#include <unistd.h> + +#include <errno.h> + +QT_BEGIN_NAMESPACE + +#define EINTR_LOOP(var, cmd) \ + do { \ + var = cmd; \ + } while (var == -1 && errno == EINTR) + +// don't call QT_OPEN or ::open +// call qt_safe_open +static inline int qt_safe_open(const char *pathname, int flags, mode_t mode = 0777) +{ +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif + int fd; + EINTR_LOOP(fd, ::open(pathname, flags, mode)); + + // unknown flags are ignored, so we have no way of verifying if + // O_CLOEXEC was accepted + if (fd != -1) + ::fcntl(fd, F_SETFD, FD_CLOEXEC); + return fd; +} + +static inline qint64 qt_safe_write(int fd, const void *data, qint64 len) +{ + qint64 ret = 0; + EINTR_LOOP(ret, ::write(fd, data, len)); + return ret; +} + +static QString localHostName() // from QHostInfo::localHostName() +{ + char hostName[512]; + if (gethostname(hostName, sizeof(hostName)) == -1) + return QString(); + hostName[sizeof(hostName) - 1] = '\0'; + return QString::fromLocal8Bit(hostName); +} + +// ### merge into qt_safe_write? +static qint64 qt_write_loop(int fd, const char *data, qint64 len) +{ + qint64 pos = 0; + while (pos < len) { + const qint64 ret = qt_safe_write(fd, data + pos, len - pos); + if (ret == -1) // e.g. partition full + return pos; + pos += ret; + } + return pos; +} + +static bool setNativeLocks(int fd) +{ +#if defined(LOCK_EX) && defined(LOCK_NB) + if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs + return false; +#endif + struct flock flockData; + flockData.l_type = F_WRLCK; + flockData.l_whence = SEEK_SET; + flockData.l_start = 0; + flockData.l_len = 0; // 0 = entire file + flockData.l_pid = getpid(); + if (fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems + return false; + return true; +} + +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' + + qAppName().toUtf8() + '\n' + + localHostName().toUtf8() + '\n'; + + const QByteArray lockFileName = QFile::encodeName(fileName); + const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0) { + switch (errno) { + case EEXIST: + return QLockFile::LockFailedError; + case EACCES: + case EROFS: + return QLockFile::PermissionError; + default: + return QLockFile::UnknownError; + } + } + // Ensure nobody else can delete the file while we have it + if (!setNativeLocks(fd)) + qWarning() << "setNativeLocks failed:" << strerror(errno); + + // We hold the lock, continue. + fileHandle = fd; + + QLockFile::LockError error = QLockFile::NoError; + if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) + error = QLockFile::UnknownError; // partition full + return error; +} + +bool QLockFilePrivate::removeStaleLock() +{ + const QByteArray lockFileName = QFile::encodeName(fileName); + const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0644); + if (fd < 0) // gone already? + return false; + bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0); + close(fd); + return success; +} + +bool QLockFilePrivate::isApparentlyStale() const +{ + qint64 pid; + QString hostname, appname; + if (!getLockInfo(&pid, &hostname, &appname)) + return false; + if (hostname == localHostName()) { + if (::kill(pid, 0) == -1 && errno == ESRCH) + return true; // PID doesn't exist anymore + } + const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime()); + return staleLockTime > 0 && age > staleLockTime; +} + +void QLockFile::unlock() +{ + Q_D(QLockFile); + if (!d->isLocked) + return; + close(d->fileHandle); + d->fileHandle = -1; + QFile::remove(d->fileName); + d->lockError = QLockFile::NoError; + d->isLocked = false; +} + +QT_END_NAMESPACE diff --git a/src/serialport/qttylocker_unix.cpp b/src/serialport/qttylocker_unix.cpp deleted file mode 100644 index 8184bd9..0000000 --- a/src/serialport/qttylocker_unix.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2012 Denis Shienkov <denis.shienkov@gmail.com> -** Contact: http://www.qt-project.org/legal -** -** This file is part of the QtSerialPort module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qttylocker_unix_p.h" - -#ifdef HAVE_BAUDBOY_H -# include <baudboy.h> -# include <cstdlib> -#elif defined (HAVE_LOCKDEV_H) -# include <lockdev.h> -# include <unistd.h> -#else -# include <signal.h> -# include <errno.h> -# include <fcntl.h> -# include <sys/stat.h> -# include <unistd.h> -# include <QtCore/qfile.h> -# include <QtCore/qdir.h> -# include <QtCore/qstringlist.h> -#endif // defined (HAVE_BAUDBOY_H) - -QT_BEGIN_NAMESPACE - -#if !(defined (HAVE_BAUDBOY_H) || defined (HAVE_LOCKDEV_H)) - -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) -# define QStringLiteral QLatin1String -#endif - -static inline const QStringList& lockDirectoryList() -{ - static const QStringList lockDirectoryEntries = QStringList() - << QStringLiteral("/var/lock") - << QStringLiteral("/etc/locks") - << QStringLiteral("/var/spool/locks") - << QStringLiteral("/var/spool/uucp") - << QStringLiteral("/tmp"); - - return lockDirectoryEntries; -} - -// Returns the full path first found in the directory where you can create a lock file -// (ie a directory with access to the read/write). -// Verification of directories is of the order in accordance with the order -// of records in the variable lockDirList. -static -QString lookupFirstSharedLockDir() -{ - QStringList directoryList = lockDirectoryList(); - - foreach (const QString &lockDirectory, directoryList) { - if (::access(lockDirectory.toLocal8Bit().constData(), R_OK | W_OK) == 0) - return lockDirectory; - } - return QString(); -} - -// Returns the name of the lock file which is tied to the -// device name, eg "LCK..ttyS0", etc. -static -QString generateLockFileNameAsNamedForm(const char *portName) -{ - QString result(lookupFirstSharedLockDir()); - if (!result.isEmpty()) { - result.append(QLatin1String("/LCK..")); - result.append(QString::fromLatin1(portName).replace(QLatin1Char('/'), QLatin1Char('_'))); - } - return result; -} - -#endif //!(defined (HAVE_BAUDBOY_H) || defined (HAVE_LOCKDEV_H)) - -// Try lock serial device. However, other processes can not access it. -bool QTtyLocker::lock(const char *portName) -{ -#ifdef HAVE_BAUDBOY_H - if (::ttylock(portName) - ::ttywait(portName); - return ::ttylock(portName) != -1; -#elif defined (HAVE_LOCKDEV_H) - return ::dev_lock(portName) != -1; -#else - QFile f(generateLockFileNameAsNamedForm(portName)); - if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - QString content(QLatin1String(" %1 %2\x0A")); - content = content.arg(::getpid()).arg(::getuid()); - if (f.write(content.toLocal8Bit()) > 0) { - f.close(); - return true; - } - f.close(); - } - return false; -#endif -} - -// Try unlock serial device. However, other processes can access it. -bool QTtyLocker::unlock(const char *portName) -{ -#ifdef HAVE_BAUDBOY_H - return ::ttyunlock(portName != -1; -#elif defined (HAVE_LOCKDEV_H) - return ::dev_unlock(portName, ::getpid()) != -1; -#else - QFile f(generateLockFileNameAsNamedForm(portName)); - return f.remove(); -#endif -} - -// Verifies the device is locked or not. -// If returned currentPid = true - this means that the device is locked the current process. -bool QTtyLocker::isLocked(const char *portName, bool *currentPid) -{ - if (!currentPid) - return true; - - *currentPid = false; - -#ifdef HAVE_BAUDBOY_H - return ::ttylocked(portName) != -1; -#elif defined (HAVE_LOCKDEV_H) - return ::dev_testlock(portName) != -1; -#else - - QFile f(generateLockFileNameAsNamedForm(portName)); - if (!f.exists()) - return false; - if (!f.open(QIODevice::ReadOnly)) - return true; - - QString content(QLatin1String(f.readAll())); - f.close(); - - const pid_t pid = content.section(' ', 0, 0, QString::SectionSkipEmpty).toInt(); - - if (::kill(pid, 0) == -1) { - if (errno == ESRCH) // Process does not exists - return false; - } else { - if (::getpid() == pid) // Process exists and it is "their", i.e current - *currentPid = true; - } - - return true; - -#endif -} - -QT_END_NAMESPACE diff --git a/src/serialport/serialport-lib.pri b/src/serialport/serialport-lib.pri index 522f96a..7ad55f8 100644 --- a/src/serialport/serialport-lib.pri +++ b/src/serialport/serialport-lib.pri @@ -72,11 +72,9 @@ symbian { unix:!symbian { PRIVATE_HEADERS += \ - $$PWD/qttylocker_unix_p.h \ $$PWD/qserialport_unix_p.h SOURCES += \ - $$PWD/qttylocker_unix.cpp \ $$PWD/qserialport_unix.cpp \ $$PWD/qserialportinfo_unix.cpp |