/* ** SWISH++ ** search_thread.c ** ** Copyright (C) 1998 Paul J. Lucas ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef SEARCH_DAEMON // standard #include #include #include /* for ARG_MAX */ #include #include #include #include /* for recv(3) */ #include #include /* for close(2) */ // // We need to know the maximum number of command-line arguments so we can split // a command-line string into individual arguments. If the OS defines the // POSIX.1 ARG_MAX macro, see if it's insanely large (Solaris's limit is over a // million!) because we don't want to allocate that much space for argument // pointers since it would probably blow our thread stack space; however, if // it's small, we might as well use that number since there's no reason to // exceed it. // // See also: W. Richard Stevens. "Advanced Programming in the Unix // Environment," Addison-Wesley, Reading, MA, 1993. pp. 32-40. // #define REASONABLE_ARG_MAX 50 #ifdef ARG_MAX # if ARG_MAX > REASONABLE_ARG_MAX # undef ARG_MAX # endif #endif #ifndef ARG_MAX # define ARG_MAX REASONABLE_ARG_MAX #endif // local #include "fdbuf.h" #include "platform.h" #include "search.h" #include "search_thread.h" #include "util.h" using namespace PJL; using namespace std; unsigned search_thread::socket_timeout; extern void reset_socket( int fd ); static int split_args( char *s, char *argv[], int arg_max ); static bool timed_read_line( int fd, char *buf, int buf_size, int seconds ); //***************************************************************************** // // SYNOPSIS // void search_thread::main( argument_type arg ) // // DESCRIPTION // // Read a "command-line" from the client via a socket, service a request, // and return the results via the same socket. // // PARAMETERS // // arg The 'i' member is the socket file descriptor. // //***************************************************************************** { #define SEARCH_DAEMON_OPTIONS_ONLY #include "search_options.c" /* defines opt_spec */ # ifdef DEBUG_threads cerr << "in search_thread::main()\n"; # endif char buf[ 1024 ]; bool ok = false; if ( timed_read_line( arg.i, buf, sizeof buf, socket_timeout ) ) { # ifdef DEBUG_threads cerr << "query=" << buf << "\n"; # endif char* argv_vec[ ARG_MAX ]; char** argv = argv_vec; int argc = split_args( buf, argv, ARG_MAX ); fdbuf buf( arg.i ); ostream out( &buf ); if ( !argc ) out << usage; else if ( argc == ARG_MAX ) out << error << "more than " << ARG_MAX << " arguments" << endl; else { search_options const opt( &argc, &argv, opt_spec, out ); if ( opt ) ok = service_request( argv, opt, out, out ); } out << flush; } if ( !ok ) { // // It was a bad request because it (a) timed out, (b) had too few or // many arguments, (c) had an error in usage, or (d) was malformed. // That being the case, reset the TCP connection. // // The reason for doing this is so we don't potentially have a socket // lingering in TIME-WAIT from a client that was too dumb to give us a // valid request in the first place. This helps alleviate // denial-of-service attacks (if that's what's going on). // reset_socket( arg.i ); } ::close( arg.i ); } //***************************************************************************** // // SYNOPSIS // int split_args( register char *s, char *argv[], int arg_max ) // // DESCRIPTION // // Split a string into individual, argv-like arguments at whitespace. // This code is based on buf_args() in [Stevens 1993], p. 495, except that // it: // // 1. Is thread-safe by not using strtok(). // 2. Discards leading whitespace in the buffer. // 3. Just does the split and doesn't call any function. // // PARAMETERS // // s The string to be split. // // argv The array to deposit the pointers to arguments in. // // arg_max The maximum number of argument to allow. // // RETURN VALUE // // Upon success, returns the number of arguments; upon failure, returns // arg_max. // // SEE ALSO // // W. Richard Stevens. "Advanced Programming in the Unix Environment," // Addison-Wesley, Reading, MA, 1993. p. 495. // //***************************************************************************** { for ( ; *s && is_space( *s ); ++s ) ; // skip leading whitespace if ( !*s ) return 0; register int argc = 0; while ( argv[ argc++ ] = s ) { if ( argc >= arg_max - 1 ) // -1 to allow for null at end return arg_max; if ( s = ::strpbrk( s, " \t\n\r" ) ) { *s = '\0'; // // We must skip *ALL* whitespace characters separating arguments. // while ( *++s && is_space( *s ) ) ; } } return argc; } //***************************************************************************** // // SYNOPSIS // bool timed_read_line( int fd, char *buf, int buf_size, int seconds ) // // DESCRIPTION // // Read a line of text (a string of characters ending in either a carriage // return or a newline) from a Unix file descriptor and store it in the // given buffer, null-terminated; but time-out if we don't get it in a // certain amount of time. The carriage return or newline is discarded. // // PARAMETERS // // fd The Unix file descriptor to read from. // // buf The buffer to read into. // // buf_size The size of the buffer. // // seconds The number of seconds until a time-out. // // RETURN VALUE // // Returns true only if an entire line was read in the time allotted. // // SEE ALSO // // W. Richard Stevens. "Unix Network Programming, Vol 1, 2nd ed." // Prentice-Hall, Upper Saddle River, NJ, 1998. pp. 352-353. // //***************************************************************************** { // // In a single-threaded application, we could simply use alarm(2) to set a // time-out before reading; however, in a multi-threaded application, we // can't since there can be at most one alarm set for an entire process: // individual threads can not have independent alarms. // // Therefore, what we do instead is to use select(2) to do the blocking, // but with a time-out specified. // time_t const start_time = ::time( 0 ); int seconds_remaining = seconds; while ( seconds_remaining > 0 ) { fd_set rset; FD_ZERO( &rset ); FD_SET( fd, &rset ); struct timeval tv; tv.tv_sec = seconds_remaining; tv.tv_usec = 0; if ( ::select( fd + 1, &rset, 0, 0, &tv ) < 1 ) break; if ( !FD_ISSET( fd, &rset ) ) // shouldn't happen, but... break; ssize_t const bytes_read = ::recv( fd, buf, buf_size, 0 ); if ( bytes_read == -1 ) // error break; buf += bytes_read, buf_size -= bytes_read; if ( buf[-1] == '\r' || buf[-1] == '\n' || buf_size <= 0 ) { buf[-1] = '\0'; return true; // got a line: woohoo! } // // We haven't gotten a complete line yet: see how much time has elapsed // and, if there's more time left before the time-out expires, try to // read some more. // time_t const elapsed_time = ::time( 0 ) - start_time; seconds_remaining = seconds - elapsed_time; } return false; } #endif /* SEARCH_DAEMON */ /* vim:set et sw=4 ts=4: */