1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qsharedmemory.h" |
5 | #include "qsharedmemory_p.h" |
6 | |
7 | #include "qtipccommon_p.h" |
8 | |
9 | #include <qdir.h> |
10 | #include <qdebug.h> |
11 | |
12 | #include <errno.h> |
13 | |
14 | #if QT_CONFIG(sharedmemory) |
15 | #if QT_CONFIG(sysv_shm) |
16 | #include <sys/types.h> |
17 | #include <sys/ipc.h> |
18 | #include <sys/mman.h> |
19 | #include <sys/shm.h> |
20 | #include <sys/stat.h> |
21 | #include <fcntl.h> |
22 | #include <unistd.h> |
23 | |
24 | #include "private/qcore_unix_p.h" |
25 | #if defined(Q_OS_DARWIN) |
26 | #include "private/qcore_mac_p.h" |
27 | #endif |
28 | |
29 | QT_BEGIN_NAMESPACE |
30 | |
31 | using namespace Qt::StringLiterals; |
32 | using namespace QtIpcCommon; |
33 | |
34 | bool QSharedMemorySystemV::runtimeSupportCheck() |
35 | { |
36 | #if defined(Q_OS_DARWIN) |
37 | if (qt_apple_isSandboxed()) |
38 | return false; |
39 | #endif |
40 | static const bool result = []() { |
41 | (void)shmget(IPC_PRIVATE, size: ~size_t(0), shmflg: 0); // this will fail |
42 | return errno != ENOSYS; |
43 | }(); |
44 | return result; |
45 | } |
46 | |
47 | |
48 | inline void QSharedMemorySystemV::updateNativeKeyFile(const QNativeIpcKey &nativeKey) |
49 | { |
50 | Q_ASSERT(nativeKeyFile.isEmpty() ); |
51 | if (!nativeKey.nativeKey().isEmpty()) |
52 | nativeKeyFile = QFile::encodeName(fileName: nativeKey.nativeKey()); |
53 | } |
54 | |
55 | /*! |
56 | \internal |
57 | |
58 | If not already made create the handle used for accessing the shared memory. |
59 | */ |
60 | key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self) |
61 | { |
62 | // already made |
63 | if (unix_key) |
64 | return unix_key; |
65 | |
66 | // don't allow making handles on empty keys |
67 | if (nativeKeyFile.isEmpty()) |
68 | updateNativeKeyFile(nativeKey: self->nativeKey); |
69 | if (nativeKeyFile.isEmpty()) { |
70 | self->setError(e: QSharedMemory::KeyError, |
71 | message: QSharedMemory::tr(s: "%1: key is empty" ) |
72 | .arg(a: "QSharedMemory::handle:"_L1 )); |
73 | return 0; |
74 | } |
75 | |
76 | unix_key = ftok(pathname: nativeKeyFile, proj_id: int(self->nativeKey.type())); |
77 | if (unix_key < 0) { |
78 | self->setUnixErrorString("QSharedMemory::handle"_L1 ); |
79 | nativeKeyFile.clear(); |
80 | unix_key = 0; |
81 | } |
82 | return unix_key; |
83 | } |
84 | |
85 | bool QSharedMemorySystemV::cleanHandle(QSharedMemoryPrivate *self) |
86 | { |
87 | if (unix_key == 0) |
88 | return true; |
89 | |
90 | // Get the number of current attachments |
91 | struct shmid_ds shmid_ds; |
92 | QByteArray keyfile = std::exchange(obj&: nativeKeyFile, new_val: QByteArray()); |
93 | |
94 | int id = shmget(key: unix_key, size: 0, shmflg: 0400); |
95 | unix_key = 0; |
96 | if (shmctl(shmid: id, IPC_STAT, buf: &shmid_ds)) |
97 | return errno != EINVAL; |
98 | |
99 | // If there are still attachments, keep the keep file and shm |
100 | if (shmid_ds.shm_nattch != 0) |
101 | return true; |
102 | |
103 | if (shmctl(shmid: id, IPC_RMID, buf: &shmid_ds) < 0) { |
104 | if (errno != EINVAL) { |
105 | self->setUnixErrorString("QSharedMemory::remove"_L1 ); |
106 | return false; |
107 | } |
108 | }; |
109 | |
110 | // remove file |
111 | return unlink(name: keyfile) == 0; |
112 | } |
113 | |
114 | bool QSharedMemorySystemV::create(QSharedMemoryPrivate *self, qsizetype size) |
115 | { |
116 | // build file if needed |
117 | bool createdFile = false; |
118 | updateNativeKeyFile(nativeKey: self->nativeKey); |
119 | int built = createUnixKeyFile(fileName: nativeKeyFile); |
120 | if (built == -1) { |
121 | self->setError(e: QSharedMemory::KeyError, |
122 | message: QSharedMemory::tr(s: "%1: unable to make key" ) |
123 | .arg(a: "QSharedMemory::handle:"_L1 )); |
124 | return false; |
125 | } |
126 | if (built == 1) { |
127 | createdFile = true; |
128 | } |
129 | |
130 | // get handle |
131 | if (!handle(self)) { |
132 | if (createdFile) |
133 | unlink(name: nativeKeyFile); |
134 | return false; |
135 | } |
136 | |
137 | // create |
138 | if (-1 == shmget(key: unix_key, size: size_t(size), shmflg: 0600 | IPC_CREAT | IPC_EXCL)) { |
139 | const auto function = "QSharedMemory::create"_L1 ; |
140 | switch (errno) { |
141 | case EINVAL: |
142 | self->setError(e: QSharedMemory::InvalidSize, |
143 | message: QSharedMemory::tr(s: "%1: system-imposed size restrictions" ) |
144 | .arg(a: "QSharedMemory::handle"_L1 )); |
145 | break; |
146 | default: |
147 | self->setUnixErrorString(function); |
148 | } |
149 | if (createdFile && self->error != QSharedMemory::AlreadyExists) |
150 | unlink(name: nativeKeyFile); |
151 | return false; |
152 | } |
153 | |
154 | return true; |
155 | } |
156 | |
157 | bool QSharedMemorySystemV::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) |
158 | { |
159 | // grab the shared memory segment id |
160 | int id = shmget(key: unix_key, size: 0, shmflg: (mode == QSharedMemory::ReadOnly ? 0400 : 0600)); |
161 | if (-1 == id) { |
162 | self->setUnixErrorString("QSharedMemory::attach (shmget)"_L1 ); |
163 | unix_key = 0; |
164 | nativeKeyFile.clear(); |
165 | return false; |
166 | } |
167 | |
168 | // grab the memory |
169 | self->memory = shmat(shmid: id, shmaddr: nullptr, shmflg: (mode == QSharedMemory::ReadOnly ? SHM_RDONLY : 0)); |
170 | if (self->memory == MAP_FAILED) { |
171 | self->memory = nullptr; |
172 | self->setUnixErrorString("QSharedMemory::attach (shmat)"_L1 ); |
173 | return false; |
174 | } |
175 | |
176 | // grab the size |
177 | shmid_ds shmid_ds; |
178 | if (!shmctl(shmid: id, IPC_STAT, buf: &shmid_ds)) { |
179 | self->size = (qsizetype)shmid_ds.shm_segsz; |
180 | } else { |
181 | self->setUnixErrorString("QSharedMemory::attach (shmctl)"_L1 ); |
182 | return false; |
183 | } |
184 | |
185 | return true; |
186 | } |
187 | |
188 | bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self) |
189 | { |
190 | // detach from the memory segment |
191 | if (shmdt(shmaddr: self->memory) < 0) { |
192 | const auto function = "QSharedMemory::detach"_L1 ; |
193 | switch (errno) { |
194 | case EINVAL: |
195 | self->setError(e: QSharedMemory::NotFound, |
196 | message: QSharedMemory::tr(s: "%1: not attached" ).arg(a: function)); |
197 | break; |
198 | default: |
199 | self->setUnixErrorString(function); |
200 | } |
201 | return false; |
202 | } |
203 | self->memory = nullptr; |
204 | self->size = 0; |
205 | |
206 | return cleanHandle(self); |
207 | } |
208 | |
209 | QT_END_NAMESPACE |
210 | |
211 | #endif // QT_CONFIG(sysv_shm) |
212 | #endif // QT_CONFIG(sharedmemory) |
213 | |