diff options
Diffstat (limited to 'src/corelib/ipc/qsharedmemory.cpp')
-rw-r--r-- | src/corelib/ipc/qsharedmemory.cpp | 689 |
1 files changed, 689 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" |