root / tags / 1.0.8 / xapian-core / net / tcpserver.cc

Revision 9982, 13.8 kB (checked in by olly, 12 months ago)

backends/flint/flint_check.h,backends/flint/flint_table.h,
backends/quartz/btree.h,net/tcpserver.cc,tests/harness/testsuite.cc:
Add XAPIAN_NORETURN() annotations to functions and non-virtual
methods which don't return.
net/remoteserver.cc: Assign bool variable using a comparison rather
than subtraction, so the intent is clearer.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/* tcpserver.cc: class for TCP/IP-based server.
2 *
3 * Copyright 1999,2000,2001 BrightStation PLC
4 * Copyright 2002 Ananova Ltd
5 * Copyright 2002,2003,2004,2005,2006,2007,2008 Olly Betts
6 *
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.
11 *
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.
16 *
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
21 */
22
23#include <config.h>
24
25#include "tcpserver.h"
26
27#include <xapian/error.h>
28
29#include "safeerrno.h"
30#include "safefcntl.h"
31
32#include "noreturn.h"
33#include "remoteserver.h"
34#include "utils.h"
35
36#ifdef __WIN32__
37# include <process.h>    /* _beginthread, _endthread */
38#else
39# include <sys/socket.h>
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 <netdb.h>
46# include <signal.h>
47# include <sys/wait.h>
48#endif
49
50#include <iostream>
51
52#include <string.h>
53#include <sys/types.h>
54
55using namespace std;
56
57// Handle older systems.
58#if !defined SIGCHLD && defined SIGCLD
59# define SIGCHLD SIGCLD
60#endif
61
62#ifdef __WIN32__
63// We must call closesocket() (instead of just close()) under __WIN32__ or
64// else the socket remains in the CLOSE_WAIT state.
65# define CLOSESOCKET(S) closesocket(S)
66#else
67# define CLOSESOCKET(S) close(S)
68#endif
69
70/// The TcpServer constructor, taking a database and a listening port.
71TcpServer::TcpServer(const vector<std::string> &dbpaths_, const std::string & host, int port,
72                     int msecs_active_timeout_,
73                     int msecs_idle_timeout_,
74                     bool writable_,
75                     bool verbose_)
76        : dbpaths(dbpaths_), writable(writable_),
77#if defined __CYGWIN__ || defined __WIN32__
78          mutex(NULL),
79#endif
80          listen_socket(get_listening_socket(host, port
81#if defined __CYGWIN__ || defined __WIN32__
82                                             , mutex
83#endif
84                                            )),
85          msecs_active_timeout(msecs_active_timeout_),
86          msecs_idle_timeout(msecs_idle_timeout_),
87          verbose(verbose_)
88{
89}
90
91int
92TcpServer::get_listening_socket(const std::string & host, int port
93#if defined __CYGWIN__ || defined __WIN32__
94                                , HANDLE &mutex
95#endif
96                                )
97{
98    int socketfd = socket(PF_INET, SOCK_STREAM, 0);
99
100    if (socketfd < 0) {
101        throw Xapian::NetworkError("socket", socket_errno());
102    }
103
104    int retval;
105
106    {
107        int optval = 1;
108        // 4th argument might need to be void* or char* - cast it to char*
109        // since C++ allows implicit conversion to void* but not from void*.
110        retval = setsockopt(socketfd, IPPROTO_TCP, TCP_NODELAY,
111                            reinterpret_cast<char *>(&optval),
112                            sizeof(optval));
113
114#if defined __CYGWIN__ || defined __WIN32__
115        // Windows has screwy semantics for SO_REUSEADDR - it allows the user
116        // to bind to a port which is already bound and listening!  That's
117        // just not suitable as we don't want multiple xapian-tcpsrv processes
118        // listening on the same port, so we guard against that by using a named
119        // win32 mutex object (and we create it in the 'Global namespace' so
120        // that this still works in a Terminal Services environment).
121        char name[64];
122        sprintf(name, "Global\\xapian-tcpserver-listening-%d", port);
123        if ((mutex = CreateMutex(NULL, TRUE, name)) == NULL) {
124            // We failed to create the mutex, probably the error is
125            // ERROR_ACCESS_DENIED, which simply means that TcpServer is
126            // already running on this port but as a different user.
127        } else if (GetLastError() == ERROR_ALREADY_EXISTS) {
128            // The mutex already existed, so TcpServer is already running
129            // on this port.
130            CloseHandle(mutex);
131            mutex = NULL;
132        }
133        if (mutex == NULL) {
134            cerr << "xapian-tcpsrv is already running on port " << port << endl;
135            // 69 is EX_UNAVAILABLE.  Scripts can use this to detect if
136            // xapian-tcpsrv failed to bind to the requested port.
137            exit(69); // FIXME: calling exit() here isn't ideal...
138        }
139#endif
140        if (retval >= 0) {
141            retval = setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,
142                                reinterpret_cast<char *>(&optval),
143                                sizeof(optval));
144        }
145
146#if defined SO_EXCLUSIVEADDRUSE
147        // NT4 sp4 and later offer SO_EXCLUSIVEADDRUSE which nullifies the
148        // security issues from SO_REUSEADDR (which affect *any* listening
149        // process, even if doesn't use SO_REUSEADDR itself).  There's still no
150        // way of addressing the issue of not being able to listen on a port
151        // which has closed connections in TIME_WAIT state though.
152        //
153        // Note: SO_EXCLUSIVEADDRUSE requires admin privileges prior to XP SP2.
154        // Because of this and the lack support on older versions, we don't
155        // currently check the return value.
156        if (retval >= 0) {
157            (void)setsockopt(socketfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
158                             reinterpret_cast<char *>(&optval),
159                             sizeof(optval));
160        }
161#endif
162    }
163
164    if (retval < 0) {
165        int saved_errno = socket_errno(); // note down in case close hits an error
166        CLOSESOCKET(socketfd);
167        throw Xapian::NetworkError("setsockopt failed", saved_errno);
168    }
169
170    struct sockaddr_in addr;
171    addr.sin_family = AF_INET;
172    addr.sin_port = htons(port);
173    if (host.empty()) {
174        addr.sin_addr.s_addr = INADDR_ANY;
175    } else {
176        // FIXME: timeout on gethostbyname() ?
177        struct hostent *hostent = gethostbyname(host.c_str());
178
179        if (hostent == 0) {
180            throw Xapian::NetworkError(string("Couldn't resolve host ") + host,
181                "",
182#ifdef __WIN32__
183                socket_errno()
184#else
185                // "socket_errno()" is just errno on UNIX which is
186                // inappropriate here - if gethostbyname() returns NULL an
187                // error code is available in h_errno (with values
188                // incompatible with errno).  On Linux at least, if h_errno
189                // is < 0, then the error code *IS* in errno!
190                (h_errno < 0 ? errno : -h_errno)
191#endif
192                );
193        }
194
195        memcpy(&addr.sin_addr, hostent->h_addr, sizeof(addr.sin_addr));
196    }
197
198    retval = bind(socketfd,
199                  reinterpret_cast<sockaddr *>(&addr),
200                  sizeof(addr));
201
202    if (retval < 0) {
203        int saved_errno = socket_errno(); // note down in case close hits an error
204        if (saved_errno == EADDRINUSE) {
205            cerr << host << ':' << port << " already in use" << endl;
206            // 69 is EX_UNAVAILABLE.  Scripts can use this to detect if
207            // xapian-tcpsrv failed to bind to the requested port.
208            exit(69); // FIXME: calling exit() here isn't ideal...
209        }
210        CLOSESOCKET(socketfd);
211        throw Xapian::NetworkError("bind failed", saved_errno);
212    }
213
214    retval = listen(socketfd, 5);
215
216    if (retval < 0) {
217        int saved_errno = socket_errno(); // note down in case close hits an error
218        CLOSESOCKET(socketfd);
219        throw Xapian::NetworkError("listen failed", saved_errno);
220    }
221    return socketfd;
222}
223
224int
225TcpServer::accept_connection()
226{
227    struct sockaddr_in remote_address;
228    SOCKLEN_T remote_address_size = sizeof(remote_address);
229    // accept connections
230    int con_socket = accept(listen_socket,
231                            reinterpret_cast<sockaddr *>(&remote_address),
232                            &remote_address_size);
233
234    if (con_socket < 0) {
235#ifdef __WIN32__
236        if (WSAGetLastError() == WSAEINTR) {
237            // Our CtrlHandler function closed the socket.
238            if (mutex) CloseHandle(mutex);
239            mutex = NULL;
240            return -1;
241        }
242#endif
243        throw Xapian::NetworkError("accept failed", socket_errno());
244    }
245
246    if (remote_address_size != sizeof(remote_address)) {
247        throw Xapian::NetworkError("accept: unexpected remote address size");
248    }
249
250    if (verbose) {
251        cout << "Connection from " << inet_ntoa(remote_address.sin_addr)
252             << ", port " << remote_address.sin_port << endl;
253    }
254
255    return con_socket;
256}
257
258TcpServer::~TcpServer()
259{
260    CLOSESOCKET(listen_socket);
261#if defined __CYGWIN__ || defined __WIN32__
262    if (mutex) CloseHandle(mutex);
263#endif
264}
265
266void
267TcpServer::handle_one_connection(int socket)
268{
269    try {
270        RemoteServer sserv(dbpaths, socket, socket,
271                           msecs_active_timeout, msecs_idle_timeout,
272                           writable);
273        sserv.run();
274        CLOSESOCKET(socket);
275    } catch (const Xapian::NetworkTimeoutError &e) {
276        CLOSESOCKET(socket);
277        if (verbose)
278            cerr << "Connection timed out: " << e.get_description() << endl;
279    } catch (const Xapian::Error &e) {
280        CLOSESOCKET(socket);
281        cerr << "Got exception " << e.get_description() << endl;
282    } catch (...) {
283        CLOSESOCKET(socket);
284        // ignore other exceptions
285    }
286}
287
288#ifdef HAVE_FORK
289// A fork() based implementation.
290void
291TcpServer::run_once()
292{
293    int connected_socket = accept_connection();
294    pid_t pid = fork();
295    if (pid == 0) {
296        // Child process.
297        close(listen_socket);
298
299        handle_one_connection(connected_socket);
300
301        if (verbose) cout << "Closing connection." << endl;
302        exit(0);
303    }
304
305    // Parent process.
306
307    if (pid < 0) {
308        // fork() failed
309        int saved_errno = socket_errno(); // note down in case close hits an error
310        close(connected_socket);
311        throw Xapian::NetworkError("fork failed", saved_errno);
312    }
313
314    close(connected_socket);
315}
316
317extern "C" {
318
319XAPIAN_NORETURN(static void on_SIGTERM(int /*sig*/));
320
321static void
322on_SIGTERM(int /*sig*/)
323{
324    signal(SIGTERM, SIG_DFL);
325    /* terminate all processes in my process group */
326#ifdef HAVE_KILLPG
327    killpg(0, SIGTERM);
328#else
329    kill(0, SIGTERM);
330#endif
331    exit(0);
332}
333
334#ifdef HAVE_WAITPID
335static void
336on_SIGCHLD(int /*sig*/)
337{
338    int status;
339    while (waitpid(-1, &status, WNOHANG) > 0);
340}
341#endif
342
343}
344
345void
346TcpServer::run()
347{
348    // Handle connections until shutdown.
349
350    // Set up signal handlers.
351#ifdef HAVE_WAITPID
352    signal(SIGCHLD, on_SIGCHLD);
353#else
354    signal(SIGCHLD, SIG_IGN);
355#endif
356    signal(SIGTERM, on_SIGTERM);
357
358    while (true) {
359        try {
360            run_once();
361        } catch (const Xapian::Error &e) {
362            // FIXME: better error handling.
363            cerr << "Caught " << e.get_description() << endl;
364        } catch (...) {
365            // FIXME: better error handling.
366            cerr << "Caught exception." << endl;
367        }
368    }
369}
370
371#elif defined __WIN32__
372
373// A threaded, Windows specific, implementation.
374
375/** The socket which will be closed by CtrlHandler.
376 *
377 *  FIXME - is there any way to avoid using a global variable here?
378 */
379static const int *pShutdownSocket = NULL;
380
381/// Console interrupt handler.
382static BOOL
383CtrlHandler(DWORD fdwCtrlType)
384{
385    switch (fdwCtrlType) {
386        case CTRL_C_EVENT:
387        case CTRL_CLOSE_EVENT:
388            //  Console is about to die.
389            // CTRL_CLOSE_EVENT gives us 5 seconds before displaying a
390            // confirmation dialog asking if we really are sure.
391        case CTRL_LOGOFF_EVENT:
392        case CTRL_SHUTDOWN_EVENT:
393            // These 2 will probably need to change when we get service
394            // support - the service will prevent these being seen, so only
395            // apply interactively.
396            cout << "Shutting down..." << endl;
397            break; // default behaviour
398        case CTRL_BREAK_EVENT:
399            // This (probably) means the developer is struggling to get
400            // things to behave, and really wants to shutdown so let the OS
401            // handle Ctrl+Break in the default way.
402            cout << "Ctrl+Break: aborting process" << endl;
403            return FALSE;
404        default:
405            cerr << "unexpected CtrlHandler: " << fdwCtrlType << endl;
406            return FALSE;
407    }
408
409    // Note: close() does not cause a blocking accept() call to terminate.
410    // However, it appears closesocket() does.  This is much easier than trying
411    // to setup a non-blocking accept().
412    if (!pShutdownSocket || closesocket(*pShutdownSocket) == SOCKET_ERROR) {
413       // We failed to close the socket, so just let the OS handle the
414       // event in the default way.
415       return FALSE;
416    }
417
418    pShutdownSocket = NULL;
419    return TRUE; // Tell the OS that we've handled the event.
420}
421
422/// Structure which is used to pass parameters to the new threads.
423struct thread_param
424{
425    thread_param(TcpServer *s, int c) : server(s), connected_socket(c) {}
426    TcpServer *server;
427    int connected_socket;
428};
429
430/// The thread entry-point.
431static unsigned __stdcall
432run_thread(void * param_)
433{
434    thread_param * param(reinterpret_cast<thread_param *>(param_));
435    int socket = param->connected_socket;
436
437    param->server->handle_one_connection(socket);
438
439    delete param;
440
441    _endthreadex(0);
442    return 0;
443}
444
445void
446TcpServer::run()
447{
448    // Handle connections until shutdown.
449
450    // Set up the shutdown handler - this is a bit hacky, and sadly involves
451    // a global variable.
452    pShutdownSocket = &listen_socket;
453    if (!::SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE))
454        throw Xapian::NetworkError("Failed to install shutdown handler");
455
456    while (true) {
457        try {
458            int connected_socket = accept_connection();
459            if (connected_socket == -1)
460               return; // Shutdown has happened
461
462            // Spawn a new thread to handle the connection.
463            // (This seems like lots of hoops just to end up calling
464            // this->handle_one_connection() on a new thread. There might be a
465            // better way...)
466            thread_param *param = new thread_param(this, connected_socket);
467            HANDLE hthread = (HANDLE)_beginthreadex(NULL, 0, ::run_thread, param, 0, NULL);
468            if (hthread == 0) {
469               // errno holds the error code from _beginthreadex, and
470               // closesocket() doesn't set errno.
471               closesocket(connected_socket);
472               throw Xapian::NetworkError("_beginthreadex failed", errno);
473            }
474
475            // FIXME: keep track of open thread handles so we can gracefully
476            // close each thread down.  OTOH, when we want to kill them all its
477            // likely to mean the process is on its way down, so it doesn't
478            // really matter...
479            CloseHandle(hthread);
480        } catch (const Xapian::Error &e) {
481            // FIXME: better error handling.
482            cerr << "Caught " << e.get_description() << endl;
483        } catch (...) {
484            // FIXME: better error handling.
485            cerr << "Caught exception." << endl;
486        }
487    }
488}
489
490void
491TcpServer::run_once()
492{
493    // Run a single request on the current thread.
494    handle_one_connection(accept_connection());
495}
496
497#else
498# error Neither HAVE_FORK nor __WIN32__ are defined.
499#endif
Note: See TracBrowser for help on using the browser.