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:
Thiago Macieira 2022-10-07 23:09:30 -07:00
parent a467362aa8
commit 7a37083817
8 changed files with 1434 additions and 1 deletions

View File

@ -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

View 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.
*/

View File

@ -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

View File

@ -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)

View 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

View File

@ -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()

View 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
)

View 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"