| 1 | // Copyright (C) 2015 Konstantin Ritt <ritt.ks@gmail.com> | 
| 2 | // Copyright (C) 2016 The Qt Company Ltd. | 
| 3 | // Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias Koenig <tobias.koenig@kdab.com> | 
| 4 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 5 |  | 
| 6 | #include "qsharedmemory.h" | 
| 7 | #include "qsharedmemory_p.h" | 
| 8 | #include "qtipccommon_p.h" | 
| 9 | #include <qfile.h> | 
| 10 |  | 
| 11 | #include <errno.h> | 
| 12 |  | 
| 13 | #if QT_CONFIG(sharedmemory) | 
| 14 | #if QT_CONFIG(posix_shm) | 
| 15 | #include <sys/types.h> | 
| 16 | #include <sys/mman.h> | 
| 17 | #include <sys/stat.h> | 
| 18 | #include <fcntl.h> | 
| 19 | #include <unistd.h> | 
| 20 |  | 
| 21 | #include "private/qcore_unix_p.h" | 
| 22 |  | 
| 23 | #ifndef O_CLOEXEC | 
| 24 | #  define O_CLOEXEC 0 | 
| 25 | #endif | 
| 26 |  | 
| 27 | QT_BEGIN_NAMESPACE | 
| 28 |  | 
| 29 | using namespace Qt::StringLiterals; | 
| 30 | using namespace QtIpcCommon; | 
| 31 |  | 
| 32 | bool QSharedMemoryPosix::runtimeSupportCheck() | 
| 33 | { | 
| 34 |     static const bool result = []() { | 
| 35 |         (void)shm_open(name: "" , oflag: 0, mode: 0);         // this WILL fail | 
| 36 |         return errno != ENOSYS; | 
| 37 |     }(); | 
| 38 |     return result; | 
| 39 | } | 
| 40 |  | 
| 41 | bool QSharedMemoryPosix::handle(QSharedMemoryPrivate *self) | 
| 42 | { | 
| 43 |     // don't allow making handles on empty keys | 
| 44 |     if (self->nativeKey.isEmpty()) { | 
| 45 |         self->setError(e: QSharedMemory::KeyError, | 
| 46 |                        message: QSharedMemory::tr(s: "%1: key is empty" ).arg(a: "QSharedMemory::handle"_L1 )); | 
| 47 |         return false; | 
| 48 |     } | 
| 49 |  | 
| 50 |     return true; | 
| 51 | } | 
| 52 |  | 
| 53 | bool QSharedMemoryPosix::cleanHandle(QSharedMemoryPrivate *) | 
| 54 | { | 
| 55 |     if (hand != -1) | 
| 56 |         qt_safe_close(fd: hand); | 
| 57 |     hand = -1; | 
| 58 |  | 
| 59 |     return true; | 
| 60 | } | 
| 61 |  | 
| 62 | bool QSharedMemoryPosix::create(QSharedMemoryPrivate *self, qsizetype size) | 
| 63 | { | 
| 64 |     if (!handle(self)) | 
| 65 |         return false; | 
| 66 |  | 
| 67 |     const QByteArray shmName = QFile::encodeName(fileName: self->nativeKey.nativeKey()); | 
| 68 |  | 
| 69 |     int fd; | 
| 70 |     QT_EINTR_LOOP(fd, ::shm_open(shmName.constData(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600)); | 
| 71 |     if (fd == -1) { | 
| 72 |         const int errorNumber = errno; | 
| 73 |         const auto function = "QSharedMemory::attach (shm_open)"_L1 ; | 
| 74 |         switch (errorNumber) { | 
| 75 |         case EINVAL: | 
| 76 |             self->setError(e: QSharedMemory::KeyError, | 
| 77 |                            message: QSharedMemory::tr(s: "%1: bad name" ).arg(a: function)); | 
| 78 |             break; | 
| 79 |         default: | 
| 80 |             self->setUnixErrorString(function); | 
| 81 |         } | 
| 82 |         return false; | 
| 83 |     } | 
| 84 |  | 
| 85 |     // the size may only be set once | 
| 86 |     int ret; | 
| 87 |     QT_EINTR_LOOP(ret, QT_FTRUNCATE(fd, size)); | 
| 88 |     if (ret == -1) { | 
| 89 |         self->setUnixErrorString("QSharedMemory::create (ftruncate)"_L1 ); | 
| 90 |         qt_safe_close(fd); | 
| 91 |         return false; | 
| 92 |     } | 
| 93 |  | 
| 94 |     qt_safe_close(fd); | 
| 95 |  | 
| 96 |     return true; | 
| 97 | } | 
| 98 |  | 
| 99 | bool QSharedMemoryPosix::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) | 
| 100 | { | 
| 101 |     const QByteArray shmName = QFile::encodeName(fileName: self->nativeKey.nativeKey()); | 
| 102 |  | 
| 103 |     const int oflag = (mode == QSharedMemory::ReadOnly ? O_RDONLY : O_RDWR); | 
| 104 |     const mode_t omode = (mode == QSharedMemory::ReadOnly ? 0400 : 0600); | 
| 105 |  | 
| 106 |     QT_EINTR_LOOP(hand, ::shm_open(shmName.constData(), oflag | O_CLOEXEC, omode)); | 
| 107 |     if (hand == -1) { | 
| 108 |         const int errorNumber = errno; | 
| 109 |         const auto function = "QSharedMemory::attach (shm_open)"_L1 ; | 
| 110 |         switch (errorNumber) { | 
| 111 |         case EINVAL: | 
| 112 |             self->setError(e: QSharedMemory::KeyError, | 
| 113 |                            message: QSharedMemory::tr(s: "%1: bad name" ).arg(a: function)); | 
| 114 |             break; | 
| 115 |         default: | 
| 116 |             self->setUnixErrorString(function); | 
| 117 |         } | 
| 118 |         hand = -1; | 
| 119 |         return false; | 
| 120 |     } | 
| 121 |  | 
| 122 |     // grab the size | 
| 123 |     QT_STATBUF st; | 
| 124 |     if (QT_FSTAT(fd: hand, buf: &st) == -1) { | 
| 125 |         self->setUnixErrorString("QSharedMemory::attach (fstat)"_L1 ); | 
| 126 |         cleanHandle(self); | 
| 127 |         return false; | 
| 128 |     } | 
| 129 |     self->size = qsizetype(st.st_size); | 
| 130 |  | 
| 131 |     // grab the memory | 
| 132 |     const int mprot = (mode == QSharedMemory::ReadOnly ? PROT_READ : PROT_READ | PROT_WRITE); | 
| 133 |     self->memory = QT_MMAP(addr: 0, len: size_t(self->size), prot: mprot, MAP_SHARED, fd: hand, offset: 0); | 
| 134 |     if (self->memory == MAP_FAILED || !self->memory) { | 
| 135 |         self->setUnixErrorString("QSharedMemory::attach (mmap)"_L1 ); | 
| 136 |         cleanHandle(self); | 
| 137 |         self->memory = 0; | 
| 138 |         self->size = 0; | 
| 139 |         return false; | 
| 140 |     } | 
| 141 |  | 
| 142 | #ifdef F_ADD_SEALS | 
| 143 |     // Make sure the shared memory region will not shrink | 
| 144 |     // otherwise someone could cause SIGBUS on us. | 
| 145 |     // (see http://lwn.net/Articles/594919/) | 
| 146 |     fcntl(fd: hand, F_ADD_SEALS, F_SEAL_SHRINK); | 
| 147 | #endif | 
| 148 |  | 
| 149 |     return true; | 
| 150 | } | 
| 151 |  | 
| 152 | bool QSharedMemoryPosix::detach(QSharedMemoryPrivate *self) | 
| 153 | { | 
| 154 |     // detach from the memory segment | 
| 155 |     if (::munmap(addr: self->memory, len: size_t(self->size)) == -1) { | 
| 156 |         self->setUnixErrorString("QSharedMemory::detach (munmap)"_L1 ); | 
| 157 |         return false; | 
| 158 |     } | 
| 159 |     self->memory = 0; | 
| 160 |     self->size = 0; | 
| 161 |  | 
| 162 | #ifdef Q_OS_QNX | 
| 163 |     // On QNX the st_nlink field of struct stat contains the number of | 
| 164 |     // active shm_open() connections to the shared memory file, so we | 
| 165 |     // can use it to automatically clean up the file once the last | 
| 166 |     // user has detached from it. | 
| 167 |  | 
| 168 |     // get the number of current attachments | 
| 169 |     int shm_nattch = 0; | 
| 170 |     QT_STATBUF st; | 
| 171 |     if (QT_FSTAT(hand, &st) == 0) { | 
| 172 |         // subtract 2 from linkcount: one for our own open and one for the dir entry | 
| 173 |         shm_nattch = st.st_nlink - 2; | 
| 174 |     } | 
| 175 |  | 
| 176 |     cleanHandle(self); | 
| 177 |  | 
| 178 |     // if there are no attachments then unlink the shared memory | 
| 179 |     if (shm_nattch == 0) { | 
| 180 |         const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey()); | 
| 181 |         if (::shm_unlink(shmName.constData()) == -1 && errno != ENOENT) | 
| 182 |             self->setUnixErrorString("QSharedMemory::detach (shm_unlink)"_L1 ); | 
| 183 |     } | 
| 184 | #else | 
| 185 |     // On non-QNX systems (tested Linux and Haiku), the st_nlink field is always 1, | 
| 186 |     // so we'll simply leak the shared memory files. | 
| 187 |     cleanHandle(self); | 
| 188 | #endif | 
| 189 |  | 
| 190 |     return true; | 
| 191 | } | 
| 192 |  | 
| 193 | QT_END_NAMESPACE | 
| 194 |  | 
| 195 | #endif // QT_CONFIG(posix_shm) | 
| 196 | #endif // QT_CONFIG(sharedmemory) | 
| 197 |  |