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
29QT_BEGIN_NAMESPACE
30
31using namespace Qt::StringLiterals;
32using namespace QtIpcCommon;
33
34bool 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
48inline 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*/
60key_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
84bool 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
113bool 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
156bool 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
185bool 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
206QT_END_NAMESPACE
207
208#endif // QT_CONFIG(sysv_shm)
209#endif // QT_CONFIG(sharedmemory)
210

source code of qtbase/src/corelib/ipc/qsharedmemory_systemv.cpp