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