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
25QT_BEGIN_NAMESPACE
26
27using namespace Qt::StringLiterals;
28
29namespace {
30QLocalServer::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
46void QLocalServerPrivate::init()
47{
48}
49
50bool 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
65bool 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 extraCharacters = 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
193bool 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 */
233void 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 */
260void 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
279void 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
305void 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
338QT_END_NAMESPACE
339

source code of qtbase/src/network/socket/qlocalserver_unix.cpp