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 "qlocalserver.h" |
5 | #include "qlocalserver_p.h" |
6 | #include "qlocalsocket.h" |
7 | #include "qlocalsocket_p.h" |
8 | #include "qnet_unix_p.h" |
9 | #include "qtemporarydir.h" |
10 | |
11 | #include <stddef.h> |
12 | #include <sys/socket.h> |
13 | #include <sys/un.h> |
14 | |
15 | #include <qdebug.h> |
16 | #include <qdir.h> |
17 | #include <qdatetime.h> |
18 | |
19 | #include <optional> |
20 | |
21 | #ifdef Q_OS_VXWORKS |
22 | # include <selectLib.h> |
23 | #endif |
24 | |
25 | QT_BEGIN_NAMESPACE |
26 | |
27 | using namespace Qt::StringLiterals; |
28 | |
29 | namespace { |
30 | QLocalServer::SocketOptions optionsForPlatform(QLocalServer::SocketOptions srcOptions) |
31 | { |
32 | // For OS that does not support abstract namespace the AbstractNamespaceOption |
33 | // means that we go for WorldAccessOption - as it is the closest option in |
34 | // regards of access rights. In Linux/Android case we clean-up the access rights. |
35 | |
36 | if (srcOptions.testFlag(flag: QLocalServer::AbstractNamespaceOption)) { |
37 | if (PlatformSupportsAbstractNamespace) |
38 | return QLocalServer::AbstractNamespaceOption; |
39 | else |
40 | return QLocalServer::WorldAccessOption; |
41 | } |
42 | return srcOptions; |
43 | } |
44 | } |
45 | |
46 | void QLocalServerPrivate::init() |
47 | { |
48 | } |
49 | |
50 | bool QLocalServerPrivate::removeServer(const QString &name) |
51 | { |
52 | QString fileName; |
53 | if (name.startsWith(c: u'/')) { |
54 | fileName = name; |
55 | } else { |
56 | fileName = QDir::cleanPath(path: QDir::tempPath()); |
57 | fileName += u'/' + name; |
58 | } |
59 | if (QFile::exists(fileName)) |
60 | return QFile::remove(fileName); |
61 | else |
62 | return true; |
63 | } |
64 | |
65 | bool QLocalServerPrivate::listen(const QString &requestedServerName) |
66 | { |
67 | Q_Q(QLocalServer); |
68 | |
69 | // socket options adjusted for current platform |
70 | auto options = optionsForPlatform(srcOptions: socketOptions.value()); |
71 | |
72 | // determine the full server path |
73 | if (options.testFlag(flag: QLocalServer::AbstractNamespaceOption) |
74 | || requestedServerName.startsWith(c: u'/')) { |
75 | fullServerName = requestedServerName; |
76 | } else { |
77 | fullServerName = QDir::cleanPath(path: QDir::tempPath()); |
78 | fullServerName += u'/' + requestedServerName; |
79 | } |
80 | serverName = requestedServerName; |
81 | |
82 | QByteArray encodedTempPath; |
83 | const QByteArray encodedFullServerName = QFile::encodeName(fileName: fullServerName); |
84 | std::optional<QTemporaryDir> tempDir; |
85 | |
86 | if (options & QLocalServer::WorldAccessOption) { |
87 | QFileInfo serverNameFileInfo(fullServerName); |
88 | tempDir.emplace(args: serverNameFileInfo.absolutePath() + u'/'); |
89 | if (!tempDir->isValid()) { |
90 | setError("QLocalServer::listen"_L1 ); |
91 | return false; |
92 | } |
93 | encodedTempPath = QFile::encodeName(fileName: tempDir->path() + "/s"_L1 ); |
94 | } |
95 | |
96 | // create the unix socket |
97 | listenSocket = qt_safe_socket(PF_UNIX, SOCK_STREAM, protocol: 0); |
98 | if (-1 == listenSocket) { |
99 | setError("QLocalServer::listen"_L1 ); |
100 | closeServer(); |
101 | return false; |
102 | } |
103 | |
104 | // Construct the unix address |
105 | struct ::sockaddr_un addr; |
106 | |
107 | addr.sun_family = PF_UNIX; |
108 | ::memset(s: addr.sun_path, c: 0, n: sizeof(addr.sun_path)); |
109 | |
110 | // for abstract namespace add 2 to length, to take into account trailing AND leading null |
111 | constexpr unsigned int = PlatformSupportsAbstractNamespace ? 2 : 1; |
112 | |
113 | if (sizeof(addr.sun_path) < static_cast<size_t>(encodedFullServerName.size() + extraCharacters)) { |
114 | setError("QLocalServer::listen"_L1 ); |
115 | closeServer(); |
116 | return false; |
117 | } |
118 | |
119 | QT_SOCKLEN_T addrSize = sizeof(::sockaddr_un); |
120 | if (options.testFlag(flag: QLocalServer::AbstractNamespaceOption)) { |
121 | // Abstract socket address is distinguished by the fact |
122 | // that sun_path[0] is a null byte ('\0') |
123 | ::memcpy(dest: addr.sun_path + 1, src: encodedFullServerName.constData(), |
124 | n: encodedFullServerName.size() + 1); |
125 | addrSize = offsetof(::sockaddr_un, sun_path) + encodedFullServerName.size() + 1; |
126 | } else if (options & QLocalServer::WorldAccessOption) { |
127 | if (sizeof(addr.sun_path) < static_cast<size_t>(encodedTempPath.size() + 1)) { |
128 | setError("QLocalServer::listen"_L1 ); |
129 | closeServer(); |
130 | return false; |
131 | } |
132 | ::memcpy(dest: addr.sun_path, src: encodedTempPath.constData(), |
133 | n: encodedTempPath.size() + 1); |
134 | } else { |
135 | ::memcpy(dest: addr.sun_path, src: encodedFullServerName.constData(), |
136 | n: encodedFullServerName.size() + 1); |
137 | } |
138 | |
139 | // bind |
140 | if (-1 == QT_SOCKET_BIND(fd: listenSocket, addr: (sockaddr *)&addr, len: addrSize)) { |
141 | setError("QLocalServer::listen"_L1 ); |
142 | // if address is in use already, just close the socket, but do not delete the file |
143 | if (errno == EADDRINUSE) |
144 | QT_CLOSE(fd: listenSocket); |
145 | // otherwise, close the socket and delete the file |
146 | else |
147 | closeServer(); |
148 | listenSocket = -1; |
149 | return false; |
150 | } |
151 | |
152 | // listen for connections |
153 | if (-1 == qt_safe_listen(s: listenSocket, backlog: listenBacklog)) { |
154 | setError("QLocalServer::listen"_L1 ); |
155 | closeServer(); |
156 | return false; |
157 | } |
158 | |
159 | if (options & QLocalServer::WorldAccessOption) { |
160 | mode_t mode = 000; |
161 | |
162 | if (options & QLocalServer::UserAccessOption) |
163 | mode |= S_IRWXU; |
164 | |
165 | if (options & QLocalServer::GroupAccessOption) |
166 | mode |= S_IRWXG; |
167 | |
168 | if (options & QLocalServer::OtherAccessOption) |
169 | mode |= S_IRWXO; |
170 | |
171 | if (::chmod(file: encodedTempPath.constData(), mode: mode) == -1) { |
172 | setError("QLocalServer::listen"_L1 ); |
173 | closeServer(); |
174 | return false; |
175 | } |
176 | |
177 | if (::rename(old: encodedTempPath.constData(), new: encodedFullServerName.constData()) == -1) { |
178 | setError("QLocalServer::listen"_L1 ); |
179 | closeServer(); |
180 | return false; |
181 | } |
182 | } |
183 | |
184 | Q_ASSERT(!socketNotifier); |
185 | socketNotifier = new QSocketNotifier(listenSocket, |
186 | QSocketNotifier::Read, q); |
187 | q->connect(sender: socketNotifier, SIGNAL(activated(QSocketDescriptor)), |
188 | receiver: q, SLOT(_q_onNewConnection())); |
189 | socketNotifier->setEnabled(maxPendingConnections > 0); |
190 | return true; |
191 | } |
192 | |
193 | bool QLocalServerPrivate::listen(qintptr socketDescriptor) |
194 | { |
195 | Q_Q(QLocalServer); |
196 | |
197 | // Attach to the localsocket |
198 | listenSocket = socketDescriptor; |
199 | |
200 | ::fcntl(fd: listenSocket, F_SETFD, FD_CLOEXEC); |
201 | ::fcntl(fd: listenSocket, F_SETFL, ::fcntl(fd: listenSocket, F_GETFL) | O_NONBLOCK); |
202 | |
203 | bool abstractAddress = false; |
204 | struct ::sockaddr_un addr; |
205 | QT_SOCKLEN_T len = sizeof(addr); |
206 | memset(s: &addr, c: 0, n: sizeof(addr)); |
207 | if (::getsockname(fd: socketDescriptor, addr: (sockaddr *)&addr, len: &len) == 0) { |
208 | #if defined(Q_OS_QNX) |
209 | if (addr.sun_path[0] == 0 && addr.sun_path[1] == 0) |
210 | len = SUN_LEN(&addr); |
211 | #endif |
212 | if (QLocalSocketPrivate::parseSockaddr(addr, len, fullServerName, serverName, |
213 | abstractNamespace&: abstractAddress)) { |
214 | QLocalServer::SocketOptions options = socketOptions.value(); |
215 | socketOptions = options.setFlag(flag: QLocalServer::AbstractNamespaceOption, on: abstractAddress); |
216 | } |
217 | } |
218 | |
219 | Q_ASSERT(!socketNotifier); |
220 | socketNotifier = new QSocketNotifier(listenSocket, |
221 | QSocketNotifier::Read, q); |
222 | q->connect(sender: socketNotifier, SIGNAL(activated(QSocketDescriptor)), |
223 | receiver: q, SLOT(_q_onNewConnection())); |
224 | socketNotifier->setEnabled(maxPendingConnections > 0); |
225 | return true; |
226 | } |
227 | |
228 | /*! |
229 | \internal |
230 | |
231 | \sa QLocalServer::closeServer() |
232 | */ |
233 | void QLocalServerPrivate::closeServer() |
234 | { |
235 | if (socketNotifier) { |
236 | socketNotifier->setEnabled(false); // Otherwise, closed socket is checked before deleter runs |
237 | socketNotifier->deleteLater(); |
238 | socketNotifier = nullptr; |
239 | } |
240 | |
241 | if (-1 != listenSocket) |
242 | QT_CLOSE(fd: listenSocket); |
243 | listenSocket = -1; |
244 | |
245 | if (!fullServerName.isEmpty() |
246 | && !optionsForPlatform(srcOptions: socketOptions).testFlag(flag: QLocalServer::AbstractNamespaceOption)) { |
247 | QFile::remove(fileName: fullServerName); |
248 | } |
249 | |
250 | serverName.clear(); |
251 | fullServerName.clear(); |
252 | } |
253 | |
254 | /*! |
255 | \internal |
256 | |
257 | We have received a notification that we can read on the listen socket. |
258 | Accept the new socket. |
259 | */ |
260 | void QLocalServerPrivate::_q_onNewConnection() |
261 | { |
262 | Q_Q(QLocalServer); |
263 | if (-1 == listenSocket) |
264 | return; |
265 | |
266 | ::sockaddr_un addr; |
267 | QT_SOCKLEN_T length = sizeof(sockaddr_un); |
268 | int connectedSocket = qt_safe_accept(s: listenSocket, addr: (sockaddr *)&addr, addrlen: &length); |
269 | if (-1 == connectedSocket) { |
270 | setError("QLocalSocket::activated"_L1 ); |
271 | closeServer(); |
272 | } else { |
273 | socketNotifier->setEnabled(pendingConnections.size() |
274 | <= maxPendingConnections); |
275 | q->incomingConnection(socketDescriptor: connectedSocket); |
276 | } |
277 | } |
278 | |
279 | void QLocalServerPrivate::waitForNewConnection(int msec, bool *timedOut) |
280 | { |
281 | pollfd pfd = qt_make_pollfd(fd: listenSocket, POLLIN); |
282 | |
283 | switch (qt_poll_msecs(fds: &pfd, nfds: 1, timeout: msec)) { |
284 | case 0: |
285 | if (timedOut) |
286 | *timedOut = true; |
287 | |
288 | return; |
289 | break; |
290 | default: |
291 | if ((pfd.revents & POLLNVAL) == 0) { |
292 | _q_onNewConnection(); |
293 | return; |
294 | } |
295 | |
296 | errno = EBADF; |
297 | Q_FALLTHROUGH(); |
298 | case -1: |
299 | setError("QLocalServer::waitForNewConnection"_L1 ); |
300 | closeServer(); |
301 | break; |
302 | } |
303 | } |
304 | |
305 | void QLocalServerPrivate::setError(const QString &function) |
306 | { |
307 | if (EAGAIN == errno) |
308 | return; |
309 | |
310 | switch (errno) { |
311 | case EACCES: |
312 | errorString = QLocalServer::tr(s: "%1: Permission denied" ).arg(a: function); |
313 | error = QAbstractSocket::SocketAccessError; |
314 | break; |
315 | case ELOOP: |
316 | case ENOENT: |
317 | case ENAMETOOLONG: |
318 | case EROFS: |
319 | case ENOTDIR: |
320 | errorString = QLocalServer::tr(s: "%1: Name error" ).arg(a: function); |
321 | error = QAbstractSocket::HostNotFoundError; |
322 | break; |
323 | case EADDRINUSE: |
324 | errorString = QLocalServer::tr(s: "%1: Address in use" ).arg(a: function); |
325 | error = QAbstractSocket::AddressInUseError; |
326 | break; |
327 | |
328 | default: |
329 | errorString = QLocalServer::tr(s: "%1: Unknown error %2" ) |
330 | .arg(a: function).arg(errno); |
331 | error = QAbstractSocket::UnknownSocketError; |
332 | #if defined QLOCALSERVER_DEBUG |
333 | qWarning() << errorString << "fullServerName:" << fullServerName; |
334 | #endif |
335 | } |
336 | } |
337 | |
338 | QT_END_NAMESPACE |
339 | |