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:
parent
3ae052d3bb
commit
2c286561bb
@ -313,6 +313,15 @@ int QMetaType::idHelper() const
|
||||
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"
|
||||
|
||||
// these implementations aren't as efficient as they used to be prior to
|
||||
|
@ -49,192 +49,83 @@ inline QNativeIpcKey QSharedMemoryPrivate::semaphoreNativeKey() const
|
||||
|
||||
\brief The QSharedMemory class provides access to a shared memory segment.
|
||||
|
||||
QSharedMemory provides access to a shared memory segment by multiple
|
||||
threads and processes. It also provides a way for a single thread or
|
||||
process to lock the memory for exclusive access.
|
||||
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().
|
||||
|
||||
When using this class, be aware of the following platform
|
||||
differences:
|
||||
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.
|
||||
|
||||
\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.
|
||||
When all threads or processes that have an instance of QSharedMemory
|
||||
attached to a particular shared memory segment have either destroyed
|
||||
their instance of QSharedMemory or exited, the Windows kernel
|
||||
releases the shared memory segment automatically.
|
||||
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.
|
||||
|
||||
\li Unix: QSharedMemory "owns" the shared memory segment. When the
|
||||
last thread or process that has an instance of QSharedMemory
|
||||
attached to a particular shared memory segment detaches from the
|
||||
segment by destroying its instance of QSharedMemory, the destructor
|
||||
releases the shared memory segment. But if that last thread or
|
||||
process crashes without running the QSharedMemory destructor, the
|
||||
shared memory segment survives the crash.
|
||||
For details on the key types, platform-specific limitations, and
|
||||
interoperability with older or non-Qt applications, see the \l{Native IPC
|
||||
Key} documentation. That includes important information for sandboxed
|
||||
applications on Apple platforms, including all apps obtained via the Apple
|
||||
App Store.
|
||||
|
||||
\li Unix: QSharedMemory can be implemented by one of two different
|
||||
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.
|
||||
\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 setKey() or setNativeKey()
|
||||
before create() or attach() can be used.
|
||||
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 setKey()
|
||||
\sa setNativeKey()
|
||||
*/
|
||||
|
||||
QSharedMemory::QSharedMemory(QObject *parent)
|
||||
: QObject(*new QSharedMemoryPrivate, 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, 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()
|
||||
*/
|
||||
QSharedMemory::QSharedMemory(const QString &key, QObject *parent)
|
||||
: QObject(*new QSharedMemoryPrivate, parent)
|
||||
{
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
setKey(key);
|
||||
QT_WARNING_POP
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -248,67 +139,91 @@ QSharedMemory::QSharedMemory(const QString &key, QObject *parent)
|
||||
*/
|
||||
QSharedMemory::~QSharedMemory()
|
||||
{
|
||||
setKey(QString());
|
||||
setNativeKey(QNativeIpcKey());
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the platform independent \a key for this shared memory object. If \a key
|
||||
is the same as the current key, the function returns without doing anything.
|
||||
\overload
|
||||
|
||||
You can call key() to retrieve the platform independent key. Internally,
|
||||
QSharedMemory converts this key into a platform specific key. If you instead
|
||||
call nativeKey(), you will get the platform specific, converted key.
|
||||
|
||||
If the shared memory object is attached to an underlying shared memory
|
||||
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)
|
||||
{
|
||||
Q_D(QSharedMemory);
|
||||
QString newNativeKey =
|
||||
QtIpcCommon::legacyPlatformSafeKey(key, QtIpcCommon::IpcType::SharedMemory);
|
||||
if (key == d->key && newNativeKey == d->nativeKey)
|
||||
return;
|
||||
|
||||
if (isAttached())
|
||||
detach();
|
||||
d->cleanHandle();
|
||||
d->key = key;
|
||||
d->nativeKey = newNativeKey;
|
||||
setNativeKey(legacyNativeKey(key));
|
||||
d->legacyKey = 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(), nativeKeyType(), 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. If all you want is to assign a key to a segment, you should
|
||||
call setKey() instead.
|
||||
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 nativeKey() to retrieve the native key. If a native key has been
|
||||
assigned, calling key() will return a null string.
|
||||
This function is useful if the native key was shared from another process.
|
||||
See \l{Native IPC Keys} for more information.
|
||||
|
||||
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().
|
||||
Portable native keys can be obtained using platformSafeKey().
|
||||
|
||||
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);
|
||||
if (key == d->nativeKey && d->key.isNull())
|
||||
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();
|
||||
d->key = QString();
|
||||
d->legacyKey = QString();
|
||||
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
|
||||
key is the identifier used by Qt applications to identify the shared memory
|
||||
segment.
|
||||
@ -365,7 +281,7 @@ bool QSharedMemoryPrivate::initKey()
|
||||
QString QSharedMemory::key() const
|
||||
{
|
||||
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
|
||||
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
|
||||
{
|
||||
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;
|
||||
@ -388,7 +324,7 @@ QString QSharedMemory::nativeKey() const
|
||||
|
||||
/*!
|
||||
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
|
||||
\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
|
||||
@ -407,14 +343,14 @@ bool QSharedMemory::create(qsizetype size, AccessMode mode)
|
||||
#ifndef Q_OS_WIN
|
||||
// Take ownership and force set initialValue because the semaphore
|
||||
// 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
|
||||
|
||||
QString function = "QSharedMemory::create"_L1;
|
||||
#if QT_CONFIG(systemsemaphore)
|
||||
QSharedMemoryLocker lock(this);
|
||||
if (!d->key.isNull() && !d->tryLocker(&lock, function))
|
||||
if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, function))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
@ -461,7 +397,7 @@ qsizetype QSharedMemory::size() const
|
||||
/*!
|
||||
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 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::}
|
||||
{ReadOnly}. Returns \c true if the attach operation is successful. If
|
||||
false is returned, call error() to determine which error occurred.
|
||||
@ -478,7 +414,7 @@ bool QSharedMemory::attach(AccessMode mode)
|
||||
return false;
|
||||
#if QT_CONFIG(systemsemaphore)
|
||||
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;
|
||||
#endif
|
||||
|
||||
@ -518,7 +454,7 @@ bool QSharedMemory::detach()
|
||||
|
||||
#if QT_CONFIG(systemsemaphore)
|
||||
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;
|
||||
#endif
|
||||
|
||||
@ -526,11 +462,14 @@ bool QSharedMemory::detach()
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a pointer to the contents of the shared memory segment, if
|
||||
one is attached. Otherwise it returns null. 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.
|
||||
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()
|
||||
*/
|
||||
@ -541,11 +480,14 @@ void *QSharedMemory::data()
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a const pointer to the contents of the shared memory
|
||||
segment, if one is attached. Otherwise it returns null. 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.
|
||||
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()
|
||||
*/
|
||||
@ -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)
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -4,7 +4,7 @@
|
||||
#ifndef QSHAREDMEMORY_H
|
||||
#define QSHAREDMEMORY_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QtCore/qtipccommon.h>
|
||||
#ifndef QT_NO_QOBJECT
|
||||
# include <QtCore/qobject.h>
|
||||
#else
|
||||
@ -12,8 +12,8 @@
|
||||
# include <QtCore/qscopedpointer.h>
|
||||
# include <QtCore/qstring.h>
|
||||
#endif
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
#if QT_CONFIG(sharedmemory)
|
||||
|
||||
@ -45,13 +45,26 @@ public:
|
||||
};
|
||||
|
||||
QSharedMemory(QObject *parent = nullptr);
|
||||
QSharedMemory(const QString &key, QObject *parent = nullptr);
|
||||
QSharedMemory(const QNativeIpcKey &key, QObject *parent = nullptr);
|
||||
~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);
|
||||
QT_DEPRECATED_VERSION_X_6_9("Please refer to 'Native IPC Key' documentation")
|
||||
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;
|
||||
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;
|
||||
@ -72,6 +85,12 @@ public:
|
||||
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)
|
||||
};
|
||||
|
@ -69,6 +69,9 @@ private:
|
||||
class QSharedMemoryPosix
|
||||
{
|
||||
public:
|
||||
static bool supports(QNativeIpcKey::Type type)
|
||||
{ return type == QNativeIpcKey::Type::PosixRealtime; }
|
||||
|
||||
bool handle(QSharedMemoryPrivate *self);
|
||||
bool cleanHandle(QSharedMemoryPrivate *self);
|
||||
bool create(QSharedMemoryPrivate *self, qsizetype size);
|
||||
@ -81,6 +84,9 @@ public:
|
||||
class QSharedMemorySystemV
|
||||
{
|
||||
public:
|
||||
static bool supports(QNativeIpcKey::Type type)
|
||||
{ return quint16(type) <= 0xff; }
|
||||
|
||||
#if QT_CONFIG(sysv_sem)
|
||||
key_t handle(QSharedMemoryPrivate *self);
|
||||
bool cleanHandle(QSharedMemoryPrivate *self);
|
||||
@ -88,6 +94,10 @@ public:
|
||||
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
|
||||
};
|
||||
@ -95,6 +105,9 @@ public:
|
||||
class QSharedMemoryWin32
|
||||
{
|
||||
public:
|
||||
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);
|
||||
@ -111,8 +124,7 @@ class Q_AUTOTEST_EXPORT QSharedMemoryPrivate : public QObjectPrivate
|
||||
public:
|
||||
void *memory = nullptr;
|
||||
qsizetype size = 0;
|
||||
QString key;
|
||||
QString nativeKey;
|
||||
QNativeIpcKey nativeKey;
|
||||
QString errorString;
|
||||
#if QT_CONFIG(systemsemaphore)
|
||||
QSystemSemaphore systemSemaphore{ QNativeIpcKey() };
|
||||
@ -168,6 +180,8 @@ public:
|
||||
}
|
||||
QNativeIpcKey semaphoreNativeKey() const;
|
||||
#endif // QT_CONFIG(systemsemaphore)
|
||||
|
||||
QString legacyKey; // deprecated
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -27,8 +27,7 @@ using namespace QtIpcCommon;
|
||||
bool QSharedMemoryPosix::handle(QSharedMemoryPrivate *self)
|
||||
{
|
||||
// don't allow making handles on empty keys
|
||||
const QString safeKey = legacyPlatformSafeKey(self->key, IpcType::SharedMemory);
|
||||
if (safeKey.isEmpty()) {
|
||||
if (self->nativeKey.isEmpty()) {
|
||||
self->setError(QSharedMemory::KeyError,
|
||||
QSharedMemory::tr("%1: key is empty").arg("QSharedMemory::handle"_L1));
|
||||
return false;
|
||||
@ -50,7 +49,7 @@ bool QSharedMemoryPosix::create(QSharedMemoryPrivate *self, qsizetype size)
|
||||
if (!handle(self))
|
||||
return false;
|
||||
|
||||
const QByteArray shmName = QFile::encodeName(legacyPlatformSafeKey(self->key, IpcType::SharedMemory));
|
||||
const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey());
|
||||
|
||||
int fd;
|
||||
#ifdef O_CLOEXEC
|
||||
@ -91,7 +90,7 @@ bool QSharedMemoryPosix::create(QSharedMemoryPrivate *self, qsizetype size)
|
||||
|
||||
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 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 (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)
|
||||
self->setUnixErrorString("QSharedMemory::detach (shm_unlink)"_L1);
|
||||
}
|
||||
|
@ -26,6 +26,13 @@ QT_BEGIN_NAMESPACE
|
||||
using namespace Qt::StringLiterals;
|
||||
using namespace QtIpcCommon;
|
||||
|
||||
inline void QSharedMemorySystemV::updateNativeKeyFile(const QNativeIpcKey &nativeKey)
|
||||
{
|
||||
Q_ASSERT(nativeKeyFile.isEmpty() );
|
||||
if (!nativeKey.nativeKey().isEmpty())
|
||||
nativeKeyFile = QFile::encodeName(nativeKey.nativeKey());
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
@ -38,7 +45,9 @@ key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self)
|
||||
return unix_key;
|
||||
|
||||
// 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,
|
||||
QSharedMemory::tr("%1: key is empty")
|
||||
.arg("QSharedMemory::handle:"_L1));
|
||||
@ -46,14 +55,14 @@ key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self)
|
||||
}
|
||||
|
||||
// ftok requires that an actual file exists somewhere
|
||||
if (!QFile::exists(self->nativeKey)) {
|
||||
if (!QFile::exists(nativeKeyFile)) {
|
||||
self->setError(QSharedMemory::NotFound,
|
||||
QSharedMemory::tr("%1: UNIX key file doesn't exist")
|
||||
.arg("QSharedMemory::handle:"_L1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
unix_key = ftok(QFile::encodeName(self->nativeKey).constData(), 'Q');
|
||||
unix_key = ftok(nativeKeyFile, int(self->nativeKey.type()));
|
||||
if (-1 == unix_key) {
|
||||
self->setError(QSharedMemory::KeyError,
|
||||
QSharedMemory::tr("%1: ftok failed")
|
||||
@ -66,6 +75,7 @@ key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self)
|
||||
bool QSharedMemorySystemV::cleanHandle(QSharedMemoryPrivate *)
|
||||
{
|
||||
unix_key = 0;
|
||||
nativeKeyFile.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -73,7 +83,7 @@ bool QSharedMemorySystemV::create(QSharedMemoryPrivate *self, qsizetype size)
|
||||
{
|
||||
// build file if needed
|
||||
bool createdFile = false;
|
||||
QByteArray nativeKeyFile = QFile::encodeName(self->nativeKey);
|
||||
updateNativeKeyFile(self->nativeKey);
|
||||
int built = createUnixKeyFile(nativeKeyFile);
|
||||
if (built == -1) {
|
||||
self->setError(QSharedMemory::KeyError,
|
||||
@ -161,6 +171,7 @@ bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self)
|
||||
|
||||
// Get the number of current attachments
|
||||
int id = shmget(unix_key, 0, 0400);
|
||||
QByteArray oldNativeKeyFile = nativeKeyFile;
|
||||
cleanHandle(self);
|
||||
|
||||
struct shmid_ds shmid_ds;
|
||||
@ -186,7 +197,7 @@ bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self)
|
||||
}
|
||||
|
||||
// remove file
|
||||
if (!unlink(QFile::encodeName(self->nativeKey)))
|
||||
if (unlink(oldNativeKeyFile) < 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -60,7 +60,7 @@ HANDLE QSharedMemoryWin32::handle(QSharedMemoryPrivate *self)
|
||||
return 0;
|
||||
}
|
||||
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) {
|
||||
self->setWindowsErrorString(function);
|
||||
return 0;
|
||||
@ -96,7 +96,7 @@ bool QSharedMemoryWin32::create(QSharedMemoryPrivate *self, qsizetype size)
|
||||
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.utf16()));
|
||||
reinterpret_cast<const wchar_t *>(self->nativeKey.nativeKey().utf16()));
|
||||
self->setWindowsErrorString(function);
|
||||
|
||||
// hand is valid when it already exists unlike unix so explicitly check
|
||||
|
@ -5,7 +5,7 @@ if(NOT ANDROID AND NOT UIKIT)
|
||||
if(QT_FEATURE_sharedmemory OR QT_FEATURE_systemsemaphore)
|
||||
add_subdirectory(qnativeipckey)
|
||||
endif()
|
||||
if(QT_FEATURE_sharedmemory AND QT_FEATURE_private_tests)
|
||||
if(QT_FEATURE_sharedmemory)
|
||||
add_subdirectory(qsharedmemory)
|
||||
endif()
|
||||
if(QT_FEATURE_systemsemaphore)
|
||||
|
@ -17,10 +17,9 @@ QChar get(QSharedMemory &sm, int i)
|
||||
return QChar::fromLatin1(((char*)sm.data())[i]);
|
||||
}
|
||||
|
||||
int readonly_segfault()
|
||||
int readonly_segfault(const QNativeIpcKey &key)
|
||||
{
|
||||
QSharedMemory sharedMemory;
|
||||
sharedMemory.setKey("readonly_segfault");
|
||||
QSharedMemory sharedMemory(key);
|
||||
sharedMemory.create(1024, QSharedMemory::ReadOnly);
|
||||
sharedMemory.lock();
|
||||
set(sharedMemory, 0, 'a');
|
||||
@ -28,10 +27,9 @@ int readonly_segfault()
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int producer()
|
||||
int producer(const QNativeIpcKey &key)
|
||||
{
|
||||
QSharedMemory producer;
|
||||
producer.setKey("market");
|
||||
QSharedMemory producer(key);
|
||||
|
||||
int size = 1024;
|
||||
if (!producer.create(size)) {
|
||||
@ -100,10 +98,9 @@ int producer()
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int consumer()
|
||||
int consumer(const QNativeIpcKey &key)
|
||||
{
|
||||
QSharedMemory consumer;
|
||||
consumer.setKey("market");
|
||||
QSharedMemory consumer(key);
|
||||
|
||||
//qDebug("consumer starting");
|
||||
int tries = 0;
|
||||
@ -155,17 +152,21 @@ int main(int argc, char *argv[])
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
QStringList arguments = app.arguments();
|
||||
if (app.arguments().size() != 2) {
|
||||
qWarning("Please call the helper with the function to call as argument");
|
||||
if (app.arguments().size() != 3) {
|
||||
fprintf(stderr, "Usage: %s <mode> <key>\n"
|
||||
"<mode> is one of: readonly_segfault, producer, consumer\n",
|
||||
argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
QString function = arguments.at(1);
|
||||
QNativeIpcKey key = QNativeIpcKey::fromString(arguments.at(2));
|
||||
|
||||
if (function == QLatin1String("readonly_segfault"))
|
||||
return readonly_segfault();
|
||||
return readonly_segfault(key);
|
||||
else if (function == QLatin1String("producer"))
|
||||
return producer();
|
||||
return producer(key);
|
||||
else if (function == QLatin1String("consumer"))
|
||||
return consumer();
|
||||
return consumer(key);
|
||||
else
|
||||
qWarning() << "Unknown function" << arguments.at(1);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// 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
|
||||
|
||||
#include <QDebug>
|
||||
@ -11,6 +12,14 @@
|
||||
#include <QThread>
|
||||
#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_SIZE 1024
|
||||
|
||||
@ -33,8 +42,10 @@ public Q_SLOTS:
|
||||
private slots:
|
||||
// basics
|
||||
void constructor();
|
||||
void key_data();
|
||||
void key();
|
||||
void nativeKey_data();
|
||||
void nativeKey();
|
||||
void legacyKey_data() { nativeKey_data(); }
|
||||
void legacyKey();
|
||||
void create_data();
|
||||
void create();
|
||||
void attach_data();
|
||||
@ -76,20 +87,25 @@ private slots:
|
||||
void uniqueKey();
|
||||
|
||||
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)
|
||||
return key;
|
||||
if (!keys.contains(key)) {
|
||||
keys.append(key);
|
||||
remove(key);
|
||||
}
|
||||
return key;
|
||||
QNativeIpcKey::Type keyType = QNativeIpcKey::DefaultTypeForOs;
|
||||
return QSharedMemory::platformSafeKey(key, keyType);
|
||||
}
|
||||
|
||||
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;
|
||||
QSharedMemory *existingSharedMemory;
|
||||
|
||||
@ -109,10 +125,12 @@ tst_QSharedMemory::~tst_QSharedMemory()
|
||||
|
||||
void tst_QSharedMemory::init()
|
||||
{
|
||||
existingSharedMemory = new QSharedMemory(EXISTING_SHARE);
|
||||
QNativeIpcKey key = platformSafeKey(EXISTING_SHARE);
|
||||
existingSharedMemory = new QSharedMemory(key);
|
||||
if (!existingSharedMemory->create(EXISTING_SIZE)) {
|
||||
QCOMPARE(existingSharedMemory->error(), QSharedMemory::AlreadyExists);
|
||||
}
|
||||
keys.append(key);
|
||||
}
|
||||
|
||||
void tst_QSharedMemory::cleanup()
|
||||
@ -121,7 +139,6 @@ void tst_QSharedMemory::cleanup()
|
||||
qDeleteAll(jail.begin(), jail.end());
|
||||
jail.clear();
|
||||
|
||||
keys.append(EXISTING_SHARE);
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
QSharedMemory sm(keys.at(i));
|
||||
if (!sm.create(1024)) {
|
||||
@ -134,65 +151,70 @@ void tst_QSharedMemory::cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#include <private/qtipccommon_p.h>
|
||||
#if QT_CONFIG(posix_shm)
|
||||
#include <sys/types.h>
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#if QT_CONFIG(sysv_shm)
|
||||
#include <sys/types.h>
|
||||
#ifndef QT_POSIX_IPC
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif // QT_POSIX_IPC
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
int tst_QSharedMemory::remove(const QString &key)
|
||||
void tst_QSharedMemory::remove(const QNativeIpcKey &key)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
Q_UNUSED(key);
|
||||
return 0;
|
||||
#else
|
||||
// On unix the shared memory might exists from a previously failed test
|
||||
// On Unix, the shared memory might exist from a previously failed test
|
||||
// or segfault, remove it it does
|
||||
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
|
||||
// ftok requires that an actual file exists somewhere
|
||||
if (!QFile::exists(fileName)) {
|
||||
//qDebug() << "exits failed";
|
||||
return -2;
|
||||
case QNativeIpcKey::Type::PosixRealtime:
|
||||
#if QT_CONFIG(posix_shm)
|
||||
if (shm_unlink(QFile::encodeName(key.nativeKey()).constData()) == -1) {
|
||||
if (errno != ENOENT) {
|
||||
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) {
|
||||
qDebug() << "ftok failed";
|
||||
return -3;
|
||||
perror("ftok");
|
||||
return;
|
||||
}
|
||||
|
||||
int id = shmget(unix_key, 0, 0600);
|
||||
if (-1 == id) {
|
||||
qDebug() << "shmget failed" << strerror(errno);
|
||||
return -4;
|
||||
if (errno != ENOENT)
|
||||
perror("shmget");
|
||||
return;
|
||||
}
|
||||
|
||||
struct shmid_ds shmid_ds;
|
||||
if (-1 == shmctl(id, IPC_RMID, &shmid_ds)) {
|
||||
qDebug() << "shmctl failed";
|
||||
return -5;
|
||||
perror("shmctl");
|
||||
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
|
||||
}
|
||||
|
||||
@ -202,19 +224,25 @@ int tst_QSharedMemory::remove(const QString &key)
|
||||
void tst_QSharedMemory::constructor()
|
||||
{
|
||||
QSharedMemory sm;
|
||||
QCOMPARE(sm.key(), QString());
|
||||
QVERIFY(!sm.isAttached());
|
||||
QVERIFY(!sm.data());
|
||||
QCOMPARE(sm.nativeKey(), QString());
|
||||
QCOMPARE(sm.nativeIpcKey(), QNativeIpcKey());
|
||||
QCOMPARE(sm.size(), 0);
|
||||
QCOMPARE(sm.error(), QSharedMemory::NoError);
|
||||
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>("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("one, null, null") << QString("one") << QString() << QString();
|
||||
@ -230,12 +258,43 @@ void tst_QSharedMemory::key_data()
|
||||
/*!
|
||||
Basic key testing
|
||||
*/
|
||||
void tst_QSharedMemory::key()
|
||||
void tst_QSharedMemory::nativeKey()
|
||||
{
|
||||
QFETCH(QString, constructorKey);
|
||||
QFETCH(QString, setKey);
|
||||
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);
|
||||
QCOMPARE(sm.key(), constructorKey);
|
||||
QCOMPARE(sm.nativeKey().isEmpty(), constructorKey.isEmpty());
|
||||
@ -254,6 +313,7 @@ void tst_QSharedMemory::key()
|
||||
|
||||
QCOMPARE(sm.detach(), false);
|
||||
}
|
||||
QT_WARNING_POP
|
||||
|
||||
void tst_QSharedMemory::create_data()
|
||||
{
|
||||
@ -282,11 +342,12 @@ void tst_QSharedMemory::create()
|
||||
QFETCH(bool, canCreate);
|
||||
QFETCH(QSharedMemory::SharedMemoryError, error);
|
||||
|
||||
QSharedMemory sm(rememberKey(key));
|
||||
QNativeIpcKey nativeKey = rememberKey(key);
|
||||
QSharedMemory sm(nativeKey);
|
||||
QCOMPARE(sm.create(size), canCreate);
|
||||
if (sm.error() != error)
|
||||
qDebug() << sm.errorString();
|
||||
QCOMPARE(sm.key(), key);
|
||||
QCOMPARE(sm.nativeIpcKey(), nativeKey);
|
||||
if (canCreate) {
|
||||
QCOMPARE(sm.errorString(), QString());
|
||||
QVERIFY(sm.data() != 0);
|
||||
@ -303,13 +364,10 @@ void tst_QSharedMemory::attach_data()
|
||||
QTest::addColumn<bool>("exists");
|
||||
QTest::addColumn<QSharedMemory::SharedMemoryError>("error");
|
||||
|
||||
QTest::newRow("null key") << QString() << false << QSharedMemory::KeyError;
|
||||
QTest::newRow("doesn't exists") << QString("doesntexists") << false << QSharedMemory::NotFound;
|
||||
QTest::newRow("null") << QString() << false << QSharedMemory::KeyError;
|
||||
QTest::newRow("doesntexists") << QString("doesntexist") << false << QSharedMemory::NotFound;
|
||||
|
||||
// HPUX doesn't allow for multiple attaches per process.
|
||||
#ifndef Q_OS_HPUX
|
||||
QTest::newRow("already exists") << QString(EXISTING_SHARE) << true << QSharedMemory::NoError;
|
||||
#endif
|
||||
QTest::newRow(EXISTING_SHARE) << QString(EXISTING_SHARE) << true << QSharedMemory::NoError;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -321,11 +379,12 @@ void tst_QSharedMemory::attach()
|
||||
QFETCH(bool, exists);
|
||||
QFETCH(QSharedMemory::SharedMemoryError, error);
|
||||
|
||||
QSharedMemory sm(key);
|
||||
QNativeIpcKey nativeKey = platformSafeKey(key);
|
||||
QSharedMemory sm(nativeKey);
|
||||
QCOMPARE(sm.attach(), exists);
|
||||
QCOMPARE(sm.isAttached(), exists);
|
||||
QCOMPARE(sm.error(), error);
|
||||
QCOMPARE(sm.key(), key);
|
||||
QCOMPARE(sm.nativeIpcKey(), nativeKey);
|
||||
if (exists) {
|
||||
QVERIFY(sm.data() != 0);
|
||||
QVERIFY(sm.size() != 0);
|
||||
@ -352,7 +411,7 @@ void tst_QSharedMemory::lock()
|
||||
QVERIFY(!shm.lock());
|
||||
QCOMPARE(shm.error(), QSharedMemory::LockError);
|
||||
|
||||
shm.setKey(rememberKey(QLatin1String("qsharedmemory")));
|
||||
shm.setNativeKey(rememberKey(QLatin1String("qsharedmemory")));
|
||||
|
||||
QVERIFY(!shm.lock());
|
||||
QCOMPARE(shm.error(), QSharedMemory::LockError);
|
||||
@ -376,12 +435,13 @@ void tst_QSharedMemory::removeWhileAttached()
|
||||
rememberKey("one");
|
||||
|
||||
// attach 1
|
||||
QSharedMemory *smOne = new QSharedMemory(QLatin1String("one"));
|
||||
QNativeIpcKey keyOne = platformSafeKey("one");
|
||||
QSharedMemory *smOne = new QSharedMemory(keyOne);
|
||||
QVERIFY(smOne->create(1024));
|
||||
QVERIFY(smOne->isAttached());
|
||||
|
||||
// attach 2
|
||||
QSharedMemory *smTwo = new QSharedMemory(QLatin1String("one"));
|
||||
QSharedMemory *smTwo = new QSharedMemory(platformSafeKey("one"));
|
||||
QVERIFY(smTwo->attach());
|
||||
QVERIFY(smTwo->isAttached());
|
||||
|
||||
@ -389,13 +449,13 @@ void tst_QSharedMemory::removeWhileAttached()
|
||||
delete smOne;
|
||||
delete smTwo;
|
||||
|
||||
#ifdef QT_POSIX_IPC
|
||||
// POSIX IPC doesn't guarantee that the shared memory is removed
|
||||
remove("one");
|
||||
#endif
|
||||
if (keyOne.type() == QNativeIpcKey::Type::PosixRealtime) {
|
||||
// POSIX IPC doesn't guarantee that the shared memory is removed
|
||||
remove(keyOne);
|
||||
}
|
||||
|
||||
// three shouldn't be able to attach
|
||||
QSharedMemory smThree(QLatin1String("one"));
|
||||
QSharedMemory smThree(keyOne);
|
||||
QVERIFY(!smThree.attach());
|
||||
QCOMPARE(smThree.error(), QSharedMemory::NotFound);
|
||||
}
|
||||
@ -430,11 +490,12 @@ void tst_QSharedMemory::readOnly()
|
||||
#elif defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
|
||||
QSKIP("ASan prevents the crash this test is looking for.", SkipAll);
|
||||
#else
|
||||
rememberKey("readonly_segfault");
|
||||
QNativeIpcKey key = rememberKey("readonly_segfault");
|
||||
|
||||
// ### on windows disable the popup somehow
|
||||
QProcess p;
|
||||
p.start(m_helperBinary, QStringList("readonly_segfault"));
|
||||
p.setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
p.start(m_helperBinary, { "readonly_segfault", key.toString() });
|
||||
p.waitForFinished();
|
||||
QCOMPARE(p.error(), QProcess::Crashed);
|
||||
#endif
|
||||
@ -451,7 +512,8 @@ void tst_QSharedMemory::useTooMuchMemory()
|
||||
int count = 0;
|
||||
while (success) {
|
||||
QString key = QLatin1String("maxmemorytest_") + QString::number(count++);
|
||||
QSharedMemory *sm = new QSharedMemory(rememberKey(key));
|
||||
QNativeIpcKey nativeKey = rememberKey(key);
|
||||
QSharedMemory *sm = new QSharedMemory(nativeKey);
|
||||
QVERIFY(sm);
|
||||
jail.append(sm);
|
||||
int size = 32768 * 1024;
|
||||
@ -465,7 +527,7 @@ void tst_QSharedMemory::useTooMuchMemory()
|
||||
|
||||
if (!success) {
|
||||
QVERIFY(!sm->isAttached());
|
||||
QCOMPARE(sm->key(), key);
|
||||
QCOMPARE(sm->nativeIpcKey(), nativeKey);
|
||||
QCOMPARE(sm->size(), 0);
|
||||
QVERIFY(!sm->data());
|
||||
if (sm->error() != QSharedMemory::OutOfResources)
|
||||
@ -496,12 +558,12 @@ void tst_QSharedMemory::attachTooMuch()
|
||||
QSharedMemory government(rememberKey("government"));
|
||||
QVERIFY(government.create(1024));
|
||||
while (true) {
|
||||
QSharedMemory *war = new QSharedMemory(government.key());
|
||||
QSharedMemory *war = new QSharedMemory(government.nativeIpcKey());
|
||||
QVERIFY(war);
|
||||
jail.append(war);
|
||||
if (!war->attach()) {
|
||||
QVERIFY(!war->isAttached());
|
||||
QCOMPARE(war->key(), government.key());
|
||||
QCOMPARE(war->nativeIpcKey(), government.nativeIpcKey());
|
||||
QCOMPARE(war->size(), 0);
|
||||
QVERIFY(!war->data());
|
||||
QCOMPARE(war->error(), QSharedMemory::OutOfResources);
|
||||
@ -537,8 +599,8 @@ void tst_QSharedMemory::simpleProducerConsumer()
|
||||
QFETCH(QSharedMemory::AccessMode, mode);
|
||||
|
||||
rememberKey(QLatin1String("market"));
|
||||
QSharedMemory producer(QLatin1String("market"));
|
||||
QSharedMemory consumer(QLatin1String("market"));
|
||||
QSharedMemory producer(platformSafeKey("market"));
|
||||
QSharedMemory consumer(platformSafeKey("market"));
|
||||
int size = 512;
|
||||
QVERIFY(producer.create(size));
|
||||
QVERIFY(consumer.attach(mode));
|
||||
@ -560,19 +622,21 @@ void tst_QSharedMemory::simpleProducerConsumer()
|
||||
#ifndef Q_OS_HPUX
|
||||
void tst_QSharedMemory::simpleDoubleProducerConsumer()
|
||||
{
|
||||
rememberKey(QLatin1String("market"));
|
||||
QSharedMemory producer(QLatin1String("market"));
|
||||
QNativeIpcKey nativeKey = rememberKey(QLatin1String("market"));
|
||||
QSharedMemory producer(nativeKey);
|
||||
int size = 512;
|
||||
QVERIFY(producer.create(size));
|
||||
QVERIFY(producer.detach());
|
||||
#ifdef QT_POSIX_IPC
|
||||
// POSIX IPC doesn't guarantee that the shared memory is removed
|
||||
remove("market");
|
||||
#endif
|
||||
|
||||
if (nativeKey.type() == QNativeIpcKey::Type::PosixRealtime) {
|
||||
// POSIX IPC doesn't guarantee that the shared memory is removed
|
||||
remove(nativeKey);
|
||||
}
|
||||
|
||||
QVERIFY(producer.create(size));
|
||||
|
||||
{
|
||||
QSharedMemory consumer(QLatin1String("market"));
|
||||
QSharedMemory consumer(nativeKey);
|
||||
QVERIFY(consumer.attach());
|
||||
}
|
||||
}
|
||||
@ -580,11 +644,13 @@ void tst_QSharedMemory::simpleDoubleProducerConsumer()
|
||||
|
||||
class Consumer : public QThread
|
||||
{
|
||||
|
||||
public:
|
||||
QNativeIpcKey nativeKey;
|
||||
Consumer(const QNativeIpcKey &nativeKey) : nativeKey(nativeKey) {}
|
||||
|
||||
void run() override
|
||||
{
|
||||
QSharedMemory consumer(QLatin1String("market"));
|
||||
QSharedMemory consumer(nativeKey);
|
||||
while (!consumer.attach()) {
|
||||
if (consumer.error() != QSharedMemory::NotFound)
|
||||
qDebug() << "consumer: failed to connect" << consumer.error() << consumer.errorString();
|
||||
@ -615,9 +681,8 @@ public:
|
||||
|
||||
class Producer : public QThread
|
||||
{
|
||||
|
||||
public:
|
||||
Producer() : producer(QLatin1String("market"))
|
||||
Producer(const QNativeIpcKey &nativeKey) : producer(nativeKey)
|
||||
{
|
||||
int size = 1024;
|
||||
if (!producer.create(size)) {
|
||||
@ -682,16 +747,16 @@ void tst_QSharedMemory::simpleThreadedProducerConsumer()
|
||||
{
|
||||
QFETCH(bool, producerIsThread);
|
||||
QFETCH(int, threads);
|
||||
rememberKey(QLatin1String("market"));
|
||||
QNativeIpcKey nativeKey = rememberKey(QLatin1String("market"));
|
||||
|
||||
Producer p;
|
||||
Producer p(nativeKey);
|
||||
QVERIFY(p.producer.isAttached());
|
||||
if (producerIsThread)
|
||||
p.start();
|
||||
|
||||
QList<Consumer*> consumers;
|
||||
for (int i = 0; i < threads; ++i) {
|
||||
consumers.append(new Consumer());
|
||||
consumers.append(new Consumer(nativeKey));
|
||||
consumers.last()->start();
|
||||
}
|
||||
|
||||
@ -730,17 +795,17 @@ void tst_QSharedMemory::simpleProcessProducerConsumer()
|
||||
|
||||
QSKIP("This test is unstable: QTBUG-25655");
|
||||
|
||||
rememberKey("market");
|
||||
QNativeIpcKey nativeKey = rememberKey("market");
|
||||
|
||||
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.waitForReadyRead(), "Helper process failed to create shared memory segment: " +
|
||||
producer.readAllStandardError());
|
||||
|
||||
QList<QProcess*> consumers;
|
||||
unsigned int failedProcesses = 0;
|
||||
const QStringList consumerArguments = QStringList("consumer");
|
||||
QStringList consumerArguments = { "consumer", nativeKey.toString() };
|
||||
for (int i = 0; i < processes; ++i) {
|
||||
QProcess *p = new QProcess;
|
||||
p->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
@ -791,11 +856,11 @@ void tst_QSharedMemory::uniqueKey()
|
||||
QFETCH(QString, key1);
|
||||
QFETCH(QString, key2);
|
||||
|
||||
QSharedMemory sm1(key1);
|
||||
QSharedMemory sm2(key2);
|
||||
QSharedMemory sm1(platformSafeKey(key1));
|
||||
QSharedMemory sm2(platformSafeKey(key2));
|
||||
|
||||
bool setEqual = (key1 == key2);
|
||||
bool keyEqual = (sm1.key() == sm2.key());
|
||||
bool keyEqual = (sm1.nativeIpcKey() == sm2.nativeIpcKey());
|
||||
bool nativeEqual = (sm1.nativeKey() == sm2.nativeKey());
|
||||
|
||||
QCOMPARE(keyEqual, setEqual);
|
||||
|
Loading…
Reference in New Issue
Block a user