Drop special handling for Compaq C++
[xapian.git] / xapian-core / net / tcpserver.cc
blob815813f16b0188a4d535c6085a8ef91a01dd0934
1 /* tcpserver.cc: class for TCP/IP-based server.
3 * Copyright 1999,2000,2001 BrightStation PLC
4 * Copyright 2002 Ananova Ltd
5 * Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2015,2017,2018 Olly Betts
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
23 #include <config.h>
25 #include "tcpserver.h"
27 #include <xapian/error.h>
29 #include "safefcntl.h"
30 #include "safenetdb.h"
31 #include "safesyssocket.h"
33 #include "remoteconnection.h"
34 #include "resolver.h"
35 #include "str.h"
37 #ifdef __WIN32__
38 # include <process.h> /* _beginthreadex, _endthreadex */
39 #else
40 # include <netinet/in_systm.h>
41 # include <netinet/in.h>
42 # include <netinet/ip.h>
43 # include <netinet/tcp.h>
44 # include <arpa/inet.h>
45 # include <signal.h>
46 # include <sys/wait.h>
47 #endif
49 #include <iostream>
51 #include <cerrno>
52 #include <cstring>
53 #include <cstdlib>
54 #include <sys/types.h>
56 using namespace std;
58 // Handle older systems.
59 #if !defined SIGCHLD && defined SIGCLD
60 # define SIGCHLD SIGCLD
61 #endif
63 #ifdef __WIN32__
64 // We must call closesocket() (instead of just close()) under __WIN32__ or
65 // else the socket remains in the CLOSE_WAIT state.
66 # define CLOSESOCKET(S) closesocket(S)
67 #else
68 # define CLOSESOCKET(S) close(S)
69 #endif
71 /// The TcpServer constructor, taking a database and a listening port.
72 TcpServer::TcpServer(const std::string & host, int port, bool tcp_nodelay,
73 bool verbose_)
75 #if defined __CYGWIN__ || defined __WIN32__
76 mutex(NULL),
77 #endif
78 listen_socket(get_listening_socket(host, port, tcp_nodelay
79 #if defined __CYGWIN__ || defined __WIN32__
80 , mutex
81 #endif
82 )),
83 verbose(verbose_)
87 int
88 TcpServer::get_listening_socket(const std::string & host, int port,
89 bool tcp_nodelay
90 #if defined __CYGWIN__ || defined __WIN32__
91 , HANDLE &mutex
92 #endif
95 int socketfd = -1;
96 int bind_errno = 0;
97 for (auto&& r : Resolver(host, port, AI_PASSIVE)) {
98 int socktype = r.ai_socktype | SOCK_CLOEXEC;
99 int fd = socket(r.ai_family, socktype, r.ai_protocol);
100 if (fd == -1)
101 continue;
103 #if !defined __WIN32__ && defined F_SETFD && defined FD_CLOEXEC
104 // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as on
105 // Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC SOCK_CLOEXEC'
106 // to allow '#ifdef SOCK_CLOEXEC' to work.
107 if (SOCK_CLOEXEC == 0)
108 (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
109 #endif
111 int retval = 0;
113 if (tcp_nodelay) {
114 int optval = 1;
115 // 4th argument might need to be void* or char* - cast it to char*
116 // since C++ allows implicit conversion to void* but not from
117 // void*.
118 retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
119 reinterpret_cast<char *>(&optval),
120 sizeof(optval));
123 int optval = 1;
124 #if defined __CYGWIN__ || defined __WIN32__
125 // Windows has screwy semantics for SO_REUSEADDR - it allows the user
126 // to bind to a port which is already bound and listening! That's
127 // just not suitable as we don't want multiple processes listening on
128 // the same port, so we guard against that by using a named win32 mutex
129 // object (and we create it in the 'Global namespace' so that this
130 // still works in a Terminal Services environment).
131 string name = "Global\\xapian-tcpserver-listening-" + str(port);
132 if ((mutex = CreateMutex(NULL, TRUE, name.c_str())) == NULL) {
133 // We failed to create the mutex, probably the error is
134 // ERROR_ACCESS_DENIED, which simply means that TcpServer is
135 // already running on this port but as a different user.
136 } else if (GetLastError() == ERROR_ALREADY_EXISTS) {
137 // The mutex already existed, so TcpServer is already running
138 // on this port.
139 CloseHandle(mutex);
140 mutex = NULL;
142 if (mutex == NULL) {
143 cerr << "Server is already running on port " << port << endl;
144 // 69 is EX_UNAVAILABLE. Scripts can use this to detect if the
145 // server failed to bind to the requested port.
146 exit(69); // FIXME: calling exit() here isn't ideal...
148 #endif
149 if (retval >= 0) {
150 retval = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
151 reinterpret_cast<char *>(&optval),
152 sizeof(optval));
155 #if defined SO_EXCLUSIVEADDRUSE
156 // NT4 sp4 and later offer SO_EXCLUSIVEADDRUSE which nullifies the
157 // security issues from SO_REUSEADDR (which affect *any* listening
158 // process, even if doesn't use SO_REUSEADDR itself). There's still no
159 // way of addressing the issue of not being able to listen on a port
160 // which has closed connections in TIME_WAIT state though.
162 // Note: SO_EXCLUSIVEADDRUSE requires admin privileges prior to XP SP2.
163 // Because of this and the lack support on older versions, we don't
164 // currently check the return value.
165 if (retval >= 0) {
166 (void)setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
167 reinterpret_cast<char *>(&optval),
168 sizeof(optval));
170 #endif
172 if (retval < 0) {
173 int saved_errno = socket_errno(); // note down in case close hits an error
174 CLOSESOCKET(fd);
175 throw Xapian::NetworkError("setsockopt failed", saved_errno);
178 if (::bind(fd, r.ai_addr, r.ai_addrlen) == 0) {
179 socketfd = fd;
180 break;
183 // Note down the error code for the first address we try, which seems
184 // likely to be more helpful than the last in the case where they
185 // differ.
186 if (bind_errno == 0)
187 bind_errno = socket_errno();
189 CLOSESOCKET(fd);
192 if (socketfd == -1) {
193 if (bind_errno == EADDRINUSE) {
194 cerr << host << ':' << port << " already in use" << endl;
195 // 69 is EX_UNAVAILABLE. Scripts can use this to detect if the
196 // server failed to bind to the requested port.
197 exit(69); // FIXME: calling exit() here isn't ideal...
199 if (bind_errno == EACCES) {
200 cerr << "Can't bind to privileged port " << port << endl;
201 // 77 is EX_NOPERM. Scripts can use this to detect if
202 // xapian-tcpsrv failed to bind to the requested port.
203 exit(77); // FIXME: calling exit() here isn't ideal...
205 CLOSESOCKET(socketfd);
206 throw Xapian::NetworkError("bind failed", bind_errno);
209 if (listen(socketfd, 5) < 0) {
210 int saved_errno = socket_errno(); // note down in case close hits an error
211 CLOSESOCKET(socketfd);
212 throw Xapian::NetworkError("listen failed", saved_errno);
214 return socketfd;
218 TcpServer::accept_connection()
220 struct sockaddr_in remote_address;
221 SOCKLEN_T remote_address_size = sizeof(remote_address);
222 // accept connections
223 int con_socket = accept(listen_socket,
224 reinterpret_cast<sockaddr *>(&remote_address),
225 &remote_address_size);
227 if (con_socket < 0) {
228 #ifdef __WIN32__
229 if (WSAGetLastError() == WSAEINTR) {
230 // Our CtrlHandler function closed the socket.
231 if (mutex) CloseHandle(mutex);
232 mutex = NULL;
233 return -1;
235 #endif
236 throw Xapian::NetworkError("accept failed", socket_errno());
239 if (remote_address_size != sizeof(remote_address)) {
240 throw Xapian::NetworkError("accept: unexpected remote address size");
243 if (verbose) {
244 char buf[INET_ADDRSTRLEN];
245 #ifndef __WIN32__
246 // Under __WIN32__, inet_ntop()'s second parameter isn't const for some
247 // reason. We don't currently use inet_ntop() there, but allow for a
248 // non-const second parameter in case it's more widespread.
249 void * src = &remote_address.sin_addr;
250 const char * r = inet_ntop(AF_INET, src, buf, sizeof(buf));
251 if (!r)
252 throw Xapian::NetworkError("inet_ntop failed", errno);
253 #else
254 // inet_ntop() isn't always available, at least with mingw.
255 // WSAAddressToString() supports both IPv4 and IPv6, so just use that.
256 DWORD size = sizeof(buf);
257 if (WSAAddressToString(reinterpret_cast<sockaddr*>(&remote_address),
258 sizeof(remote_address), NULL, buf, &size) != 0) {
259 throw Xapian::NetworkError("WSAAddressToString failed",
260 WSAGetLastError());
262 const char * r = buf;
263 #endif
264 int port = remote_address.sin_port;
265 cout << "Connection from " << r << ", port " << port << endl;
268 return con_socket;
271 TcpServer::~TcpServer()
273 CLOSESOCKET(listen_socket);
274 #if defined __CYGWIN__ || defined __WIN32__
275 if (mutex) CloseHandle(mutex);
276 #endif
279 #ifdef HAVE_FORK
280 // A fork() based implementation.
282 extern "C" {
284 [[noreturn]]
285 static void
286 on_SIGTERM(int /*sig*/)
288 signal(SIGTERM, SIG_DFL);
289 /* terminate all processes in my process group */
290 #ifdef HAVE_KILLPG
291 killpg(0, SIGTERM);
292 #else
293 kill(0, SIGTERM);
294 #endif
295 exit(0);
298 #ifdef HAVE_WAITPID
299 static void
300 on_SIGCHLD(int /*sig*/)
302 int status;
303 while (waitpid(-1, &status, WNOHANG) > 0);
305 #endif
309 void
310 TcpServer::run()
312 // Handle connections until shutdown.
314 // Set up signal handlers.
315 #ifdef HAVE_WAITPID
316 signal(SIGCHLD, on_SIGCHLD);
317 #else
318 signal(SIGCHLD, SIG_IGN);
319 #endif
320 signal(SIGTERM, on_SIGTERM);
322 while (true) {
323 try {
324 int connected_socket = accept_connection();
325 pid_t pid = fork();
326 if (pid == 0) {
327 // Child process.
328 close(listen_socket);
330 handle_one_connection(connected_socket);
331 close(connected_socket);
333 if (verbose) cout << "Connection closed." << endl;
334 exit(0);
337 // Parent process.
339 if (pid < 0) {
340 // fork() failed.
342 // Note down errno from fork() in case close() hits an error.
343 int saved_errno = socket_errno();
344 close(connected_socket);
345 throw Xapian::NetworkError("fork failed", saved_errno);
348 close(connected_socket);
349 } catch (const Xapian::Error &e) {
350 // FIXME: better error handling.
351 cerr << "Caught " << e.get_description() << endl;
352 } catch (...) {
353 // FIXME: better error handling.
354 cerr << "Caught exception." << endl;
359 #elif defined __WIN32__
361 // A threaded, Windows specific, implementation.
363 /** The socket which will be closed by CtrlHandler.
365 * FIXME - is there any way to avoid using a global variable here?
367 static const int *pShutdownSocket = NULL;
369 /// Console interrupt handler.
370 static BOOL
371 CtrlHandler(DWORD fdwCtrlType)
373 switch (fdwCtrlType) {
374 case CTRL_C_EVENT:
375 case CTRL_CLOSE_EVENT:
376 // Console is about to die.
377 // CTRL_CLOSE_EVENT gives us 5 seconds before displaying a
378 // confirmation dialog asking if we really are sure.
379 case CTRL_LOGOFF_EVENT:
380 case CTRL_SHUTDOWN_EVENT:
381 // These 2 will probably need to change when we get service
382 // support - the service will prevent these being seen, so only
383 // apply interactively.
384 cout << "Shutting down..." << endl;
385 break; // default behaviour
386 case CTRL_BREAK_EVENT:
387 // This (probably) means the developer is struggling to get
388 // things to behave, and really wants to shutdown so let the OS
389 // handle Ctrl+Break in the default way.
390 cout << "Ctrl+Break: aborting process" << endl;
391 return FALSE;
392 default:
393 cerr << "unexpected CtrlHandler: " << fdwCtrlType << endl;
394 return FALSE;
397 // Note: close() does not cause a blocking accept() call to terminate.
398 // However, it appears closesocket() does. This is much easier than trying
399 // to setup a non-blocking accept().
400 if (!pShutdownSocket || closesocket(*pShutdownSocket) == SOCKET_ERROR) {
401 // We failed to close the socket, so just let the OS handle the
402 // event in the default way.
403 return FALSE;
406 pShutdownSocket = NULL;
407 return TRUE; // Tell the OS that we've handled the event.
410 /// Structure which is used to pass parameters to the new threads.
411 struct thread_param
413 thread_param(TcpServer *s, int c) : server(s), connected_socket(c) {}
414 TcpServer *server;
415 int connected_socket;
418 /// The thread entry-point.
419 static unsigned __stdcall
420 run_thread(void * param_)
422 thread_param * param(reinterpret_cast<thread_param *>(param_));
423 int socket = param->connected_socket;
425 param->server->handle_one_connection(socket);
426 closesocket(socket);
428 delete param;
430 _endthreadex(0);
431 return 0;
434 void
435 TcpServer::run()
437 // Handle connections until shutdown.
439 // Set up the shutdown handler - this is a bit hacky, and sadly involves
440 // a global variable.
441 pShutdownSocket = &listen_socket;
442 if (!::SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE))
443 throw Xapian::NetworkError("Failed to install shutdown handler");
445 while (true) {
446 try {
447 int connected_socket = accept_connection();
448 if (connected_socket == -1)
449 return; // Shutdown has happened
451 // Spawn a new thread to handle the connection.
452 // (This seems like lots of hoops just to end up calling
453 // this->handle_one_connection() on a new thread. There might be a
454 // better way...)
455 thread_param *param = new thread_param(this, connected_socket);
456 HANDLE hthread = (HANDLE)_beginthreadex(NULL, 0, ::run_thread, param, 0, NULL);
457 if (hthread == 0) {
458 // errno holds the error code from _beginthreadex, and
459 // closesocket() doesn't set errno.
460 closesocket(connected_socket);
461 throw Xapian::NetworkError("_beginthreadex failed", errno);
464 // FIXME: keep track of open thread handles so we can gracefully
465 // close each thread down. OTOH, when we want to kill them all its
466 // likely to mean the process is on its way down, so it doesn't
467 // really matter...
468 CloseHandle(hthread);
469 } catch (const Xapian::Error &e) {
470 // FIXME: better error handling.
471 cerr << "Caught " << e.get_description() << endl;
472 } catch (...) {
473 // FIXME: better error handling.
474 cerr << "Caught exception." << endl;
479 #else
480 # error Neither HAVE_FORK nor __WIN32__ are defined.
481 #endif
483 void
484 TcpServer::run_once()
486 // Run a single request in the current process/thread.
487 int fd = accept_connection();
488 handle_one_connection(fd);
489 CLOSESOCKET(fd);
492 #ifdef DISABLE_GPL_LIBXAPIAN
493 # error GPL source we cannot relicense included in libxapian
494 #endif