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 | unix_key = 0; |
80 | } |
81 | return unix_key; |
82 | } |
83 | |
84 | bool QSharedMemorySystemV::cleanHandle(QSharedMemoryPrivate *self) |
85 | { |
86 | if (unix_key == 0) |
87 | return true; |
88 | |
89 | // Get the number of current attachments |
90 | struct shmid_ds shmid_ds; |
91 | QByteArray keyfile = std::exchange(obj&: nativeKeyFile, new_val: QByteArray()); |
92 | |
93 | int id = shmget(key: unix_key, size: 0, shmflg: 0400); |
94 | unix_key = 0; |
95 | if (shmctl(shmid: id, IPC_STAT, buf: &shmid_ds)) |
96 | return errno != EINVAL; |
97 | |
98 | // If there are still attachments, keep the keep file and shm |
99 | if (shmid_ds.shm_nattch != 0) |
100 | return true; |
101 | |
102 | if (shmctl(shmid: id, IPC_RMID, buf: &shmid_ds) < 0) { |
103 | if (errno != EINVAL) { |
104 | self->setUnixErrorString("QSharedMemory::remove"_L1 ); |
105 | return false; |
106 | } |
107 | }; |
108 | |
109 | // remove file |
110 | return unlink(name: keyfile) == 0; |
111 | } |
112 | |
113 | bool QSharedMemorySystemV::create(QSharedMemoryPrivate *self, qsizetype size) |
114 | { |
115 | // build file if needed |
116 | bool createdFile = false; |
117 | updateNativeKeyFile(nativeKey: self->nativeKey); |
118 | int built = createUnixKeyFile(fileName: nativeKeyFile); |
119 | if (built == -1) { |
120 | self->setError(e: QSharedMemory::KeyError, |
121 | message: QSharedMemory::tr(s: "%1: unable to make key" ) |
122 | .arg(a: "QSharedMemory::handle:"_L1 )); |
123 | return false; |
124 | } |
125 | if (built == 1) { |
126 | createdFile = true; |
127 | } |
128 | |
129 | // get handle |
130 | if (!handle(self)) { |
131 | if (createdFile) |
132 | unlink(name: nativeKeyFile); |
133 | return false; |
134 | } |
135 | |
136 | // create |
137 | if (-1 == shmget(key: unix_key, size: size_t(size), shmflg: 0600 | IPC_CREAT | IPC_EXCL)) { |
138 | const auto function = "QSharedMemory::create"_L1 ; |
139 | switch (errno) { |
140 | case EINVAL: |
141 | self->setError(e: QSharedMemory::InvalidSize, |
142 | message: QSharedMemory::tr(s: "%1: system-imposed size restrictions" ) |
143 | .arg(a: "QSharedMemory::handle"_L1 )); |
144 | break; |
145 | default: |
146 | self->setUnixErrorString(function); |
147 | } |
148 | if (createdFile && self->error != QSharedMemory::AlreadyExists) |
149 | unlink(name: nativeKeyFile); |
150 | return false; |
151 | } |
152 | |
153 | return true; |
154 | } |
155 | |
156 | bool QSharedMemorySystemV::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) |
157 | { |
158 | // grab the shared memory segment id |
159 | int id = shmget(key: unix_key, size: 0, shmflg: (mode == QSharedMemory::ReadOnly ? 0400 : 0600)); |
160 | if (-1 == id) { |
161 | self->setUnixErrorString("QSharedMemory::attach (shmget)"_L1 ); |
162 | return false; |
163 | } |
164 | |
165 | // grab the memory |
166 | self->memory = shmat(shmid: id, shmaddr: nullptr, shmflg: (mode == QSharedMemory::ReadOnly ? SHM_RDONLY : 0)); |
167 | if (self->memory == MAP_FAILED) { |
168 | self->memory = nullptr; |
169 | self->setUnixErrorString("QSharedMemory::attach (shmat)"_L1 ); |
170 | return false; |
171 | } |
172 | |
173 | // grab the size |
174 | shmid_ds shmid_ds; |
175 | if (!shmctl(shmid: id, IPC_STAT, buf: &shmid_ds)) { |
176 | self->size = (qsizetype)shmid_ds.shm_segsz; |
177 | } else { |
178 | self->setUnixErrorString("QSharedMemory::attach (shmctl)"_L1 ); |
179 | return false; |
180 | } |
181 | |
182 | return true; |
183 | } |
184 | |
185 | bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self) |
186 | { |
187 | // detach from the memory segment |
188 | if (shmdt(shmaddr: self->memory) < 0) { |
189 | const auto function = "QSharedMemory::detach"_L1 ; |
190 | switch (errno) { |
191 | case EINVAL: |
192 | self->setError(e: QSharedMemory::NotFound, |
193 | message: QSharedMemory::tr(s: "%1: not attached" ).arg(a: function)); |
194 | break; |
195 | default: |
196 | self->setUnixErrorString(function); |
197 | } |
198 | return false; |
199 | } |
200 | self->memory = nullptr; |
201 | self->size = 0; |
202 | |
203 | return cleanHandle(self); |
204 | } |
205 | |
206 | QT_END_NAMESPACE |
207 | |
208 | #endif // QT_CONFIG(sysv_shm) |
209 | #endif // QT_CONFIG(sharedmemory) |
210 | |