Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / ext / vsdebug / socket_transport.cpp
blob12fde7760d36d989e22d43ad742264b8a881b837
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2017-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/ext/vsdebug/socket_transport.h"
18 #include "hphp/runtime/ext/vsdebug/debugger.h"
20 namespace HPHP {
21 namespace VSDEBUG {
23 SocketTransport::SocketTransport(Debugger* debugger, int listenPort) :
24 DebugTransport(debugger),
25 m_terminating(false),
26 m_clientConnected(false),
27 m_listenPort(listenPort),
28 m_connectThread(this, &SocketTransport::listenForClientConnection) {
30 Lock lock(m_lock);
32 assertx(m_abortPipeFd[0] == -1 && m_abortPipeFd[1] == -1);
34 // Create a set of pipe file descriptors to use to inform the thread
35 // polling for socket connections that it's time to exit.
36 createAbortPipe();
37 m_connectThread.start();
40 SocketTransport::~SocketTransport() {
42 Lock lock(m_lock);
43 m_terminating = true;
46 stopConnectionThread();
49 void SocketTransport::stopConnectionThread() {
50 char value = '\0';
51 write(m_abortPipeFd[1], &value, 1);
52 close(m_abortPipeFd[1]);
53 m_abortPipeFd[1] = -1;
55 m_connectThread.waitForEnd();
58 void SocketTransport::createAbortPipe() {
60 if (m_abortPipeFd[0] != -1) {
61 close(m_abortPipeFd[0]);
64 if (m_abortPipeFd[1] != -1) {
65 close(m_abortPipeFd[1]);
68 if (pipe(m_abortPipeFd) < 0) {
69 VSDebugLogger::Log(
70 VSDebugLogger::LogLevelError,
71 "Failed to open pipe for transport termination event."
74 // This is unexpected and treated as fatal because we won't be able
75 // to stop the polling thread in an orderly fashion at this point.
76 assertx(false);
80 void SocketTransport::onClientDisconnected() {
81 VSDebugLogger::Log(
82 VSDebugLogger::LogLevelInfo,
83 "SocketTransport client disconnected."
86 DebugTransport::onClientDisconnected();
88 // If we're not shutting down, start listening for new connections.
90 Lock lock(m_lock);
92 if (!m_terminating && m_clientConnected) {
93 stopConnectionThread();
94 m_clientConnected = false;
95 createAbortPipe();
96 m_connectThread.start();
101 bool SocketTransport::clientConnected() const {
102 Lock lock(m_lock);
103 return m_clientConnected;
106 void SocketTransport::listenForClientConnection() {
107 // If there was a previous connection in the base class, shut it down.
108 // This will close the old transport and wait for both the send and receive
109 // worker threads to terminate before proceeding.
110 if (getTransportFd() >= 0) {
111 shutdown();
114 VSDebugLogger::Log(
115 VSDebugLogger::LogLevelInfo,
116 "SocketTransport worker thread listening for connections..."
119 int abortFd;
121 Lock lock(m_lock);
122 abortFd = m_abortPipeFd[0];
123 assertx(abortFd >= 0);
126 struct addrinfo hint;
127 struct addrinfo* ai = nullptr;
128 std::vector<int> socketFds;
130 memset(&hint, 0, sizeof(hint));
131 hint.ai_family = AF_UNSPEC;
132 hint.ai_socktype = SOCK_STREAM;
133 hint.ai_flags = AI_PASSIVE;
135 SCOPE_EXIT {
136 if (ai != nullptr) {
137 freeaddrinfo(ai);
140 for (auto it = socketFds.begin(); it != socketFds.end(); it++) {
141 close(*it);
144 close(abortFd);
145 m_abortPipeFd[0] = -1;
147 VSDebugLogger::Log(
148 VSDebugLogger::LogLevelInfo,
149 "SocketTransport connection polling thread exiting."
153 // Share existing DebuggerDisableIPv6 configuration with hphpd.
154 if (RuntimeOption::DebuggerDisableIPv6) {
155 hint.ai_family = AF_INET;
158 const auto name = RuntimeOption::DebuggerServerIP.empty()
159 ? "localhost"
160 : RuntimeOption::DebuggerServerIP.c_str();
161 if (getaddrinfo(name, std::to_string(m_listenPort).c_str(), &hint, &ai)) {
162 VSDebugLogger::Log(
163 VSDebugLogger::LogLevelError,
164 "Failed to call getaddrinfo: %d.",
165 errno
168 return;
171 // Attempt to listen on the specified port on each of this host's available
172 // addresses.
173 struct addrinfo* address;
174 bool anyInterfaceBound = false;
175 for (address = ai; address != nullptr; address = address->ai_next) {
176 if (bindAndListen(address, socketFds)) {
177 anyInterfaceBound = true;
181 if (!anyInterfaceBound) {
182 VSDebugLogger::Log(
183 VSDebugLogger::LogLevelWarning,
184 "Debugger failed to bind to any interface!"
186 return;
187 } else {
188 VSDebugLogger::Log(
189 VSDebugLogger::LogLevelInfo,
190 "Debugger bound to at least one interface."
194 waitForConnection(socketFds, abortFd);
197 bool SocketTransport::bindAndListen(
198 struct addrinfo* address,
199 std::vector<int>& socketFds
201 int fd = socket(address->ai_family,
202 address->ai_socktype,
203 address->ai_protocol);
205 if (fd < 0 && errno == EAFNOSUPPORT) {
206 VSDebugLogger::Log(
207 VSDebugLogger::LogLevelWarning,
208 "SocketTransport: socket() call failed: %d",
209 errno
212 // Don't bind to this address, but still try to process other
213 // addresses if there are any.
214 return true;
217 constexpr int yes = 1;
218 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
219 if (address->ai_family == AF_INET6) {
220 setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes));
223 socketFds.push_back(fd);
225 if (bind(fd, address->ai_addr, address->ai_addrlen) < 0) {
226 VSDebugLogger::Log(
227 VSDebugLogger::LogLevelError,
228 "Failed to call bind: %d. (%s)",
229 errno,
230 strerror(errno)
233 return false;
236 if (listen(fd, 1) < 0) {
237 VSDebugLogger::Log(
238 VSDebugLogger::LogLevelError,
239 "Failed to call listen on port %u: %d. (%s)",
240 m_listenPort,
241 errno,
242 strerror(errno)
245 return false;
248 return true;
251 void SocketTransport::rejectClientWithMsg(int newFd, int abortFd) {
252 VSDebugLogger::Log(
253 VSDebugLogger::LogLevelInfo,
254 "SocketTransport: new client connection rejected because another "
255 "client is already connected."
258 folly::dynamic rejectMsg = folly::dynamic::object;
259 rejectMsg["category"] = OutputLevelError;
260 rejectMsg["output"] = "Could not attach to HHVM: another debugger "
261 "client is already attached!";
263 folly::dynamic response = folly::dynamic::object;
264 response["event"] = EventTypeOutput;
265 response["body"] = rejectMsg;
266 response["type"] = MessageTypeEvent;
268 // Send the user an error message.
269 std::string serialized = folly::toJson(response);
270 const char* output = serialized.c_str();
271 write(newFd, output, strlen(output) + 1);
273 // Perform an orderly shutdown of the socket so that the message is actually
274 // sent and received. This requires us to shutdown the write end of the socket
275 // and then drain the receive buffer before closing the socket. If abortFd
276 // is signalled before this is complete, we just close the socket and bail.
277 ::shutdown(newFd, SHUT_WR);
279 int result;
280 size_t size = sizeof(struct pollfd) * 2;
281 struct pollfd* fds = (struct pollfd*)malloc(size);
282 char* buffer = (char*)malloc(1024);
283 SCOPE_EXIT {
284 if (fds != nullptr) {
285 free(fds);
288 close(newFd);
290 if (buffer != nullptr) {
291 free(buffer);
295 if (buffer == nullptr || fds == nullptr) {
296 return;
299 int eventMask = POLLIN | POLLERR | POLLHUP;
300 constexpr int abortIdx = 0;
301 constexpr int readIdx = 1;
303 memset(fds, 0, size);
304 fds[abortIdx].fd = abortFd;
305 fds[abortIdx].events = eventMask;
306 fds[readIdx].fd = newFd;
307 fds[readIdx].events = eventMask;
309 while (true) {
310 result = poll(fds, 2, -1);
311 if (result == -EINTR) {
312 continue;
313 } else if (result < 0 ||
314 fds[abortIdx].revents != 0 ||
315 fds[readIdx].revents & POLLERR ||
316 fds[readIdx].revents & POLLHUP
319 break;
320 } else if (fds[readIdx].revents & POLLIN) {
321 result = read(newFd, buffer, 1024);
322 if (result <= 0) {
323 break;
329 void SocketTransport::waitForConnection(
330 std::vector<int>& socketFds,
331 int abortFd
333 // Wait for a connection on any of the fds we are listening to. We allow only
334 // one debugger client to connect a a time, so once any bound fd accepts a
335 // connection, we stop listening on all the others.
336 int count = socketFds.size() + 1;
337 size_t size = sizeof(struct pollfd) * count;
338 struct pollfd* fds = (struct pollfd*)malloc(size);
339 if (fds == nullptr) {
340 VSDebugLogger::Log(
341 VSDebugLogger::LogLevelError,
342 "SocketTransport out of memory while trying to create pollfd."
344 return;
347 memset(fds, 0, size);
349 SCOPE_EXIT {
350 if (fds != nullptr) {
351 free(fds);
355 // fds[0] will contain the read end of our "abort" pipe. Another thread will
356 // write data to tihs pipe to signal it's time for this worker to stop
357 // blocking in poll() and terminate.
358 int eventMask = POLLIN | POLLERR | POLLHUP;
359 fds[0].fd = abortFd;
360 fds[0].events = eventMask;
362 for (unsigned int i = 1; i < count; i++) {
363 fds[i].fd = socketFds[i - 1];
364 fds[i].events = eventMask;
367 // Poll socket fds until a connection is established or we're terminated.
368 while (true) {
369 VSDebugLogger::Log(
370 VSDebugLogger::LogLevelInfo,
371 "SocketTransport polling for connections..."
374 int ret = poll(fds, count, -1);
375 if (ret < 0) {
376 if (ret == -EINTR) {
377 continue;
380 VSDebugLogger::Log(
381 VSDebugLogger::LogLevelError,
382 "Polling inputs failed: %d. (%s)",
383 errno,
384 strerror(errno)
386 return;
389 if (fds[0].revents != 0) {
390 VSDebugLogger::Log(
391 VSDebugLogger::LogLevelInfo,
392 "Socket polling thread terminating due to shutdown request."
394 return;
397 struct sockaddr sa;
398 socklen_t len = sizeof(sa);
399 for (unsigned int i = 1; i < count; i++) {
400 if (fds[i].revents & POLLIN) {
401 int newFd = ::accept(fds[i].fd, &sa, &len);
402 if (newFd < 0) {
403 VSDebugLogger::Log(
404 VSDebugLogger::LogLevelWarning,
405 "Accept returned an error: %d. (%s)",
406 errno,
407 strerror(errno)
409 } else {
410 Lock lock(m_lock);
412 if (m_clientConnected) {
413 // A client is already connected!
414 m_lock.unlock();
415 rejectClientWithMsg(newFd, abortFd);
416 m_lock.lock();
417 } else {
418 VSDebugLogger::Log(
419 VSDebugLogger::LogLevelInfo,
420 "SocketTransport: new client connection accepted."
423 // We have established a connection with a client.
424 m_clientConnected = true;
425 setTransportFd(newFd);
426 m_debugger->setClientConnected(true);
431 // Reset the event flags.
432 fds[i].revents = 0;