From 1b582d64eb6d13e60a02ebc40956302a4864eb6c Mon Sep 17 00:00:00 2001
From: David Faure <faure+bluesystems@kde.org>
Date: Sun, 3 Feb 2013 12:00:50 +0100
Subject: [PATCH] Long live QLockFile

Locking between processes, implemented with open(O_EXCL) on Unix
and CreateFile(CREATE_NEW) on Windows.

Supports detecting stale lock files and deleting them.
Advisory locking is used to prevent deletion of files that are still in use.

Change-Id: Id00ee2a4e77a29483d869037c7047c59cb909339
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
---
 .gitignore                                    |   1 +
 src/corelib/io/io.pri                         |   5 +
 src/corelib/io/qlockfile.cpp                  | 346 ++++++++++++++++
 src/corelib/io/qlockfile.h                    |  91 +++++
 src/corelib/io/qlockfile_p.h                  | 104 +++++
 src/corelib/io/qlockfile_unix.cpp             | 207 ++++++++++
 src/corelib/io/qlockfile_win.cpp              | 138 +++++++
 src/corelib/io/qtemporaryfile.h               |   2 +
 src/corelib/io/qtemporaryfile_p.h             |   2 +
 tests/auto/corelib/io/io.pro                  |   1 +
 tests/auto/corelib/io/qlockfile/qlockfile.pro |   3 +
 .../qlockfile_test_helper.cpp                 |  78 ++++
 .../qlockfile_test_helper.pro                 |   7 +
 .../corelib/io/qlockfile/tst_qlockfile.cpp    | 379 ++++++++++++++++++
 .../corelib/io/qlockfile/tst_qlockfile.pro    |   6 +
 15 files changed, 1370 insertions(+)
 create mode 100644 src/corelib/io/qlockfile.cpp
 create mode 100644 src/corelib/io/qlockfile.h
 create mode 100644 src/corelib/io/qlockfile_p.h
 create mode 100644 src/corelib/io/qlockfile_unix.cpp
 create mode 100644 src/corelib/io/qlockfile_win.cpp
 create mode 100644 tests/auto/corelib/io/qlockfile/qlockfile.pro
 create mode 100644 tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.cpp
 create mode 100644 tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.pro
 create mode 100644 tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp
 create mode 100644 tests/auto/corelib/io/qlockfile/tst_qlockfile.pro

diff --git a/.gitignore b/.gitignore
index a64b0ccf9a7..5f9854a674e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -307,6 +307,7 @@ tests/auto/corelib/thread/qthreadstorage/crashOnExit
 tests/auto/corelib/io/qresourceengine/qresourceengine
 tests/auto/corelib/codecs/qtextcodec/echo/echo
 tests/auto/corelib/plugin/quuid/testProcessUniqueness/testProcessUniqueness
+tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper
 tests/auto/dbus/qdbusabstractadaptor/qmyserver/qmyserver
 tests/auto/dbus/qdbusabstractinterface/qpinger/qpinger
 tests/auto/dbus/qdbusinterface/qmyserver/qmyserver
diff --git a/src/corelib/io/io.pri b/src/corelib/io/io.pri
index e0364a14603..d4ed8e53620 100644
--- a/src/corelib/io/io.pri
+++ b/src/corelib/io/io.pri
@@ -18,6 +18,8 @@ HEADERS +=  \
         io/qipaddress_p.h \
         io/qiodevice.h \
         io/qiodevice_p.h \
+        io/qlockfile.h \
+        io/qlockfile_p.h \
         io/qnoncontiguousbytedevice_p.h \
         io/qprocess.h \
         io/qprocess_p.h \
@@ -61,6 +63,7 @@ SOURCES += \
         io/qfileinfo.cpp \
         io/qipaddress.cpp \
         io/qiodevice.cpp \
+        io/qlockfile.cpp \
         io/qnoncontiguousbytedevice.cpp \
         io/qprocess.cpp \
         io/qtextstream.cpp \
@@ -85,6 +88,7 @@ SOURCES += \
 win32 {
         SOURCES += io/qsettings_win.cpp
         SOURCES += io/qfsfileengine_win.cpp
+        SOURCES += io/qlockfile_win.cpp
 
         SOURCES += io/qfilesystemwatcher_win.cpp
         HEADERS += io/qfilesystemwatcher_win_p.h
@@ -109,6 +113,7 @@ win32 {
         SOURCES += \
                 io/qfsfileengine_unix.cpp \
                 io/qfilesystemengine_unix.cpp \
+                io/qlockfile_unix.cpp \
                 io/qprocess_unix.cpp \
                 io/qfilesystemiterator_unix.cpp \
 
diff --git a/src/corelib/io/qlockfile.cpp b/src/corelib/io/qlockfile.cpp
new file mode 100644
index 00000000000..5d56a67f48b
--- /dev/null
+++ b/src/corelib/io/qlockfile.cpp
@@ -0,0 +1,346 @@
+/****************************************************************************
+**
+** 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 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 true if the lock was acquired by this QLockFile instance,
+    otherwise returns 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 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 true if the
+    lock was obtained; otherwise it returns 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 + QStringLiteral(".rmlock"));
+                if (rmlock.tryLock()) {
+                    if (d->isApparentlyStale() && d->removeStaleLock())
+                        continue;
+                }
+            }
+            break;
+        }
+        if (timeout == 0 || (timeout > 0 && timer.hasExpired(timeout)))
+            return false;
+        QThread::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 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 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 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 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/corelib/io/qlockfile.h b/src/corelib/io/qlockfile.h
new file mode 100644
index 00000000000..4c8b6bf31ab
--- /dev/null
+++ b/src/corelib/io/qlockfile.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** 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_H
+#define QLOCKFILE_H
+
+#include <QtCore/qstring.h>
+#include <QtCore/qscopedpointer.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QLockFilePrivate;
+
+class Q_CORE_EXPORT QLockFile
+{
+public:
+    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
+
+QT_END_HEADER
+
+#endif // QLOCKFILE_H
diff --git a/src/corelib/io/qlockfile_p.h b/src/corelib/io/qlockfile_p.h
new file mode 100644
index 00000000000..e046e87cf45
--- /dev/null
+++ b/src/corelib/io/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 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/corelib/io/qlockfile_unix.cpp b/src/corelib/io/qlockfile_unix.cpp
new file mode 100644
index 00000000000..ed3b399fbf5
--- /dev/null
+++ b/src/corelib/io/qlockfile_unix.cpp
@@ -0,0 +1,207 @@
+/****************************************************************************
+**
+** 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 "private/qcore_unix_p.h" // qt_safe_open
+#include "private/qabstractfileengine_p.h"
+#include "private/qtemporaryfile_p.h"
+
+#include <sys/file.h>  // flock
+#include <sys/types.h> // kill
+#include <signal.h>    // kill
+
+QT_BEGIN_NAMESPACE
+
+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;
+}
+
+int QLockFilePrivate::checkFcntlWorksAfterFlock()
+{
+    QTemporaryFile file;
+    if (!file.open())
+        return -2;
+    const int fd = file.d_func()->engine()->handle();
+    if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
+        return -3;
+    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 0;
+    return 1;
+}
+
+static QBasicAtomicInt fcntlOK = Q_BASIC_ATOMIC_INITIALIZER(-1);
+
+/*!
+  \internal
+  Checks that the OS isn't using POSIX locks to emulate flock().
+  Mac OS X is one of those.
+*/
+static bool fcntlWorksAfterFlock()
+{
+    int value = fcntlOK.load();
+    if (Q_UNLIKELY(value == -1)) {
+        value = QLockFilePrivate::checkFcntlWorksAfterFlock();
+        fcntlOK.store(value);
+    }
+    return value == 1;
+}
+
+static bool setNativeLocks(int fd)
+{
+    if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
+        return false;
+    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 (fcntlWorksAfterFlock() && fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems
+        return false;
+    return true;
+}
+
+QLockFile::LockError QLockFilePrivate::tryLock_sys()
+{
+    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;
+
+    // Assemble data, to write in a single call to write
+    // (otherwise we'd have to check every write call)
+    QByteArray fileData;
+    fileData += QByteArray::number(QCoreApplication::applicationPid());
+    fileData += '\n';
+    fileData += qAppName().toUtf8();
+    fileData += '\n';
+    fileData += localHostName().toUtf8();
+    fileData += '\n';
+
+    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/corelib/io/qlockfile_win.cpp b/src/corelib/io/qlockfile_win.cpp
new file mode 100644
index 00000000000..b5f6d9f3da8
--- /dev/null
+++ b/src/corelib/io/qlockfile_win.cpp
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** 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 "private/qfilesystementry_p.h"
+#include <qt_windows.h>
+
+#include "QtCore/qcoreapplication.h"
+#include "QtCore/qfileinfo.h"
+#include "QtCore/qdatetime.h"
+#include "QtCore/qdebug.h"
+
+QT_BEGIN_NAMESPACE
+
+QLockFile::LockError QLockFilePrivate::tryLock_sys()
+{
+    SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
+    const QFileSystemEntry fileEntry(fileName);
+    // When writing, allow others to read.
+    // When reading, QFile will allow others to read and write, all good.
+    // Adding FILE_SHARE_DELETE would allow forceful deletion of stale files,
+    // but Windows doesn't allow recreating it while this handle is open anyway,
+    // so this would only create confusion (can't lock, but no lock file to read from).
+    const DWORD dwShareMode = FILE_SHARE_READ;
+    HANDLE fh = CreateFile((const wchar_t*)fileEntry.nativeFilePath().utf16(),
+                           GENERIC_WRITE,
+                           dwShareMode,
+                           &securityAtts,
+                           CREATE_NEW, // error if already exists
+                           FILE_ATTRIBUTE_NORMAL,
+                           NULL);
+    if (fh == INVALID_HANDLE_VALUE) {
+        const DWORD lastError = GetLastError();
+        switch (lastError) {
+        case ERROR_SHARING_VIOLATION:
+        case ERROR_ALREADY_EXISTS:
+        case ERROR_FILE_EXISTS:
+        case ERROR_ACCESS_DENIED: // readonly file, or file still in use by another process. Assume the latter, since we don't create it readonly.
+            return QLockFile::LockFailedError;
+        default:
+            qWarning() << "Got unexpected locking error" << lastError;
+            return QLockFile::UnknownError;
+        }
+    }
+
+    // We hold the lock, continue.
+    fileHandle = fh;
+    // Assemble data, to write in a single call to write
+    // (otherwise we'd have to check every write call)
+    QByteArray fileData;
+    fileData += QByteArray::number(QCoreApplication::applicationPid());
+    fileData += '\n';
+    fileData += qAppName().toUtf8();
+    fileData += '\n';
+    //fileData += localHostname(); // gethostname requires winsock init, see QHostInfo...
+    fileData += '\n';
+    DWORD bytesWritten = 0;
+    QLockFile::LockError error = QLockFile::NoError;
+    if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh))
+        error = QLockFile::UnknownError; // partition full
+    return error;
+}
+
+bool QLockFilePrivate::removeStaleLock()
+{
+    // QFile::remove fails on Windows if the other process is still using the file, so it's not stale.
+    return QFile::remove(fileName);
+}
+
+bool QLockFilePrivate::isApparentlyStale() const
+{
+    qint64 pid;
+    QString hostname, appname;
+    if (!getLockInfo(&pid, &hostname, &appname))
+        return false;
+
+    HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
+    if (!procHandle)
+        return true;
+    // We got a handle but check if process is still alive
+    DWORD dwR = ::WaitForSingleObject(procHandle, 0);
+    ::CloseHandle(procHandle);
+    if (dwR == WAIT_TIMEOUT)
+        return true;
+    const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
+    return staleLockTime > 0 && age > staleLockTime;
+}
+
+void QLockFile::unlock()
+{
+    Q_D(QLockFile);
+     if (!d->isLocked)
+        return;
+     CloseHandle(d->fileHandle);
+     QFile::remove(d->fileName);
+     d->lockError = QLockFile::NoError;
+     d->isLocked = false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/io/qtemporaryfile.h b/src/corelib/io/qtemporaryfile.h
index 249892e7043..09aa53c33bf 100644
--- a/src/corelib/io/qtemporaryfile.h
+++ b/src/corelib/io/qtemporaryfile.h
@@ -55,6 +55,7 @@ QT_BEGIN_NAMESPACE
 #ifndef QT_NO_TEMPORARYFILE
 
 class QTemporaryFilePrivate;
+class QLockFilePrivate;
 
 class Q_CORE_EXPORT QTemporaryFile : public QFile
 {
@@ -96,6 +97,7 @@ protected:
 
 private:
     friend class QFile;
+    friend class QLockFilePrivate;
     Q_DISABLE_COPY(QTemporaryFile)
 };
 
diff --git a/src/corelib/io/qtemporaryfile_p.h b/src/corelib/io/qtemporaryfile_p.h
index dd011f56c1f..d274f60ecc3 100644
--- a/src/corelib/io/qtemporaryfile_p.h
+++ b/src/corelib/io/qtemporaryfile_p.h
@@ -62,6 +62,8 @@ protected:
     QString templateName;
 
     static QString defaultTemplateName();
+
+    friend class QLockFilePrivate;
 };
 
 class QTemporaryFileEngine : public QFSFileEngine
diff --git a/tests/auto/corelib/io/io.pro b/tests/auto/corelib/io/io.pro
index 80ae6d38c1c..b3a51c6f6ee 100644
--- a/tests/auto/corelib/io/io.pro
+++ b/tests/auto/corelib/io/io.pro
@@ -14,6 +14,7 @@ SUBDIRS=\
     qfilesystemwatcher \
     qiodevice \
     qipaddress \
+    qlockfile \
     qnodebug \
     qprocess \
     qprocess-noapplication \
diff --git a/tests/auto/corelib/io/qlockfile/qlockfile.pro b/tests/auto/corelib/io/qlockfile/qlockfile.pro
new file mode 100644
index 00000000000..91f104305c2
--- /dev/null
+++ b/tests/auto/corelib/io/qlockfile/qlockfile.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+
+SUBDIRS += tst_qlockfile.pro qlockfiletesthelper/qlockfile_test_helper.pro
diff --git a/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.cpp b/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.cpp
new file mode 100644
index 00000000000..63f62910347
--- /dev/null
+++ b/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.cpp
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite 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 <QDebug>
+#include <QCoreApplication>
+#include <QLockFile>
+#include <QThread>
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+
+    if (argc <= 1)
+        return -1;
+
+    const QString lockName = QString::fromLocal8Bit(argv[1]);
+
+    QString option;
+    if (argc > 2)
+        option = QString::fromLocal8Bit(argv[2]);
+
+    if (option == "-crash") {
+        QLockFile *lockFile = new QLockFile(lockName);
+        lockFile->lock();
+        // leak the lockFile on purpose, so that the lock remains!
+        return 0;
+    } else if (option == "-busy") {
+        QLockFile lockFile(lockName);
+        lockFile.lock();
+        QThread::msleep(500);
+        return 0;
+    } else {
+        QLockFile lockFile(lockName);
+        if (lockFile.isLocked()) // cannot happen, before calling lock or tryLock
+            return QLockFile::UnknownError;
+
+        lockFile.tryLock();
+        return lockFile.error();
+    }
+}
diff --git a/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.pro b/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.pro
new file mode 100644
index 00000000000..3ac3be9c9be
--- /dev/null
+++ b/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.pro
@@ -0,0 +1,7 @@
+TARGET = qlockfile_test_helper
+SOURCES += qlockfile_test_helper.cpp
+
+CONFIG += console
+CONFIG -= app_bundle
+QT = core
+DESTDIR = ./
diff --git a/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp b/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp
new file mode 100644
index 00000000000..4aed11a2aa6
--- /dev/null
+++ b/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp
@@ -0,0 +1,379 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite 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 <QtTest/QtTest>
+#include <QtConcurrentRun>
+#include <qlockfile.h>
+#include <qtemporarydir.h>
+
+class tst_QLockFile : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void initTestCase();
+    void lockUnlock();
+    void lockOutOtherProcess();
+    void lockOutOtherThread();
+    void waitForLock_data();
+    void waitForLock();
+    void staleLockFromCrashedProcess_data();
+    void staleLockFromCrashedProcess();
+    void staleShortLockFromBusyProcess();
+    void staleLongLockFromBusyProcess();
+    void staleLockRace();
+    void noPermissions();
+
+public:
+    QString m_helperApp;
+    QTemporaryDir dir;
+};
+
+void tst_QLockFile::initTestCase()
+{
+#ifdef QT_NO_PROCESS
+    QSKIP("This test requires QProcess support");
+#else
+    // chdir to our testdata path and execute helper apps relative to that.
+    QString testdata_dir = QFileInfo(QFINDTESTDATA("qlockfiletesthelper")).absolutePath();
+    QVERIFY2(QDir::setCurrent(testdata_dir), qPrintable("Could not chdir to " + testdata_dir));
+    m_helperApp = "qlockfiletesthelper/qlockfile_test_helper";
+#endif
+}
+
+void tst_QLockFile::lockUnlock()
+{
+    const QString fileName = dir.path() + "/lock1";
+    QVERIFY(!QFile(fileName).exists());
+    QLockFile lockFile(fileName);
+    QVERIFY(lockFile.lock());
+    QVERIFY(lockFile.isLocked());
+    QCOMPARE(int(lockFile.error()), int(QLockFile::NoError));
+    QVERIFY(QFile::exists(fileName));
+
+    // Recursive locking is not allowed
+    // (can't test lock() here, it would wait forever)
+    QVERIFY(!lockFile.tryLock());
+    QCOMPARE(int(lockFile.error()), int(QLockFile::LockFailedError));
+    qint64 pid;
+    QString hostname, appname;
+    QVERIFY(lockFile.getLockInfo(&pid, &hostname, &appname));
+    QCOMPARE(pid, QCoreApplication::applicationPid());
+    QCOMPARE(appname, qAppName());
+    QVERIFY(!lockFile.tryLock(200));
+    QCOMPARE(int(lockFile.error()), int(QLockFile::LockFailedError));
+
+    // Unlock deletes the lock file
+    lockFile.unlock();
+    QCOMPARE(int(lockFile.error()), int(QLockFile::NoError));
+    QVERIFY(!lockFile.isLocked());
+    QVERIFY(!QFile::exists(fileName));
+}
+
+void tst_QLockFile::lockOutOtherProcess()
+{
+    // Lock
+    const QString fileName = dir.path() + "/lockOtherProcess";
+    QLockFile lockFile(fileName);
+    QVERIFY(lockFile.lock());
+
+    // Other process can't acquire lock
+    QProcess proc;
+    proc.start(m_helperApp, QStringList() << fileName);
+    QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
+    QVERIFY(proc.waitForFinished());
+    QCOMPARE(proc.exitCode(), int(QLockFile::LockFailedError));
+
+    // Unlock
+    lockFile.unlock();
+    QVERIFY(!QFile::exists(fileName));
+
+    // Other process can now acquire lock
+    int ret = QProcess::execute(m_helperApp, QStringList() << fileName);
+    QCOMPARE(ret, int(QLockFile::NoError));
+    // Lock doesn't survive process though (on clean exit)
+    QVERIFY(!QFile::exists(fileName));
+}
+
+static QLockFile::LockError tryLockFromThread(const QString &fileName)
+{
+    QLockFile lockInThread(fileName);
+    lockInThread.tryLock();
+    return lockInThread.error();
+}
+
+void tst_QLockFile::lockOutOtherThread()
+{
+    const QString fileName = dir.path() + "/lockOtherThread";
+    QLockFile lockFile(fileName);
+    QVERIFY(lockFile.lock());
+
+    // Other thread can't acquire lock
+    QFuture<QLockFile::LockError> ret = QtConcurrent::run<QLockFile::LockError>(tryLockFromThread, fileName);
+    QCOMPARE(ret.result(), QLockFile::LockFailedError);
+
+    lockFile.unlock();
+
+    // Now other thread can acquire lock
+    QFuture<QLockFile::LockError> ret2 = QtConcurrent::run<QLockFile::LockError>(tryLockFromThread, fileName);
+    QCOMPARE(ret2.result(), QLockFile::NoError);
+}
+
+static bool lockFromThread(const QString &fileName, int sleepMs, QSemaphore *semThreadReady, QSemaphore *semMainThreadDone)
+{
+    QLockFile lockFile(fileName);
+    if (!lockFile.lock()) {
+        qWarning() << "Locking failed" << lockFile.error();
+        return false;
+    }
+    semThreadReady->release();
+    QThread::msleep(sleepMs);
+    semMainThreadDone->acquire();
+    lockFile.unlock();
+    return true;
+}
+
+void tst_QLockFile::waitForLock_data()
+{
+    QTest::addColumn<int>("testNumber");
+    QTest::addColumn<int>("threadSleepMs");
+    QTest::addColumn<bool>("releaseEarly");
+    QTest::addColumn<int>("tryLockTimeout");
+    QTest::addColumn<bool>("expectedResult");
+
+    int tn = 0; // test number
+    QTest::newRow("wait_forever_succeeds") << ++tn << 500 << true << -1   << true;
+    QTest::newRow("wait_longer_succeeds")  << ++tn << 500 << true << 1000 << true;
+    QTest::newRow("wait_zero_fails")       << ++tn << 500 << false << 0    << false;
+    QTest::newRow("wait_not_enough_fails") << ++tn << 500 << false << 100  << false;
+}
+
+void tst_QLockFile::waitForLock()
+{
+    QFETCH(int, testNumber);
+    QFETCH(int, threadSleepMs);
+    QFETCH(bool, releaseEarly);
+    QFETCH(int, tryLockTimeout);
+    QFETCH(bool, expectedResult);
+
+    const QString fileName = dir.path() + "/waitForLock" + QString::number(testNumber);
+    QLockFile lockFile(fileName);
+    QSemaphore semThreadReady, semMainThreadDone;
+    // Lock file from a thread
+    QFuture<bool> ret = QtConcurrent::run<bool>(lockFromThread, fileName, threadSleepMs, &semThreadReady, &semMainThreadDone);
+    semThreadReady.acquire();
+
+    if (releaseEarly) // let the thread release the lock after threadSleepMs
+        semMainThreadDone.release();
+
+    QCOMPARE(lockFile.tryLock(tryLockTimeout), expectedResult);
+    if (expectedResult)
+        QCOMPARE(int(lockFile.error()), int(QLockFile::NoError));
+    else
+        QCOMPARE(int(lockFile.error()), int(QLockFile::LockFailedError));
+
+    if (!releaseEarly) // only let the thread release the lock now
+        semMainThreadDone.release();
+
+    QVERIFY(ret); // waits for the thread to finish
+}
+
+void tst_QLockFile::staleLockFromCrashedProcess_data()
+{
+    QTest::addColumn<int>("staleLockTime");
+
+    // Test both use cases for QLockFile, should make no difference here.
+    QTest::newRow("short") << 30000;
+    QTest::newRow("long") << 0;
+}
+
+void tst_QLockFile::staleLockFromCrashedProcess()
+{
+    QFETCH(int, staleLockTime);
+    const QString fileName = dir.path() + "/staleLockFromCrashedProcess";
+
+    int ret = QProcess::execute(m_helperApp, QStringList() << fileName << "-crash");
+    QCOMPARE(ret, int(QLockFile::NoError));
+    QTRY_VERIFY(QFile::exists(fileName));
+
+    QLockFile secondLock(fileName);
+    secondLock.setStaleLockTime(staleLockTime);
+    // tryLock detects and removes the stale lock (since the PID is dead)
+#ifdef Q_OS_WIN
+    // It can take a bit of time on Windows, though.
+    QVERIFY(secondLock.tryLock(2000));
+#else
+    QVERIFY(secondLock.tryLock());
+#endif
+    QCOMPARE(int(secondLock.error()), int(QLockFile::NoError));
+}
+
+void tst_QLockFile::staleShortLockFromBusyProcess()
+{
+    const QString fileName = dir.path() + "/staleLockFromBusyProcess";
+
+    QProcess proc;
+    proc.start(m_helperApp, QStringList() << fileName << "-busy");
+    QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
+    QTRY_VERIFY(QFile::exists(fileName));
+
+    QLockFile secondLock(fileName);
+    QVERIFY(!secondLock.tryLock()); // held by other process
+    QCOMPARE(int(secondLock.error()), int(QLockFile::LockFailedError));
+    qint64 pid;
+    QString hostname, appname;
+    QTRY_VERIFY(secondLock.getLockInfo(&pid, &hostname, &appname));
+#ifdef Q_OS_UNIX
+    QCOMPARE(pid, proc.pid());
+#endif
+
+    secondLock.setStaleLockTime(100);
+    QTest::qSleep(100); // make the lock stale
+    // We can't "steal" (delete+recreate) a lock file from a running process
+    // until the file descriptor is closed.
+    QVERIFY(!secondLock.tryLock());
+
+    proc.waitForFinished();
+    QVERIFY(secondLock.tryLock());
+}
+
+void tst_QLockFile::staleLongLockFromBusyProcess()
+{
+    const QString fileName = dir.path() + "/staleLockFromBusyProcess";
+
+    QProcess proc;
+    proc.start(m_helperApp, QStringList() << fileName << "-busy");
+    QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
+    QTRY_VERIFY(QFile::exists(fileName));
+
+    QLockFile secondLock(fileName);
+    secondLock.setStaleLockTime(0);
+    QVERIFY(!secondLock.tryLock(100)); // never stale
+    QCOMPARE(int(secondLock.error()), int(QLockFile::LockFailedError));
+    qint64 pid;
+    QTRY_VERIFY(secondLock.getLockInfo(&pid, NULL, NULL));
+    QVERIFY(pid > 0);
+
+    // As long as the other process is running, we can't remove the lock file
+    QVERIFY(!secondLock.removeStaleLockFile());
+
+    proc.waitForFinished();
+}
+
+static QString tryStaleLockFromThread(const QString &fileName)
+{
+    QLockFile lockInThread(fileName + ".lock");
+    lockInThread.setStaleLockTime(1000);
+    if (!lockInThread.lock())
+        return "Error locking: " + QString::number(lockInThread.error());
+
+    // The concurrent use of the file below (write, read, delete) is protected by the lock file above.
+    // (provided that it doesn't become stale due to this operation taking too long)
+    QFile theFile(fileName);
+    if (!theFile.open(QIODevice::WriteOnly))
+        return "Couldn't open for write";
+    theFile.write("Hello world");
+    theFile.flush();
+    theFile.close();
+    QFile reader(fileName);
+    if (!reader.open(QIODevice::ReadOnly))
+        return "Couldn't open for read";
+    const QByteArray read = reader.readAll();
+    if (read != "Hello world")
+        return "File didn't have the expected contents:" + read;
+    reader.remove();
+    return QString();
+}
+
+void tst_QLockFile::staleLockRace()
+{
+    // Multiple threads notice a stale lock at the same time
+    // Only one thread should delete it, otherwise a race will ensue
+    const QString fileName = dir.path() + "/sharedFile";
+    const QString lockName = fileName + ".lock";
+    int ret = QProcess::execute(m_helperApp, QStringList() << lockName << "-crash");
+    QCOMPARE(ret, int(QLockFile::NoError));
+    QTRY_VERIFY(QFile::exists(lockName));
+
+    QThreadPool::globalInstance()->setMaxThreadCount(10);
+    QFutureSynchronizer<QString> synchronizer;
+    for (int i = 0; i < 8; ++i)
+        synchronizer.addFuture(QtConcurrent::run<QString>(tryStaleLockFromThread, fileName));
+    synchronizer.waitForFinished();
+    foreach (const QFuture<QString> &future, synchronizer.futures())
+        QVERIFY2(future.result().isEmpty(), qPrintable(future.result()));
+}
+
+void tst_QLockFile::noPermissions()
+{
+#ifdef Q_OS_WIN
+    // A readonly directory still allows us to create files, on Windows.
+    QSKIP("No permission testing on Windows");
+#endif
+    // Restore permissions so that the QTemporaryDir cleanup can happen
+    class PermissionRestorer
+    {
+        QString m_path;
+    public:
+        PermissionRestorer(const QString& path)
+            : m_path(path)
+        {}
+
+        ~PermissionRestorer()
+        {
+            QFile file(m_path);
+            file.setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner));
+        }
+    };
+
+    const QString fileName = dir.path() + "/staleLock";
+    QFile dirAsFile(dir.path()); // I have to use QFile to change a dir's permissions...
+    QVERIFY2(dirAsFile.setPermissions(QFile::Permissions(0)), qPrintable(dir.path())); // no permissions
+    PermissionRestorer permissionRestorer(dir.path());
+
+    QLockFile lockFile(fileName);
+    QVERIFY(!lockFile.lock());
+    QCOMPARE(int(lockFile.error()), int(QLockFile::PermissionError));
+}
+
+QTEST_MAIN(tst_QLockFile)
+#include "tst_qlockfile.moc"
diff --git a/tests/auto/corelib/io/qlockfile/tst_qlockfile.pro b/tests/auto/corelib/io/qlockfile/tst_qlockfile.pro
new file mode 100644
index 00000000000..2f7009b7366
--- /dev/null
+++ b/tests/auto/corelib/io/qlockfile/tst_qlockfile.pro
@@ -0,0 +1,6 @@
+CONFIG += testcase
+CONFIG -= app_bundle
+TARGET = tst_qlockfile
+SOURCES += tst_qlockfile.cpp
+
+QT = core testlib concurrent
-- 
GitLab