IPC: add native key support to QSharedMemory

And deprecate the non-native key support API. Qt 7 may not even store
the old, non-native key.

Documentation in a new commit, when the dust settles.

Drive-by updates to the tst_QSharedMemory::attach row names, to fix
grammar and remove spaces and apostrophe.

Change-Id: I12a088d1ae424825abd3fffd171d3025c77f94d2
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Thiago Macieira 2022-10-14 15:49:42 -07:00
parent 3ae052d3bb
commit 2c286561bb
10 changed files with 415 additions and 340 deletions

View File

@ -313,6 +313,15 @@ int QMetaType::idHelper() const
return registerHelper(d_ptr); return registerHelper(d_ptr);
} }
#if QT_CONFIG(sharedmemory)
#include "qsharedmemory.h"
void QSharedMemory::setNativeKey(const QString &key)
{
setNativeKey(key, QNativeIpcKey::legacyDefaultTypeForOs());
}
#endif
#include "qvariant.h" #include "qvariant.h"
// these implementations aren't as efficient as they used to be prior to // these implementations aren't as efficient as they used to be prior to

View File

@ -49,192 +49,83 @@ inline QNativeIpcKey QSharedMemoryPrivate::semaphoreNativeKey() const
\brief The QSharedMemory class provides access to a shared memory segment. \brief The QSharedMemory class provides access to a shared memory segment.
QSharedMemory provides access to a shared memory segment by multiple QSharedMemory provides access to a \l{Shared Memory}{shared memory segment}
threads and processes. It also provides a way for a single thread or by multiple threads and processes. Shared memory segments are identified by a
process to lock the memory for exclusive access. key, represented by \l QNativeIpcKey. A key can be created in a
cross-platform manner by using platformSafeKey().
When using this class, be aware of the following platform One QSharedMemory object must create() the segment and this call specifies
differences: 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.
\list 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.
\li Windows: QSharedMemory does not "own" the shared memory segment. By default, QSharedMemory automatically destroys the shared memory segment
When all threads or processes that have an instance of QSharedMemory when the last instance of QSharedMemory is \l{detach()}{detached} from the
attached to a particular shared memory segment have either destroyed segment, and no references to the segment remain.
their instance of QSharedMemory or exited, the Windows kernel
releases the shared memory segment automatically.
\li Unix: QSharedMemory "owns" the shared memory segment. When the For details on the key types, platform-specific limitations, and
last thread or process that has an instance of QSharedMemory interoperability with older or non-Qt applications, see the \l{Native IPC
attached to a particular shared memory segment detaches from the Key} documentation. That includes important information for sandboxed
segment by destroying its instance of QSharedMemory, the destructor applications on Apple platforms, including all apps obtained via the Apple
releases the shared memory segment. But if that last thread or App Store.
process crashes without running the QSharedMemory destructor, the
shared memory segment survives the crash.
\li Unix: QSharedMemory can be implemented by one of two different \sa Inter-Process Communication, QSystemSemaphore
backends, selected at Qt build time: System V or POSIX. Qt defaults to
using the System V API if it is available, and POSIX if not. These two
backends do not interoperate, so two applications must ensure they use the
same one, even if the native key (see setNativeKey()) is the same.
The POSIX backend can be explicitly selected using the
\c{-feature-ipc_posix} option to the Qt configure script. If it is enabled,
the \c{QT_POSIX_IPC} macro will be defined.
\li Sandboxed applications on Apple platforms (including apps
shipped through the Apple App Store): This environment requires
the use of POSIX shared memory (instead of System V shared memory).
Qt for iOS is built with support for POSIX shared memory out of the box.
However, Qt for \macos builds (including those from the Qt installer) default
to System V, making them unsuitable for App Store submission if QSharedMemory
is needed. See above for instructions to explicitly select the POSIX backend
when building Qt.
In addition, in a sandboxed environment, the following caveats apply:
\list
\li The key must be in the form \c {<application group identifier>/<custom identifier>},
as documented \l {https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24}
{here} and \l {https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups}
{here}.
\li The key length is limited to 30 characters.
\li On process exit, the named shared memory entries are not
cleaned up, so restarting the application and re-creating the
shared memory under the same name will fail. To work around this,
fall back to attaching to the existing shared memory entry:
\code
QSharedMemory shm("DEVTEAMID.app-group/shared");
if (!shm.create(42) && shm.error() == QSharedMemory::AlreadyExists)
shm.attach();
\endcode
\endlist
\li Android: QSharedMemory is not supported.
\endlist
Remember to lock the shared memory with lock() before reading from
or writing to the shared memory, and remember to release the lock
with unlock() after you are done.
QSharedMemory automatically destroys the shared memory segment when
the last instance of QSharedMemory is detached from the segment, and
no references to the segment remain.
\warning QSharedMemory changes the key in a Qt-specific way, unless otherwise
specified. Interoperation with non-Qt applications is achieved by first creating
a default shared memory with QSharedMemory() and then setting a native key with
setNativeKey(), after ensuring they use the same low-level API (System V or
POSIX). When using native keys, shared memory is not protected against multiple
accesses on it (for example, unable to lock()) and a user-defined mechanism
should be used to achieve such protection.
\section2 Alternative: Memory-Mapped File
Another way to share memory between processes is by opening the same file
using \l QFile and mapping it into memory using QFile::map() (without
specifying the QFileDevice::MapPrivateOption option). Any writes to the
mapped segment will be observed by all other processes that have mapped the
same file. This solution has the major advantages of being independent of the
backend API and of being simpler to interoperate with from non-Qt
applications. And since \l{QTemporaryFile} is a \l{QFile}, applications can
use that class to achieve clean-up semantics and to create unique shared
memory segments too.
To achieve locking of the shared memory segment, applications will need to
deploy their own mechanisms. This can be achieved by using \l
QBasicAtomicInteger or \c{std::atomic} in a pre-determined offset in the
segment itself. Higher-level locking primitives may be available on some
operating systems; for example, on Linux, \c{pthread_mutex_create()} can be
passed a flag to indicate that the mutex resides in a shared memory segment.
A major drawback of using file-backed shared memory is that the operating
system will attempt to write the data to permanent storage, possibly causing
noticeable performance penalties. To avoid this, applications should locate a
RAM-backed filesystem, such as \c{tmpfs} on Linux (see
QStorageInfo::fileSystemType()), or pass a flag to the native file-opening
function to inform the OS to avoid committing the contents to storage.
File-backed shared memory must be used with care if another process
participating is untrusted. The files may be truncated/shrunk and cause
applications accessing memory beyond the file's size to crash.
\section3 Linux hints on memory-mapped files
On modern Linux systems, while the \c{/tmp} directory is often a \c{tmpfs}
mount point, that is not a requirement. However, the \c{/dev/shm} directory
is required to be a \c{tmpfs} and exists for this very purpose. Do note that
it is world-readable and writable (like \c{/tmp} and \c{/var/tmp}), so one
must be careful of the contents revealed there. Another alternative is to use
the XDG Runtime Directory (see QStandardPaths::writableLocation() and
\l{QStandardPaths::RuntimeLocation}), which on Linux systems using systemd is
a user-specific \c{tmpfs}.
An even more secure solution is to create a "memfd" using \c{memfd_create(2)}
and use interprocess communication to pass the file descriptor, like
\l{QDBusUnixFileDescriptor} or by letting the child process of a \l{QProcess}
inherit it. "memfds" can also be sealed against being shrunk, so they are
safe to be used when communicating with processes with a different privilege
level.
\section3 FreeBSD hints on memory-mapped files
FreeBSD also has \c{memfd_create(2)} and can pass file descriptors to other
processes using the same techniques as Linux. It does not have temporary
filesystems mounted by default.
\section3 Windows hints on memory-mapped files
On Windows, the application can request the operating system avoid committing
the file's contents to permanent storage. This request is performed by
passing the \c{FILE_ATTRIBUTE_TEMPORARY} flag in the \c{dwFlagsAndAttributes}
\c{CreateFile} Win32 function, the \c{_O_SHORT_LIVED} flag to \c{_open()}
low-level function, or by including the modifier "T" to the \c{fopen()} C
runtime function.
There's also a flag to inform the operating system to delete the file when
the last handle to it is closed (\c{FILE_FLAG_DELETE_ON_CLOSE},
\c{_O_TEMPORARY}, and the "D" modifier), but do note that all processes
attempting to open the file must agree on using this flag or not using it. A
mismatch will likely cause a sharing violation and failure to open the file.
*/ */
/*! /*!
\overload QSharedMemory() \overload QSharedMemory()
Constructs a shared memory object with the given \a parent. The Constructs a shared memory object with the given \a parent. The shared memory
shared memory object's key is not set by the constructor, so the object's key is not set by the constructor, so the shared memory object does
shared memory object does not have an underlying shared memory not have an underlying shared memory segment attached. The key must be set
segment attached. The key must be set with setKey() or setNativeKey() with setNativeKey() before create() or attach() can be used.
before create() or attach() can be used.
\sa setKey() \sa setNativeKey()
*/ */
QSharedMemory::QSharedMemory(QObject *parent) QSharedMemory::QSharedMemory(QObject *parent)
: QObject(*new QSharedMemoryPrivate, parent) : QSharedMemory(QNativeIpcKey(), parent)
{ {
} }
/*! /*!
\overload
Constructs a shared memory object with the given \a parent and with 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 its key set to \a key. Because its key is set, its create() and
attach() functions can be called. attach() functions can be called.
\sa setNativeKey(), create(), attach()
*/
QSharedMemory::QSharedMemory(const QNativeIpcKey &key, QObject *parent)
: QObject(*new QSharedMemoryPrivate, parent)
{
setNativeKey(key);
}
/*!
\deprecated
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.
Legacy keys are deprecated. See \l{Native IPC Keys} for more information.
\sa setKey(), create(), attach() \sa setKey(), create(), attach()
*/ */
QSharedMemory::QSharedMemory(const QString &key, QObject *parent) QSharedMemory::QSharedMemory(const QString &key, QObject *parent)
: QObject(*new QSharedMemoryPrivate, parent) : QObject(*new QSharedMemoryPrivate, parent)
{ {
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
setKey(key); setKey(key);
QT_WARNING_POP
} }
/*! /*!
@ -248,67 +139,91 @@ QSharedMemory::QSharedMemory(const QString &key, QObject *parent)
*/ */
QSharedMemory::~QSharedMemory() QSharedMemory::~QSharedMemory()
{ {
setKey(QString()); setNativeKey(QNativeIpcKey());
} }
/*! /*!
Sets the platform independent \a key for this shared memory object. If \a key \overload
is the same as the current key, the function returns without doing anything.
You can call key() to retrieve the platform independent key. Internally, Sets the legacy \a key for this shared memory object. If \a key is the same
QSharedMemory converts this key into a platform specific key. If you instead as the current key, the function returns without doing anything. Otherwise,
call nativeKey(), you will get the platform specific, converted key. if the shared memory object is attached to an underlying shared memory
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. segment, it will \l {detach()} {detach} from it before setting the new key.
This function does not do an attach(). 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() \sa key(), nativeKey(), isAttached()
*/ */
void QSharedMemory::setKey(const QString &key) void QSharedMemory::setKey(const QString &key)
{ {
Q_D(QSharedMemory); Q_D(QSharedMemory);
QString newNativeKey = setNativeKey(legacyNativeKey(key));
QtIpcCommon::legacyPlatformSafeKey(key, QtIpcCommon::IpcType::SharedMemory); d->legacyKey = key;
if (key == d->key && newNativeKey == d->nativeKey)
return;
if (isAttached())
detach();
d->cleanHandle();
d->key = key;
d->nativeKey = newNativeKey;
} }
/*! /*!
\since 4.8 \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(), nativeKeyType(), isAttached()
*/
/*!
\since 6.6
Sets the native, platform specific, \a key for this shared memory object. If 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 \a key is the same as the current native key, the function returns without
doing anything. If all you want is to assign a key to a segment, you should doing anything. Otherwise, if the shared memory object is attached to an
call setKey() instead. 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 nativeKey() to retrieve the native key. If a native key has been This function is useful if the native key was shared from another process.
assigned, calling key() will return a null string. See \l{Native IPC Keys} for more information.
If the shared memory object is attached to an underlying shared memory Portable native keys can be obtained using platformSafeKey().
segment, it will \l {detach()} {detach} from it before setting the new key.
This function does not do an attach().
The application will not be portable if you set a native key. You can call nativeKey() to retrieve the native key.
\sa nativeKey(), key(), isAttached() \sa nativeKey(), nativeKeyType(), isAttached()
*/ */
void QSharedMemory::setNativeKey(const QString &key) void QSharedMemory::setNativeKey(const QNativeIpcKey &key)
{ {
Q_D(QSharedMemory); Q_D(QSharedMemory);
if (key == d->nativeKey && d->key.isNull()) if (key == d->nativeKey && key.isEmpty())
return; return;
if (!isKeyTypeSupported(key.type())) {
d->setError(KeyError, tr("%1: unsupported key type")
.arg("QSharedMemory::setNativeKey"_L1));
return;
}
if (isAttached()) if (isAttached())
detach(); detach();
d->cleanHandle(); d->cleanHandle();
d->key = QString(); d->legacyKey = QString();
d->nativeKey = key; d->nativeKey = key;
} }
@ -352,7 +267,8 @@ bool QSharedMemoryPrivate::initKey()
} }
/*! /*!
Returns the key assigned with setKey() to this shared memory, or a null key \deprecated
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 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 key is the identifier used by Qt applications to identify the shared memory
segment. segment.
@ -365,7 +281,7 @@ bool QSharedMemoryPrivate::initKey()
QString QSharedMemory::key() const QString QSharedMemory::key() const
{ {
Q_D(const QSharedMemory); Q_D(const QSharedMemory);
return d->key; return d->legacyKey;
} }
/*! /*!
@ -377,10 +293,30 @@ QString QSharedMemory::key() const
You can use the native key to access shared memory segments that have not 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. been created by Qt, or to grant shared memory access to non-Qt applications.
See \l{Native IPC Keys} for more information.
\sa setKey(), setNativeKey() \sa setNativeKey(), nativeKeyType()
*/ */
QString QSharedMemory::nativeKey() const 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); Q_D(const QSharedMemory);
return d->nativeKey; return d->nativeKey;
@ -388,7 +324,7 @@ QString QSharedMemory::nativeKey() const
/*! /*!
Creates a shared memory segment of \a size bytes with the key passed to the Creates a shared memory segment of \a size bytes with the key passed to the
constructor, set with setKey() or set with setNativeKey(), then attaches to constructor or set with setNativeKey(), then attaches to
the new shared memory segment with the given access \a mode and returns 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, \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 the attach operation is not performed and \tt false is returned. When the
@ -407,14 +343,14 @@ bool QSharedMemory::create(qsizetype size, AccessMode mode)
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
// Take ownership and force set initialValue because the semaphore // Take ownership and force set initialValue because the semaphore
// might have already existed from a previous crash. // might have already existed from a previous crash.
d->systemSemaphore.setKey(d->key, 1, QSystemSemaphore::Create); d->systemSemaphore.setKey(d->semaphoreNativeKey(), 1, QSystemSemaphore::Create);
#endif #endif
#endif #endif
QString function = "QSharedMemory::create"_L1; QString function = "QSharedMemory::create"_L1;
#if QT_CONFIG(systemsemaphore) #if QT_CONFIG(systemsemaphore)
QSharedMemoryLocker lock(this); QSharedMemoryLocker lock(this);
if (!d->key.isNull() && !d->tryLocker(&lock, function)) if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, function))
return false; return false;
#endif #endif
@ -461,7 +397,7 @@ qsizetype QSharedMemory::size() const
/*! /*!
Attempts to attach the process to the shared memory segment Attempts to attach the process to the shared memory segment
identified by the key that was passed to the constructor or to a identified by the key that was passed to the constructor or to a
call to setKey() or setNativeKey(). The access \a mode is \l {QSharedMemory::} call to setNativeKey(). The access \a mode is \l {QSharedMemory::}
{ReadWrite} by default. It can also be \l {QSharedMemory::} {ReadWrite} by default. It can also be \l {QSharedMemory::}
{ReadOnly}. Returns \c true if the attach operation is successful. If {ReadOnly}. Returns \c true if the attach operation is successful. If
false is returned, call error() to determine which error occurred. false is returned, call error() to determine which error occurred.
@ -478,7 +414,7 @@ bool QSharedMemory::attach(AccessMode mode)
return false; return false;
#if QT_CONFIG(systemsemaphore) #if QT_CONFIG(systemsemaphore)
QSharedMemoryLocker lock(this); QSharedMemoryLocker lock(this);
if (!d->key.isNull() && !d->tryLocker(&lock, "QSharedMemory::attach"_L1)) if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, "QSharedMemory::attach"_L1))
return false; return false;
#endif #endif
@ -518,7 +454,7 @@ bool QSharedMemory::detach()
#if QT_CONFIG(systemsemaphore) #if QT_CONFIG(systemsemaphore)
QSharedMemoryLocker lock(this); QSharedMemoryLocker lock(this);
if (!d->key.isNull() && !d->tryLocker(&lock, "QSharedMemory::detach"_L1)) if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, "QSharedMemory::detach"_L1))
return false; return false;
#endif #endif
@ -526,11 +462,14 @@ bool QSharedMemory::detach()
} }
/*! /*!
Returns a pointer to the contents of the shared memory segment, if Returns a pointer to the contents of the shared memory segment, if one is
one is attached. Otherwise it returns null. Remember to lock the attached. Otherwise it returns null. The value returned by this function will
shared memory with lock() before reading from or writing to the not change until a \l {detach()}{detach} happens, so it is safe to store this
shared memory, and remember to release the lock with unlock() after pointer.
you are done.
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() \sa attach()
*/ */
@ -541,11 +480,14 @@ void *QSharedMemory::data()
} }
/*! /*!
Returns a const pointer to the contents of the shared memory Returns a const pointer to the contents of the shared memory segment, if one
segment, if one is attached. Otherwise it returns null. Remember to is attached. Otherwise it returns null. The value returned by this function
lock the shared memory with lock() before reading from or writing to will not change until a \l {detach()}{detach} happens, so it is safe to store
the shared memory, and remember to release the lock with unlock() this pointer.
after you are done.
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() \sa attach(), create()
*/ */
@ -703,6 +645,21 @@ void QSharedMemoryPrivate::setUnixErrorString(QLatin1StringView function)
} }
} }
bool QSharedMemory::isKeyTypeSupported(QNativeIpcKey::Type type)
{
return QSharedMemoryPrivate::DefaultBackend::supports(type);
}
QNativeIpcKey QSharedMemory::platformSafeKey(const QString &key, QNativeIpcKey::Type type)
{
return { QtIpcCommon::platformSafeKey(key, IpcType::SharedMemory, type), type };
}
QNativeIpcKey QSharedMemory::legacyNativeKey(const QString &key, QNativeIpcKey::Type type)
{
return { legacyPlatformSafeKey(key, IpcType::SharedMemory, type), type };
}
#endif // QT_CONFIG(sharedmemory) #endif // QT_CONFIG(sharedmemory)
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -4,7 +4,7 @@
#ifndef QSHAREDMEMORY_H #ifndef QSHAREDMEMORY_H
#define QSHAREDMEMORY_H #define QSHAREDMEMORY_H
#include <QtCore/qglobal.h> #include <QtCore/qtipccommon.h>
#ifndef QT_NO_QOBJECT #ifndef QT_NO_QOBJECT
# include <QtCore/qobject.h> # include <QtCore/qobject.h>
#else #else
@ -12,8 +12,8 @@
# include <QtCore/qscopedpointer.h> # include <QtCore/qscopedpointer.h>
# include <QtCore/qstring.h> # include <QtCore/qstring.h>
#endif #endif
QT_BEGIN_NAMESPACE
QT_BEGIN_NAMESPACE
#if QT_CONFIG(sharedmemory) #if QT_CONFIG(sharedmemory)
@ -45,13 +45,26 @@ public:
}; };
QSharedMemory(QObject *parent = nullptr); QSharedMemory(QObject *parent = nullptr);
QSharedMemory(const QString &key, QObject *parent = nullptr); QSharedMemory(const QNativeIpcKey &key, QObject *parent = nullptr);
~QSharedMemory(); ~QSharedMemory();
#if QT_DEPRECATED_SINCE(6, 9)
QT_DEPRECATED_VERSION_X_6_9("Please refer to 'Native IPC Key' documentation")
QSharedMemory(const QString &key, QObject *parent = nullptr);
QT_DEPRECATED_VERSION_X_6_9("Please refer to 'Native IPC Key' documentation")
void setKey(const QString &key); void setKey(const QString &key);
QT_DEPRECATED_VERSION_X_6_9("Please refer to 'Native IPC Key' documentation")
QString key() const; QString key() const;
void setNativeKey(const QString &key); #endif
void setNativeKey(const QNativeIpcKey &key);
void setNativeKey(const QString &key, QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs())
{ setNativeKey({ key, type }); }
QString nativeKey() const; 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); bool create(qsizetype size, AccessMode mode = ReadWrite);
qsizetype size() const; qsizetype size() const;
@ -72,6 +85,12 @@ public:
SharedMemoryError error() const; SharedMemoryError error() const;
QString errorString() 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: private:
Q_DISABLE_COPY(QSharedMemory) Q_DISABLE_COPY(QSharedMemory)
}; };

View File

@ -69,6 +69,9 @@ private:
class QSharedMemoryPosix class QSharedMemoryPosix
{ {
public: public:
static bool supports(QNativeIpcKey::Type type)
{ return type == QNativeIpcKey::Type::PosixRealtime; }
bool handle(QSharedMemoryPrivate *self); bool handle(QSharedMemoryPrivate *self);
bool cleanHandle(QSharedMemoryPrivate *self); bool cleanHandle(QSharedMemoryPrivate *self);
bool create(QSharedMemoryPrivate *self, qsizetype size); bool create(QSharedMemoryPrivate *self, qsizetype size);
@ -81,6 +84,9 @@ public:
class QSharedMemorySystemV class QSharedMemorySystemV
{ {
public: public:
static bool supports(QNativeIpcKey::Type type)
{ return quint16(type) <= 0xff; }
#if QT_CONFIG(sysv_sem) #if QT_CONFIG(sysv_sem)
key_t handle(QSharedMemoryPrivate *self); key_t handle(QSharedMemoryPrivate *self);
bool cleanHandle(QSharedMemoryPrivate *self); bool cleanHandle(QSharedMemoryPrivate *self);
@ -88,6 +94,10 @@ public:
bool attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode); bool attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode);
bool detach(QSharedMemoryPrivate *self); bool detach(QSharedMemoryPrivate *self);
private:
void updateNativeKeyFile(const QNativeIpcKey &nativeKey);
QByteArray nativeKeyFile;
key_t unix_key = 0; key_t unix_key = 0;
#endif #endif
}; };
@ -95,6 +105,9 @@ public:
class QSharedMemoryWin32 class QSharedMemoryWin32
{ {
public: public:
static bool supports(QNativeIpcKey::Type type)
{ return type == QNativeIpcKey::Type::Windows; }
Qt::HANDLE handle(QSharedMemoryPrivate *self); Qt::HANDLE handle(QSharedMemoryPrivate *self);
bool cleanHandle(QSharedMemoryPrivate *self); bool cleanHandle(QSharedMemoryPrivate *self);
bool create(QSharedMemoryPrivate *self, qsizetype size); bool create(QSharedMemoryPrivate *self, qsizetype size);
@ -111,8 +124,7 @@ class Q_AUTOTEST_EXPORT QSharedMemoryPrivate : public QObjectPrivate
public: public:
void *memory = nullptr; void *memory = nullptr;
qsizetype size = 0; qsizetype size = 0;
QString key; QNativeIpcKey nativeKey;
QString nativeKey;
QString errorString; QString errorString;
#if QT_CONFIG(systemsemaphore) #if QT_CONFIG(systemsemaphore)
QSystemSemaphore systemSemaphore{ QNativeIpcKey() }; QSystemSemaphore systemSemaphore{ QNativeIpcKey() };
@ -168,6 +180,8 @@ public:
} }
QNativeIpcKey semaphoreNativeKey() const; QNativeIpcKey semaphoreNativeKey() const;
#endif // QT_CONFIG(systemsemaphore) #endif // QT_CONFIG(systemsemaphore)
QString legacyKey; // deprecated
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -27,8 +27,7 @@ using namespace QtIpcCommon;
bool QSharedMemoryPosix::handle(QSharedMemoryPrivate *self) bool QSharedMemoryPosix::handle(QSharedMemoryPrivate *self)
{ {
// don't allow making handles on empty keys // don't allow making handles on empty keys
const QString safeKey = legacyPlatformSafeKey(self->key, IpcType::SharedMemory); if (self->nativeKey.isEmpty()) {
if (safeKey.isEmpty()) {
self->setError(QSharedMemory::KeyError, self->setError(QSharedMemory::KeyError,
QSharedMemory::tr("%1: key is empty").arg("QSharedMemory::handle"_L1)); QSharedMemory::tr("%1: key is empty").arg("QSharedMemory::handle"_L1));
return false; return false;
@ -50,7 +49,7 @@ bool QSharedMemoryPosix::create(QSharedMemoryPrivate *self, qsizetype size)
if (!handle(self)) if (!handle(self))
return false; return false;
const QByteArray shmName = QFile::encodeName(legacyPlatformSafeKey(self->key, IpcType::SharedMemory)); const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey());
int fd; int fd;
#ifdef O_CLOEXEC #ifdef O_CLOEXEC
@ -91,7 +90,7 @@ bool QSharedMemoryPosix::create(QSharedMemoryPrivate *self, qsizetype size)
bool QSharedMemoryPosix::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) bool QSharedMemoryPosix::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode)
{ {
const QByteArray shmName = QFile::encodeName(legacyPlatformSafeKey(self->key, IpcType::SharedMemory)); const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey());
const int oflag = (mode == QSharedMemory::ReadOnly ? O_RDONLY : O_RDWR); const int oflag = (mode == QSharedMemory::ReadOnly ? O_RDONLY : O_RDWR);
const mode_t omode = (mode == QSharedMemory::ReadOnly ? 0400 : 0600); const mode_t omode = (mode == QSharedMemory::ReadOnly ? 0400 : 0600);
@ -177,7 +176,7 @@ bool QSharedMemoryPosix::detach(QSharedMemoryPrivate *self)
// if there are no attachments then unlink the shared memory // if there are no attachments then unlink the shared memory
if (shm_nattch == 0) { if (shm_nattch == 0) {
const QByteArray shmName = QFile::encodeName(legacyPlatformSafeKey(self->key, IpcType::SharedMemory)); const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey());
if (::shm_unlink(shmName.constData()) == -1 && errno != ENOENT) if (::shm_unlink(shmName.constData()) == -1 && errno != ENOENT)
self->setUnixErrorString("QSharedMemory::detach (shm_unlink)"_L1); self->setUnixErrorString("QSharedMemory::detach (shm_unlink)"_L1);
} }

View File

@ -26,6 +26,13 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
using namespace QtIpcCommon; using namespace QtIpcCommon;
inline void QSharedMemorySystemV::updateNativeKeyFile(const QNativeIpcKey &nativeKey)
{
Q_ASSERT(nativeKeyFile.isEmpty() );
if (!nativeKey.nativeKey().isEmpty())
nativeKeyFile = QFile::encodeName(nativeKey.nativeKey());
}
/*! /*!
\internal \internal
@ -38,7 +45,9 @@ key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self)
return unix_key; return unix_key;
// don't allow making handles on empty keys // don't allow making handles on empty keys
if (self->nativeKey.isEmpty()) { if (nativeKeyFile.isEmpty())
updateNativeKeyFile(self->nativeKey);
if (nativeKeyFile.isEmpty()) {
self->setError(QSharedMemory::KeyError, self->setError(QSharedMemory::KeyError,
QSharedMemory::tr("%1: key is empty") QSharedMemory::tr("%1: key is empty")
.arg("QSharedMemory::handle:"_L1)); .arg("QSharedMemory::handle:"_L1));
@ -46,14 +55,14 @@ key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self)
} }
// ftok requires that an actual file exists somewhere // ftok requires that an actual file exists somewhere
if (!QFile::exists(self->nativeKey)) { if (!QFile::exists(nativeKeyFile)) {
self->setError(QSharedMemory::NotFound, self->setError(QSharedMemory::NotFound,
QSharedMemory::tr("%1: UNIX key file doesn't exist") QSharedMemory::tr("%1: UNIX key file doesn't exist")
.arg("QSharedMemory::handle:"_L1)); .arg("QSharedMemory::handle:"_L1));
return 0; return 0;
} }
unix_key = ftok(QFile::encodeName(self->nativeKey).constData(), 'Q'); unix_key = ftok(nativeKeyFile, int(self->nativeKey.type()));
if (-1 == unix_key) { if (-1 == unix_key) {
self->setError(QSharedMemory::KeyError, self->setError(QSharedMemory::KeyError,
QSharedMemory::tr("%1: ftok failed") QSharedMemory::tr("%1: ftok failed")
@ -66,6 +75,7 @@ key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self)
bool QSharedMemorySystemV::cleanHandle(QSharedMemoryPrivate *) bool QSharedMemorySystemV::cleanHandle(QSharedMemoryPrivate *)
{ {
unix_key = 0; unix_key = 0;
nativeKeyFile.clear();
return true; return true;
} }
@ -73,7 +83,7 @@ bool QSharedMemorySystemV::create(QSharedMemoryPrivate *self, qsizetype size)
{ {
// build file if needed // build file if needed
bool createdFile = false; bool createdFile = false;
QByteArray nativeKeyFile = QFile::encodeName(self->nativeKey); updateNativeKeyFile(self->nativeKey);
int built = createUnixKeyFile(nativeKeyFile); int built = createUnixKeyFile(nativeKeyFile);
if (built == -1) { if (built == -1) {
self->setError(QSharedMemory::KeyError, self->setError(QSharedMemory::KeyError,
@ -161,6 +171,7 @@ bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self)
// Get the number of current attachments // Get the number of current attachments
int id = shmget(unix_key, 0, 0400); int id = shmget(unix_key, 0, 0400);
QByteArray oldNativeKeyFile = nativeKeyFile;
cleanHandle(self); cleanHandle(self);
struct shmid_ds shmid_ds; struct shmid_ds shmid_ds;
@ -186,7 +197,7 @@ bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self)
} }
// remove file // remove file
if (!unlink(QFile::encodeName(self->nativeKey))) if (unlink(oldNativeKeyFile) < 0)
return false; return false;
} }
return true; return true;

View File

@ -60,7 +60,7 @@ HANDLE QSharedMemoryWin32::handle(QSharedMemoryPrivate *self)
return 0; return 0;
} }
hand = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, hand = OpenFileMapping(FILE_MAP_ALL_ACCESS, false,
reinterpret_cast<const wchar_t *>(self->nativeKey.utf16())); reinterpret_cast<const wchar_t *>(self->nativeKey.nativeKey().utf16()));
if (!hand) { if (!hand) {
self->setWindowsErrorString(function); self->setWindowsErrorString(function);
return 0; return 0;
@ -96,7 +96,7 @@ bool QSharedMemoryWin32::create(QSharedMemoryPrivate *self, qsizetype size)
high = 0; high = 0;
low = DWORD(size_t(size) & 0xffffffff); low = DWORD(size_t(size) & 0xffffffff);
hand = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, high, low, hand = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, high, low,
reinterpret_cast<const wchar_t *>(self->nativeKey.utf16())); reinterpret_cast<const wchar_t *>(self->nativeKey.nativeKey().utf16()));
self->setWindowsErrorString(function); self->setWindowsErrorString(function);
// hand is valid when it already exists unlike unix so explicitly check // hand is valid when it already exists unlike unix so explicitly check

View File

@ -5,7 +5,7 @@ if(NOT ANDROID AND NOT UIKIT)
if(QT_FEATURE_sharedmemory OR QT_FEATURE_systemsemaphore) if(QT_FEATURE_sharedmemory OR QT_FEATURE_systemsemaphore)
add_subdirectory(qnativeipckey) add_subdirectory(qnativeipckey)
endif() endif()
if(QT_FEATURE_sharedmemory AND QT_FEATURE_private_tests) if(QT_FEATURE_sharedmemory)
add_subdirectory(qsharedmemory) add_subdirectory(qsharedmemory)
endif() endif()
if(QT_FEATURE_systemsemaphore) if(QT_FEATURE_systemsemaphore)

View File

@ -17,10 +17,9 @@ QChar get(QSharedMemory &sm, int i)
return QChar::fromLatin1(((char*)sm.data())[i]); return QChar::fromLatin1(((char*)sm.data())[i]);
} }
int readonly_segfault() int readonly_segfault(const QNativeIpcKey &key)
{ {
QSharedMemory sharedMemory; QSharedMemory sharedMemory(key);
sharedMemory.setKey("readonly_segfault");
sharedMemory.create(1024, QSharedMemory::ReadOnly); sharedMemory.create(1024, QSharedMemory::ReadOnly);
sharedMemory.lock(); sharedMemory.lock();
set(sharedMemory, 0, 'a'); set(sharedMemory, 0, 'a');
@ -28,10 +27,9 @@ int readonly_segfault()
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
int producer() int producer(const QNativeIpcKey &key)
{ {
QSharedMemory producer; QSharedMemory producer(key);
producer.setKey("market");
int size = 1024; int size = 1024;
if (!producer.create(size)) { if (!producer.create(size)) {
@ -100,10 +98,9 @@ int producer()
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
int consumer() int consumer(const QNativeIpcKey &key)
{ {
QSharedMemory consumer; QSharedMemory consumer(key);
consumer.setKey("market");
//qDebug("consumer starting"); //qDebug("consumer starting");
int tries = 0; int tries = 0;
@ -155,17 +152,21 @@ int main(int argc, char *argv[])
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
QStringList arguments = app.arguments(); QStringList arguments = app.arguments();
if (app.arguments().size() != 2) { if (app.arguments().size() != 3) {
qWarning("Please call the helper with the function to call as argument"); fprintf(stderr, "Usage: %s <mode> <key>\n"
"<mode> is one of: readonly_segfault, producer, consumer\n",
argv[0]);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
QString function = arguments.at(1); QString function = arguments.at(1);
QNativeIpcKey key = QNativeIpcKey::fromString(arguments.at(2));
if (function == QLatin1String("readonly_segfault")) if (function == QLatin1String("readonly_segfault"))
return readonly_segfault(); return readonly_segfault(key);
else if (function == QLatin1String("producer")) else if (function == QLatin1String("producer"))
return producer(); return producer(key);
else if (function == QLatin1String("consumer")) else if (function == QLatin1String("consumer"))
return consumer(); return consumer(key);
else else
qWarning() << "Unknown function" << arguments.at(1); qWarning() << "Unknown function" << arguments.at(1);

View File

@ -1,4 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2022 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QDebug> #include <QDebug>
@ -11,6 +12,14 @@
#include <QThread> #include <QThread>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <errno.h>
#include <stdio.h>
#ifdef Q_OS_UNIX
# include <unistd.h>
#endif
#include "private/qtcore-config_p.h"
#define EXISTING_SHARE "existing" #define EXISTING_SHARE "existing"
#define EXISTING_SIZE 1024 #define EXISTING_SIZE 1024
@ -33,8 +42,10 @@ public Q_SLOTS:
private slots: private slots:
// basics // basics
void constructor(); void constructor();
void key_data(); void nativeKey_data();
void key(); void nativeKey();
void legacyKey_data() { nativeKey_data(); }
void legacyKey();
void create_data(); void create_data();
void create(); void create();
void attach_data(); void attach_data();
@ -76,20 +87,25 @@ private slots:
void uniqueKey(); void uniqueKey();
protected: protected:
int remove(const QString &key); void remove(const QNativeIpcKey &key);
QString rememberKey(const QString &key) QNativeIpcKey platformSafeKey(const QString &key)
{ {
if (key == EXISTING_SHARE) QNativeIpcKey::Type keyType = QNativeIpcKey::DefaultTypeForOs;
return key; return QSharedMemory::platformSafeKey(key, keyType);
if (!keys.contains(key)) {
keys.append(key);
remove(key);
}
return key;
} }
QStringList keys; QNativeIpcKey rememberKey(const QString &key)
{
QNativeIpcKey ipcKey = platformSafeKey(key);
if (!keys.contains(ipcKey)) {
keys.append(ipcKey);
remove(ipcKey);
}
return ipcKey;
}
QList<QNativeIpcKey> keys;
QList<QSharedMemory*> jail; QList<QSharedMemory*> jail;
QSharedMemory *existingSharedMemory; QSharedMemory *existingSharedMemory;
@ -109,10 +125,12 @@ tst_QSharedMemory::~tst_QSharedMemory()
void tst_QSharedMemory::init() void tst_QSharedMemory::init()
{ {
existingSharedMemory = new QSharedMemory(EXISTING_SHARE); QNativeIpcKey key = platformSafeKey(EXISTING_SHARE);
existingSharedMemory = new QSharedMemory(key);
if (!existingSharedMemory->create(EXISTING_SIZE)) { if (!existingSharedMemory->create(EXISTING_SIZE)) {
QCOMPARE(existingSharedMemory->error(), QSharedMemory::AlreadyExists); QCOMPARE(existingSharedMemory->error(), QSharedMemory::AlreadyExists);
} }
keys.append(key);
} }
void tst_QSharedMemory::cleanup() void tst_QSharedMemory::cleanup()
@ -121,7 +139,6 @@ void tst_QSharedMemory::cleanup()
qDeleteAll(jail.begin(), jail.end()); qDeleteAll(jail.begin(), jail.end());
jail.clear(); jail.clear();
keys.append(EXISTING_SHARE);
for (int i = 0; i < keys.size(); ++i) { for (int i = 0; i < keys.size(); ++i) {
QSharedMemory sm(keys.at(i)); QSharedMemory sm(keys.at(i));
if (!sm.create(1024)) { if (!sm.create(1024)) {
@ -134,65 +151,70 @@ void tst_QSharedMemory::cleanup()
} }
} }
#ifndef Q_OS_WIN #if QT_CONFIG(posix_shm)
#include <private/qtipccommon_p.h> #include <sys/types.h>
#include <sys/mman.h>
#endif
#if QT_CONFIG(sysv_shm)
#include <sys/types.h> #include <sys/types.h>
#ifndef QT_POSIX_IPC
#include <sys/ipc.h> #include <sys/ipc.h>
#include <sys/shm.h> #include <sys/shm.h>
#else
#include <sys/mman.h>
#endif // QT_POSIX_IPC
#include <errno.h>
#endif #endif
int tst_QSharedMemory::remove(const QString &key) void tst_QSharedMemory::remove(const QNativeIpcKey &key)
{ {
#ifdef Q_OS_WIN // On Unix, the shared memory might exist from a previously failed test
Q_UNUSED(key);
return 0;
#else
// On unix the shared memory might exists from a previously failed test
// or segfault, remove it it does // or segfault, remove it it does
if (key.isEmpty()) if (key.isEmpty())
return -1; return;
QString fileName = QtIpcCommon::legacyPlatformSafeKey(key, QtIpcCommon::IpcType::SharedMemory); switch (key.type()) {
case QNativeIpcKey::Type::Windows:
return;
#ifndef QT_POSIX_IPC case QNativeIpcKey::Type::PosixRealtime:
// ftok requires that an actual file exists somewhere #if QT_CONFIG(posix_shm)
if (!QFile::exists(fileName)) { if (shm_unlink(QFile::encodeName(key.nativeKey()).constData()) == -1) {
//qDebug() << "exits failed"; if (errno != ENOENT) {
return -2; perror("shm_unlink");
return;
}
}
#endif
return;
case QNativeIpcKey::Type::SystemV:
break;
} }
int unix_key = ftok(fileName.toLatin1().constData(), 'Q'); #if QT_CONFIG(sysv_shm)
// ftok requires that an actual file exists somewhere
QString fileName = key.nativeKey();
if (!QFile::exists(fileName)) {
//qDebug() << "exits failed";
return;
}
int unix_key = ftok(fileName.toLatin1().constData(), int(key.type()));
if (-1 == unix_key) { if (-1 == unix_key) {
qDebug() << "ftok failed"; perror("ftok");
return -3; return;
} }
int id = shmget(unix_key, 0, 0600); int id = shmget(unix_key, 0, 0600);
if (-1 == id) { if (-1 == id) {
qDebug() << "shmget failed" << strerror(errno); if (errno != ENOENT)
return -4; perror("shmget");
return;
} }
struct shmid_ds shmid_ds; struct shmid_ds shmid_ds;
if (-1 == shmctl(id, IPC_RMID, &shmid_ds)) { if (-1 == shmctl(id, IPC_RMID, &shmid_ds)) {
qDebug() << "shmctl failed"; perror("shmctl");
return -5; return;
} }
#else
if (shm_unlink(QFile::encodeName(fileName).constData()) == -1) {
if (errno != ENOENT) {
qDebug() << "shm_unlink failed";
return -5;
}
}
#endif // QT_POSIX_IPC
return QFile::remove(fileName); QFile::remove(fileName);
#endif // Q_OS_WIN #endif // Q_OS_WIN
} }
@ -202,19 +224,25 @@ int tst_QSharedMemory::remove(const QString &key)
void tst_QSharedMemory::constructor() void tst_QSharedMemory::constructor()
{ {
QSharedMemory sm; QSharedMemory sm;
QCOMPARE(sm.key(), QString());
QVERIFY(!sm.isAttached()); QVERIFY(!sm.isAttached());
QVERIFY(!sm.data()); QVERIFY(!sm.data());
QCOMPARE(sm.nativeKey(), QString());
QCOMPARE(sm.nativeIpcKey(), QNativeIpcKey());
QCOMPARE(sm.size(), 0); QCOMPARE(sm.size(), 0);
QCOMPARE(sm.error(), QSharedMemory::NoError); QCOMPARE(sm.error(), QSharedMemory::NoError);
QCOMPARE(sm.errorString(), QString()); QCOMPARE(sm.errorString(), QString());
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
QCOMPARE(sm.key(), QString());
QT_WARNING_POP
} }
void tst_QSharedMemory::key_data() void tst_QSharedMemory::nativeKey_data()
{ {
QTest::addColumn<QString>("constructorKey"); QTest::addColumn<QString>("constructorKey");
QTest::addColumn<QString>("setKey"); QTest::addColumn<QString>("setKey");
QTest::addColumn<QString>("setNativeKey"); QTest::addColumn<QString>("setNativeKey"); // only used in the legacyKey test
QTest::newRow("null, null, null") << QString() << QString() << QString(); QTest::newRow("null, null, null") << QString() << QString() << QString();
QTest::newRow("one, null, null") << QString("one") << QString() << QString(); QTest::newRow("one, null, null") << QString("one") << QString() << QString();
@ -230,12 +258,43 @@ void tst_QSharedMemory::key_data()
/*! /*!
Basic key testing Basic key testing
*/ */
void tst_QSharedMemory::key() void tst_QSharedMemory::nativeKey()
{ {
QFETCH(QString, constructorKey); QFETCH(QString, constructorKey);
QFETCH(QString, setKey); QFETCH(QString, setKey);
QFETCH(QString, setNativeKey); QFETCH(QString, setNativeKey);
QNativeIpcKey constructorIpcKey = platformSafeKey(constructorKey);
QNativeIpcKey setIpcKey = platformSafeKey(setKey);
QSharedMemory sm(constructorIpcKey);
QCOMPARE(sm.nativeIpcKey(), constructorIpcKey);
QCOMPARE(sm.nativeKey(), constructorIpcKey.nativeKey());
sm.setNativeKey(setIpcKey);
QCOMPARE(sm.nativeIpcKey(), setIpcKey);
QCOMPARE(sm.nativeKey(), setIpcKey.nativeKey());
QCOMPARE(sm.isAttached(), false);
QCOMPARE(sm.error(), QSharedMemory::NoError);
QCOMPARE(sm.errorString(), QString());
QVERIFY(!sm.data());
QCOMPARE(sm.size(), 0);
QCOMPARE(sm.detach(), false);
}
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
void tst_QSharedMemory::legacyKey()
{
QFETCH(QString, constructorKey);
QFETCH(QString, setKey);
QFETCH(QString, setNativeKey);
#ifdef Q_OS_QNX
QSKIP("The legacy native key type is incorrectly set on QNX");
#endif
QSharedMemory sm(constructorKey); QSharedMemory sm(constructorKey);
QCOMPARE(sm.key(), constructorKey); QCOMPARE(sm.key(), constructorKey);
QCOMPARE(sm.nativeKey().isEmpty(), constructorKey.isEmpty()); QCOMPARE(sm.nativeKey().isEmpty(), constructorKey.isEmpty());
@ -254,6 +313,7 @@ void tst_QSharedMemory::key()
QCOMPARE(sm.detach(), false); QCOMPARE(sm.detach(), false);
} }
QT_WARNING_POP
void tst_QSharedMemory::create_data() void tst_QSharedMemory::create_data()
{ {
@ -282,11 +342,12 @@ void tst_QSharedMemory::create()
QFETCH(bool, canCreate); QFETCH(bool, canCreate);
QFETCH(QSharedMemory::SharedMemoryError, error); QFETCH(QSharedMemory::SharedMemoryError, error);
QSharedMemory sm(rememberKey(key)); QNativeIpcKey nativeKey = rememberKey(key);
QSharedMemory sm(nativeKey);
QCOMPARE(sm.create(size), canCreate); QCOMPARE(sm.create(size), canCreate);
if (sm.error() != error) if (sm.error() != error)
qDebug() << sm.errorString(); qDebug() << sm.errorString();
QCOMPARE(sm.key(), key); QCOMPARE(sm.nativeIpcKey(), nativeKey);
if (canCreate) { if (canCreate) {
QCOMPARE(sm.errorString(), QString()); QCOMPARE(sm.errorString(), QString());
QVERIFY(sm.data() != 0); QVERIFY(sm.data() != 0);
@ -303,13 +364,10 @@ void tst_QSharedMemory::attach_data()
QTest::addColumn<bool>("exists"); QTest::addColumn<bool>("exists");
QTest::addColumn<QSharedMemory::SharedMemoryError>("error"); QTest::addColumn<QSharedMemory::SharedMemoryError>("error");
QTest::newRow("null key") << QString() << false << QSharedMemory::KeyError; QTest::newRow("null") << QString() << false << QSharedMemory::KeyError;
QTest::newRow("doesn't exists") << QString("doesntexists") << false << QSharedMemory::NotFound; QTest::newRow("doesntexists") << QString("doesntexist") << false << QSharedMemory::NotFound;
// HPUX doesn't allow for multiple attaches per process. QTest::newRow(EXISTING_SHARE) << QString(EXISTING_SHARE) << true << QSharedMemory::NoError;
#ifndef Q_OS_HPUX
QTest::newRow("already exists") << QString(EXISTING_SHARE) << true << QSharedMemory::NoError;
#endif
} }
/*! /*!
@ -321,11 +379,12 @@ void tst_QSharedMemory::attach()
QFETCH(bool, exists); QFETCH(bool, exists);
QFETCH(QSharedMemory::SharedMemoryError, error); QFETCH(QSharedMemory::SharedMemoryError, error);
QSharedMemory sm(key); QNativeIpcKey nativeKey = platformSafeKey(key);
QSharedMemory sm(nativeKey);
QCOMPARE(sm.attach(), exists); QCOMPARE(sm.attach(), exists);
QCOMPARE(sm.isAttached(), exists); QCOMPARE(sm.isAttached(), exists);
QCOMPARE(sm.error(), error); QCOMPARE(sm.error(), error);
QCOMPARE(sm.key(), key); QCOMPARE(sm.nativeIpcKey(), nativeKey);
if (exists) { if (exists) {
QVERIFY(sm.data() != 0); QVERIFY(sm.data() != 0);
QVERIFY(sm.size() != 0); QVERIFY(sm.size() != 0);
@ -352,7 +411,7 @@ void tst_QSharedMemory::lock()
QVERIFY(!shm.lock()); QVERIFY(!shm.lock());
QCOMPARE(shm.error(), QSharedMemory::LockError); QCOMPARE(shm.error(), QSharedMemory::LockError);
shm.setKey(rememberKey(QLatin1String("qsharedmemory"))); shm.setNativeKey(rememberKey(QLatin1String("qsharedmemory")));
QVERIFY(!shm.lock()); QVERIFY(!shm.lock());
QCOMPARE(shm.error(), QSharedMemory::LockError); QCOMPARE(shm.error(), QSharedMemory::LockError);
@ -376,12 +435,13 @@ void tst_QSharedMemory::removeWhileAttached()
rememberKey("one"); rememberKey("one");
// attach 1 // attach 1
QSharedMemory *smOne = new QSharedMemory(QLatin1String("one")); QNativeIpcKey keyOne = platformSafeKey("one");
QSharedMemory *smOne = new QSharedMemory(keyOne);
QVERIFY(smOne->create(1024)); QVERIFY(smOne->create(1024));
QVERIFY(smOne->isAttached()); QVERIFY(smOne->isAttached());
// attach 2 // attach 2
QSharedMemory *smTwo = new QSharedMemory(QLatin1String("one")); QSharedMemory *smTwo = new QSharedMemory(platformSafeKey("one"));
QVERIFY(smTwo->attach()); QVERIFY(smTwo->attach());
QVERIFY(smTwo->isAttached()); QVERIFY(smTwo->isAttached());
@ -389,13 +449,13 @@ void tst_QSharedMemory::removeWhileAttached()
delete smOne; delete smOne;
delete smTwo; delete smTwo;
#ifdef QT_POSIX_IPC if (keyOne.type() == QNativeIpcKey::Type::PosixRealtime) {
// POSIX IPC doesn't guarantee that the shared memory is removed // POSIX IPC doesn't guarantee that the shared memory is removed
remove("one"); remove(keyOne);
#endif }
// three shouldn't be able to attach // three shouldn't be able to attach
QSharedMemory smThree(QLatin1String("one")); QSharedMemory smThree(keyOne);
QVERIFY(!smThree.attach()); QVERIFY(!smThree.attach());
QCOMPARE(smThree.error(), QSharedMemory::NotFound); QCOMPARE(smThree.error(), QSharedMemory::NotFound);
} }
@ -430,11 +490,12 @@ void tst_QSharedMemory::readOnly()
#elif defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) #elif defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
QSKIP("ASan prevents the crash this test is looking for.", SkipAll); QSKIP("ASan prevents the crash this test is looking for.", SkipAll);
#else #else
rememberKey("readonly_segfault"); QNativeIpcKey key = rememberKey("readonly_segfault");
// ### on windows disable the popup somehow // ### on windows disable the popup somehow
QProcess p; QProcess p;
p.start(m_helperBinary, QStringList("readonly_segfault"));
p.setProcessChannelMode(QProcess::ForwardedChannels); p.setProcessChannelMode(QProcess::ForwardedChannels);
p.start(m_helperBinary, { "readonly_segfault", key.toString() });
p.waitForFinished(); p.waitForFinished();
QCOMPARE(p.error(), QProcess::Crashed); QCOMPARE(p.error(), QProcess::Crashed);
#endif #endif
@ -451,7 +512,8 @@ void tst_QSharedMemory::useTooMuchMemory()
int count = 0; int count = 0;
while (success) { while (success) {
QString key = QLatin1String("maxmemorytest_") + QString::number(count++); QString key = QLatin1String("maxmemorytest_") + QString::number(count++);
QSharedMemory *sm = new QSharedMemory(rememberKey(key)); QNativeIpcKey nativeKey = rememberKey(key);
QSharedMemory *sm = new QSharedMemory(nativeKey);
QVERIFY(sm); QVERIFY(sm);
jail.append(sm); jail.append(sm);
int size = 32768 * 1024; int size = 32768 * 1024;
@ -465,7 +527,7 @@ void tst_QSharedMemory::useTooMuchMemory()
if (!success) { if (!success) {
QVERIFY(!sm->isAttached()); QVERIFY(!sm->isAttached());
QCOMPARE(sm->key(), key); QCOMPARE(sm->nativeIpcKey(), nativeKey);
QCOMPARE(sm->size(), 0); QCOMPARE(sm->size(), 0);
QVERIFY(!sm->data()); QVERIFY(!sm->data());
if (sm->error() != QSharedMemory::OutOfResources) if (sm->error() != QSharedMemory::OutOfResources)
@ -496,12 +558,12 @@ void tst_QSharedMemory::attachTooMuch()
QSharedMemory government(rememberKey("government")); QSharedMemory government(rememberKey("government"));
QVERIFY(government.create(1024)); QVERIFY(government.create(1024));
while (true) { while (true) {
QSharedMemory *war = new QSharedMemory(government.key()); QSharedMemory *war = new QSharedMemory(government.nativeIpcKey());
QVERIFY(war); QVERIFY(war);
jail.append(war); jail.append(war);
if (!war->attach()) { if (!war->attach()) {
QVERIFY(!war->isAttached()); QVERIFY(!war->isAttached());
QCOMPARE(war->key(), government.key()); QCOMPARE(war->nativeIpcKey(), government.nativeIpcKey());
QCOMPARE(war->size(), 0); QCOMPARE(war->size(), 0);
QVERIFY(!war->data()); QVERIFY(!war->data());
QCOMPARE(war->error(), QSharedMemory::OutOfResources); QCOMPARE(war->error(), QSharedMemory::OutOfResources);
@ -537,8 +599,8 @@ void tst_QSharedMemory::simpleProducerConsumer()
QFETCH(QSharedMemory::AccessMode, mode); QFETCH(QSharedMemory::AccessMode, mode);
rememberKey(QLatin1String("market")); rememberKey(QLatin1String("market"));
QSharedMemory producer(QLatin1String("market")); QSharedMemory producer(platformSafeKey("market"));
QSharedMemory consumer(QLatin1String("market")); QSharedMemory consumer(platformSafeKey("market"));
int size = 512; int size = 512;
QVERIFY(producer.create(size)); QVERIFY(producer.create(size));
QVERIFY(consumer.attach(mode)); QVERIFY(consumer.attach(mode));
@ -560,19 +622,21 @@ void tst_QSharedMemory::simpleProducerConsumer()
#ifndef Q_OS_HPUX #ifndef Q_OS_HPUX
void tst_QSharedMemory::simpleDoubleProducerConsumer() void tst_QSharedMemory::simpleDoubleProducerConsumer()
{ {
rememberKey(QLatin1String("market")); QNativeIpcKey nativeKey = rememberKey(QLatin1String("market"));
QSharedMemory producer(QLatin1String("market")); QSharedMemory producer(nativeKey);
int size = 512; int size = 512;
QVERIFY(producer.create(size)); QVERIFY(producer.create(size));
QVERIFY(producer.detach()); QVERIFY(producer.detach());
#ifdef QT_POSIX_IPC
// POSIX IPC doesn't guarantee that the shared memory is removed if (nativeKey.type() == QNativeIpcKey::Type::PosixRealtime) {
remove("market"); // POSIX IPC doesn't guarantee that the shared memory is removed
#endif remove(nativeKey);
}
QVERIFY(producer.create(size)); QVERIFY(producer.create(size));
{ {
QSharedMemory consumer(QLatin1String("market")); QSharedMemory consumer(nativeKey);
QVERIFY(consumer.attach()); QVERIFY(consumer.attach());
} }
} }
@ -580,11 +644,13 @@ void tst_QSharedMemory::simpleDoubleProducerConsumer()
class Consumer : public QThread class Consumer : public QThread
{ {
public: public:
QNativeIpcKey nativeKey;
Consumer(const QNativeIpcKey &nativeKey) : nativeKey(nativeKey) {}
void run() override void run() override
{ {
QSharedMemory consumer(QLatin1String("market")); QSharedMemory consumer(nativeKey);
while (!consumer.attach()) { while (!consumer.attach()) {
if (consumer.error() != QSharedMemory::NotFound) if (consumer.error() != QSharedMemory::NotFound)
qDebug() << "consumer: failed to connect" << consumer.error() << consumer.errorString(); qDebug() << "consumer: failed to connect" << consumer.error() << consumer.errorString();
@ -615,9 +681,8 @@ public:
class Producer : public QThread class Producer : public QThread
{ {
public: public:
Producer() : producer(QLatin1String("market")) Producer(const QNativeIpcKey &nativeKey) : producer(nativeKey)
{ {
int size = 1024; int size = 1024;
if (!producer.create(size)) { if (!producer.create(size)) {
@ -682,16 +747,16 @@ void tst_QSharedMemory::simpleThreadedProducerConsumer()
{ {
QFETCH(bool, producerIsThread); QFETCH(bool, producerIsThread);
QFETCH(int, threads); QFETCH(int, threads);
rememberKey(QLatin1String("market")); QNativeIpcKey nativeKey = rememberKey(QLatin1String("market"));
Producer p; Producer p(nativeKey);
QVERIFY(p.producer.isAttached()); QVERIFY(p.producer.isAttached());
if (producerIsThread) if (producerIsThread)
p.start(); p.start();
QList<Consumer*> consumers; QList<Consumer*> consumers;
for (int i = 0; i < threads; ++i) { for (int i = 0; i < threads; ++i) {
consumers.append(new Consumer()); consumers.append(new Consumer(nativeKey));
consumers.last()->start(); consumers.last()->start();
} }
@ -730,17 +795,17 @@ void tst_QSharedMemory::simpleProcessProducerConsumer()
QSKIP("This test is unstable: QTBUG-25655"); QSKIP("This test is unstable: QTBUG-25655");
rememberKey("market"); QNativeIpcKey nativeKey = rememberKey("market");
QProcess producer; QProcess producer;
producer.start(m_helperBinary, QStringList("producer")); producer.start(m_helperBinary, { "producer", nativeKey.toString() });
QVERIFY2(producer.waitForStarted(), "Could not start helper binary"); QVERIFY2(producer.waitForStarted(), "Could not start helper binary");
QVERIFY2(producer.waitForReadyRead(), "Helper process failed to create shared memory segment: " + QVERIFY2(producer.waitForReadyRead(), "Helper process failed to create shared memory segment: " +
producer.readAllStandardError()); producer.readAllStandardError());
QList<QProcess*> consumers; QList<QProcess*> consumers;
unsigned int failedProcesses = 0; unsigned int failedProcesses = 0;
const QStringList consumerArguments = QStringList("consumer"); QStringList consumerArguments = { "consumer", nativeKey.toString() };
for (int i = 0; i < processes; ++i) { for (int i = 0; i < processes; ++i) {
QProcess *p = new QProcess; QProcess *p = new QProcess;
p->setProcessChannelMode(QProcess::ForwardedChannels); p->setProcessChannelMode(QProcess::ForwardedChannels);
@ -791,11 +856,11 @@ void tst_QSharedMemory::uniqueKey()
QFETCH(QString, key1); QFETCH(QString, key1);
QFETCH(QString, key2); QFETCH(QString, key2);
QSharedMemory sm1(key1); QSharedMemory sm1(platformSafeKey(key1));
QSharedMemory sm2(key2); QSharedMemory sm2(platformSafeKey(key2));
bool setEqual = (key1 == key2); bool setEqual = (key1 == key2);
bool keyEqual = (sm1.key() == sm2.key()); bool keyEqual = (sm1.nativeIpcKey() == sm2.nativeIpcKey());
bool nativeEqual = (sm1.nativeKey() == sm2.nativeKey()); bool nativeEqual = (sm1.nativeKey() == sm2.nativeKey());
QCOMPARE(keyEqual, setEqual); QCOMPARE(keyEqual, setEqual);