diff options
Diffstat (limited to 'src/corelib/ipc')
-rw-r--r-- | src/corelib/ipc/qsharedmemory.cpp | 689 | ||||
-rw-r--r-- | src/corelib/ipc/qsharedmemory.h | 99 | ||||
-rw-r--r-- | src/corelib/ipc/qsharedmemory_p.h | 215 | ||||
-rw-r--r-- | src/corelib/ipc/qsharedmemory_posix.cpp | 196 | ||||
-rw-r--r-- | src/corelib/ipc/qsharedmemory_systemv.cpp | 212 | ||||
-rw-r--r-- | src/corelib/ipc/qsharedmemory_win.cpp | 148 | ||||
-rw-r--r-- | src/corelib/ipc/qsystemsemaphore.cpp | 402 | ||||
-rw-r--r-- | src/corelib/ipc/qsystemsemaphore.h | 75 | ||||
-rw-r--r-- | src/corelib/ipc/qsystemsemaphore_p.h | 152 | ||||
-rw-r--r-- | src/corelib/ipc/qsystemsemaphore_posix.cpp | 171 | ||||
-rw-r--r-- | src/corelib/ipc/qsystemsemaphore_systemv.cpp | 201 | ||||
-rw-r--r-- | src/corelib/ipc/qsystemsemaphore_win.cpp | 98 | ||||
-rw-r--r-- | src/corelib/ipc/qtipccommon.cpp | 610 | ||||
-rw-r--r-- | src/corelib/ipc/qtipccommon.h | 206 | ||||
-rw-r--r-- | src/corelib/ipc/qtipccommon_p.h | 173 |
15 files changed, 3647 insertions, 0 deletions
diff --git a/src/corelib/ipc/qsharedmemory.cpp b/src/corelib/ipc/qsharedmemory.cpp new file mode 100644 index 0000000000..02761c0263 --- /dev/null +++ b/src/corelib/ipc/qsharedmemory.cpp @@ -0,0 +1,689 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsharedmemory.h" +#include "qsharedmemory_p.h" + +#include "qtipccommon_p.h" +#include "qsystemsemaphore.h" + +#include <q20memory.h> +#include <qdebug.h> +#ifdef Q_OS_WIN +# include <qt_windows.h> +#endif + +#ifndef MAX_PATH +# define MAX_PATH PATH_MAX +#endif + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(sharedmemory) + +using namespace QtIpcCommon; +using namespace Qt::StringLiterals; + +QSharedMemoryPrivate::~QSharedMemoryPrivate() +{ + destructBackend(); +} + +inline void QSharedMemoryPrivate::constructBackend() +{ + using namespace q20; + visit([](auto p) { construct_at(p); }); +} + +inline void QSharedMemoryPrivate::destructBackend() +{ + visit([](auto p) { std::destroy_at(p); }); +} + +#if QT_CONFIG(systemsemaphore) +inline QNativeIpcKey QSharedMemoryPrivate::semaphoreNativeKey() const +{ + if (isIpcSupported(IpcType::SharedMemory, QNativeIpcKey::Type::Windows) + && nativeKey.type() == QNativeIpcKey::Type::Windows) { + // native keys are plain kernel object names, limited to MAX_PATH + auto suffix = "_sem"_L1; + QString semkey = nativeKey.nativeKey(); + semkey.truncate(MAX_PATH - suffix.size() - 1); + semkey += suffix; + return { semkey, QNativeIpcKey::Type::Windows }; + } + + // System V and POSIX keys appear to operate in different namespaces, so we + // can just use the same native key + return nativeKey; +} +#endif + +/*! + \class QSharedMemory + \inmodule QtCore + \since 4.4 + + \brief The QSharedMemory class provides access to a shared memory segment. + + QSharedMemory provides access to a \l{Shared Memory}{shared memory segment} + by multiple threads and processes. Shared memory segments are identified by a + key, represented by \l QNativeIpcKey. A key can be created in a + cross-platform manner by using platformSafeKey(). + + One QSharedMemory object must create() the segment and this call specifies + the size of the segment. All other processes simply attach() to the segment + that must already exist. After either operation is successful, the + application may call data() to obtain a pointer to the data. + + To support non-atomic operations, QSharedMemory provides API to gain + exclusive access: you may lock the shared memory with lock() before reading + from or writing to the shared memory, but remember to release the lock with + unlock() after you are done. + + By default, QSharedMemory automatically destroys the shared memory segment + when the last instance of QSharedMemory is \l{detach()}{detached} from the + segment, and no references to the segment remain. + + For details on the key types, platform-specific limitations, and + interoperability with older or non-Qt applications, see the \l{Native IPC + Keys} documentation. That includes important information for sandboxed + applications on Apple platforms, including all apps obtained via the Apple + App Store. + + \sa {Inter-Process Communication}, QSystemSemaphore + */ + +/*! + \overload QSharedMemory() + + Constructs a shared memory object with the given \a parent. The shared memory + object's key is not set by the constructor, so the shared memory object does + not have an underlying shared memory segment attached. The key must be set + with setNativeKey() before create() or attach() can be used. + + \sa setNativeKey() + */ + +QSharedMemory::QSharedMemory(QObject *parent) + : QSharedMemory(QNativeIpcKey(), parent) +{ +} + +/*! + \overload + + Constructs a shared memory object with the given \a parent and with + its key set to \a key. Because its key is set, its create() and + attach() functions can be called. + + \sa setNativeKey(), create(), attach() + */ +QSharedMemory::QSharedMemory(const QNativeIpcKey &key, QObject *parent) + : QObject(*new QSharedMemoryPrivate(key.type()), parent) +{ + setNativeKey(key); +} + +/*! + Constructs a shared memory object with the given \a parent and with + the legacy key set to \a key. Because its key is set, its create() and + attach() functions can be called. + + \sa setKey(), create(), attach() + */ +QSharedMemory::QSharedMemory(const QString &key, QObject *parent) + : QSharedMemory(legacyNativeKey(key), parent) +{ +} + +/*! + The destructor clears the key, which forces the shared memory object + to \l {detach()} {detach} from its underlying shared memory + segment. If this shared memory object is the last one connected to + the shared memory segment, the detach() operation destroys the + shared memory segment. + + \sa detach(), isAttached() + */ +QSharedMemory::~QSharedMemory() +{ + Q_D(QSharedMemory); + if (isAttached()) + detach(); + d->cleanHandle(); +} + +/*! + \overload + + Sets the legacy \a key for this shared memory object. If \a key is the same + as the current key, the function returns without doing anything. Otherwise, + if the shared memory object is attached to an underlying shared memory + segment, it will \l {detach()} {detach} from it before setting the new key. + This function does not do an attach(). + + You can call key() to retrieve the legacy key. This function is mostly the + same as: + + \code + shm.setNativeKey(QSharedMemory::legacyNativeKey(key)); + \endcode + + except that it enables obtaining the legacy key using key(). + + \sa key(), nativeKey(), isAttached() +*/ +void QSharedMemory::setKey(const QString &key) +{ + setNativeKey(legacyNativeKey(key)); +} + +/*! + \since 4.8 + \fn void QSharedMemory::setNativeKey(const QString &key, QNativeIpcKey::Type type) + + Sets the native, platform specific, \a key for this shared memory object of + type \a type (the type parameter has been available since Qt 6.6). If \a key + is the same as the current native key, the function returns without doing + anything. Otherwise, if the shared memory object is attached to an underlying + shared memory segment, it will \l {detach()} {detach} from it before setting + the new key. This function does not do an attach(). + + This function is useful if the native key was shared from another process, + though the application must take care to ensure the key type matches what the + other process expects. See \l{Native IPC Keys} for more information. + + Portable native keys can be obtained using platformSafeKey(). + + You can call nativeKey() to retrieve the native key. + + \sa nativeKey(), nativeIpcKey(), isAttached() +*/ + +/*! + \since 6.6 + + Sets the native, platform specific, \a key for this shared memory object. If + \a key is the same as the current native key, the function returns without + doing anything. Otherwise, if the shared memory object is attached to an + underlying shared memory segment, it will \l {detach()} {detach} from it + before setting the new key. This function does not do an attach(). + + This function is useful if the native key was shared from another process. + See \l{Native IPC Keys} for more information. + + Portable native keys can be obtained using platformSafeKey(). + + You can call nativeKey() to retrieve the native key. + + \sa nativeKey(), nativeIpcKey(), isAttached() +*/ +void QSharedMemory::setNativeKey(const QNativeIpcKey &key) +{ + Q_D(QSharedMemory); + if (key == d->nativeKey && key.isEmpty()) + return; + if (!isKeyTypeSupported(key.type())) { + d->setError(KeyError, tr("%1: unsupported key type") + .arg("QSharedMemory::setNativeKey"_L1)); + return; + } + + if (isAttached()) + detach(); + d->cleanHandle(); + if (key.type() == d->nativeKey.type()) { + // we can reuse the backend + d->nativeKey = key; + } else { + // we must recreate the backend + d->destructBackend(); + d->nativeKey = key; + d->constructBackend(); + } +} + +bool QSharedMemoryPrivate::initKey(SemaphoreAccessMode mode) +{ + if (!cleanHandle()) + return false; +#if QT_CONFIG(systemsemaphore) + const QString legacyKey = QNativeIpcKeyPrivate::legacyKey(nativeKey); + const QNativeIpcKey semKey = legacyKey.isEmpty() + ? semaphoreNativeKey() + : QSystemSemaphore::legacyNativeKey(legacyKey, nativeKey.type()); + systemSemaphore.setNativeKey(semKey, 1, mode); + if (systemSemaphore.error() != QSystemSemaphore::NoError) { + QString function = "QSharedMemoryPrivate::initKey"_L1; + errorString = QSharedMemory::tr("%1: unable to set key on lock (%2)") + .arg(function, systemSemaphore.errorString()); + switch(systemSemaphore.error()) { + case QSystemSemaphore::PermissionDenied: + error = QSharedMemory::PermissionDenied; + break; + case QSystemSemaphore::KeyError: + error = QSharedMemory::KeyError; + break; + case QSystemSemaphore::AlreadyExists: + error = QSharedMemory::AlreadyExists; + break; + case QSystemSemaphore::NotFound: + error = QSharedMemory::NotFound; + break; + case QSystemSemaphore::OutOfResources: + error = QSharedMemory::OutOfResources; + break; + case QSystemSemaphore::UnknownError: + default: + error = QSharedMemory::UnknownError; + break; + } + return false; + } +#else + Q_UNUSED(mode); +#endif + errorString = QString(); + error = QSharedMemory::NoError; + return true; +} + +/*! + Returns the legacy key assigned with setKey() to this shared memory, or a null key + if no key has been assigned, or if the segment is using a nativeKey(). The + key is the identifier used by Qt applications to identify the shared memory + segment. + + You can find the native, platform specific, key used by the operating system + by calling nativeKey(). + + \sa setKey(), setNativeKey() + */ +QString QSharedMemory::key() const +{ + Q_D(const QSharedMemory); + return QNativeIpcKeyPrivate::legacyKey(d->nativeKey); +} + +/*! + \since 4.8 + + Returns the native, platform specific, key for this shared memory object. The + native key is the identifier used by the operating system to identify the + shared memory segment. + + You can use the native key to access shared memory segments that have not + been created by Qt, or to grant shared memory access to non-Qt applications. + See \l{Native IPC Keys} for more information. + + \sa setNativeKey(), nativeIpcKey() +*/ +QString QSharedMemory::nativeKey() const +{ + Q_D(const QSharedMemory); + return d->nativeKey.nativeKey(); +} + +/*! + \since 6.6 + + Returns the key type for this shared memory object. The key type complements + the nativeKey() as the identifier used by the operating system to identify + the shared memory segment. + + You can use the native key to access shared memory segments that have not + been created by Qt, or to grant shared memory access to non-Qt applications. + See \l{Native IPC Keys} for more information. + + \sa nativeKey(), setNativeKey() +*/ +QNativeIpcKey QSharedMemory::nativeIpcKey() const +{ + Q_D(const QSharedMemory); + return d->nativeKey; +} + +/*! + Creates a shared memory segment of \a size bytes with the key passed to the + constructor or set with setNativeKey(), then attaches to + the new shared memory segment with the given access \a mode and returns + \tt true. If a shared memory segment identified by the key already exists, + the attach operation is not performed and \tt false is returned. When the + return value is \tt false, call error() to determine which error occurred. + + \sa error() + */ +bool QSharedMemory::create(qsizetype size, AccessMode mode) +{ + Q_D(QSharedMemory); + QLatin1StringView function = "QSharedMemory::create"_L1; + +#if QT_CONFIG(systemsemaphore) + if (!d->initKey(QSystemSemaphore::Create)) + return false; + QSharedMemoryLocker lock(this); + if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, function)) + return false; +#else + if (!d->initKey({})) + return false; +#endif + + if (size <= 0) { + d->error = QSharedMemory::InvalidSize; + d->errorString = + QSharedMemory::tr("%1: create size is less then 0").arg(function); + return false; + } + + if (!d->create(size)) + return false; + + return d->attach(mode); +} + +/*! + Returns the size of the attached shared memory segment. If no shared + memory segment is attached, 0 is returned. + + \note The size of the segment may be larger than the requested size that was + passed to create(). + + \sa create(), attach() + */ +qsizetype QSharedMemory::size() const +{ + Q_D(const QSharedMemory); + return d->size; +} + +/*! + \enum QSharedMemory::AccessMode + + \value ReadOnly The shared memory segment is read-only. Writing to + the shared memory segment is not allowed. An attempt to write to a + shared memory segment created with ReadOnly causes the program to + abort. + + \value ReadWrite Reading and writing the shared memory segment are + both allowed. +*/ + +/*! + Attempts to attach the process to the shared memory segment + identified by the key that was passed to the constructor or to a + call to setNativeKey(). The access \a mode is \l {QSharedMemory::} + {ReadWrite} by default. It can also be \l {QSharedMemory::} + {ReadOnly}. Returns \c true if the attach operation is successful. If + false is returned, call error() to determine which error occurred. + After attaching the shared memory segment, a pointer to the shared + memory can be obtained by calling data(). + + \sa isAttached(), detach(), create() + */ +bool QSharedMemory::attach(AccessMode mode) +{ + Q_D(QSharedMemory); + + if (isAttached() || !d->initKey({})) + return false; +#if QT_CONFIG(systemsemaphore) + QSharedMemoryLocker lock(this); + if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, "QSharedMemory::attach"_L1)) + return false; +#endif + + if (isAttached() || !d->handle()) + return false; + + return d->attach(mode); +} + +/*! + Returns \c true if this process is attached to the shared memory + segment. + + \sa attach(), detach() + */ +bool QSharedMemory::isAttached() const +{ + Q_D(const QSharedMemory); + return (nullptr != d->memory); +} + +/*! + Detaches the process from the shared memory segment. If this was the + last process attached to the shared memory segment, then the shared + memory segment is released by the system, i.e., the contents are + destroyed. The function returns \c true if it detaches the shared + memory segment. If it returns \c false, it usually means the segment + either isn't attached, or it is locked by another process. + + \sa attach(), isAttached() + */ +bool QSharedMemory::detach() +{ + Q_D(QSharedMemory); + if (!isAttached()) + return false; + +#if QT_CONFIG(systemsemaphore) + QSharedMemoryLocker lock(this); + if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, "QSharedMemory::detach"_L1)) + return false; +#endif + + return d->detach(); +} + +/*! + Returns a pointer to the contents of the shared memory segment, if one is + attached. Otherwise it returns null. The value returned by this function will + not change until a \l {detach()}{detach} happens, so it is safe to store this + pointer. + + If the memory operations are not atomic, you may lock the shared memory with + lock() before reading from or writing, but remember to release the lock with + unlock() after you are done. + + \sa attach() + */ +void *QSharedMemory::data() +{ + Q_D(QSharedMemory); + return d->memory; +} + +/*! + Returns a const pointer to the contents of the shared memory segment, if one + is attached. Otherwise it returns null. The value returned by this function + will not change until a \l {detach()}{detach} happens, so it is safe to store + this pointer. + + If the memory operations are not atomic, you may lock the shared memory with + lock() before reading from or writing, but remember to release the lock with + unlock() after you are done. + + \sa attach(), create() + */ +const void *QSharedMemory::constData() const +{ + Q_D(const QSharedMemory); + return d->memory; +} + +/*! + \overload data() + */ +const void *QSharedMemory::data() const +{ + Q_D(const QSharedMemory); + return d->memory; +} + +#if QT_CONFIG(systemsemaphore) +/*! + This is a semaphore that locks the shared memory segment for access + by this process and returns \c true. If another process has locked the + segment, this function blocks until the lock is released. Then it + acquires the lock and returns \c true. If this function returns \c false, + it means that you have ignored a false return from create() or attach(), + that you have set the key with setNativeKey() or that + QSystemSemaphore::acquire() failed due to an unknown system error. + + \sa unlock(), data(), QSystemSemaphore::acquire() + */ +bool QSharedMemory::lock() +{ + Q_D(QSharedMemory); + if (d->lockedByMe) { + qWarning("QSharedMemory::lock: already locked"); + return true; + } + if (d->systemSemaphore.acquire()) { + d->lockedByMe = true; + return true; + } + const auto function = "QSharedMemory::lock"_L1; + d->errorString = QSharedMemory::tr("%1: unable to lock").arg(function); + d->error = QSharedMemory::LockError; + return false; +} + +/*! + Releases the lock on the shared memory segment and returns \c true, if + the lock is currently held by this process. If the segment is not + locked, or if the lock is held by another process, nothing happens + and false is returned. + + \sa lock() + */ +bool QSharedMemory::unlock() +{ + Q_D(QSharedMemory); + if (!d->lockedByMe) + return false; + d->lockedByMe = false; + if (d->systemSemaphore.release()) + return true; + const auto function = "QSharedMemory::unlock"_L1; + d->errorString = QSharedMemory::tr("%1: unable to unlock").arg(function); + d->error = QSharedMemory::LockError; + return false; +} +#endif // QT_CONFIG(systemsemaphore) + +/*! + \enum QSharedMemory::SharedMemoryError + + \value NoError No error occurred. + + \value PermissionDenied The operation failed because the caller + didn't have the required permissions. + + \value InvalidSize A create operation failed because the requested + size was invalid. + + \value KeyError The operation failed because of an invalid key. + + \value AlreadyExists A create() operation failed because a shared + memory segment with the specified key already existed. + + \value NotFound An attach() failed because a shared memory segment + with the specified key could not be found. + + \value LockError The attempt to lock() the shared memory segment + failed because create() or attach() failed and returned false, or + because a system error occurred in QSystemSemaphore::acquire(). + + \value OutOfResources A create() operation failed because there was + not enough memory available to fill the request. + + \value UnknownError Something else happened and it was bad. +*/ + +/*! + Returns a value indicating whether an error occurred, and, if so, + which error it was. + + \sa errorString() + */ +QSharedMemory::SharedMemoryError QSharedMemory::error() const +{ + Q_D(const QSharedMemory); + return d->error; +} + +/*! + Returns a text description of the last error that occurred. If + error() returns an \l {QSharedMemory::SharedMemoryError} {error + value}, call this function to get a text string that describes the + error. + + \sa error() + */ +QString QSharedMemory::errorString() const +{ + Q_D(const QSharedMemory); + return d->errorString; +} + +void QSharedMemoryPrivate::setUnixErrorString(QLatin1StringView function) +{ + // EINVAL is handled in functions so they can give better error strings + switch (errno) { + case EACCES: + errorString = QSharedMemory::tr("%1: permission denied").arg(function); + error = QSharedMemory::PermissionDenied; + break; + case EEXIST: + errorString = QSharedMemory::tr("%1: already exists").arg(function); + error = QSharedMemory::AlreadyExists; + break; + case ENOENT: + errorString = QSharedMemory::tr("%1: doesn't exist").arg(function); + error = QSharedMemory::NotFound; + break; + case EMFILE: + case ENOMEM: + case ENOSPC: + errorString = QSharedMemory::tr("%1: out of resources").arg(function); + error = QSharedMemory::OutOfResources; + break; + default: + errorString = QSharedMemory::tr("%1: unknown error: %2") + .arg(function, qt_error_string(errno)); + error = QSharedMemory::UnknownError; +#if defined QSHAREDMEMORY_DEBUG + qDebug() << errorString << "key" << key << "errno" << errno << EINVAL; +#endif + } +} + +bool QSharedMemory::isKeyTypeSupported(QNativeIpcKey::Type type) +{ + if (!isIpcSupported(IpcType::SharedMemory, type)) + return false; + using Variant = decltype(QSharedMemoryPrivate::backend); + return Variant::staticVisit(type, [](auto ptr) { + using Impl = std::decay_t<decltype(*ptr)>; + return Impl::runtimeSupportCheck(); + }); +} + +QNativeIpcKey QSharedMemory::platformSafeKey(const QString &key, QNativeIpcKey::Type type) +{ + return QtIpcCommon::platformSafeKey(key, IpcType::SharedMemory, type); +} + +QNativeIpcKey QSharedMemory::legacyNativeKey(const QString &key, QNativeIpcKey::Type type) +{ + return QtIpcCommon::legacyPlatformSafeKey(key, IpcType::SharedMemory, type); +} + +#endif // QT_CONFIG(sharedmemory) + +QT_END_NAMESPACE + +#include "moc_qsharedmemory.cpp" diff --git a/src/corelib/ipc/qsharedmemory.h b/src/corelib/ipc/qsharedmemory.h new file mode 100644 index 0000000000..ab448b15c1 --- /dev/null +++ b/src/corelib/ipc/qsharedmemory.h @@ -0,0 +1,99 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QSHAREDMEMORY_H +#define QSHAREDMEMORY_H + +#include <QtCore/qtipccommon.h> +#ifndef QT_NO_QOBJECT +# include <QtCore/qobject.h> +#else +# include <QtCore/qobjectdefs.h> +# include <QtCore/qscopedpointer.h> +# include <QtCore/qstring.h> +#endif + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(sharedmemory) + +class QSharedMemoryPrivate; + +class Q_CORE_EXPORT QSharedMemory : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSharedMemory) + +public: + enum AccessMode + { + ReadOnly, + ReadWrite + }; + Q_ENUM(AccessMode) + + enum SharedMemoryError + { + NoError, + PermissionDenied, + InvalidSize, + KeyError, + AlreadyExists, + NotFound, + LockError, + OutOfResources, + UnknownError + }; + Q_ENUM(SharedMemoryError) + + QSharedMemory(QObject *parent = nullptr); + QSharedMemory(const QNativeIpcKey &key, QObject *parent = nullptr); + ~QSharedMemory(); + + QSharedMemory(const QString &key, QObject *parent = nullptr); + void setKey(const QString &key); + QString key() const; + + void setNativeKey(const QNativeIpcKey &key); + void setNativeKey(const QString &key, QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs()) + { setNativeKey({ key, type }); } + QString nativeKey() const; + QNativeIpcKey nativeIpcKey() const; +#if QT_CORE_REMOVED_SINCE(6, 5) + void setNativeKey(const QString &key); +#endif + + bool create(qsizetype size, AccessMode mode = ReadWrite); + qsizetype size() const; + + bool attach(AccessMode mode = ReadWrite); + bool isAttached() const; + bool detach(); + + void *data(); + const void* constData() const; + const void *data() const; + +#if QT_CONFIG(systemsemaphore) + bool lock(); + bool unlock(); +#endif + + SharedMemoryError error() const; + QString errorString() const; + + static bool isKeyTypeSupported(QNativeIpcKey::Type type) Q_DECL_CONST_FUNCTION; + static QNativeIpcKey platformSafeKey(const QString &key, + QNativeIpcKey::Type type = QNativeIpcKey::DefaultTypeForOs); + static QNativeIpcKey legacyNativeKey(const QString &key, + QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs()); + +private: + Q_DISABLE_COPY(QSharedMemory) +}; + +#endif // QT_CONFIG(sharedmemory) + +QT_END_NAMESPACE + +#endif // QSHAREDMEMORY_H diff --git a/src/corelib/ipc/qsharedmemory_p.h b/src/corelib/ipc/qsharedmemory_p.h new file mode 100644 index 0000000000..987bb38642 --- /dev/null +++ b/src/corelib/ipc/qsharedmemory_p.h @@ -0,0 +1,215 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QSHAREDMEMORY_P_H +#define QSHAREDMEMORY_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 "qsharedmemory.h" + +#include <QtCore/qstring.h> + +#if QT_CONFIG(sharedmemory) +#include "qsystemsemaphore.h" +#include "qtipccommon_p.h" +#include "private/qobject_p.h" + +#if QT_CONFIG(posix_shm) +# include <sys/mman.h> +#endif +#if QT_CONFIG(sysv_shm) +# include <sys/shm.h> +#endif + +QT_BEGIN_NAMESPACE + +class QSharedMemoryPrivate; + +#if QT_CONFIG(systemsemaphore) +/*! + Helper class + */ +class QSharedMemoryLocker +{ + +public: + Q_NODISCARD_CTOR QSharedMemoryLocker(QSharedMemory *sharedMemory) : q_sm(sharedMemory) + { + Q_ASSERT(q_sm); + } + + inline ~QSharedMemoryLocker() + { + if (q_sm) + q_sm->unlock(); + } + + inline bool lock() + { + if (q_sm && q_sm->lock()) + return true; + q_sm = nullptr; + return false; + } + +private: + QSharedMemory *q_sm; +}; +#endif // QT_CONFIG(systemsemaphore) + +class QSharedMemoryPosix +{ +public: + static constexpr bool Enabled = QT_CONFIG(posix_shm); + static bool supports(QNativeIpcKey::Type type) + { return type == QNativeIpcKey::Type::PosixRealtime; } + static bool runtimeSupportCheck(); + + bool handle(QSharedMemoryPrivate *self); + bool cleanHandle(QSharedMemoryPrivate *self); + bool create(QSharedMemoryPrivate *self, qsizetype size); + bool attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode); + bool detach(QSharedMemoryPrivate *self); + + int hand = -1; +}; + +class QSharedMemorySystemV +{ +public: + static constexpr bool Enabled = QT_CONFIG(sysv_shm); + static bool supports(QNativeIpcKey::Type type) + { return quint16(type) <= 0xff; } + static bool runtimeSupportCheck(); + +#if QT_CONFIG(sysv_sem) + key_t handle(QSharedMemoryPrivate *self); + bool cleanHandle(QSharedMemoryPrivate *self); + bool create(QSharedMemoryPrivate *self, qsizetype size); + bool attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode); + bool detach(QSharedMemoryPrivate *self); + +private: + void updateNativeKeyFile(const QNativeIpcKey &nativeKey); + + QByteArray nativeKeyFile; + key_t unix_key = 0; +#endif +}; + +class QSharedMemoryWin32 +{ +public: +#ifdef Q_OS_WIN32 + static constexpr bool Enabled = true; +#else + static constexpr bool Enabled = false; +#endif + static bool runtimeSupportCheck() { return Enabled; } + static bool supports(QNativeIpcKey::Type type) + { return type == QNativeIpcKey::Type::Windows; } + + Qt::HANDLE handle(QSharedMemoryPrivate *self); + bool cleanHandle(QSharedMemoryPrivate *self); + bool create(QSharedMemoryPrivate *self, qsizetype size); + bool attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode); + bool detach(QSharedMemoryPrivate *self); + + Qt::HANDLE hand = nullptr; +}; + +class Q_AUTOTEST_EXPORT QSharedMemoryPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSharedMemory) + +public: + QSharedMemoryPrivate(QNativeIpcKey::Type type) : nativeKey(type) + { constructBackend(); } + ~QSharedMemoryPrivate(); + + void *memory = nullptr; + qsizetype size = 0; + QNativeIpcKey nativeKey; + QString errorString; +#if QT_CONFIG(systemsemaphore) + using SemaphoreAccessMode = QSystemSemaphore::AccessMode; + QSystemSemaphore systemSemaphore{ QNativeIpcKey() }; + bool lockedByMe = false; +#else + enum SemaphoreAccessMode {}; +#endif + QSharedMemory::SharedMemoryError error = QSharedMemory::NoError; + + union Backend { + Backend() {} + ~Backend() {} + QSharedMemoryPosix posix; + QSharedMemorySystemV sysv; + QSharedMemoryWin32 win32; + }; + QtIpcCommon::IpcStorageVariant<&Backend::posix, &Backend::sysv, &Backend::win32> backend; + + void constructBackend(); + void destructBackend(); + bool initKey(SemaphoreAccessMode mode); + + template <typename Lambda> auto visit(const Lambda &lambda) + { + return backend.visit(nativeKey.type(), lambda); + } + + bool handle() + { + return visit([&](auto p) { return !!p->handle(this); }); + } + bool cleanHandle() + { + return visit([&](auto p) { return p->cleanHandle(this); }); + } + bool create(qsizetype size) + { + return visit([&](auto p) { return p->create(this, size); }); + } + bool attach(QSharedMemory::AccessMode mode) + { + return visit([&](auto p) { return p->attach(this, mode); }); + } + bool detach() + { + return visit([&](auto p) { return p->detach(this); }); + } + + inline void setError(QSharedMemory::SharedMemoryError e, const QString &message) + { error = e; errorString = message; } + void setUnixErrorString(QLatin1StringView function); + void setWindowsErrorString(QLatin1StringView function); + +#if QT_CONFIG(systemsemaphore) + bool tryLocker(QSharedMemoryLocker *locker, const QString &function) { + if (!locker->lock()) { + errorString = QSharedMemory::tr("%1: unable to lock").arg(function); + error = QSharedMemory::LockError; + return false; + } + return true; + } + QNativeIpcKey semaphoreNativeKey() const; +#endif // QT_CONFIG(systemsemaphore) +}; + +QT_END_NAMESPACE + +#endif // QT_CONFIG(sharedmemory) + +#endif // QSHAREDMEMORY_P_H + diff --git a/src/corelib/ipc/qsharedmemory_posix.cpp b/src/corelib/ipc/qsharedmemory_posix.cpp new file mode 100644 index 0000000000..582c6628e1 --- /dev/null +++ b/src/corelib/ipc/qsharedmemory_posix.cpp @@ -0,0 +1,196 @@ +// Copyright (C) 2015 Konstantin Ritt <ritt.ks@gmail.com> +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias Koenig <tobias.koenig@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsharedmemory.h" +#include "qsharedmemory_p.h" +#include "qtipccommon_p.h" +#include <qfile.h> + +#include <errno.h> + +#if QT_CONFIG(sharedmemory) +#if QT_CONFIG(posix_shm) +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "private/qcore_unix_p.h" + +#ifndef O_CLOEXEC +# define O_CLOEXEC 0 +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; +using namespace QtIpcCommon; + +bool QSharedMemoryPosix::runtimeSupportCheck() +{ + static const bool result = []() { + (void)shm_open("", 0, 0); // this WILL fail + return errno != ENOSYS; + }(); + return result; +} + +bool QSharedMemoryPosix::handle(QSharedMemoryPrivate *self) +{ + // don't allow making handles on empty keys + if (self->nativeKey.isEmpty()) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: key is empty").arg("QSharedMemory::handle"_L1)); + return false; + } + + return true; +} + +bool QSharedMemoryPosix::cleanHandle(QSharedMemoryPrivate *) +{ + if (hand != -1) + qt_safe_close(hand); + hand = -1; + + return true; +} + +bool QSharedMemoryPosix::create(QSharedMemoryPrivate *self, qsizetype size) +{ + if (!handle(self)) + return false; + + const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey()); + + int fd; + QT_EINTR_LOOP(fd, ::shm_open(shmName.constData(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600)); + if (fd == -1) { + const int errorNumber = errno; + const auto function = "QSharedMemory::attach (shm_open)"_L1; + switch (errorNumber) { + case EINVAL: + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: bad name").arg(function)); + break; + default: + self->setUnixErrorString(function); + } + return false; + } + + // the size may only be set once + int ret; + QT_EINTR_LOOP(ret, QT_FTRUNCATE(fd, size)); + if (ret == -1) { + self->setUnixErrorString("QSharedMemory::create (ftruncate)"_L1); + qt_safe_close(fd); + return false; + } + + qt_safe_close(fd); + + return true; +} + +bool QSharedMemoryPosix::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) +{ + const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey()); + + const int oflag = (mode == QSharedMemory::ReadOnly ? O_RDONLY : O_RDWR); + const mode_t omode = (mode == QSharedMemory::ReadOnly ? 0400 : 0600); + + QT_EINTR_LOOP(hand, ::shm_open(shmName.constData(), oflag | O_CLOEXEC, omode)); + if (hand == -1) { + const int errorNumber = errno; + const auto function = "QSharedMemory::attach (shm_open)"_L1; + switch (errorNumber) { + case EINVAL: + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: bad name").arg(function)); + break; + default: + self->setUnixErrorString(function); + } + hand = -1; + return false; + } + + // grab the size + QT_STATBUF st; + if (QT_FSTAT(hand, &st) == -1) { + self->setUnixErrorString("QSharedMemory::attach (fstat)"_L1); + cleanHandle(self); + return false; + } + self->size = qsizetype(st.st_size); + + // grab the memory + const int mprot = (mode == QSharedMemory::ReadOnly ? PROT_READ : PROT_READ | PROT_WRITE); + self->memory = QT_MMAP(0, size_t(self->size), mprot, MAP_SHARED, hand, 0); + if (self->memory == MAP_FAILED || !self->memory) { + self->setUnixErrorString("QSharedMemory::attach (mmap)"_L1); + cleanHandle(self); + self->memory = 0; + self->size = 0; + return false; + } + +#ifdef F_ADD_SEALS + // Make sure the shared memory region will not shrink + // otherwise someone could cause SIGBUS on us. + // (see http://lwn.net/Articles/594919/) + fcntl(hand, F_ADD_SEALS, F_SEAL_SHRINK); +#endif + + return true; +} + +bool QSharedMemoryPosix::detach(QSharedMemoryPrivate *self) +{ + // detach from the memory segment + if (::munmap(self->memory, size_t(self->size)) == -1) { + self->setUnixErrorString("QSharedMemory::detach (munmap)"_L1); + return false; + } + self->memory = 0; + self->size = 0; + +#ifdef Q_OS_QNX + // On QNX the st_nlink field of struct stat contains the number of + // active shm_open() connections to the shared memory file, so we + // can use it to automatically clean up the file once the last + // user has detached from it. + + // get the number of current attachments + int shm_nattch = 0; + QT_STATBUF st; + if (QT_FSTAT(hand, &st) == 0) { + // subtract 2 from linkcount: one for our own open and one for the dir entry + shm_nattch = st.st_nlink - 2; + } + + cleanHandle(self); + + // if there are no attachments then unlink the shared memory + if (shm_nattch == 0) { + const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey()); + if (::shm_unlink(shmName.constData()) == -1 && errno != ENOENT) + self->setUnixErrorString("QSharedMemory::detach (shm_unlink)"_L1); + } +#else + // On non-QNX systems (tested Linux and Haiku), the st_nlink field is always 1, + // so we'll simply leak the shared memory files. + cleanHandle(self); +#endif + + return true; +} + +QT_END_NAMESPACE + +#endif // QT_CONFIG(posix_shm) +#endif // QT_CONFIG(sharedmemory) diff --git a/src/corelib/ipc/qsharedmemory_systemv.cpp b/src/corelib/ipc/qsharedmemory_systemv.cpp new file mode 100644 index 0000000000..dc9de11091 --- /dev/null +++ b/src/corelib/ipc/qsharedmemory_systemv.cpp @@ -0,0 +1,212 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsharedmemory.h" +#include "qsharedmemory_p.h" + +#include "qtipccommon_p.h" + +#include <qdir.h> +#include <qdebug.h> + +#include <errno.h> + +#if QT_CONFIG(sharedmemory) +#if QT_CONFIG(sysv_shm) +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/mman.h> +#include <sys/shm.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "private/qcore_unix_p.h" +#if defined(Q_OS_DARWIN) +#include "private/qcore_mac_p.h" +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; +using namespace QtIpcCommon; + +bool QSharedMemorySystemV::runtimeSupportCheck() +{ +#if defined(Q_OS_DARWIN) + if (qt_apple_isSandboxed()) + return false; +#endif + static const bool result = []() { + (void)shmget(IPC_PRIVATE, ~size_t(0), 0); // this will fail + return errno != ENOSYS; + }(); + return result; +} + + +inline void QSharedMemorySystemV::updateNativeKeyFile(const QNativeIpcKey &nativeKey) +{ + Q_ASSERT(nativeKeyFile.isEmpty() ); + if (!nativeKey.nativeKey().isEmpty()) + nativeKeyFile = QFile::encodeName(nativeKey.nativeKey()); +} + +/*! + \internal + + If not already made create the handle used for accessing the shared memory. +*/ +key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self) +{ + // already made + if (unix_key) + return unix_key; + + // don't allow making handles on empty keys + if (nativeKeyFile.isEmpty()) + updateNativeKeyFile(self->nativeKey); + if (nativeKeyFile.isEmpty()) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: key is empty") + .arg("QSharedMemory::handle:"_L1)); + return 0; + } + + unix_key = ftok(nativeKeyFile, int(self->nativeKey.type())); + if (unix_key < 0) { + self->setUnixErrorString("QSharedMemory::handle"_L1); + nativeKeyFile.clear(); + unix_key = 0; + } + return unix_key; +} + +bool QSharedMemorySystemV::cleanHandle(QSharedMemoryPrivate *self) +{ + if (unix_key == 0) + return true; + + // Get the number of current attachments + struct shmid_ds shmid_ds; + QByteArray keyfile = std::exchange(nativeKeyFile, QByteArray()); + + int id = shmget(unix_key, 0, 0400); + unix_key = 0; + if (shmctl(id, IPC_STAT, &shmid_ds)) + return errno != EINVAL; + + // If there are still attachments, keep the keep file and shm + if (shmid_ds.shm_nattch != 0) + return true; + + if (shmctl(id, IPC_RMID, &shmid_ds) < 0) { + if (errno != EINVAL) { + self->setUnixErrorString("QSharedMemory::remove"_L1); + return false; + } + }; + + // remove file + return unlink(keyfile) == 0; +} + +bool QSharedMemorySystemV::create(QSharedMemoryPrivate *self, qsizetype size) +{ + // build file if needed + bool createdFile = false; + updateNativeKeyFile(self->nativeKey); + int built = createUnixKeyFile(nativeKeyFile); + if (built == -1) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: unable to make key") + .arg("QSharedMemory::handle:"_L1)); + return false; + } + if (built == 1) { + createdFile = true; + } + + // get handle + if (!handle(self)) { + if (createdFile) + unlink(nativeKeyFile); + return false; + } + + // create + if (-1 == shmget(unix_key, size_t(size), 0600 | IPC_CREAT | IPC_EXCL)) { + const auto function = "QSharedMemory::create"_L1; + switch (errno) { + case EINVAL: + self->setError(QSharedMemory::InvalidSize, + QSharedMemory::tr("%1: system-imposed size restrictions") + .arg("QSharedMemory::handle"_L1)); + break; + default: + self->setUnixErrorString(function); + } + if (createdFile && self->error != QSharedMemory::AlreadyExists) + unlink(nativeKeyFile); + return false; + } + + return true; +} + +bool QSharedMemorySystemV::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) +{ + // grab the shared memory segment id + int id = shmget(unix_key, 0, (mode == QSharedMemory::ReadOnly ? 0400 : 0600)); + if (-1 == id) { + self->setUnixErrorString("QSharedMemory::attach (shmget)"_L1); + unix_key = 0; + nativeKeyFile.clear(); + return false; + } + + // grab the memory + self->memory = shmat(id, nullptr, (mode == QSharedMemory::ReadOnly ? SHM_RDONLY : 0)); + if (self->memory == MAP_FAILED) { + self->memory = nullptr; + self->setUnixErrorString("QSharedMemory::attach (shmat)"_L1); + return false; + } + + // grab the size + shmid_ds shmid_ds; + if (!shmctl(id, IPC_STAT, &shmid_ds)) { + self->size = (qsizetype)shmid_ds.shm_segsz; + } else { + self->setUnixErrorString("QSharedMemory::attach (shmctl)"_L1); + return false; + } + + return true; +} + +bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self) +{ + // detach from the memory segment + if (shmdt(self->memory) < 0) { + const auto function = "QSharedMemory::detach"_L1; + switch (errno) { + case EINVAL: + self->setError(QSharedMemory::NotFound, + QSharedMemory::tr("%1: not attached").arg(function)); + break; + default: + self->setUnixErrorString(function); + } + return false; + } + self->memory = nullptr; + self->size = 0; + + return cleanHandle(self); +} + +QT_END_NAMESPACE + +#endif // QT_CONFIG(sysv_shm) +#endif // QT_CONFIG(sharedmemory) diff --git a/src/corelib/ipc/qsharedmemory_win.cpp b/src/corelib/ipc/qsharedmemory_win.cpp new file mode 100644 index 0000000000..472f34f9a1 --- /dev/null +++ b/src/corelib/ipc/qsharedmemory_win.cpp @@ -0,0 +1,148 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsharedmemory.h" +#include "qsharedmemory_p.h" +#include "qsystemsemaphore.h" +#include <qdebug.h> +#include <qt_windows.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +#if QT_CONFIG(sharedmemory) + +void QSharedMemoryPrivate::setWindowsErrorString(QLatin1StringView function) +{ + DWORD windowsError = GetLastError(); + if (windowsError == 0) + return; + switch (windowsError) { + case ERROR_ALREADY_EXISTS: + error = QSharedMemory::AlreadyExists; + errorString = QSharedMemory::tr("%1: already exists").arg(function); + break; + case ERROR_FILE_NOT_FOUND: + error = QSharedMemory::NotFound; + errorString = QSharedMemory::tr("%1: doesn't exist").arg(function); + break; + case ERROR_COMMITMENT_LIMIT: + error = QSharedMemory::InvalidSize; + errorString = QSharedMemory::tr("%1: invalid size").arg(function); + break; + case ERROR_NO_SYSTEM_RESOURCES: + case ERROR_NOT_ENOUGH_MEMORY: + error = QSharedMemory::OutOfResources; + errorString = QSharedMemory::tr("%1: out of resources").arg(function); + break; + case ERROR_ACCESS_DENIED: + error = QSharedMemory::PermissionDenied; + errorString = QSharedMemory::tr("%1: permission denied").arg(function); + break; + default: + errorString = QSharedMemory::tr("%1: unknown error: %2") + .arg(function, qt_error_string(windowsError)); + error = QSharedMemory::UnknownError; +#if defined QSHAREDMEMORY_DEBUG + qDebug() << errorString << "key" << key; +#endif + } +} + +HANDLE QSharedMemoryWin32::handle(QSharedMemoryPrivate *self) +{ + if (!hand) { + const auto function = "QSharedMemory::handle"_L1; + if (self->nativeKey.isEmpty()) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: unable to make key").arg(function)); + return 0; + } + hand = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, + reinterpret_cast<const wchar_t *>(self->nativeKey.nativeKey().utf16())); + if (!hand) { + self->setWindowsErrorString(function); + return 0; + } + } + return hand; +} + +bool QSharedMemoryWin32::cleanHandle(QSharedMemoryPrivate *) +{ + if (hand != 0 && !CloseHandle(hand)) { + hand = 0; + return false; + } + hand = 0; + return true; +} + +bool QSharedMemoryWin32::create(QSharedMemoryPrivate *self, qsizetype size) +{ + const auto function = "QSharedMemory::create"_L1; + if (self->nativeKey.isEmpty()) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: key error").arg(function)); + return false; + } + + // Create the file mapping. + DWORD high, low; + if constexpr (sizeof(qsizetype) == 8) + high = DWORD(quint64(size) >> 32); + else + high = 0; + low = DWORD(size_t(size) & 0xffffffff); + hand = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, high, low, + reinterpret_cast<const wchar_t *>(self->nativeKey.nativeKey().utf16())); + self->setWindowsErrorString(function); + + // hand is valid when it already exists unlike unix so explicitly check + return self->error != QSharedMemory::AlreadyExists && hand; +} + +bool QSharedMemoryWin32::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) +{ + // Grab a pointer to the memory block + int permissions = (mode == QSharedMemory::ReadOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS); + self->memory = (void *)MapViewOfFile(handle(self), permissions, 0, 0, 0); + if (!self->memory) { + self->setWindowsErrorString("QSharedMemory::attach"_L1); + cleanHandle(self); + return false; + } + + // Grab the size of the memory we have been given (a multiple of 4K on windows) + MEMORY_BASIC_INFORMATION info; + if (!VirtualQuery(self->memory, &info, sizeof(info))) { + // Windows doesn't set an error code on this one, + // it should only be a kernel memory error. + self->setError(QSharedMemory::UnknownError, + QSharedMemory::tr("%1: size query failed") + .arg("QSharedMemory::attach: "_L1)); + return false; + } + self->size = qsizetype(info.RegionSize); + + return true; +} + +bool QSharedMemoryWin32::detach(QSharedMemoryPrivate *self) +{ + // umap memory + if (!UnmapViewOfFile(self->memory)) { + self->setWindowsErrorString("QSharedMemory::detach"_L1); + return false; + } + self->memory = 0; + self->size = 0; + + // close handle + return cleanHandle(self); +} + +#endif // QT_CONFIG(sharedmemory) + +QT_END_NAMESPACE diff --git a/src/corelib/ipc/qsystemsemaphore.cpp b/src/corelib/ipc/qsystemsemaphore.cpp new file mode 100644 index 0000000000..4c24ef6043 --- /dev/null +++ b/src/corelib/ipc/qsystemsemaphore.cpp @@ -0,0 +1,402 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsystemsemaphore.h" +#include "qsystemsemaphore_p.h" + +#if QT_CONFIG(systemsemaphore) +#include <QtCore/q20memory.h> + +QT_BEGIN_NAMESPACE + +using namespace QtIpcCommon; +using namespace Qt::StringLiterals; + +inline void QSystemSemaphorePrivate::constructBackend() +{ + visit([](auto p) { q20::construct_at(p); }); +} + +inline void QSystemSemaphorePrivate::destructBackend() +{ + visit([](auto p) { std::destroy_at(p); }); +} + +/*! + \class QSystemSemaphore + \inmodule QtCore + \since 4.4 + + \brief The QSystemSemaphore class provides a general counting system semaphore. + + A system semaphore is a generalization of \l QSemaphore. Typically, a + semaphore is used to protect a certain number of identical resources. + + Like its lighter counterpart, a QSystemSemaphore can be + accessed from multiple \l {QThread} {threads}. Unlike QSemaphore, a + QSystemSemaphore can also be accessed from multiple \l {QProcess} + {processes}. This means QSystemSemaphore is a much heavier class, so + if your application doesn't need to access your semaphores across + multiple processes, you will probably want to use QSemaphore. + + Semaphores support two fundamental operations, acquire() and release(): + + acquire() tries to acquire one resource. If there isn't a resource + available, the call blocks until a resource becomes available. Then + the resource is acquired and the call returns. + + release() releases one resource so it can be acquired by another + process. The function can also be called with a parameter n > 1, + which releases n resources. + + System semaphores are identified by a key, represented by \l QNativeIpcKey. A + key can be created in a cross-platform manner by using platformSafeKey(). A + system semaphore is created by the QSystemSemaphore constructor when passed + an access mode parameter of AccessMode::Create. Once it is created, other + processes may attach to the same semaphore using the same key and an access + mode parameter of AccessMode::Open. + + Example: Create a system semaphore + \snippet code/src_corelib_kernel_qsystemsemaphore.cpp 0 + + For details on the key types, platform-specific limitations, and + interoperability with older or non-Qt applications, see the \l{Native IPC + Keys} documentation. That includes important information for sandboxed + applications on Apple platforms, including all apps obtained via the Apple + App Store. + + \sa {Inter-Process Communication}, QSharedMemory, QSemaphore + */ + +/*! + Requests a system semaphore identified by the legacy key \a key. + */ +QSystemSemaphore::QSystemSemaphore(const QString &key, int initialValue, AccessMode mode) + : QSystemSemaphore(legacyNativeKey(key), initialValue, mode) +{ +} + +/*! + Requests a system semaphore for the specified \a key. The parameters + \a initialValue and \a mode are used according to the following + rules, which are system dependent. + + In Unix, if the \a mode is \l {QSystemSemaphore::} {Open} and the + system already has a semaphore identified by \a key, that semaphore + is used, and the semaphore's resource count is not changed, i.e., \a + initialValue is ignored. But if the system does not already have a + semaphore identified by \a key, it creates a new semaphore for that + key and sets its resource count to \a initialValue. + + In Unix, if the \a mode is \l {QSystemSemaphore::} {Create} and the + system already has a semaphore identified by \a key, that semaphore + is used, and its resource count is set to \a initialValue. If the + system does not already have a semaphore identified by \a key, it + creates a new semaphore for that key and sets its resource count to + \a initialValue. + + In Windows, \a mode is ignored, and the system always tries to + create a semaphore for the specified \a key. If the system does not + already have a semaphore identified as \a key, it creates the + semaphore and sets its resource count to \a initialValue. But if the + system already has a semaphore identified as \a key it uses that + semaphore and ignores \a initialValue. + + The \l {QSystemSemaphore::AccessMode} {mode} parameter is only used + in Unix systems to handle the case where a semaphore survives a + process crash. In that case, the next process to allocate a + semaphore with the same \a key will get the semaphore that survived + the crash, and unless \a mode is \l {QSystemSemaphore::} {Create}, + the resource count will not be reset to \a initialValue but will + retain the initial value it had been given by the crashed process. + + \sa acquire(), key() + */ +QSystemSemaphore::QSystemSemaphore(const QNativeIpcKey &key, int initialValue, AccessMode mode) + : d(new QSystemSemaphorePrivate(key.type())) +{ + setNativeKey(key, initialValue, mode); +} + +/*! + The destructor destroys the QSystemSemaphore object, but the + underlying system semaphore is not removed from the system unless + this instance of QSystemSemaphore is the last one existing for that + system semaphore. + + Two important side effects of the destructor depend on the system. + In Windows, if acquire() has been called for this semaphore but not + release(), release() will not be called by the destructor, nor will + the resource be released when the process exits normally. This would + be a program bug which could be the cause of a deadlock in another + process trying to acquire the same resource. In Unix, acquired + resources that are not released before the destructor is called are + automatically released when the process exits. +*/ +QSystemSemaphore::~QSystemSemaphore() +{ + d->cleanHandle(); +} + +/*! + \enum QSystemSemaphore::AccessMode + + This enum is used by the constructor and setKey(). Its purpose is to + enable handling the problem in Unix implementations of semaphores + that survive a crash. In Unix, when a semaphore survives a crash, we + need a way to force it to reset its resource count, when the system + reuses the semaphore. In Windows, where semaphores can't survive a + crash, this enum has no effect. + + \value Open If the semaphore already exists, its initial resource + count is not reset. If the semaphore does not already exist, it is + created and its initial resource count set. + + \value Create QSystemSemaphore takes ownership of the semaphore and + sets its resource count to the requested value, regardless of + whether the semaphore already exists by having survived a crash. + This value should be passed to the constructor, when the first + semaphore for a particular key is constructed and you know that if + the semaphore already exists it could only be because of a crash. In + Windows, where a semaphore can't survive a crash, Create and Open + have the same behavior. +*/ + +/*! + This function works the same as the constructor. It reconstructs + this QSystemSemaphore object. If the new \a key is different from + the old key, calling this function is like calling the destructor of + the semaphore with the old key, then calling the constructor to + create a new semaphore with the new \a key. The \a initialValue and + \a mode parameters are as defined for the constructor. + + This function is useful if the native key was shared from another process. + See \l{Native IPC Keys} for more information. + + \sa QSystemSemaphore(), nativeIpcKey() + */ +void QSystemSemaphore::setNativeKey(const QNativeIpcKey &key, int initialValue, AccessMode mode) +{ + if (key == d->nativeKey && mode == Open) + return; + if (!isKeyTypeSupported(key.type())) { + d->setError(KeyError, tr("%1: unsupported key type") + .arg("QSystemSemaphore::setNativeKey"_L1)); + return; + } + + d->clearError(); + d->cleanHandle(); + if (key.type() == d->nativeKey.type()) { + // we can reuse the backend + d->nativeKey = key; + } else { + // we must recreate the backend + d->destructBackend(); + d->nativeKey = key; + d->constructBackend(); + } + d->initialValue = initialValue; + d->handle(mode); +} + +/*! + Returns the key assigned to this system semaphore. The key is the + name by which the semaphore can be accessed from other processes. + + You can use the native key to access system semaphores that have not been + created by Qt, or to grant access to non-Qt applications. See \l{Native IPC + Keys} for more information. + + \sa setNativeKey() + */ +QNativeIpcKey QSystemSemaphore::nativeIpcKey() const +{ + return d->nativeKey; +} + +/*! + This function works the same as the constructor. It reconstructs + this QSystemSemaphore object. If the new \a key is different from + the old key, calling this function is like calling the destructor of + the semaphore with the old key, then calling the constructor to + create a new semaphore with the new \a key. The \a initialValue and + \a mode parameters are as defined for the constructor. + + \sa QSystemSemaphore(), key() + */ +void QSystemSemaphore::setKey(const QString &key, int initialValue, AccessMode mode) +{ + setNativeKey(legacyNativeKey(key), initialValue, mode); +} + +/*! + Returns the legacy key assigned to this system semaphore. The key is the + name by which the semaphore can be accessed from other processes. + + \sa setKey() + */ +QString QSystemSemaphore::key() const +{ + return QNativeIpcKeyPrivate::legacyKey(d->nativeKey); +} + +/*! + Acquires one of the resources guarded by this semaphore, if there is + one available, and returns \c true. If all the resources guarded by this + semaphore have already been acquired, the call blocks until one of + them is released by another process or thread having a semaphore + with the same key. + + If false is returned, a system error has occurred. Call error() + to get a value of QSystemSemaphore::SystemSemaphoreError that + indicates which error occurred. + + \sa release() + */ +bool QSystemSemaphore::acquire() +{ + return d->modifySemaphore(-1); +} + +/*! + Releases \a n resources guarded by the semaphore. Returns \c true + unless there is a system error. + + Example: Create a system semaphore having five resources; acquire + them all and then release them all. + + \snippet code/src_corelib_kernel_qsystemsemaphore.cpp 1 + + This function can also "create" resources. For example, immediately + following the sequence of statements above, suppose we add the + statement: + + \snippet code/src_corelib_kernel_qsystemsemaphore.cpp 2 + + Ten new resources are now guarded by the semaphore, in addition to + the five that already existed. You would not normally use this + function to create more resources. + + \sa acquire() + */ +bool QSystemSemaphore::release(int n) +{ + if (n == 0) + return true; + if (n < 0) { + qWarning("QSystemSemaphore::release: n is negative."); + return false; + } + return d->modifySemaphore(n); +} + +/*! + Returns a value indicating whether an error occurred, and, if so, + which error it was. + + \sa errorString() + */ +QSystemSemaphore::SystemSemaphoreError QSystemSemaphore::error() const +{ + return d->error; +} + +/*! + \enum QSystemSemaphore::SystemSemaphoreError + + \value NoError No error occurred. + + \value PermissionDenied The operation failed because the caller + didn't have the required permissions. + + \value KeyError The operation failed because of an invalid key. + + \value AlreadyExists The operation failed because a system + semaphore with the specified key already existed. + + \value NotFound The operation failed because a system semaphore + with the specified key could not be found. + + \value OutOfResources The operation failed because there was + not enough memory available to fill the request. + + \value UnknownError Something else happened and it was bad. +*/ + +/*! + Returns a text description of the last error that occurred. If + error() returns an \l {QSystemSemaphore::SystemSemaphoreError} {error + value}, call this function to get a text string that describes the + error. + + \sa error() + */ +QString QSystemSemaphore::errorString() const +{ + return d->errorString; +} + +void QSystemSemaphorePrivate::setUnixErrorString(QLatin1StringView function) +{ + // EINVAL is handled in functions so they can give better error strings + switch (errno) { + case EPERM: + case EACCES: + errorString = QSystemSemaphore::tr("%1: permission denied").arg(function); + error = QSystemSemaphore::PermissionDenied; + break; + case EEXIST: + errorString = QSystemSemaphore::tr("%1: already exists").arg(function); + error = QSystemSemaphore::AlreadyExists; + break; + case ENOENT: + errorString = QSystemSemaphore::tr("%1: does not exist").arg(function); + error = QSystemSemaphore::NotFound; + break; + case ERANGE: + case ENOSPC: + case EMFILE: + errorString = QSystemSemaphore::tr("%1: out of resources").arg(function); + error = QSystemSemaphore::OutOfResources; + break; + case ENAMETOOLONG: + errorString = QSystemSemaphore::tr("%1: key too long").arg(function); + error = QSystemSemaphore::KeyError; + break; + default: + errorString = QSystemSemaphore::tr("%1: unknown error: %2") + .arg(function, qt_error_string(errno)); + error = QSystemSemaphore::UnknownError; +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug() << errorString << "key" << key << "errno" << errno << EINVAL; +#endif + } +} + +bool QSystemSemaphore::isKeyTypeSupported(QNativeIpcKey::Type type) +{ + if (!isIpcSupported(IpcType::SystemSemaphore, type)) + return false; + using Variant = decltype(QSystemSemaphorePrivate::backend); + return Variant::staticVisit(type, [](auto ptr) { + using Impl = std::decay_t<decltype(*ptr)>; + return Impl::runtimeSupportCheck(); + }); +} + +QNativeIpcKey QSystemSemaphore::platformSafeKey(const QString &key, QNativeIpcKey::Type type) +{ + return QtIpcCommon::platformSafeKey(key, IpcType::SystemSemaphore, type); +} + +QNativeIpcKey QSystemSemaphore::legacyNativeKey(const QString &key, QNativeIpcKey::Type type) +{ + return QtIpcCommon::legacyPlatformSafeKey(key, IpcType::SystemSemaphore, type); +} + +QT_END_NAMESPACE + +#include "moc_qsystemsemaphore.cpp" + +#endif // QT_CONFIG(systemsemaphore) diff --git a/src/corelib/ipc/qsystemsemaphore.h b/src/corelib/ipc/qsystemsemaphore.h new file mode 100644 index 0000000000..df6fd28342 --- /dev/null +++ b/src/corelib/ipc/qsystemsemaphore.h @@ -0,0 +1,75 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QSYSTEMSEMAPHORE_H +#define QSYSTEMSEMAPHORE_H + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qtipccommon.h> +#include <QtCore/qstring.h> +#include <QtCore/qscopedpointer.h> + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(systemsemaphore) + +class QSystemSemaphorePrivate; + +class Q_CORE_EXPORT QSystemSemaphore +{ + Q_GADGET + Q_DECLARE_TR_FUNCTIONS(QSystemSemaphore) +public: + enum AccessMode + { + Open, + Create + }; + Q_ENUM(AccessMode) + + enum SystemSemaphoreError + { + NoError, + PermissionDenied, + KeyError, + AlreadyExists, + NotFound, + OutOfResources, + UnknownError + }; + + QSystemSemaphore(const QNativeIpcKey &key, int initialValue = 0, AccessMode = Open); + ~QSystemSemaphore(); + + void setNativeKey(const QNativeIpcKey &key, int initialValue = 0, AccessMode = Open); + void setNativeKey(const QString &key, int initialValue = 0, AccessMode mode = Open, + QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs()) + { setNativeKey({ key, type }, initialValue, mode); } + QNativeIpcKey nativeIpcKey() const; + + QSystemSemaphore(const QString &key, int initialValue = 0, AccessMode mode = Open); + void setKey(const QString &key, int initialValue = 0, AccessMode mode = Open); + QString key() const; + + bool acquire(); + bool release(int n = 1); + + SystemSemaphoreError error() const; + QString errorString() const; + + static bool isKeyTypeSupported(QNativeIpcKey::Type type) Q_DECL_CONST_FUNCTION; + static QNativeIpcKey platformSafeKey(const QString &key, + QNativeIpcKey::Type type = QNativeIpcKey::DefaultTypeForOs); + static QNativeIpcKey legacyNativeKey(const QString &key, + QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs()); + +private: + Q_DISABLE_COPY(QSystemSemaphore) + QScopedPointer<QSystemSemaphorePrivate> d; +}; + +#endif // QT_CONFIG(systemsemaphore) + +QT_END_NAMESPACE + +#endif // QSYSTEMSEMAPHORE_H diff --git a/src/corelib/ipc/qsystemsemaphore_p.h b/src/corelib/ipc/qsystemsemaphore_p.h new file mode 100644 index 0000000000..788c4fb784 --- /dev/null +++ b/src/corelib/ipc/qsystemsemaphore_p.h @@ -0,0 +1,152 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QSYSTEMSEMAPHORE_P_H +#define QSYSTEMSEMAPHORE_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 "qsystemsemaphore.h" + +#if QT_CONFIG(systemsemaphore) + +#include "qcoreapplication.h" +#include "qtipccommon_p.h" +#include "private/qtcore-config_p.h" + +#include <sys/types.h> +#if QT_CONFIG(posix_sem) +# include <semaphore.h> +#endif +#ifndef SEM_FAILED +# define SEM_FAILED nullptr +struct sem_t; +#endif +#if QT_CONFIG(sysv_sem) +# include <sys/sem.h> +#endif + +QT_BEGIN_NAMESPACE + +class QSystemSemaphorePrivate; + +struct QSystemSemaphorePosix +{ + static constexpr bool Enabled = QT_CONFIG(posix_sem); + static bool supports(QNativeIpcKey::Type type) + { return type == QNativeIpcKey::Type::PosixRealtime; } + static bool runtimeSupportCheck(); + + bool handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode); + void cleanHandle(QSystemSemaphorePrivate *self); + bool modifySemaphore(QSystemSemaphorePrivate *self, int count); + + sem_t *semaphore = SEM_FAILED; + bool createdSemaphore = false; +}; + +struct QSystemSemaphoreSystemV +{ + static constexpr bool Enabled = QT_CONFIG(sysv_sem); + static bool supports(QNativeIpcKey::Type type) + { return quint16(type) <= 0xff; } + static bool runtimeSupportCheck(); + +#if QT_CONFIG(sysv_sem) + key_t handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode); + void cleanHandle(QSystemSemaphorePrivate *self); + bool modifySemaphore(QSystemSemaphorePrivate *self, int count); + + QByteArray nativeKeyFile; + key_t unix_key = -1; + int semaphore = -1; + bool createdFile = false; + bool createdSemaphore = false; +#endif +}; + +struct QSystemSemaphoreWin32 +{ +#ifdef Q_OS_WIN32 + static constexpr bool Enabled = true; +#else + static constexpr bool Enabled = false; +#endif + static bool supports(QNativeIpcKey::Type type) + { return type == QNativeIpcKey::Type::Windows; } + static bool runtimeSupportCheck() { return Enabled; } + + // we can declare the members without the #if + Qt::HANDLE handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode); + void cleanHandle(QSystemSemaphorePrivate *self); + bool modifySemaphore(QSystemSemaphorePrivate *self, int count); + + Qt::HANDLE semaphore = nullptr; +}; + +class QSystemSemaphorePrivate +{ +public: + QSystemSemaphorePrivate(QNativeIpcKey::Type type) : nativeKey(type) + { constructBackend(); } + ~QSystemSemaphorePrivate() { destructBackend(); } + + void setWindowsErrorString(QLatin1StringView function); // Windows only + void setUnixErrorString(QLatin1StringView function); + inline void setError(QSystemSemaphore::SystemSemaphoreError e, const QString &message) + { error = e; errorString = message; } + inline void clearError() + { setError(QSystemSemaphore::NoError, QString()); } + + QNativeIpcKey nativeKey; + QString errorString; + int initialValue; + QSystemSemaphore::SystemSemaphoreError error = QSystemSemaphore::NoError; + + union Backend { + Backend() {} + ~Backend() {} + QSystemSemaphorePosix posix; + QSystemSemaphoreSystemV sysv; + QSystemSemaphoreWin32 win32; + }; + QtIpcCommon::IpcStorageVariant<&Backend::posix, &Backend::sysv, &Backend::win32> backend; + + void constructBackend(); + void destructBackend(); + + template <typename Lambda> auto visit(const Lambda &lambda) + { + return backend.visit(nativeKey.type(), lambda); + } + + void handle(QSystemSemaphore::AccessMode mode) + { + visit([&](auto p) { p->handle(this, mode); }); + } + void cleanHandle() + { + visit([&](auto p) { p->cleanHandle(this); }); + } + bool modifySemaphore(int count) + { + return visit([&](auto p) { return p->modifySemaphore(this, count); }); + } +}; + +QT_END_NAMESPACE + +#endif // QT_CONFIG(systemsemaphore) + +#endif // QSYSTEMSEMAPHORE_P_H + diff --git a/src/corelib/ipc/qsystemsemaphore_posix.cpp b/src/corelib/ipc/qsystemsemaphore_posix.cpp new file mode 100644 index 0000000000..7df9593513 --- /dev/null +++ b/src/corelib/ipc/qsystemsemaphore_posix.cpp @@ -0,0 +1,171 @@ +// Copyright (C) 2015 Konstantin Ritt <ritt.ks@gmail.com> +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias Koenig <tobias.koenig@kdab.com> +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsystemsemaphore.h" +#include "qsystemsemaphore_p.h" + +#include <qdebug.h> +#include <qfile.h> +#include <qcoreapplication.h> + +#if QT_CONFIG(posix_sem) + +#include <sys/types.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef Q_OS_UNIX +# include "private/qcore_unix_p.h" +#else +# define QT_EINTR_LOOP_VAL(var, val, cmd) \ + (void)var; var = cmd +# define QT_EINTR_LOOP(var, cmd) QT_EINTR_LOOP_VAL(var, -1, cmd) +#endif + +// OpenBSD 4.2 doesn't define EIDRM, see BUGS section: +// http://www.openbsd.org/cgi-bin/man.cgi?query=semop&manpath=OpenBSD+4.2 +#if defined(Q_OS_OPENBSD) && !defined(EIDRM) +#define EIDRM EINVAL +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +bool QSystemSemaphorePosix::runtimeSupportCheck() +{ + static const bool result = []() { + sem_open("/", 0, 0, 0); // this WILL fail + return errno != ENOSYS; + }(); + return result; +} + +bool QSystemSemaphorePosix::handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode) +{ + if (semaphore != SEM_FAILED) + return true; // we already have a semaphore + + const QByteArray semName = QFile::encodeName(self->nativeKey.nativeKey()); + if (semName.isEmpty()) { + self->setError(QSystemSemaphore::KeyError, + QSystemSemaphore::tr("%1: key is empty") + .arg("QSystemSemaphore::handle"_L1)); + return false; + } + + // Always try with O_EXCL so we know whether we created the semaphore. + int oflag = O_CREAT | O_EXCL; + for (int tryNum = 0, maxTries = 1; tryNum < maxTries; ++tryNum) { + do { + semaphore = ::sem_open(semName.constData(), oflag, 0600, self->initialValue); + } while (semaphore == SEM_FAILED && errno == EINTR); + if (semaphore == SEM_FAILED && errno == EEXIST) { + if (mode == QSystemSemaphore::Create) { + if (::sem_unlink(semName.constData()) == -1 && errno != ENOENT) { + self->setUnixErrorString("QSystemSemaphore::handle (sem_unlink)"_L1); + return false; + } + // Race condition: the semaphore might be recreated before + // we call sem_open again, so we'll retry several times. + maxTries = 3; + } else { + // Race condition: if it no longer exists at the next sem_open + // call, we won't realize we created it, so we'll leak it later. + oflag &= ~O_EXCL; + maxTries = 2; + } + } else { + break; + } + } + + if (semaphore == SEM_FAILED) { + self->setUnixErrorString("QSystemSemaphore::handle"_L1); + return false; + } + + createdSemaphore = (oflag & O_EXCL) != 0; + + return true; +} + +void QSystemSemaphorePosix::cleanHandle(QSystemSemaphorePrivate *self) +{ + if (semaphore != SEM_FAILED) { + if (::sem_close(semaphore) == -1) { + self->setUnixErrorString("QSystemSemaphore::cleanHandle (sem_close)"_L1); +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug("QSystemSemaphore::cleanHandle sem_close failed."); +#endif + } + semaphore = SEM_FAILED; + } + + if (createdSemaphore) { + const QByteArray semName = QFile::encodeName(self->nativeKey.nativeKey()); + if (::sem_unlink(semName) == -1 && errno != ENOENT) { + self->setUnixErrorString("QSystemSemaphore::cleanHandle (sem_unlink)"_L1); +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug("QSystemSemaphorePosix::cleanHandle sem_unlink failed."); +#endif + } + createdSemaphore = false; + } +} + +bool QSystemSemaphorePosix::modifySemaphore(QSystemSemaphorePrivate *self, int count) +{ + if (!handle(self, QSystemSemaphore::Open)) + return false; + + if (count > 0) { + int cnt = count; + do { + if (::sem_post(semaphore) == -1) { +#if defined(Q_OS_VXWORKS) + if (errno == EINVAL) { + semaphore = SEM_FAILED; + return modifySemaphore(self, cnt); + } +#endif + self->setUnixErrorString("QSystemSemaphore::modifySemaphore (sem_post)"_L1); +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug("QSystemSemaphorePosix::modify sem_post failed %d %d", count, errno); +#endif + // rollback changes to preserve the SysV semaphore behavior + for ( ; cnt < count; ++cnt) { + int res; + QT_EINTR_LOOP(res, ::sem_wait(semaphore)); + } + return false; + } + --cnt; + } while (cnt > 0); + } else { + int res; + QT_EINTR_LOOP(res, ::sem_wait(semaphore)); + if (res == -1) { + // If the semaphore was removed be nice and create it and then modifySemaphore again + if (errno == EINVAL || errno == EIDRM) { + semaphore = SEM_FAILED; + return modifySemaphore(self, count); + } + self->setUnixErrorString("QSystemSemaphore::modifySemaphore (sem_wait)"_L1); +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug("QSystemSemaphorePosix::modify sem_wait failed %d %d", count, errno); +#endif + return false; + } + } + + self->clearError(); + return true; +} + +QT_END_NAMESPACE + +#endif // QT_CONFIG(posix_sem) diff --git a/src/corelib/ipc/qsystemsemaphore_systemv.cpp b/src/corelib/ipc/qsystemsemaphore_systemv.cpp new file mode 100644 index 0000000000..e5d231d1d4 --- /dev/null +++ b/src/corelib/ipc/qsystemsemaphore_systemv.cpp @@ -0,0 +1,201 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsystemsemaphore.h" +#include "qsystemsemaphore_p.h" + +#include <qdebug.h> +#include <qfile.h> +#include <qcoreapplication.h> + +#if QT_CONFIG(sysv_sem) + +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/sem.h> +#include <fcntl.h> +#include <errno.h> + +#if defined(Q_OS_DARWIN) +#include "private/qcore_mac_p.h" +#endif + +#include "private/qcore_unix_p.h" + +// OpenBSD 4.2 doesn't define EIDRM, see BUGS section: +// http://www.openbsd.org/cgi-bin/man.cgi?query=semop&manpath=OpenBSD+4.2 +#if defined(Q_OS_OPENBSD) && !defined(EIDRM) +#define EIDRM EINVAL +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +bool QSystemSemaphoreSystemV::runtimeSupportCheck() +{ +#if defined(Q_OS_DARWIN) + if (qt_apple_isSandboxed()) + return false; +#endif + static const bool result = []() { + (void)semget(IPC_PRIVATE, -1, 0); // this will fail + return errno != ENOSYS; + }(); + return result; +} + +/*! + \internal + + Setup unix_key + */ +key_t QSystemSemaphoreSystemV::handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode) +{ + if (unix_key != -1) + return unix_key; // we already have a semaphore + +#if defined(Q_OS_DARWIN) + if (qt_apple_isSandboxed()) { + // attempting to use System V semaphores will get us a SIGSYS + self->setError(QSystemSemaphore::PermissionDenied, + QSystemSemaphore::tr("%1: System V semaphores are not available for " + "sandboxed applications. Please build Qt with " + "-feature-ipc_posix") + .arg("QSystemSemaphore::handle:"_L1)); + return -1; + } +#endif + + nativeKeyFile = QFile::encodeName(self->nativeKey.nativeKey()); + if (nativeKeyFile.isEmpty()) { + self->setError(QSystemSemaphore::KeyError, + QSystemSemaphore::tr("%1: key is empty") + .arg("QSystemSemaphore::handle:"_L1)); + return -1; + } + + // ftok requires that an actual file exists somewhere + int built = QtIpcCommon::createUnixKeyFile(nativeKeyFile); + if (-1 == built) { + self->setError(QSystemSemaphore::KeyError, + QSystemSemaphore::tr("%1: unable to make key") + .arg("QSystemSemaphore::handle:"_L1)); + + return -1; + } + createdFile = (1 == built); + + // Get the unix key for the created file + unix_key = ftok(nativeKeyFile, int(self->nativeKey.type())); + if (-1 == unix_key) { + self->setError(QSystemSemaphore::KeyError, + QSystemSemaphore::tr("%1: ftok failed") + .arg("QSystemSemaphore::handle:"_L1)); + return -1; + } + + // Get semaphore + semaphore = semget(unix_key, 1, 0600 | IPC_CREAT | IPC_EXCL); + if (-1 == semaphore) { + if (errno == EEXIST) + semaphore = semget(unix_key, 1, 0600 | IPC_CREAT); + if (-1 == semaphore) { + self->setUnixErrorString("QSystemSemaphore::handle"_L1); + cleanHandle(self); + return -1; + } + } else { + createdSemaphore = true; + // Force cleanup of file, it is possible that it can be left over from a crash + createdFile = true; + } + + if (mode == QSystemSemaphore::Create) { + createdSemaphore = true; + createdFile = true; + } + + // Created semaphore so initialize its value. + if (createdSemaphore && self->initialValue >= 0) { + qt_semun init_op; + init_op.val = self->initialValue; + if (-1 == semctl(semaphore, 0, SETVAL, init_op)) { + self->setUnixErrorString("QSystemSemaphore::handle"_L1); + cleanHandle(self); + return -1; + } + } + + return unix_key; +} + +/*! + \internal + + Cleanup the unix_key + */ +void QSystemSemaphoreSystemV::cleanHandle(QSystemSemaphorePrivate *self) +{ + unix_key = -1; + + // remove the file if we made it + if (createdFile) { + unlink(nativeKeyFile.constData()); + createdFile = false; + } + + if (createdSemaphore) { + if (-1 != semaphore) { + if (-1 == semctl(semaphore, 0, IPC_RMID, 0)) { + self->setUnixErrorString("QSystemSemaphore::cleanHandle"_L1); +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug("QSystemSemaphoreSystemV::cleanHandle semctl failed."); +#endif + } + semaphore = -1; + } + createdSemaphore = false; + } +} + +/*! + \internal + */ +bool QSystemSemaphoreSystemV::modifySemaphore(QSystemSemaphorePrivate *self, int count) +{ + if (handle(self, QSystemSemaphore::Open) == -1) + return false; + + struct sembuf operation; + operation.sem_num = 0; + operation.sem_op = count; + operation.sem_flg = SEM_UNDO; + + int res; + QT_EINTR_LOOP(res, semop(semaphore, &operation, 1)); + if (-1 == res) { + // If the semaphore was removed be nice and create it and then modifySemaphore again + if (errno == EINVAL || errno == EIDRM) { + semaphore = -1; + cleanHandle(self); + handle(self, QSystemSemaphore::Open); + return modifySemaphore(self, count); + } + self->setUnixErrorString("QSystemSemaphore::modifySemaphore"_L1); +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug("QSystemSemaphoreSystemV::modify failed %d %d %d %d %d", + count, int(semctl(semaphore, 0, GETVAL)), int(errno), int(EIDRM), int(EINVAL); +#endif + return false; + } + + self->clearError(); + return true; +} + + +QT_END_NAMESPACE + +#endif // QT_CONFIG(sysv_sem) diff --git a/src/corelib/ipc/qsystemsemaphore_win.cpp b/src/corelib/ipc/qsystemsemaphore_win.cpp new file mode 100644 index 0000000000..f42fecf71f --- /dev/null +++ b/src/corelib/ipc/qsystemsemaphore_win.cpp @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsystemsemaphore.h" +#include "qsystemsemaphore_p.h" +#include "qcoreapplication.h" +#include <qdebug.h> +#include <qt_windows.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +#if QT_CONFIG(systemsemaphore) + +void QSystemSemaphorePrivate::setWindowsErrorString(QLatin1StringView function) +{ + BOOL windowsError = GetLastError(); + if (windowsError == 0) + return; + + switch (windowsError) { + case ERROR_NO_SYSTEM_RESOURCES: + case ERROR_NOT_ENOUGH_MEMORY: + error = QSystemSemaphore::OutOfResources; + errorString = QCoreApplication::translate("QSystemSemaphore", "%1: out of resources").arg(function); + break; + case ERROR_ACCESS_DENIED: + error = QSystemSemaphore::PermissionDenied; + errorString = QCoreApplication::translate("QSystemSemaphore", "%1: permission denied").arg(function); + break; + default: + errorString = QCoreApplication::translate("QSystemSemaphore", "%1: unknown error: %2") + .arg(function, qt_error_string(windowsError)); + error = QSystemSemaphore::UnknownError; +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug() << errorString << "key" << key; +#endif + } +} + +HANDLE QSystemSemaphoreWin32::handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode) +{ + // don't allow making handles on empty keys + if (self->nativeKey.isEmpty()) + return 0; + + // Create it if it doesn't already exists. + if (semaphore == 0) { + semaphore = CreateSemaphore(0, self->initialValue, MAXLONG, + reinterpret_cast<const wchar_t*>(self->nativeKey.nativeKey().utf16())); + if (semaphore == NULL) + self->setWindowsErrorString("QSystemSemaphore::handle"_L1); + } + + return semaphore; +} + +void QSystemSemaphoreWin32::cleanHandle(QSystemSemaphorePrivate *) +{ + if (semaphore && !CloseHandle(semaphore)) { +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug("QSystemSemaphoreWin32::CloseHandle: sem failed"); +#endif + } + semaphore = 0; +} + +bool QSystemSemaphoreWin32::modifySemaphore(QSystemSemaphorePrivate *self, int count) +{ + if (handle(self, QSystemSemaphore::Open) == nullptr) + return false; + + if (count > 0) { + if (0 == ReleaseSemaphore(semaphore, count, 0)) { + self->setWindowsErrorString("QSystemSemaphore::modifySemaphore"_L1); +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug("QSystemSemaphore::modifySemaphore ReleaseSemaphore failed"); +#endif + return false; + } + } else { + if (WAIT_OBJECT_0 != WaitForSingleObjectEx(semaphore, INFINITE, FALSE)) { + self->setWindowsErrorString("QSystemSemaphore::modifySemaphore"_L1); +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug("QSystemSemaphore::modifySemaphore WaitForSingleObject failed"); +#endif + return false; + } + } + + self->clearError(); + return true; +} + +#endif // QT_CONFIG(systemsemaphore) + +QT_END_NAMESPACE diff --git a/src/corelib/ipc/qtipccommon.cpp b/src/corelib/ipc/qtipccommon.cpp new file mode 100644 index 0000000000..b2ae9172fa --- /dev/null +++ b/src/corelib/ipc/qtipccommon.cpp @@ -0,0 +1,610 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtipccommon.h" +#include "qtipccommon_p.h" + +#include <qcryptographichash.h> +#include <qstandardpaths.h> +#include <qstringconverter.h> +#include <qurl.h> +#include <qurlquery.h> + +#if defined(Q_OS_DARWIN) +# include "private/qcore_mac_p.h" +# if !defined(SHM_NAME_MAX) + // Based on PSEMNAMLEN in XNU's posix_sem.c, which would + // indicate the max length is 31, _excluding_ the zero + // terminator. But in practice (possibly due to an off- + // by-one bug in the kernel) the usable bytes are only 30. +# define SHM_NAME_MAX 30 +# endif +#elif defined(Q_OS_WINDOWS) +# include "qt_windows.h" +#endif + +#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QStringView staticTypeToString(QNativeIpcKey::Type type) +{ + switch (type) { + case QNativeIpcKey::Type::SystemV: + return u"systemv"; + case QNativeIpcKey::Type::PosixRealtime: + return u"posix"; + case QNativeIpcKey::Type::Windows: + return u"windows"; + } + return {}; +} + +static QString typeToString(QNativeIpcKey::Type type) +{ + QStringView typeString = staticTypeToString(type); + switch (type) { + case QNativeIpcKey::Type::SystemV: + case QNativeIpcKey::Type::PosixRealtime: + case QNativeIpcKey::Type::Windows: + return QString::fromRawData(typeString.constData(), typeString.size()); + } + + int value = int(type); + if (value >= 1 && value <= 0xff) { + // System V key with id different from 'Q' + typeString = staticTypeToString(QNativeIpcKey::Type::SystemV); + return typeString + QString::number(-value); // negative so it prepends a dash + } + + return QString(); // invalid! +} + +static QNativeIpcKey::Type stringToType(QStringView typeString) +{ + if (typeString == staticTypeToString(QNativeIpcKey::Type::PosixRealtime)) + return QNativeIpcKey::Type::PosixRealtime; + if (typeString == staticTypeToString(QNativeIpcKey::Type::Windows)) + return QNativeIpcKey::Type::Windows; + + auto fromNumber = [](QStringView number, int low, int high) { + bool ok; + int n = -number.toInt(&ok, 10); + if (!ok || n < low || n > high) + return QNativeIpcKey::Type{}; + return QNativeIpcKey::Type(n); + }; + + QStringView sysv = staticTypeToString(QNativeIpcKey::Type::SystemV); + if (typeString.startsWith(sysv)) { + if (typeString.size() == sysv.size()) + return QNativeIpcKey::Type::SystemV; + return fromNumber(typeString.sliced(sysv.size()), 1, 0xff); + } + + // invalid! + return QNativeIpcKey::Type{}; +} + +/*! + \internal + + Legacy: this exists for compatibility with QSharedMemory and + QSystemSemaphore between 4.4 and 6.6. + + Returns a QNativeIpcKey that contains a platform-safe key using rules + similar to QtIpcCommon::platformSafeKey() below, but using an algorithm + that is compatible with Qt 4.4 to 6.6. Additionally, the returned + QNativeIpcKey will record the input \a key so it can be included in the + string form if necessary to pass to other processes. +*/ +QNativeIpcKey QtIpcCommon::legacyPlatformSafeKey(const QString &key, QtIpcCommon::IpcType ipcType, + QNativeIpcKey::Type type) +{ + QNativeIpcKey k(type); + if (key.isEmpty()) + return k; + + QByteArray hex = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1).toHex(); + + if (type == QNativeIpcKey::Type::PosixRealtime) { +#if defined(Q_OS_DARWIN) + if (qt_apple_isSandboxed()) { + // Sandboxed applications on Apple platforms require the shared memory name + // to be in the form <application group identifier>/<custom identifier>. + // Since we don't know which application group identifier the user wants + // to apply, we instead document that requirement, and use the key directly. + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, key, key); + } else { + // The shared memory name limit on Apple platforms is very low (30 characters), + // so we can't use the logic below of combining the prefix, key, and a hash, + // to ensure a unique and valid name. Instead we use the first part of the + // hash, which should still long enough to avoid collisions in practice. + QString native = u'/' + QLatin1StringView(hex).left(SHM_NAME_MAX - 1); + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, native, key); + } + return k; +#endif + } + + QString result; + result.reserve(1 + 18 + key.size() + 40); + switch (ipcType) { + case IpcType::SharedMemory: + result += "qipc_sharedmemory_"_L1; + break; + case IpcType::SystemSemaphore: + result += "qipc_systemsem_"_L1; + break; + } + + for (QChar ch : key) { + if ((ch >= u'a' && ch <= u'z') || + (ch >= u'A' && ch <= u'Z')) + result += ch; + } + result.append(QLatin1StringView(hex)); + + switch (type) { + case QNativeIpcKey::Type::Windows: + if (isIpcSupported(ipcType, QNativeIpcKey::Type::Windows)) + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, result, key); + return k; + case QNativeIpcKey::Type::PosixRealtime: + result.prepend(u'/'); + if (isIpcSupported(ipcType, QNativeIpcKey::Type::PosixRealtime)) + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, result, key); + return k; + case QNativeIpcKey::Type::SystemV: + break; + } + if (isIpcSupported(ipcType, QNativeIpcKey::Type::SystemV)) { + result = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + u'/' + result; + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, result, key); + } + return k; +} + +/*! + \internal + Returns a QNativeIpcKey of type \a type, suitable for QSystemSemaphore or + QSharedMemory depending on \a ipcType. The returned native key is generated + from the Unicode input \a key and is safe for use on for the key type in + question in the current OS. +*/ +QNativeIpcKey QtIpcCommon::platformSafeKey(const QString &key, QtIpcCommon::IpcType ipcType, + QNativeIpcKey::Type type) +{ + QNativeIpcKey k(type); + if (key.isEmpty()) + return k; + + switch (type) { + case QNativeIpcKey::Type::PosixRealtime: + if (isIpcSupported(ipcType, QNativeIpcKey::Type::PosixRealtime)) { +#ifdef SHM_NAME_MAX + // The shared memory name limit on Apple platforms is very low (30 + // characters), so we have to cut it down to avoid ENAMETOOLONG. We + // hope that there won't be too many collisions... + k.setNativeKey(u'/' + QStringView(key).left(SHM_NAME_MAX - 1)); +#else + k.setNativeKey(u'/' + key); +#endif + } + return k; + + case QNativeIpcKey::Type::Windows: + if (isIpcSupported(ipcType, QNativeIpcKey::Type::Windows)) { + QStringView prefix; + QStringView payload = key; + // see https://learn.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces + for (QStringView candidate : { u"Local\\", u"Global\\" }) { + if (!key.startsWith(candidate)) + continue; + prefix = candidate; + payload = payload.sliced(prefix.size()); + break; + } + + QStringView mid; + switch (ipcType) { + case IpcType::SharedMemory: mid = u"shm_"; break; + case IpcType::SystemSemaphore: mid = u"sem_"; break; + } + + QString result = prefix + mid + payload; +#ifdef Q_OS_WINDOWS + result.truncate(MAX_PATH); +#endif + k.setNativeKey(result); + } + return k; + + case QNativeIpcKey::Type::SystemV: + break; + } + + // System V + if (isIpcSupported(ipcType, QNativeIpcKey::Type::SystemV)) { + if (key.startsWith(u'/')) { + k.setNativeKey(key); + } else { + QString baseDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + k.setNativeKey(baseDir + u'/' + key); + } + } + return k; +} + +/*! + \class QNativeIpcKey + \inmodule QtCore + \since 6.6 + \brief The QNativeIpcKey class holds a native key used by QSystemSemaphore and QSharedMemory. + + \compares equality + + The \l QSharedMemory and \l QSystemSemaphore classes identify their + resource using a system-wide identifier known as a "key". The low-level key + value as well as the key type are encapsulated in Qt using the \l + QNativeIpcKey class. + + Those two classes also provide the means to create native keys from a + cross-platform identifier, using QSharedMemory::platformSafeKey() and + QSystemSemaphore::platformSafeKey(). Applications should never share the + input to those functions, as different versions of Qt may perform different + transformations, resulting in different native keys. Instead, the + application that created the IPC object should communicate the resulting + native key using the methods described below. + + For details on the key types, platform-specific limitations, and + interoperability with older or non-Qt applications, see the \l{Native IPC + Keys} documentation. That includes important information for sandboxed + applications on Apple platforms, including all apps obtained via the Apple + App Store. + + \section1 Communicating keys to other processes + \section2 Communicating keys to other Qt processes + + If the other process supports QNativeIpcKey, the best way of communicating + is via the string representation obtained from toString() and parsing it + using fromString(). This representation can be stored on a file whose name + is well-known or passed on the command-line to a child process using + QProcess::setArguments(). + + If the other process does not support QNativeIpcKey, then the two processes + can exchange the nativeKey() but the older code is likely unable to adjust + its key type. The legacyDefaultTypeForOs() function returns the type that + legacy code used, which may not match the \l{DefaultTypeForOs} constant. + This is still true even if the old application is not using the same build + as the new one (for example, it is a Qt 5 application), provided the + options passed to the Qt configure script are the same. + + \section2 Communicating keys to non-Qt processes + + When communicating with non-Qt processes, the application must arrange to + obtain the key type the other process is using. This is important + particularly on Unix systems, where both \l PosixRealtime and \l SystemV + are common. + + \section1 String representation of native keys + + The format of the string representation of a QNativeIpcKey is meant to be + stable and therefore backwards and forwards compatible, provided the key + type is supported by the Qt version in question. That is to say, an older + Qt will fail to parse the string representation of a key type introduced + after it was released. However, successfully parsing a string + representation does not imply the Qt classes can successfully create an + object of that type; applications should verify support using + QSharedMemory::isKeyTypeSupported() and QSystemSemaphore::isKeyTypeSupported(). + + The format of the string representation is formed by two components, + separated by a colon (':'). The first component is the key type, described + in the table below. The second component is a type-specific payload, using + \l{QByteArray::fromPercentEncoding}{percent-encoding}. For all currently + supported key types, the decoded form is identical to the contents of the + nativeKey() field. + + \table + \row \li Key type \li String representation + \row \li \l PosixRealtime \li \c "posix" + \row \li \l SystemV \li \c "systemv" + \row \li \l Windows \li \c "windows" + \row \li Non-standard SystemV \li \c "systemv-" followed by a decimal number + \endtable + + This format resembles a URI and allows parsing using URI/URL-parsing + functions, such as \l QUrl. When parsed by such API, the key type will show + up as the \l{QUrl::scheme()}{scheme}, and the payload will be the + \l{QUrl::path()}{path}. Use of query or fragments is reserved. + + \sa QSharedMemory, QSystemSemaphore +*/ + +/*! + \enum QNativeIpcKey::Type + + This enum describes the backend type for the IPC object. For details on the + key types, see the \l{Native IPC Keys} documentation. + + \value SystemV X/Open System Initiative (XSI) or System V (SVr4) API + \value PosixRealtime IEEE 1003.1b (POSIX.1b) API + \value Windows Win32 API + + \sa setType(), type() +*/ + +/*! + \variable QNativeIpcKey::DefaultTypeForOs + + This constant expression variable holds the default native IPC type for the + current OS. It will be Type::Windows for Windows systems and + Type::PosixRealtime elsewhere. Note that this constant is different from + what \l QSharedMemory and \l QSystemSemaphore defaulted to on the majority + of Unix systems prior to Qt 6.6; see legacyDefaultTypeForOs() for more + information. +*/ + +/*! + \fn QNativeIpcKey::legacyDefaultTypeForOs() noexcept + + Returns the \l{Type} that corresponds to the native IPC key that + \l{QSharedMemory} and \l{QSystemSemaphore} used to use prior to Qt 6.6. + Applications and libraries that must retain compatibility with code using + either class that was compiled with Qt prior to version 6.6 can use this + function to determine what IPC type the other applications may be using. + + Note that this function relies on Qt having been built with identical + configure-time options. +*/ +#if defined(Q_OS_DARWIN) +QNativeIpcKey::Type QNativeIpcKey::defaultTypeForOs_internal() noexcept +{ + if (qt_apple_isSandboxed()) + return Type::PosixRealtime; + return Type::SystemV; +} +#endif + +/*! + \fn QNativeIpcKey::QNativeIpcKey() noexcept + + Constructs a QNativeIpcKey object of type \l DefaultTypeForOs with an empty key. +*/ + +/*! + \fn QNativeIpcKey::QNativeIpcKey(Type type) noexcept + \fn QNativeIpcKey::QNativeIpcKey(const QString &key, Type type) + + Constructs a QNativeIpcKey object holding native key \a key (or empty on + the overload without the parameter) for type \a type. +*/ + +/*! + \fn QNativeIpcKey::QNativeIpcKey(const QNativeIpcKey &other) + \fn QNativeIpcKey::QNativeIpcKey(QNativeIpcKey &&other) noexcept + \fn QNativeIpcKey &QNativeIpcKey::operator=(const QNativeIpcKey &other) + \fn QNativeIpcKey &QNativeIpcKey::operator=(QNativeIpcKey &&other) noexcept + + Copies or moves the content of \a other. +*/ +void QNativeIpcKey::copy_internal(const QNativeIpcKey &other) +{ + d = new QNativeIpcKeyPrivate(*other.d); +} + +void QNativeIpcKey::move_internal(QNativeIpcKey &&) noexcept +{ + // inline code already moved properly, nothing for us to do here +} + +QNativeIpcKey &QNativeIpcKey::assign_internal(const QNativeIpcKey &other) +{ + Q_ASSERT(d || other.d); // only 3 cases to handle + if (d && !other.d) + *d = {}; + else if (d) + *d = *other.d; + else + d = new QNativeIpcKeyPrivate(*other.d); + return *this; +} + +/*! + \fn QNativeIpcKey::~QNativeIpcKey() + + Disposes of this QNativeIpcKey object. +*/ +void QNativeIpcKey::destroy_internal() noexcept +{ + delete d; +} + +/*! + \fn QNativeIpcKey::swap(QNativeIpcKey &other) noexcept + + Swaps the native IPC key and type \a other with this object. + This operation is very fast and never fails. +*/ + +/*! + \fn swap(QNativeIpcKey &value1, QNativeIpcKey &value2) noexcept + \relates QNativeIpcKey + + Swaps the native IPC key and type \a value1 with \a value2. + This operation is very fast and never fails. +*/ + +/*! + \fn QNativeIpcKey::isEmpty() const + + Returns true if the nativeKey() is empty. + + \sa nativeKey() +*/ + +/*! + \fn QNativeIpcKey::isValid() const + + Returns true if this object contains a valid native IPC key type. Invalid + types are usually the result of a failure to parse a string representation + using fromString(). + + This function performs no check on the whether the key string is actually + supported or valid for the current operating system. + + \sa type(), fromString() +*/ + +/*! + \fn QNativeIpcKey::type() const noexcept + + Returns the key type associated with this object. + + \sa nativeKey(), setType() +*/ + +/*! + \fn QNativeIpcKey::setType(Type type) + + Sets the IPC type of this object to \a type. + + \sa type(), setNativeKey() +*/ +void QNativeIpcKey::setType_internal(Type type) +{ + Q_UNUSED(type); +} + +/*! + \fn QNativeIpcKey::nativeKey() const noexcept + + Returns the native key string associated with this object. + + \sa setNativeKey(), type() +*/ + +/*! + \fn QNativeIpcKey::setNativeKey(const QString &newKey) + + Sets the native key for this object to \a newKey. + + \sa nativeKey(), setType() +*/ +void QNativeIpcKey::setNativeKey_internal(const QString &) +{ + d->legacyKey_.clear(); +} + +/*! + \fn size_t QNativeIpcKey::qHash(const QNativeIpcKey &ipcKey) noexcept + + Returns the hash value for \a ipcKey, using a default seed of \c 0. +*/ + +/*! + \fn size_t QNativeIpcKey::qHash(const QNativeIpcKey &ipcKey, size_t seed) noexcept + + Returns the hash value for \a ipcKey, using \a seed to seed the calculation. +*/ +size_t qHash(const QNativeIpcKey &ipcKey, size_t seed) noexcept +{ + // by *choice*, we're not including d->legacyKey_ in the hash -- it's + // already partially encoded in the key + return qHashMulti(seed, ipcKey.key, ipcKey.type()); +} + +/*! + \fn bool QNativeIpcKey::operator==(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept + \fn bool QNativeIpcKey::operator!=(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept + + Returns true if the \a lhs and \a rhs objects hold the same (or different) contents. +*/ +int QNativeIpcKey::compare_internal(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept +{ + return (QNativeIpcKeyPrivate::legacyKey(lhs) == QNativeIpcKeyPrivate::legacyKey(rhs)) ? 0 : 1; +} + +/*! + Returns the string representation of this object. String representations + are useful to inform other processes of the key this process created and + that they should attach to. + + This function returns a null string if the current object is + \l{isValid()}{invalid}. + + \sa fromString() +*/ +QString QNativeIpcKey::toString() const +{ + QString prefix = typeToString(type()); + if (prefix.isEmpty()) { + Q_ASSERT(prefix.isNull()); + return prefix; + } + + QString copy = nativeKey(); + copy.replace(u'%', "%25"_L1); + if (copy.startsWith("//"_L1)) + copy.replace(0, 2, u"/%2F"_s); // ensure it's parsed as a URL path + + QUrl u; + u.setScheme(prefix); + u.setPath(copy, QUrl::TolerantMode); + if (isSlowPath()) { + QUrlQuery q; + if (!d->legacyKey_.isEmpty()) + q.addQueryItem(u"legacyKey"_s, QString(d->legacyKey_).replace(u'%', "%25"_L1)); + u.setQuery(q); + } + return u.toString(QUrl::DecodeReserved); +} + +/*! + Parses the string form \a text and returns the corresponding QNativeIpcKey. + String representations are useful to inform other processes of the key this + process created and they should attach to. + + If the string could not be parsed, this function returns an + \l{isValid()}{invalid} object. + + \sa toString(), isValid() +*/ +QNativeIpcKey QNativeIpcKey::fromString(const QString &text) +{ + QUrl u(text, QUrl::TolerantMode); + Type invalidType = {}; + Type type = stringToType(u.scheme()); + if (type == invalidType || !u.isValid() || !u.userInfo().isEmpty() || !u.host().isEmpty() + || u.port() != -1) + return QNativeIpcKey(invalidType); + + QNativeIpcKey result(QString(), type); + if (result.type() != type) // range check, just in case + return QNativeIpcKey(invalidType); + + // decode the payload + result.setNativeKey(u.path()); + + if (u.hasQuery()) { + const QList items = QUrlQuery(u).queryItems(); + for (const auto &item : items) { + if (item.first == u"legacyKey"_s) { + QString legacyKey = QUrl::fromPercentEncoding(item.second.toUtf8()); + QNativeIpcKeyPrivate::setLegacyKey(result, std::move(legacyKey)); + } else { + // unknown query item + return QNativeIpcKey(invalidType); + } + } + } + return result; +} + +QT_END_NAMESPACE + +#include "moc_qtipccommon.cpp" + +#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) diff --git a/src/corelib/ipc/qtipccommon.h b/src/corelib/ipc/qtipccommon.h new file mode 100644 index 0000000000..74f30cb6a4 --- /dev/null +++ b/src/corelib/ipc/qtipccommon.h @@ -0,0 +1,206 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QNATIVEIPCKEY_H +#define QNATIVEIPCKEY_H + +#include <QtCore/qglobal.h> +#include <QtCore/qtcore-config.h> + +#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) +# include <QtCore/qstring.h> +# include <QtCore/qobjectdefs.h> + +QT_BEGIN_NAMESPACE + +class QNativeIpcKeyPrivate; +class QNativeIpcKey +{ + Q_GADGET_EXPORT(Q_CORE_EXPORT) +public: + enum class Type : quint16 { + // 0 is reserved for the invalid type + // keep 1 through 0xff free, except for SystemV + SystemV = 0x51, // 'Q' + + PosixRealtime = 0x100, + Windows, + }; + Q_ENUM(Type) + + static constexpr Type DefaultTypeForOs = +#ifdef Q_OS_WIN + Type::Windows +#else + Type::PosixRealtime +#endif + ; + static Type legacyDefaultTypeForOs() noexcept; + + constexpr QNativeIpcKey() noexcept = default; + + explicit constexpr QNativeIpcKey(Type type) noexcept + : typeAndFlags{type} + { + } + + Q_IMPLICIT QNativeIpcKey(const QString &k, Type type = DefaultTypeForOs) + : key(k), typeAndFlags{type} + { + } + + QNativeIpcKey(const QNativeIpcKey &other) + : d(other.d), key(other.key), typeAndFlags(other.typeAndFlags) + { + if (isSlowPath()) + copy_internal(other); + } + + QNativeIpcKey(QNativeIpcKey &&other) noexcept + : d(std::exchange(other.d, nullptr)), key(std::move(other.key)), + typeAndFlags(std::move(other.typeAndFlags)) + { + if (isSlowPath()) + move_internal(std::move(other)); + } + + ~QNativeIpcKey() + { + if (isSlowPath()) + destroy_internal(); + } + + QNativeIpcKey &operator=(const QNativeIpcKey &other) + { + typeAndFlags = other.typeAndFlags; + key = other.key; + if (isSlowPath() || other.isSlowPath()) + return assign_internal(other); + Q_ASSERT(!d); + return *this; + } + + QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QNativeIpcKey) + void swap(QNativeIpcKey &other) noexcept + { + std::swap(d, other.d); + key.swap(other.key); + typeAndFlags.swap(other.typeAndFlags); + } + + bool isEmpty() const noexcept + { + return key.isEmpty(); + } + + bool isValid() const noexcept + { + return type() != Type{}; + } + + constexpr Type type() const noexcept + { + return typeAndFlags.type; + } + + constexpr void setType(Type type) + { + if (isSlowPath()) + return setType_internal(type); + typeAndFlags.type = type; + } + + QString nativeKey() const noexcept + { + return key; + } + void setNativeKey(const QString &newKey) + { + key = newKey; + if (isSlowPath()) + setNativeKey_internal(newKey); + } + + Q_CORE_EXPORT QString toString() const; + Q_CORE_EXPORT static QNativeIpcKey fromString(const QString &string); + +private: + struct TypeAndFlags { + Type type = DefaultTypeForOs; + quint16 reserved1 = {}; + quint32 reserved2 = {}; + + void swap(TypeAndFlags &other) noexcept + { + std::swap(type, other.type); + std::swap(reserved1, other.reserved1); + std::swap(reserved2, other.reserved2); + } + + friend constexpr bool operator==(const TypeAndFlags &lhs, const TypeAndFlags &rhs) noexcept + { + return lhs.type == rhs.type && + lhs.reserved1 == rhs.reserved1 && + lhs.reserved2 == rhs.reserved2; + } + }; + + QNativeIpcKeyPrivate *d = nullptr; + QString key; + TypeAndFlags typeAndFlags; + + friend class QNativeIpcKeyPrivate; + constexpr bool isSlowPath() const noexcept + { return Q_UNLIKELY(d); } + + friend Q_CORE_EXPORT size_t qHash(const QNativeIpcKey &ipcKey, size_t seed) noexcept; + friend size_t qHash(const QNativeIpcKey &ipcKey) noexcept + { return qHash(ipcKey, 0); } + + Q_CORE_EXPORT void copy_internal(const QNativeIpcKey &other); + Q_CORE_EXPORT void move_internal(QNativeIpcKey &&other) noexcept; + Q_CORE_EXPORT QNativeIpcKey &assign_internal(const QNativeIpcKey &other); + Q_CORE_EXPORT void destroy_internal() noexcept; + Q_CORE_EXPORT void setType_internal(Type); + Q_CORE_EXPORT void setNativeKey_internal(const QString &); + Q_DECL_PURE_FUNCTION Q_CORE_EXPORT static int + compare_internal(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept; + +#ifdef Q_OS_DARWIN + Q_DECL_CONST_FUNCTION Q_CORE_EXPORT static Type defaultTypeForOs_internal() noexcept; +#endif + friend bool comparesEqual(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept + { + if (!(lhs.typeAndFlags == rhs.typeAndFlags)) + return false; + if (lhs.key != rhs.key) + return false; + if (lhs.d == rhs.d) + return true; + return compare_internal(lhs, rhs) == 0; + } + Q_DECLARE_EQUALITY_COMPARABLE(QNativeIpcKey) +}; + +// not a shared type, exactly, but this works too +Q_DECLARE_SHARED(QNativeIpcKey) + +inline auto QNativeIpcKey::legacyDefaultTypeForOs() noexcept -> Type +{ +#if defined(Q_OS_WIN) + return Type::Windows; +#elif defined(QT_POSIX_IPC) + return Type::PosixRealtime; +#elif defined(Q_OS_DARWIN) + return defaultTypeForOs_internal(); +#else + return Type::SystemV; +#endif +} + +QT_END_NAMESPACE + +#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) + + +#endif // QNATIVEIPCKEY_H diff --git a/src/corelib/ipc/qtipccommon_p.h b/src/corelib/ipc/qtipccommon_p.h new file mode 100644 index 0000000000..72762c5ba7 --- /dev/null +++ b/src/corelib/ipc/qtipccommon_p.h @@ -0,0 +1,173 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QTIPCCOMMON_P_H +#define QTIPCCOMMON_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 "qtipccommon.h" +#include <private/qglobal_p.h> +#include <private/qtcore-config_p.h> + +#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) + +#if defined(Q_OS_UNIX) +# include <qfile.h> +# include <private/qcore_unix_p.h> +#endif + +QT_BEGIN_NAMESPACE + +class QNativeIpcKeyPrivate +{ +public: + QString legacyKey_; + + static QString legacyKey(const QNativeIpcKey &key) + { + if (key.isSlowPath()) + return key.d->legacyKey_; + return QString(); + } + static void setLegacyKey(QNativeIpcKey &key, const QString &legacyKey) + { + QNativeIpcKeyPrivate::makeExtended(key)->legacyKey_ = legacyKey; + } + static void setNativeAndLegacyKeys(QNativeIpcKey &key, const QString &nativeKey, + const QString &legacyKey) + { + key.setNativeKey(nativeKey); + setLegacyKey(key, legacyKey); + } + +private: + static QNativeIpcKeyPrivate *makeExtended(QNativeIpcKey &key) + { + if (!key.isSlowPath()) + key.d = new QNativeIpcKeyPrivate; + return key.d; + } +}; + +namespace QtIpcCommon { +enum class IpcType { + SharedMemory, + SystemSemaphore +}; + +static constexpr bool isIpcSupported(IpcType ipcType, QNativeIpcKey::Type type) +{ + switch (type) { + case QNativeIpcKey::Type::SystemV: + break; + + case QNativeIpcKey::Type::PosixRealtime: + if (ipcType == IpcType::SharedMemory) + return QT_CONFIG(posix_shm); + return QT_CONFIG(posix_sem); + + case QNativeIpcKey::Type::Windows: +#ifdef Q_OS_WIN + return true; +#else + return false; +#endif + } + + if (ipcType == IpcType::SharedMemory) + return QT_CONFIG(sysv_shm); + return QT_CONFIG(sysv_sem); +} + +template <auto Member1, auto... Members> class IpcStorageVariant +{ + template <typename T, typename C> static C extractClass(T C::*); + template <typename T, typename C> static T extractObject(T C::*); + + template <auto M> + static constexpr bool IsEnabled = decltype(extractObject(M))::Enabled; + + static_assert(std::is_member_object_pointer_v<decltype(Member1)>); + using StorageType = decltype(extractClass(Member1)); + StorageType d; + +public: + template <typename Lambda> static auto + visit_internal(StorageType &storage, QNativeIpcKey::Type keyType, const Lambda &lambda) + { + if constexpr ((IsEnabled<Member1> || ... || IsEnabled<Members>)) { + if constexpr (IsEnabled<Member1>) { + using MemberType1 = decltype(extractObject(Member1)); + if (MemberType1::supports(keyType)) + return lambda(&(storage.*Member1)); + } + if constexpr ((... || IsEnabled<Members>)) + return IpcStorageVariant<Members...>::visit_internal(storage, keyType, lambda); + Q_UNREACHABLE(); + } else { + // no backends enabled, but we can't return void + return false; + } + } + + template <typename Lambda> auto visit(QNativeIpcKey::Type keyType, const Lambda &lambda) + { + return visit_internal(d, keyType, lambda); + } + + template <typename Lambda> static auto + staticVisit(QNativeIpcKey::Type keyType, const Lambda &lambda) + { + if constexpr ((IsEnabled<Member1> || ... || IsEnabled<Members>)) { + if constexpr (IsEnabled<Member1>) { + using MemberType1 = decltype(extractObject(Member1)); + if (MemberType1::supports(keyType)) + return lambda(static_cast<MemberType1 *>(nullptr)); + } + if constexpr ((... || IsEnabled<Members>)) + return IpcStorageVariant<Members...>::staticVisit(keyType, lambda); + Q_UNREACHABLE(); + } else { + // no backends enabled, but we can't return void + return false; + } + } +}; + +QNativeIpcKey legacyPlatformSafeKey(const QString &key, IpcType ipcType, QNativeIpcKey::Type type); +QNativeIpcKey platformSafeKey(const QString &key, IpcType ipcType, QNativeIpcKey::Type type); + +#ifdef Q_OS_UNIX +// Convenience function to create the file if needed +inline int createUnixKeyFile(const QByteArray &fileName) +{ + int fd = qt_safe_open(fileName.constData(), O_EXCL | O_CREAT | O_RDWR, 0640); + if (fd < 0) { + if (errno == EEXIST) + return 0; + return -1; + } else { + close(fd); + } + return 1; + +} +#endif // Q_OS_UNIX +} // namespace QtIpcCommon + +QT_END_NAMESPACE + +#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) + + +#endif // QTIPCCOMMON_P_H |