diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 19393c5d7e..ecf237f71a 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -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 diff --git a/src/corelib/doc/src/ipc.qdoc b/src/corelib/doc/src/ipc.qdoc new file mode 100644 index 0000000000..e78f412f78 --- /dev/null +++ b/src/corelib/doc/src/ipc.qdoc @@ -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 {/}. 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. +*/ diff --git a/src/corelib/doc/src/qtcore-index.qdoc b/src/corelib/doc/src/qtcore-index.qdoc index 99cc114d64..48f7765568 100644 --- a/src/corelib/doc/src/qtcore-index.qdoc +++ b/src/corelib/doc/src/qtcore-index.qdoc @@ -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 diff --git a/src/corelib/ipc/qtipccommon.cpp b/src/corelib/ipc/qtipccommon.cpp index 461f5b09c2..c29de77de4 100644 --- a/src/corelib/ipc/qtipccommon.cpp +++ b/src/corelib/ipc/qtipccommon.cpp @@ -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 #include +#include +#include #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) diff --git a/src/corelib/ipc/qtipccommon.h b/src/corelib/ipc/qtipccommon.h new file mode 100644 index 0000000000..59e8e0966c --- /dev/null +++ b/src/corelib/ipc/qtipccommon.h @@ -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 +#include + +#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) +# include +# include + +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 diff --git a/tests/auto/corelib/ipc/CMakeLists.txt b/tests/auto/corelib/ipc/CMakeLists.txt index 5b9ffae7d9..cd6f28d553 100644 --- a/tests/auto/corelib/ipc/CMakeLists.txt +++ b/tests/auto/corelib/ipc/CMakeLists.txt @@ -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() diff --git a/tests/auto/corelib/ipc/qnativeipckey/CMakeLists.txt b/tests/auto/corelib/ipc/qnativeipckey/CMakeLists.txt new file mode 100644 index 0000000000..c52b5d221a --- /dev/null +++ b/tests/auto/corelib/ipc/qnativeipckey/CMakeLists.txt @@ -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 +) diff --git a/tests/auto/corelib/ipc/qnativeipckey/tst_qnativeipckey.cpp b/tests/auto/corelib/ipc/qnativeipckey/tst_qnativeipckey.cpp new file mode 100644 index 0000000000..0f4bce8097 --- /dev/null +++ b/tests/auto/corelib/ipc/qnativeipckey/tst_qnativeipckey.cpp @@ -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 +#include + +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(""); + + 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("string"); + QTest::addColumn("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"