summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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 239a9a3c..76360f8a 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 6799814b..81aafe73 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 2d09ee42..15bb5f8f 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 e475e9b2..38f9124b 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 9dce5d05..d46f07ab 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 00000000..e08f86ca
--- /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 6037caf5..a6b579f0 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 00000000..b861a623
--- /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 00000000..04e0a3b0
--- /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 8184bd9c..00000000
--- 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 522f96a0..7ad55f8d 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