IPC: Long live QNativeIpcKey key
Common to both QSharedMemory and QSystemSemaphore, this will hold the native key and will replace the concept of non-native key in those classes. Change-Id: Id8d5e3999fe94b03acc1fffd171c03197aea6016 Reviewed-by: Nicholas Bennett <nicholas.bennett@qt.io>
This commit is contained in:
parent
a467362aa8
commit
7a37083817
@ -110,7 +110,7 @@ qt_internal_add_module(Core
|
||||
global/qxptype_traits.h
|
||||
ipc/qsharedmemory.cpp ipc/qsharedmemory.h ipc/qsharedmemory_p.h
|
||||
ipc/qsystemsemaphore.cpp ipc/qsystemsemaphore.h ipc/qsystemsemaphore_p.h
|
||||
ipc/qtipccommon.cpp ipc/qtipccommon_p.h
|
||||
ipc/qtipccommon.cpp ipc/qtipccommon.h ipc/qtipccommon_p.h
|
||||
io/qabstractfileengine.cpp io/qabstractfileengine_p.h
|
||||
io/qbuffer.cpp io/qbuffer.h
|
||||
io/qdataurl.cpp io/qdataurl_p.h
|
||||
|
488
src/corelib/doc/src/ipc.qdoc
Normal file
488
src/corelib/doc/src/ipc.qdoc
Normal file
@ -0,0 +1,488 @@
|
||||
// Copyright (C) 2022 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\page ipc.html
|
||||
\title Inter-Process Communication
|
||||
\ingroup groups
|
||||
\ingroup frameworks-technologies
|
||||
\keyword ipc
|
||||
|
||||
\brief An overview of Qt's inter-process communication functionality
|
||||
|
||||
Qt supports many ways of communicating with other processes running in the
|
||||
same system or in different systems. There are basically three types of
|
||||
inter-process communication mechanisms:
|
||||
|
||||
\list 1
|
||||
\li Synchronization primitives
|
||||
\li Exchanging of arbitrary byte-level data
|
||||
\li Passing structured messages
|
||||
\endlist
|
||||
|
||||
\section1 Synchronization primitives
|
||||
|
||||
Qt only provides one class for explicit inter-process synchronization:
|
||||
\l{QSystemSemaphore}. A QSystemSemaphore is like a \l{QSemaphore} that is
|
||||
accessible by multiple processes in the same system. It is globally
|
||||
identified by a "key", which in Qt is represented by the \l{QNativeIpcKey}
|
||||
class. Additionally, depending on the OS, Qt may support multiple different
|
||||
backends for sharing memory; see the \l{Native IPC Keys} documentation for
|
||||
more information and limitations.
|
||||
|
||||
It is possible to use regular thread-synchronization primitives such as
|
||||
mutexes, wait conditions, and read-write locks, located in memory that is
|
||||
shared between processes. Qt does not provide any class to support this,
|
||||
but applications can use low-level operations on certain operating systems.
|
||||
|
||||
Other Qt classes may be used to provide higher-level locking, like
|
||||
\l{QLockFile}, or by acquiring a unique, system-wide resource. Such
|
||||
techniques include \l{QTcpServer}{TCP} or \l{QUdpSocket}{UDP} ports or
|
||||
well-known names in \l{QDBusConnection::registerService}{D-Bus}.
|
||||
|
||||
\section1 Byte-level data sharing
|
||||
|
||||
Using byte-level data, applications can implement any communication
|
||||
protocol they may choose. Sharing of byte data can be stream-oriented
|
||||
(serialized) or can allow random access (a similar condition to
|
||||
QFileDevice::isSequential()).
|
||||
|
||||
For serial communication, Qt provides a number of different classes and
|
||||
even full modules:
|
||||
\list
|
||||
\li Pipes and FIFOs: \l QFile
|
||||
\li Child processes: \l QProcess
|
||||
\li Sockets: \l QTcpSocket, \l QUdpSocket (in \l{Qt Network})
|
||||
\li HTTP(S): \l QNetworkAccessManager (in \l{Qt Network}) and
|
||||
\l QHttpServer (in \l{Qt HTTP Server})
|
||||
\li CoAP(S): \l QCoapClient (in \l{Qt CoAP})
|
||||
\endlist
|
||||
|
||||
For random-access data sharing within the same system, Qt provides
|
||||
\l{QSharedMemory}. See the \l{Shared Memory} documentation for detailed
|
||||
information.
|
||||
|
||||
\section1 Structured message passing
|
||||
|
||||
Qt also provides a number of techniques to exchange structured messages
|
||||
with other processes. Applications can build on top of the byte-level
|
||||
solutions above, such as by using \l QJsonDocument or \l QXmlStreamReader /
|
||||
\l QXmlStreamWriter over HTTP to perform JSONRPC or XMLRPC, respectively,
|
||||
or \l QCborValue with QtCoAP.
|
||||
|
||||
Dedicated Qt modules for structured messages and remote procedure-calling
|
||||
include:
|
||||
\list
|
||||
\li \l{Qt D-Bus}
|
||||
\li \l{Qt Remote Objects}
|
||||
\li \l{Qt WebSockets}
|
||||
\endlist
|
||||
*/
|
||||
|
||||
/*!
|
||||
\page shared-memory.html
|
||||
\title Shared Memory
|
||||
\keyword ipc
|
||||
\keyword shared memory
|
||||
|
||||
\brief Overview of the techniques for sharing memory between processes
|
||||
|
||||
Qt provides two techniques to share memory with other processes in the same
|
||||
system: \l{QSharedMemory} and memory-mapped files using \l{QFile}. Memory
|
||||
that is shared with other processes is often referred to as a "segment",
|
||||
and although it may have been implemented as specific segments on
|
||||
processors with segmented memory models in the past, this is not the case
|
||||
in any modern operating system. Shared memory segments are simply regions
|
||||
of memory that the operating system will ensure are available to all
|
||||
processes participating.
|
||||
|
||||
\note The address at which the segment is located in memory will almost
|
||||
always be different for each process that is participating in the sharing.
|
||||
Therefore, applications must take care to share only position-independent
|
||||
data, such as primitive C++ types or arrays of such types.
|
||||
|
||||
\section1 Sharing memory using QSharedMemory
|
||||
|
||||
QSharedMemory provides a simple API to create a shared memory segment of a
|
||||
given size or attach to one that was created by another process.
|
||||
Additionally, it provides a pair of methods to \l{QSharedMemory::}{lock}
|
||||
and \l{QSharedMemory::}{unlock} the whole segment, using an internal
|
||||
\l{QSystemSemaphore}.
|
||||
|
||||
Shared memory segments and system semaphores are globally identified in the
|
||||
system through a "key", which in Qt is represented by the \l{QNativeIpcKey}
|
||||
class. Additionally, depending on the OS, Qt may support multiple different
|
||||
backends for sharing memory; see the \l{Native IPC Keys} documentation for
|
||||
more information and limitations.
|
||||
|
||||
QSharedMemory is designed to share memory only within the same privilege
|
||||
level (that is, not with untrusted other processes, such as those started
|
||||
by other users). For backends that support it, QSharedMemory will create
|
||||
segments such that only processes with the same privilege level can attach.
|
||||
|
||||
\section1 Sharing memory via memory-mapped files
|
||||
|
||||
Most files can be mapped to memory using QFile::map() and, if the
|
||||
\l{QFileDevice::MapPrivateOption}{MapPrivateOption} option is not specified,
|
||||
any writes to the mapped segment will be observed by all other processes
|
||||
that have mapped the same file. Exceptions to files that can be mapped to
|
||||
memory include remote files found in network shares or those located in
|
||||
certain filesystems. Even if the operating system does allow mapping remote
|
||||
files to memory, I/O operations on the file will likely be cached and
|
||||
delayed, thus making true memory sharing impossible.
|
||||
|
||||
This solution has the major advantages of being independent of any backend
|
||||
API and of being simpler to interoperate with from non-Qt applications.
|
||||
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. One way may be to use \l QLockFile. Another
|
||||
and less costly solution is to \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, applications can set the "pshared" flag in the mutex attribute
|
||||
passed to \c{pthread_mutex_create()} to indicate that the mutex resides in
|
||||
a shared memory segment.
|
||||
|
||||
Be aware that the operating system will likely attempt to commit to
|
||||
permanent storage any writes made to the shared memory. This may be desired
|
||||
or it may be a performance penalty if the file itself was meant to be
|
||||
temporary. In that case, 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.
|
||||
|
||||
It is possible to use file-backed shared memory to communicate with
|
||||
untrusted processes, in which case the application should exercise great
|
||||
care. The files may be truncated/shrunk and cause applications accessing
|
||||
memory beyond the file's size to crash.
|
||||
|
||||
\section2 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 the very purpose of sharing
|
||||
memory. Do note that it is world-readable and writable (like \c{/tmp} and
|
||||
\c{/var/tmp}), so applications 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.
|
||||
|
||||
\section2 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.
|
||||
|
||||
\section2 Windows hints on memory-mapped files
|
||||
|
||||
On Windows, the application can request the operating system avoid saving
|
||||
the file's contents on permanent storage. This request is performed by
|
||||
passing the \c{FILE_ATTRIBUTE_TEMPORARY} flag in the
|
||||
\c{dwFlagsAndAttributes} parameter to the \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.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\page native-ipc-keys.html
|
||||
\title Native IPC Keys
|
||||
\keyword ipc
|
||||
\keyword shared memory
|
||||
\keyword system semaphore
|
||||
|
||||
\brief An overview of keys for QSharedMemory and QSystemSemaphore
|
||||
|
||||
The \l QSharedMemory and \l QSystemSemaphore classes identify their
|
||||
resource using a system-wide identifier known as a "key". The low-level key
|
||||
value as well as the key type are encapsulated in Qt using the \l
|
||||
QNativeIpcKey class. That class also provides the proper means of
|
||||
exchanging the key with other processes, by way of
|
||||
QNativeIpcKey::toString() and QNativeIpcKey::fromString().
|
||||
|
||||
Qt currently supports three distinct backends for those two classes, which
|
||||
match the values available in the \l{QNativeIpcKey::Type} enumeration.
|
||||
\list
|
||||
\li POSIX Realtime extensions (IEEE 1003.1b, POSIX.1b)
|
||||
\li X/Open System Interfaces (XSI) or System V (SVr4), though also now part of POSIX
|
||||
\li Windows primitives
|
||||
\endlist
|
||||
|
||||
As the name indicates, the Windows primitives are only available on the
|
||||
Windows operating system, where they are the default backend. The other two
|
||||
are usually both available on Unix operating systems. The following table
|
||||
provides an overview of typical availability since Qt 6.6:
|
||||
|
||||
\table
|
||||
\header \li Operating system \li POSIX \li System V \li Windows
|
||||
\row \li Android \li \li \li
|
||||
\row \li INTEGRITY \li \li \li
|
||||
\row \li QNX \li Yes \li \li
|
||||
\row \li \macos \li Yes \li Usually (1) \li
|
||||
\row \li Other Apple OSes \li Yes \li \li
|
||||
\row \li Other Unix systems \li Yes \li Yes \li
|
||||
\row \li Windows \li Rarely (2) \li \li Yes
|
||||
\endtable
|
||||
|
||||
\note 1 Sandboxed \macos applications, which include all applications
|
||||
distributed via the Apple App Store, may not use System V objects.
|
||||
|
||||
\note 2 Some GCC-compatible C runtimes on Windows provide POSIX-compatible
|
||||
shared memory support, but this is rare. It is always absent with the
|
||||
Microsoft compiler.
|
||||
|
||||
To determine whether a given key type is supported, applications should
|
||||
call QSharedMemory::isKeyTypeSupported() and
|
||||
QSystemSemaphore::isKeyTypeSupported().
|
||||
|
||||
QNativeIpcKey also provides support for compatibility with Qt applications
|
||||
prior to its introduction. The following sections detail the limitations of
|
||||
the backends, the contents of the string keys themselves, and
|
||||
compatibility.
|
||||
|
||||
\section1 Cross-platform safe key format
|
||||
|
||||
QNativeIpcKey::setNativeKey() and QNativeIpcKey::nativeKey() handle the
|
||||
low-level native key, which may be used with the native APIs and shared
|
||||
with other, non-Qt processes (see below for the API). This format is not
|
||||
usually cross-platform, so both QSharedMemory and QSystemSemaphore provide
|
||||
a function to translate a cross-platform identifier string to the native
|
||||
key: QSharedMemory::platformSafeKey() and
|
||||
QSystemSemaphore::platformSafeKey().
|
||||
|
||||
The length of the cross-platform key on most platforms is the same as that
|
||||
of a file name, but is severely limited on Apple platforms to only 30
|
||||
usable bytes (be mindful of UTF-8 encoding if using characters outside the
|
||||
US-ASCII range). The format of the key is also similar to that of a file
|
||||
path component, meaning it should not contain any characters not allowed in
|
||||
file names, in particular those that separate path components (slash and
|
||||
backslash), with the exception of sandboxed applications on Apple operating
|
||||
systems. The following are good examples of cross-platform keys: "myapp",
|
||||
"org.example.myapp", "org.example.myapp-12345".
|
||||
|
||||
\b{Apple sandbox limitations:} if the application is running inside of a
|
||||
sandbox in an Apple operating system, the key must be in a very specific
|
||||
format: \c {<application group identifier>/<custom identifier>}. Sandboxing
|
||||
is implied for all applications distributed through the Apple App Store.
|
||||
See Apple's documentation
|
||||
\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} for more information, including how to obtain the application's group identifier.
|
||||
|
||||
\section1 Native key format
|
||||
|
||||
This section details the format of the native keys of the supported
|
||||
backends.
|
||||
|
||||
\section3 POSIX Realtime
|
||||
Native keys resemble file names and may contain any character that file
|
||||
names do, except for a slash. POSIX requires the first character in the key
|
||||
name to be a slash and leaves undetermined whether any additional slashes
|
||||
are permitted. On most operating systems, the key length is the same as a
|
||||
file name, but it is limited to 32 characters on Apple operating systems
|
||||
(this includes the first slash and the terminating null, so only 30 usable
|
||||
characters are possible).
|
||||
|
||||
The following are good examples of native POSIX keys: "/myapp",
|
||||
"/org.example.myapp", "/org.example.myapp-12345".
|
||||
|
||||
QSharedMemory::platformSafeKey() and QSystemSemaphore::platformSafeKey()
|
||||
simply prepend the slash. On Apple operating systems, they also truncate
|
||||
the result to the available size.
|
||||
|
||||
\section3 Windows
|
||||
Windows key types are NT
|
||||
\l{https://learn.microsoft.com/en-us/windows/win32/sync/object-namespaces}{kernel
|
||||
object names} and may be up to \c{MAX_PATH} (260) characters in length.
|
||||
They look like relative paths (that is, they don't start with a backslash
|
||||
or a drive letter), but unlike file names on Windows, they are
|
||||
case-sensitive.
|
||||
|
||||
The following are good examples of native Windows keys: "myapp",
|
||||
"org.example.myapp", "org.example.myapp-12345".
|
||||
|
||||
QSharedMemory::platformSafeKey() and QSystemSemaphore::platformSafeKey()
|
||||
insert a prefix to disambiguate shared memory and system semaphores,
|
||||
respectively.
|
||||
|
||||
\section3 X/Open System Interfaces (XSI) / System V
|
||||
System V keys take the form of the name of a file in the system, and thus
|
||||
have the exact same limitations as file paths do. Both QSharedMemory and
|
||||
QSystemSemaphore will create this file if it does not exist when creating
|
||||
the object. If auto-removal is disabled, it may also be shared between
|
||||
QSharedMemory and QSystemSemaphore without conflict and can be any extant
|
||||
file (for example, it can be the process executable itself, see
|
||||
QCoreApplication::applicationFilePath()). The path should be an absolute
|
||||
one to avoid mistakes due to different current directories.
|
||||
|
||||
QSharedMemory::platformSafeKey() and QSystemSemaphore::platformSafeKey()
|
||||
always return an absolute path. If the input was already absolute, they
|
||||
will return their input unchanged. Otherwise, they will prepend a suitable
|
||||
path where the application usually has permission to create files in.
|
||||
|
||||
\section1 Ownership
|
||||
|
||||
Shared memory and system semaphore objects need to be created before use,
|
||||
which is accomplished with QSharedMemory::create() or by passing
|
||||
QSystemSemaphore::Create to the constructor, respectively.
|
||||
|
||||
On Unix systems, the Qt classes that created the object will be responsible
|
||||
for cleaning up the object in question. Therefore, if the application with
|
||||
that C++ object exits uncleanly (a crash, qFatal(), etc.), the object may
|
||||
be left behind. If that happens, applications may fail to create the
|
||||
object again and should instead attach to an existing one. For example, for
|
||||
QSharedMemory:
|
||||
|
||||
\code
|
||||
if (!shm.create(4096) && shm.error() == QSharedMemory::AlreadyExists)
|
||||
shm.attach();
|
||||
\endcode
|
||||
|
||||
Re-attaching to a QSystemSemaphore is probably unwise, as the token counter
|
||||
in it is probably in an unknown state and therefore may lead to deadlocks.
|
||||
|
||||
\section3 POSIX Realtime
|
||||
POSIX Realtime object ownership is patterned after files, in the sense that
|
||||
they exist independent of any process using them or not. Qt is unable to
|
||||
determine if the object is still in use, so auto-removal will remove it
|
||||
even then, which will make attaching to the same object impossible but
|
||||
otherwise not affecting existing attachments.
|
||||
|
||||
Prior to Qt 6.6, Qt never cleaned up POSIX Realtime objects, except on QNX.
|
||||
|
||||
\section3 X/Open System Interfaces (XSI) / System V
|
||||
There are two resources managed by the Qt classes: the file the key refers
|
||||
to and the object itself. QSharedMemory manages the object cooperatively:
|
||||
the last attachment is responsible for removing the object itself and then
|
||||
removing the key file. QSystemSemaphore will remove the object if and only
|
||||
if it was passed QSystemSemaphore::Create; additionally, if it created the
|
||||
key file, it will remove that too.
|
||||
|
||||
Since Qt 6.6, it is possible to ask either class not to clean up.
|
||||
|
||||
\section3 Windows
|
||||
The operating system owns the object and will clean up after the last
|
||||
handle to the object is closed.
|
||||
|
||||
\section1 Interoperability with old Qt applications
|
||||
|
||||
The QNativeIpcKey class was introduced in Qt 6.6. Prior to this version,
|
||||
QSharedMemory and QSystemSemaphore backends were determined at the time of
|
||||
Qt's own build. For Windows systems, it was always the Windows backend. For
|
||||
Unix systems, it defaulted to the System V backend if the configuration
|
||||
script determined it was available. If it was not available, it fell back
|
||||
to the POSIX one (since Qt 4.8). The POSIX backend could be explicitly
|
||||
selected using the \c{-feature-ipc_posix} option to the Qt configure
|
||||
script; if it was enabled, the \c{QT_POSIX_IPC} macro would be defined.
|
||||
|
||||
Qt 6.6 retains the configure script option but it no longer controls the
|
||||
availability of the backends. Instead, it changes what
|
||||
QNativeIpcKey::legacyDefaultTypeForOs() will return. Applications that need
|
||||
to retain compatibility must use this key type exclusively to guarantee
|
||||
interoperability.
|
||||
|
||||
The API in both QSharedMemory and QSystemSemaphore had the concept of a
|
||||
cross-platform key, which is now deprecated in favor of using
|
||||
QSharedMemory::legacyNativeKey() and QSystemSemaphore::legacyNativeKey().
|
||||
Those two functions produce the same native key as the deprecated functions
|
||||
did in prior versions. If the old code was for example:
|
||||
|
||||
\code
|
||||
QSharedMemory shm("org.example.myapplication");
|
||||
QSystemSemaphore sem("org.example.myapplication");
|
||||
\endcode
|
||||
|
||||
It can be updated to be:
|
||||
\code
|
||||
QSharedMemory shm(QSharedMemory::legacyNativeKey("org.example.myapplication"));
|
||||
QSystemSempahore sem(QSystemSemaphore::legacyNativeKey("org.example.myapplication"));
|
||||
\endcode
|
||||
|
||||
If the two applications exchanged native keys, there is no need to update
|
||||
code such as:
|
||||
|
||||
\code
|
||||
QSharedMemory shm;
|
||||
shm.setNativeKey(key);
|
||||
\endcode
|
||||
|
||||
Though if the older application did accept a native key, the new one may
|
||||
opt to use \c{platformSafeKey()} with a second argument of
|
||||
QNativeIpcKey::legacyDefaultTypeForOs().
|
||||
|
||||
\section3 X/Open System Interfaces (XSI) / System V
|
||||
Never use existing files for QSharedMemory keys, as the old Qt application
|
||||
may attempt to remove it. Instead, let QSharedMemory create it.
|
||||
|
||||
\section1 Interoperability with non-Qt applications
|
||||
|
||||
Interoperability with non-Qt applications is possible, with some limitations:
|
||||
\list
|
||||
\li Creation of shared memory segments must not race
|
||||
\li QSharedMemory support for locking the segment is unavailable
|
||||
\endlist
|
||||
|
||||
Communication with non-Qt applications must always be through the native
|
||||
key.
|
||||
|
||||
QSharedMemory always maps the entire segment to memory. The non-Qt
|
||||
application may choose to only map a subset of it to memory, with no ill
|
||||
effects.
|
||||
|
||||
\section3 POSIX Realtime
|
||||
POSIX shared memory can be opened using
|
||||
\l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html}{shm_open()}
|
||||
and POSIX system semaphores can be opened using
|
||||
\l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_open.html}{sem_open()}.
|
||||
|
||||
Both of those functions take a \c name parameter that is the result of
|
||||
QNativeIpcKey::nativeKey(), encoded for file names using
|
||||
QFile::encodeName() / QFile::decodeName().
|
||||
|
||||
\section3 Windows
|
||||
Windows shared memory objects can be opened using
|
||||
\l{https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw}{CreateFileMappingW}
|
||||
and Windows system semaphore objects can be opened using
|
||||
\l{https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createsemaphorew}{CreateSemaphoreW}.
|
||||
Despite the name of both functions starting with "Create", they are able
|
||||
to attach to existing objects.
|
||||
|
||||
The \c lpName parameter to those functions is the result of
|
||||
QNativeIpcKey::nativeKey(), without transformation.
|
||||
|
||||
If the foreign application uses the non-Unicode version of those functions
|
||||
(ending in "A"), the name can be converted to and from 8-bit using QString.
|
||||
|
||||
\section3 X/Open System Interfaces (XSI) / System V
|
||||
System V shared memory can be obtained using
|
||||
\l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmget.html}{shmget()}
|
||||
and System V system semaphores can be obtained using
|
||||
\l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/semget.html}{semget()}.
|
||||
|
||||
The \c{key} parameter to either of those functions is the result of the
|
||||
\l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftok.html}{ftok()}
|
||||
function when passed the file name obtained from QNativeIpcKey::nativeKey()
|
||||
with an \c id of 81 or 0x51 (the ASCII capital letter 'Q').
|
||||
|
||||
System V semaphore objects may contain multiple semaphores, but
|
||||
QSystemSemaphore only uses the first one (number 0 for \c{sem_num}).
|
||||
|
||||
Both QSharedMemory and QSystemSemaphore default to removing the object
|
||||
using the \c{IPC_RMID} operation to \c{shmctl()} and \c{semctl()}
|
||||
respectively if they are the last attachment.
|
||||
*/
|
@ -76,6 +76,7 @@
|
||||
\li \l{The Animation Framework}
|
||||
\li \l{JSON Support in Qt}
|
||||
\li \l{CBOR Support in Qt}
|
||||
\li \l{Inter-Process Communication}
|
||||
\li \l{How to Create Qt Plugins}
|
||||
\li \l{The Event System}
|
||||
\endlist
|
||||
|
@ -1,10 +1,13 @@
|
||||
// Copyright (C) 2022 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "qtipccommon.h"
|
||||
#include "qtipccommon_p.h"
|
||||
|
||||
#include <qcryptographichash.h>
|
||||
#include <qdir.h>
|
||||
#include <qstringconverter.h>
|
||||
#include <private/qurl_p.h>
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
# include "private/qcore_mac_p.h"
|
||||
@ -23,6 +26,65 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static QStringView staticTypeToString(QNativeIpcKey::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case QNativeIpcKey::Type::SystemV:
|
||||
return u"systemv";
|
||||
case QNativeIpcKey::Type::PosixRealtime:
|
||||
return u"posix";
|
||||
case QNativeIpcKey::Type::Windows:
|
||||
return u"windows";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static QString typeToString(QNativeIpcKey::Type type)
|
||||
{
|
||||
QStringView typeString = staticTypeToString(type);
|
||||
switch (type) {
|
||||
case QNativeIpcKey::Type::SystemV:
|
||||
case QNativeIpcKey::Type::PosixRealtime:
|
||||
case QNativeIpcKey::Type::Windows:
|
||||
return QString::fromRawData(typeString.constData(), typeString.size());
|
||||
}
|
||||
|
||||
int value = int(type);
|
||||
if (value >= 1 && value <= 0xff) {
|
||||
// System V key with id different from 'Q'
|
||||
typeString = staticTypeToString(QNativeIpcKey::Type::SystemV);
|
||||
return typeString + QString::number(-value); // negative so it prepends a dash
|
||||
}
|
||||
|
||||
return QString(); // invalid!
|
||||
}
|
||||
|
||||
static QNativeIpcKey::Type stringToType(QStringView typeString)
|
||||
{
|
||||
if (typeString == staticTypeToString(QNativeIpcKey::Type::PosixRealtime))
|
||||
return QNativeIpcKey::Type::PosixRealtime;
|
||||
if (typeString == staticTypeToString(QNativeIpcKey::Type::Windows))
|
||||
return QNativeIpcKey::Type::Windows;
|
||||
|
||||
auto fromNumber = [](QStringView number, int low, int high) {
|
||||
bool ok;
|
||||
int n = -number.toInt(&ok, 10);
|
||||
if (!ok || n < low || n > high)
|
||||
return QNativeIpcKey::Type{};
|
||||
return QNativeIpcKey::Type(n);
|
||||
};
|
||||
|
||||
QStringView sysv = staticTypeToString(QNativeIpcKey::Type::SystemV);
|
||||
if (typeString.startsWith(sysv)) {
|
||||
if (typeString.size() == sysv.size())
|
||||
return QNativeIpcKey::Type::SystemV;
|
||||
return fromNumber(typeString.sliced(sysv.size()), 1, 0xff);
|
||||
}
|
||||
|
||||
// invalid!
|
||||
return QNativeIpcKey::Type{};
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
@ -84,6 +146,340 @@ QString QtIpcCommon::legacyPlatformSafeKey(const QString &key, QtIpcCommon::IpcT
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
\class QNativeIpcKey
|
||||
\inmodule QtCore
|
||||
\since 6.6
|
||||
\brief The QNativeIpcKey class holds a native key used by QSystemSemaphore and QSharedMemory.
|
||||
|
||||
The \l QSharedMemory and \l QSystemSemaphore classes identify their
|
||||
resource using a system-wide identifier known as a "key". The low-level key
|
||||
value as well as the key type are encapsulated in Qt using the \l
|
||||
QNativeIpcKey class.
|
||||
|
||||
Those two classes also provide the means to create native keys from a
|
||||
cross-platform identifier, using QSharedMemory::platformSafeKey() and
|
||||
QSystemSemaphore::platformSafeKey(). Applications should never share the
|
||||
input to those functions, as different versions of Qt may perform different
|
||||
transformations, resulting in different native keys. Instead, the
|
||||
application that created the IPC object should communicate the resulting
|
||||
native key using the methods described below.
|
||||
|
||||
For details on the key types, platform-specific limitations, and
|
||||
interoperability with older or non-Qt applications, see the \l{Native IPC
|
||||
Key} documentation. That includes important information for sandboxed
|
||||
applications on Apple platforms, including all apps obtained via the Apple
|
||||
App Store.
|
||||
|
||||
\section1 Communicating keys to other processes
|
||||
\section2 Communicating keys to other Qt processes
|
||||
|
||||
If the other process supports QNativeIpcKey, the best way of communicating
|
||||
is via the string representation obtained from toString() and parsing it
|
||||
using fromString(). This representation can be stored on a file whose name
|
||||
is well-known or passed on the command-line to a child process using
|
||||
QProcess::setArguments().
|
||||
|
||||
If the other process does not support QNativeIpcKey, then the two processes
|
||||
can exchange the nativeKey() but the older code is likely unable to adjust
|
||||
its key type. The legacyDefaultTypeForOs() function returns the type that
|
||||
legacy code used, which may not match the \l{DefaultTypeForOs} constant.
|
||||
This is still true even if the old application is not using the same build
|
||||
as the new one (for example, it is a Qt 5 application), provided the
|
||||
options passed to the Qt configure script are the same.
|
||||
|
||||
\section2 Communicating keys to non-Qt processes
|
||||
|
||||
When communicating with non-Qt processes, the application must arrange to
|
||||
obtain the key type the other process is using. This is important
|
||||
particularly on Unix systems, where both \l PosixRealtime and \l SystemV
|
||||
are common.
|
||||
|
||||
\section1 String representation of native keys
|
||||
|
||||
The format of the string representation of a QNativeIpcKey is meant to be
|
||||
stable and therefore backwards and forwards compatible, provided the key
|
||||
type is supported by the Qt version in question. That is to say, an older
|
||||
Qt will fail to parse the string representation of a key type introduced
|
||||
after it was released. However, successfully parsing a string
|
||||
representation does not imply the Qt classes can successfully create an
|
||||
object of that type; applications should verify support using
|
||||
QSharedMemory::isKeyTypeSupported() and QSystemSemaphore::isKeyTypeSupported().
|
||||
|
||||
The format of the string representation is formed by two components,
|
||||
separated by a colon (':'). The first component is the key type, described
|
||||
in the table below. The second component is a type-specific payload, using
|
||||
\l{QByteArray::fromPercentEncoding}{percent-encoding}. For all currently
|
||||
supported key types, the decoded form is identical to the contents of the
|
||||
nativeKey() field.
|
||||
|
||||
\table
|
||||
\row \li Key type \li String representation
|
||||
\row \li \l PosixRealtime \li \c "posix"
|
||||
\row \li \l SystemV \li \c "systemv"
|
||||
\row \li \l Windows \li \c "windows"
|
||||
\row \li Non-standard SystemV \li \c "systemv-" followed by a decimal number
|
||||
\endtable
|
||||
|
||||
This format resembles a URI and allows parsing using URI/URL-parsing
|
||||
functions, such as \l QUrl. When parsed by such API, the key type will show
|
||||
up as the \l{QUrl::scheme()}{scheme}, and the payload will be the
|
||||
\l{QUrl::path()}{path}. Use of query or fragments is reserved.
|
||||
|
||||
\sa QSharedMemory, QSystemSemaphore
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum QNativeIpcKey::Type
|
||||
|
||||
This enum describes the backend type for the IPC object. For details on the
|
||||
key types, see the \l{Native IPC Key} documentation.
|
||||
|
||||
\value SystemV X/Open System Initiative (XSI) or System V (SVr4) API
|
||||
\value PosixRealtime IEEE 1003.1b (POSIX.1b) API
|
||||
\value Windows Win32 API
|
||||
|
||||
\sa setType(), type()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\variable QNativeIpcKey::DefaultTypeForOs
|
||||
|
||||
This constant expression variable holds the default native IPC type for the
|
||||
current OS. It will be Type::Windows for Windows systems and
|
||||
Type::PosixRealtime elsewhere. Note that this constant is different from
|
||||
what \l QSharedMemory and \l QSystemSemaphore defaulted to on the majority
|
||||
of Unix systems prior to Qt 6.6; see legacyDefaultTypeForOs() for more
|
||||
information.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::legacyDefaultTypeForOs() noexcept
|
||||
|
||||
Returns the \l{Type} that corresponds to the native IPC key that
|
||||
\l{QSharedMemory} and \l{QSystemSemaphore} used to use prior to Qt 6.6.
|
||||
Applications and libraries that must retain compatibility with code using
|
||||
either class that was compiled with Qt prior to version 6.6 can use this
|
||||
function to determine what IPC type the other applications may be using.
|
||||
|
||||
Note that this function relies on Qt having been built with identical
|
||||
configure-time options.
|
||||
*/
|
||||
#if defined(Q_OS_DARWIN)
|
||||
QNativeIpcKey::Type QNativeIpcKey::defaultTypeForOs_internal() noexcept
|
||||
{
|
||||
if (qt_apple_isSandboxed())
|
||||
return Type::PosixRealtime;
|
||||
return Type::SystemV;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::QNativeIpcKey(Type type) noexcept
|
||||
\fn QNativeIpcKey::QNativeIpcKey(const QString &key, Type type)
|
||||
|
||||
Constructs a QNativeIpcKey object holding native key \a key (or empty on
|
||||
the overload without the parameter) for type \a type.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::QNativeIpcKey(const QNativeIpcKey &other)
|
||||
\fn QNativeIpcKey::QNativeIpcKey(QNativeIpcKey &&other) noexcept
|
||||
\fn QNativeIpcKey &QNativeIpcKey::operator=(const QNativeIpcKey &other)
|
||||
\fn QNativeIpcKey &QNativeIpcKey::operator=(QNativeIpcKey &&other) noexcept
|
||||
|
||||
Copies or moves the content of \a other.
|
||||
*/
|
||||
void QNativeIpcKey::copy_internal(const QNativeIpcKey &)
|
||||
{
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
void QNativeIpcKey::move_internal(QNativeIpcKey &&) noexcept
|
||||
{
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
QNativeIpcKey &QNativeIpcKey::assign_internal(const QNativeIpcKey &)
|
||||
{
|
||||
Q_UNREACHABLE_RETURN(*this);
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::~QNativeIpcKey()
|
||||
|
||||
Disposes of this QNativeIpcKey object.
|
||||
*/
|
||||
void QNativeIpcKey::destroy_internal() noexcept
|
||||
{
|
||||
Q_ASSERT(isSlowPath());
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::swap(QNativeIpcKey &other) noexcept
|
||||
\fn swap(QNativeIpcKey &lhs, QNativeIpcKey &rhs) noexcept
|
||||
|
||||
Swaps the native IPC key and type \a other with this object, or \a lhs with
|
||||
\a rhs. This operation is very fast and never fails.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::isEmpty() const
|
||||
|
||||
Returns true if the nativeKey() is empty.
|
||||
|
||||
\sa nativeKey()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::isValid() const
|
||||
|
||||
Returns true if this object contains a valid native IPC key type. Invalid
|
||||
types are usually the result of a failure to parse a string representation
|
||||
using fromString().
|
||||
|
||||
This function performs no check on the whether the key string is actually
|
||||
supported or valid for the current operating system.
|
||||
|
||||
\sa type(), fromString()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::type() const noexcept
|
||||
|
||||
Returns the key type associated with this object.
|
||||
|
||||
\sa nativeKey(), setType()
|
||||
*/
|
||||
QNativeIpcKey::Type QNativeIpcKey::type_internal() const noexcept
|
||||
{
|
||||
Q_ASSERT(isSlowPath());
|
||||
Q_UNREACHABLE_RETURN({});
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::setType(Type type)
|
||||
|
||||
Sets the IPC type of this object to \a type.
|
||||
|
||||
\sa type(), setNativeKey()
|
||||
*/
|
||||
void QNativeIpcKey::setType_internal(Type)
|
||||
{
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::nativeKey() const noexcept
|
||||
|
||||
Returns the native key string associated with this object.
|
||||
|
||||
\sa setNativeKey(), type()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QNativeIpcKey::setNativeKey(const QString &newKey)
|
||||
|
||||
Sets the native key for this object to \a newKey.
|
||||
|
||||
\sa nativeKey(), setType()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn bool QNativeIpcKey::operator==(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept
|
||||
\fn bool QNativeIpcKey::operator!=(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept
|
||||
|
||||
Returns true if the \a lhs and \a rhs objects hold the same (or different) contents.
|
||||
*/
|
||||
int QNativeIpcKey::compare_internal(const QNativeIpcKey &, const QNativeIpcKey &) noexcept
|
||||
{
|
||||
Q_UNREACHABLE_RETURN(0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the string representation of this object. String representations
|
||||
are useful to inform other processes of the key this process created and
|
||||
that they should attach to.
|
||||
|
||||
This function returns a null string if the current object is
|
||||
\l{isValid()}{invalid}.
|
||||
|
||||
\sa fromString()
|
||||
*/
|
||||
QString QNativeIpcKey::toString() const
|
||||
{
|
||||
Q_ASSERT(!isSlowPath());
|
||||
QString prefix = typeToString(typeAndFlags.type);
|
||||
if (prefix.isEmpty()) {
|
||||
Q_ASSERT(prefix.isNull());
|
||||
return prefix;
|
||||
}
|
||||
|
||||
prefix += u':';
|
||||
QString copy = nativeKey();
|
||||
copy.replace(u'%', "%25"_L1);
|
||||
if (copy.startsWith("//"_L1))
|
||||
copy.replace(0, 2, u"/%2F"_s); // ensure it's parsed as a URL path
|
||||
|
||||
const ushort recodeActions[] = {
|
||||
'\\' | 0, // decode
|
||||
'?' | 0x200, // encode
|
||||
'#' | 0x200, // encode
|
||||
0
|
||||
};
|
||||
if (!qt_urlRecode(prefix, copy, QUrl::DecodeReserved, recodeActions))
|
||||
prefix += copy;
|
||||
return prefix;
|
||||
}
|
||||
|
||||
/*!
|
||||
Parses the string form \a text and returns the corresponding QNativeIpcKey.
|
||||
String representations are useful to inform other processes of the key this
|
||||
process created and they should attach to.
|
||||
|
||||
If the string could not be parsed, this function returns an
|
||||
\l{isValid()}{invalid} object.
|
||||
|
||||
\sa toString(), isValid()
|
||||
*/
|
||||
QNativeIpcKey QNativeIpcKey::fromString(const QString &text)
|
||||
{
|
||||
// this duplicates QUrlPrivate::parse a little
|
||||
Type invalidType = {};
|
||||
qsizetype colon = text.indexOf(u':');
|
||||
if (colon < 0)
|
||||
return QNativeIpcKey(invalidType);
|
||||
|
||||
Type type = stringToType(QStringView(text).left(colon));
|
||||
if (type == invalidType)
|
||||
return QNativeIpcKey(invalidType);
|
||||
|
||||
QNativeIpcKey result(QString(), type);
|
||||
if (result.type() != type) // range check, just in case
|
||||
return QNativeIpcKey(invalidType);
|
||||
|
||||
// decode the payload
|
||||
QStringView payload = QStringView(text).sliced(colon + 1);
|
||||
if (qsizetype pos = payload.indexOf(u'?'); pos >= 0)
|
||||
payload.truncate(pos);
|
||||
if (qsizetype pos = payload.indexOf(u'#'); pos >= 0)
|
||||
payload.truncate(pos);
|
||||
|
||||
// qt_urlRecode requires a two-step decoding for non-ASCII content
|
||||
QString nativeKey, intermediate;
|
||||
if (qt_urlRecode(intermediate, payload, QUrl::PrettyDecoded))
|
||||
payload = intermediate;
|
||||
if (!qt_urlRecode(nativeKey, payload, QUrl::FullyDecoded))
|
||||
nativeKey = payload.toString();
|
||||
|
||||
result.setNativeKey(nativeKey);
|
||||
return result;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include "moc_qtipccommon.cpp"
|
||||
|
||||
#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore)
|
||||
|
191
src/corelib/ipc/qtipccommon.h
Normal file
191
src/corelib/ipc/qtipccommon.h
Normal file
@ -0,0 +1,191 @@
|
||||
// Copyright (C) 2022 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QNATIVEIPCKEY_H
|
||||
#define QNATIVEIPCKEY_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QtCore/qtcore-config.h>
|
||||
|
||||
#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore)
|
||||
# include <QtCore/qstring.h>
|
||||
# include <QtCore/qobjectdefs.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QNativeIpcKeyPrivate;
|
||||
class QNativeIpcKey
|
||||
{
|
||||
Q_GADGET_EXPORT(Q_CORE_EXPORT)
|
||||
public:
|
||||
enum class Type : quint16 {
|
||||
// 0 is reserved for the invalid type
|
||||
// keep 1 through 0xff free, except for SystemV
|
||||
SystemV = 0x51, // 'Q'
|
||||
|
||||
PosixRealtime = 0x100,
|
||||
Windows,
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
|
||||
static constexpr Type DefaultTypeForOs =
|
||||
#ifdef Q_OS_WIN
|
||||
Type::Windows
|
||||
#elif !defined(QT_POSIX_IPC)
|
||||
Type::SystemV
|
||||
#else
|
||||
Type::PosixRealtime
|
||||
#endif
|
||||
;
|
||||
static Type legacyDefaultTypeForOs() noexcept;
|
||||
|
||||
explicit constexpr QNativeIpcKey(Type type = DefaultTypeForOs) noexcept
|
||||
: d()
|
||||
{
|
||||
typeAndFlags.type = type;
|
||||
}
|
||||
|
||||
Q_IMPLICIT QNativeIpcKey(const QString &k, Type type = DefaultTypeForOs)
|
||||
: d(), key(k)
|
||||
{
|
||||
typeAndFlags.type = type;
|
||||
}
|
||||
|
||||
QNativeIpcKey(const QNativeIpcKey &other)
|
||||
: d(other.d), key(other.key)
|
||||
{
|
||||
if (isSlowPath())
|
||||
copy_internal(other);
|
||||
}
|
||||
|
||||
QNativeIpcKey(QNativeIpcKey &&other) noexcept
|
||||
: d(other.d), key(std::move(other.key))
|
||||
{
|
||||
if (isSlowPath())
|
||||
move_internal(std::move(other));
|
||||
}
|
||||
|
||||
~QNativeIpcKey()
|
||||
{
|
||||
if (isSlowPath())
|
||||
destroy_internal();
|
||||
}
|
||||
|
||||
QNativeIpcKey &operator=(const QNativeIpcKey &other)
|
||||
{
|
||||
if (isSlowPath() || other.isSlowPath())
|
||||
return assign_internal(other);
|
||||
d = other.d;
|
||||
key = other.key;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QNativeIpcKey)
|
||||
void swap(QNativeIpcKey &other)
|
||||
{
|
||||
qt_ptr_swap(d, other.d);
|
||||
key.swap(other.key);
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return key.isEmpty();
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return type() != Type{};
|
||||
}
|
||||
|
||||
constexpr Type type() const noexcept
|
||||
{
|
||||
if (isSlowPath())
|
||||
return type_internal();
|
||||
return typeAndFlags.type;
|
||||
}
|
||||
|
||||
constexpr void setType(Type type)
|
||||
{
|
||||
if (isSlowPath())
|
||||
return setType_internal(type);
|
||||
typeAndFlags.type = type;
|
||||
}
|
||||
|
||||
QString nativeKey() const noexcept
|
||||
{ return key; }
|
||||
void setNativeKey(const QString &newKey)
|
||||
{ key = newKey; }
|
||||
|
||||
Q_CORE_EXPORT QString toString() const;
|
||||
Q_CORE_EXPORT static QNativeIpcKey fromString(const QString &string);
|
||||
|
||||
private:
|
||||
struct TypeAndFlags {
|
||||
quint16 isExtended : 1;
|
||||
Type type : 15;
|
||||
quint16 reserved;
|
||||
};
|
||||
|
||||
// Bit 0: if set, holds a pointer (with the LSB set); if clear, holds the
|
||||
// the TypeAndFlags structure.
|
||||
union {
|
||||
QNativeIpcKeyPrivate *d = nullptr;
|
||||
TypeAndFlags typeAndFlags;
|
||||
static_assert(sizeof(typeAndFlags) <= sizeof(d));
|
||||
};
|
||||
|
||||
QString key;
|
||||
|
||||
constexpr bool isSlowPath() const noexcept
|
||||
{ return Q_UNLIKELY(typeAndFlags.isExtended); }
|
||||
|
||||
friend bool operator==(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept
|
||||
{
|
||||
if (lhs.key != rhs.key)
|
||||
return false;
|
||||
if (lhs.d == rhs.d)
|
||||
return true;
|
||||
if (lhs.isSlowPath() && rhs.isSlowPath())
|
||||
return compare_internal(lhs, rhs) == 0;
|
||||
return lhs.d == rhs.d;
|
||||
}
|
||||
friend bool operator!=(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
Q_CORE_EXPORT void copy_internal(const QNativeIpcKey &other);
|
||||
Q_CORE_EXPORT void move_internal(QNativeIpcKey &&other) noexcept;
|
||||
Q_CORE_EXPORT QNativeIpcKey &assign_internal(const QNativeIpcKey &other);
|
||||
Q_CORE_EXPORT void destroy_internal() noexcept;
|
||||
Q_DECL_PURE_FUNCTION Q_CORE_EXPORT Type type_internal() const noexcept;
|
||||
Q_CORE_EXPORT void setType_internal(Type);
|
||||
Q_DECL_PURE_FUNCTION Q_CORE_EXPORT static int
|
||||
compare_internal(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept;
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
Q_DECL_CONST_FUNCTION Q_CORE_EXPORT static Type defaultTypeForOs_internal() noexcept;
|
||||
#endif
|
||||
};
|
||||
|
||||
// not a shared type, exactly, but this works too
|
||||
Q_DECLARE_SHARED(QNativeIpcKey)
|
||||
|
||||
inline auto QNativeIpcKey::legacyDefaultTypeForOs() noexcept -> Type
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
return Type::Windows;
|
||||
#elif defined(QT_POSIX_IPC)
|
||||
return Type::PosixRealtime;
|
||||
#elif defined(Q_OS_DARWIN)
|
||||
return defaultTypeForOs_internal();
|
||||
#else
|
||||
return Type::SystemV;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore)
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QNATIVEIPCKEY_H
|
@ -2,6 +2,9 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
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)
|
||||
add_subdirectory(qsharedmemory)
|
||||
endif()
|
||||
|
7
tests/auto/corelib/ipc/qnativeipckey/CMakeLists.txt
Normal file
7
tests/auto/corelib/ipc/qnativeipckey/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
||||
# Copyright (C) 2022 Intel Corporation.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
qt_internal_add_test(tst_qnativeipckey
|
||||
SOURCES
|
||||
tst_qnativeipckey.cpp
|
||||
)
|
347
tests/auto/corelib/ipc/qnativeipckey/tst_qnativeipckey.cpp
Normal file
347
tests/auto/corelib/ipc/qnativeipckey/tst_qnativeipckey.cpp
Normal file
@ -0,0 +1,347 @@
|
||||
// Copyright (C) 2022 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include <QtCore/QNativeIpcKey>
|
||||
#include <QtTest/QTest>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
namespace QTest {
|
||||
template<> inline char *toString(const QNativeIpcKey::Type &type)
|
||||
{
|
||||
switch (type) {
|
||||
case QNativeIpcKey::Type::SystemV: return qstrdup("SystemV");
|
||||
case QNativeIpcKey::Type::PosixRealtime: return qstrdup("PosixRealTime");
|
||||
case QNativeIpcKey::Type::Windows: return qstrdup("Windows");
|
||||
}
|
||||
if (type == QNativeIpcKey::Type{})
|
||||
return qstrdup("Invalid");
|
||||
|
||||
char buf[32];
|
||||
qsnprintf(buf, sizeof(buf), "%u", unsigned(type));
|
||||
return qstrdup(buf);
|
||||
}
|
||||
|
||||
template<> inline char *toString(const QNativeIpcKey &key)
|
||||
{
|
||||
if (!key.isValid())
|
||||
return qstrdup("<invalid>");
|
||||
|
||||
const char *type = toString(key.type());
|
||||
const char *text = toString(key.nativeKey());
|
||||
char buf[256];
|
||||
qsnprintf(buf, sizeof(buf), "QNativeIpcKey(%s, %s)", text, type);
|
||||
delete[] type;
|
||||
delete[] text;
|
||||
return qstrdup(buf);
|
||||
}
|
||||
} // namespace QTest
|
||||
|
||||
class tst_QNativeIpcKey : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void defaultTypes();
|
||||
void construct();
|
||||
void getSetCheck();
|
||||
void equality();
|
||||
void swap();
|
||||
void toString_data();
|
||||
void toString();
|
||||
void fromString_data();
|
||||
void fromString();
|
||||
};
|
||||
|
||||
void tst_QNativeIpcKey::defaultTypes()
|
||||
{
|
||||
auto isKnown = [](QNativeIpcKey::Type t) {
|
||||
switch (t) {
|
||||
case QNativeIpcKey::Type::SystemV:
|
||||
case QNativeIpcKey::Type::PosixRealtime:
|
||||
case QNativeIpcKey::Type::Windows:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// because the letter Q looked nice in Håvard's Emacs font back in the 1990s
|
||||
static_assert(qToUnderlying(QNativeIpcKey::Type::SystemV) == 'Q',
|
||||
"QNativeIpcKey::Type::SystemV must be equal to the letter Q");
|
||||
|
||||
auto type = QNativeIpcKey::DefaultTypeForOs;
|
||||
auto legacy = QNativeIpcKey::legacyDefaultTypeForOs();
|
||||
QVERIFY(isKnown(type));
|
||||
QVERIFY(isKnown(legacy));
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QCOMPARE(type, QNativeIpcKey::Type::Windows);
|
||||
#elif !defined(QT_POSIX_IPC)
|
||||
QCOMPARE(type, QNativeIpcKey::Type::SystemV);
|
||||
#else
|
||||
QCOMPARE(type, QNativeIpcKey::Type::PosixRealtime);
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
QCOMPARE(legacy, QNativeIpcKey::Type::Windows);
|
||||
#elif defined(QT_POSIX_IPC)
|
||||
QCOMPARE(legacy, QNativeIpcKey::Type::PosixRealtime);
|
||||
#elif !defined(Q_OS_DARWIN)
|
||||
QCOMPARE(legacy, QNativeIpcKey::Type::SystemV);
|
||||
#endif
|
||||
}
|
||||
|
||||
void tst_QNativeIpcKey::construct()
|
||||
{
|
||||
{
|
||||
QNativeIpcKey invalid(QNativeIpcKey::Type{});
|
||||
QVERIFY(!invalid.isValid());
|
||||
}
|
||||
|
||||
{
|
||||
QNativeIpcKey key;
|
||||
QVERIFY(key.nativeKey().isEmpty());
|
||||
QVERIFY(key.isEmpty());
|
||||
QVERIFY(key.isValid());
|
||||
QCOMPARE(key.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
|
||||
QNativeIpcKey copy(key);
|
||||
QVERIFY(copy.nativeKey().isEmpty());
|
||||
QVERIFY(copy.isEmpty());
|
||||
QVERIFY(copy.isValid());
|
||||
QCOMPARE(copy.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
|
||||
QNativeIpcKey moved(std::move(copy));
|
||||
QVERIFY(moved.nativeKey().isEmpty());
|
||||
QVERIFY(moved.isEmpty());
|
||||
QVERIFY(moved.isValid());
|
||||
QCOMPARE(moved.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
|
||||
key.setType({});
|
||||
key.setNativeKey("something else");
|
||||
key = std::move(moved);
|
||||
QVERIFY(key.nativeKey().isEmpty());
|
||||
QVERIFY(key.isEmpty());
|
||||
QVERIFY(key.isValid());
|
||||
QCOMPARE(key.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
|
||||
copy.setType({});
|
||||
copy.setNativeKey("something else");
|
||||
copy = key;
|
||||
QVERIFY(copy.nativeKey().isEmpty());
|
||||
QVERIFY(copy.isEmpty());
|
||||
QVERIFY(copy.isValid());
|
||||
QCOMPARE(copy.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
}
|
||||
|
||||
{
|
||||
QNativeIpcKey key("dummy");
|
||||
QCOMPARE(key.nativeKey(), "dummy");
|
||||
QVERIFY(!key.isEmpty());
|
||||
QVERIFY(key.isValid());
|
||||
QCOMPARE(key.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
|
||||
QNativeIpcKey copy(key);
|
||||
QCOMPARE(key.nativeKey(), "dummy");
|
||||
QCOMPARE(copy.nativeKey(), "dummy");
|
||||
QVERIFY(!copy.isEmpty());
|
||||
QVERIFY(copy.isValid());
|
||||
QCOMPARE(copy.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
|
||||
QNativeIpcKey moved(std::move(copy));
|
||||
QCOMPARE(key.nativeKey(), "dummy");
|
||||
QCOMPARE(moved.nativeKey(), "dummy");
|
||||
QVERIFY(!moved.isEmpty());
|
||||
QVERIFY(moved.isValid());
|
||||
QCOMPARE(moved.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
|
||||
key.setType({});
|
||||
key.setNativeKey("something else");
|
||||
key = std::move(moved);
|
||||
QCOMPARE(key.nativeKey(), "dummy");
|
||||
QVERIFY(!key.isEmpty());
|
||||
QVERIFY(key.isValid());
|
||||
QCOMPARE(key.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
|
||||
copy.setType({});
|
||||
copy.setNativeKey("something else");
|
||||
copy = key;
|
||||
QCOMPARE(key.nativeKey(), "dummy");
|
||||
QCOMPARE(copy.nativeKey(), "dummy");
|
||||
QVERIFY(!copy.isEmpty());
|
||||
QVERIFY(copy.isValid());
|
||||
QCOMPARE(copy.type(), QNativeIpcKey::DefaultTypeForOs);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QNativeIpcKey::getSetCheck()
|
||||
{
|
||||
QNativeIpcKey key("key1", QNativeIpcKey::Type::Windows);
|
||||
QVERIFY(key.isValid());
|
||||
QVERIFY(!key.isEmpty());
|
||||
QCOMPARE(key.nativeKey(), "key1");
|
||||
QCOMPARE(key.type(), QNativeIpcKey::Type::Windows);
|
||||
|
||||
key.setType(QNativeIpcKey::Type::SystemV);
|
||||
QVERIFY(key.isValid());
|
||||
QVERIFY(!key.isEmpty());
|
||||
QCOMPARE(key.type(), QNativeIpcKey::Type::SystemV);
|
||||
|
||||
key.setNativeKey("key2");
|
||||
QCOMPARE(key.nativeKey(), "key2");
|
||||
}
|
||||
|
||||
void tst_QNativeIpcKey::equality()
|
||||
{
|
||||
QNativeIpcKey key1, key2;
|
||||
QCOMPARE(key1, key2);
|
||||
QVERIFY(!(key1 != key2));
|
||||
|
||||
key1.setNativeKey("key1");
|
||||
QCOMPARE_NE(key1, key2);
|
||||
QVERIFY(!(key1 == key2));
|
||||
|
||||
key2.setType({});
|
||||
QCOMPARE_NE(key1, key2);
|
||||
QVERIFY(!(key1 == key2));
|
||||
|
||||
key2.setNativeKey(key1.nativeKey());
|
||||
QCOMPARE_NE(key1, key2);
|
||||
QVERIFY(!(key1 == key2));
|
||||
|
||||
key2.setType(QNativeIpcKey::DefaultTypeForOs);
|
||||
QCOMPARE(key1, key2);
|
||||
QVERIFY(!(key1 != key2));
|
||||
}
|
||||
|
||||
void tst_QNativeIpcKey::swap()
|
||||
{
|
||||
QNativeIpcKey key1("key1", QNativeIpcKey::Type::PosixRealtime);
|
||||
QNativeIpcKey key2("key2", QNativeIpcKey::Type::Windows);
|
||||
|
||||
// self-swaps
|
||||
key1.swap(key1);
|
||||
key2.swap(key2);
|
||||
QCOMPARE(key1.nativeKey(), "key1");
|
||||
QCOMPARE(key1.type(), QNativeIpcKey::Type::PosixRealtime);
|
||||
QCOMPARE(key2.nativeKey(), "key2");
|
||||
QCOMPARE(key2.type(), QNativeIpcKey::Type::Windows);
|
||||
|
||||
key1.swap(key2);
|
||||
QCOMPARE(key2.nativeKey(), "key1");
|
||||
QCOMPARE(key2.type(), QNativeIpcKey::Type::PosixRealtime);
|
||||
QCOMPARE(key1.nativeKey(), "key2");
|
||||
QCOMPARE(key1.type(), QNativeIpcKey::Type::Windows);
|
||||
|
||||
key1.swap(key2);
|
||||
QCOMPARE(key1.nativeKey(), "key1");
|
||||
QCOMPARE(key1.type(), QNativeIpcKey::Type::PosixRealtime);
|
||||
QCOMPARE(key2.nativeKey(), "key2");
|
||||
QCOMPARE(key2.type(), QNativeIpcKey::Type::Windows);
|
||||
}
|
||||
|
||||
void tst_QNativeIpcKey::toString_data()
|
||||
{
|
||||
QTest::addColumn<QString>("string");
|
||||
QTest::addColumn<QNativeIpcKey>("key");
|
||||
|
||||
QTest::newRow("invalid") << QString() << QNativeIpcKey(QNativeIpcKey::Type(0));
|
||||
|
||||
auto addRow = [](const char *prefix, QNativeIpcKey::Type type) {
|
||||
auto add = [=](const char *name, QLatin1StringView key, QLatin1StringView encoded = {}) {
|
||||
if (encoded.isNull())
|
||||
encoded = key;
|
||||
QTest::addRow("%s-%s", prefix, name)
|
||||
<< prefix + u":"_s + encoded << QNativeIpcKey(key, type);
|
||||
};
|
||||
add("empty", {});
|
||||
add("text", "foobar"_L1);
|
||||
add("pathlike", "/sometext"_L1);
|
||||
add("objectlike", "Global\\sometext"_L1);
|
||||
add("colon-slash", ":/"_L1);
|
||||
add("slash-colon", "/:"_L1);
|
||||
add("non-ascii", "\xa0\xff"_L1);
|
||||
add("percent", "%"_L1, "%25"_L1);
|
||||
add("question-hash", "?#"_L1, "%3F%23"_L1);
|
||||
add("hash-question", "#?"_L1, "%23%3F"_L1);
|
||||
add("double-slash", "//"_L1, "/%2F"_L1);
|
||||
add("triple-slash", "///"_L1, "/%2F/"_L1);
|
||||
add("non-ascii", "/\xe9"_L1);
|
||||
QTest::addRow("%s-%s", prefix, "non-latin1")
|
||||
<< prefix + u":\u0100.\u2000.\U00010000"_s
|
||||
<< QNativeIpcKey(u"\u0100.\u2000.\U00010000"_s, type);
|
||||
};
|
||||
addRow("systemv", QNativeIpcKey::Type::SystemV);
|
||||
addRow("posix", QNativeIpcKey::Type::PosixRealtime);
|
||||
addRow("windows", QNativeIpcKey::Type::Windows);
|
||||
|
||||
addRow("systemv-1", QNativeIpcKey::Type(1));
|
||||
addRow("systemv-84", QNativeIpcKey::Type('T'));
|
||||
addRow("systemv-255", QNativeIpcKey::Type(0xff));
|
||||
}
|
||||
|
||||
void tst_QNativeIpcKey::toString()
|
||||
{
|
||||
QFETCH(QString, string);
|
||||
QFETCH(QNativeIpcKey, key);
|
||||
|
||||
QCOMPARE(key.toString(), string);
|
||||
}
|
||||
|
||||
void tst_QNativeIpcKey::fromString_data()
|
||||
{
|
||||
toString_data();
|
||||
QTest::addRow("systemv-alias") << "systemv-81:" << QNativeIpcKey(QNativeIpcKey::Type::SystemV);
|
||||
QTest::addRow("systemv-zeropadded") << "systemv-009:" << QNativeIpcKey(QNativeIpcKey::Type(9));
|
||||
|
||||
QNativeIpcKey valid("/foo", QNativeIpcKey::Type::PosixRealtime);
|
||||
QNativeIpcKey invalid(QNativeIpcKey::Type(0));
|
||||
|
||||
// percent-decoding
|
||||
QTest::addRow("percent-encoded") << "posix:%2f%66o%6f" << valid;
|
||||
QTest::addRow("percent-utf8")
|
||||
<< "posix:%C4%80.%E2%80%80.%F0%90%80%80"
|
||||
<< QNativeIpcKey(u"\u0100.\u2000.\U00010000"_s, QNativeIpcKey::Type::PosixRealtime);
|
||||
|
||||
// query and fragment are ignored
|
||||
QTest::addRow("with-query") << "posix:/foo?bar" << valid;
|
||||
QTest::addRow("with-fragment") << "posix:/foo#bar" << valid;
|
||||
QTest::addRow("with-queryfragment") << "posix:/foo?bar#baz" << valid;
|
||||
QTest::addRow("with-fragmentquery") << "posix:/foo#bar?baz" << valid;
|
||||
|
||||
// add some ones that won't parse well
|
||||
QTest::addRow("positive-number") << "81" << invalid;
|
||||
QTest::addRow("negative-number") << "-81" << invalid;
|
||||
QTest::addRow("invalidprefix") << "invalidprefix:" << invalid;
|
||||
QTest::addRow("systemv-nodash") << "systemv255" << invalid;
|
||||
QTest::addRow("systemv-doubledash") << "systemv--255:" << invalid;
|
||||
QTest::addRow("systemv-plus") << "systemv+255" << invalid;
|
||||
QTest::addRow("systemv-hex") << "systemv-0x01:" << invalid;
|
||||
QTest::addRow("systemv-too-low") << "systemv-0:" << invalid;
|
||||
QTest::addRow("systemv-too-high") << "systemv-256" << invalid;
|
||||
QTest::addRow("systemv-overflow-15bit") << "systemv-32769:" << invalid;
|
||||
QTest::addRow("systemv-overflow-16bit") << "systemv-65537:" << invalid;
|
||||
QTest::addRow("systemv-overflow-31bit") << "systemv-2147483649:" << invalid;
|
||||
QTest::addRow("systemv-overflow-32bit") << "systemv-4294967297:" << invalid;
|
||||
|
||||
auto addRows = [=](const char *name) {
|
||||
QTest::addRow("%s-nocolon", name) << name << invalid;
|
||||
QTest::addRow("%s-junk", name) << name + u"junk"_s << invalid;
|
||||
QTest::addRow("junk-%s-colon", name) << u"junk:"_s + name + u':' << invalid;
|
||||
};
|
||||
addRows("systemv");
|
||||
addRows("posix");
|
||||
addRows("windows");
|
||||
addRows("systemv-1");
|
||||
addRows("systemv-255");
|
||||
}
|
||||
|
||||
void tst_QNativeIpcKey::fromString()
|
||||
{
|
||||
QFETCH(QString, string);
|
||||
QFETCH(QNativeIpcKey, key);
|
||||
|
||||
QCOMPARE(QNativeIpcKey::fromString(string), key);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QNativeIpcKey)
|
||||
#include "tst_qnativeipckey.moc"
|
Loading…
Reference in New Issue
Block a user