summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaszlo Papp <lpapp@kde.org>2013-11-06 07:04:22 +0000
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-11-06 08:54:01 +0100
commited974e0b6cfda1b6b2ac33161ccf2db42b68c710 (patch)
tree40dfe560176f6967e227c10ffedce844d503df22
parent26458aef426cde967433035d2af0ab244db3aec0 (diff)
downloadqtserialport-ed974e0b6cfda1b6b2ac33161ccf2db42b68c710.tar.gz
Replace the home-grown QTtyLocker with QLockFile
This can be more widely adopted later, and not just for the Linux backend. It would be more than a few-liner change for stable, so this change change is kept minimal (of course apart from the big file integration). The only big problem with QLockFile currently is that we cannot change the file name on the fly as it seems, just for construction. Even the copy constructor and assignment operators are private. It means the class currently seems to be non-eligible for QtSerialPort needs where it is necessary to change for open anytime, and be accessible by open/serial for the before and after read/write session for the same file name. Also, I had to get rid of the QTemporary file internals because we cannot have access to the internal engine with Qt 4. I also had to make a small thread class for the protected msleep in QThread for Qt 4. There was a small improvement for the hidden dependency problem with gethostname. That was also sent to QtCore proper. Also, the windows backend for the QLockFile class is not copied for simplicity. It would not be used as of now. There is also a short convenience around QLockFile established to handle system-wide lock file paths. There is also some error handling added if the lock directory paths are not readable or writable. This will end users identify the problems like on Android. Tested on Archlinux with Qt 4 and then 5. Task-number: QTBUG-34474 Change-Id: I7adf29527c01ad331d3eeff5ae4c5a4113bde083 Reviewed-by: Sergey Belyashov <Sergey.Belyashov@gmail.com>
-rw-r--r--dist/changes-5.2.04
-rw-r--r--src/serialport/qserialport_unix.cpp64
-rw-r--r--src/serialport/qserialport_unix_p.h9
-rw-r--r--src/serialport/qserialportinfo_unix.cpp11
-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.h104
-rw-r--r--src/serialport/qt4support/install-helper.pri11
-rw-r--r--src/serialport/qt4support/src/qlockfile.cpp352
-rw-r--r--src/serialport/qt4support/src/qlockfile_unix.cpp199
-rw-r--r--src/serialport/qttylocker_unix.cpp188
-rw-r--r--src/serialport/serialport-lib.pri2
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(), &currentPid);
+ 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