net/remoteconnection.cc

Go to the documentation of this file.
00001 
00004 /* Copyright (C) 2006,2007 Olly Betts
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
00019  */
00020 
00021 #include <config.h>
00022 
00023 #include <xapian/error.h>
00024 
00025 #include "safeerrno.h"
00026 #include "safefcntl.h"
00027 #include "safeunistd.h"
00028 
00029 #include <string>
00030 
00031 #include "omassert.h"
00032 #include "omdebug.h"
00033 #include "omtime.h"
00034 #include "remoteconnection.h"
00035 #include "serialise.h"
00036 
00037 #ifndef __WIN32__
00038 # include "safesysselect.h"
00039 #endif
00040 
00041 using namespace std;
00042 
00043 #ifdef __WIN32__
00044 // __STDC_SECURE_LIB__ doesn't appear to be publicly documented, but appears
00045 // to be a good idea.  We cribbed this test from the python sources - see, for
00046 // example, http://svn.python.org/view?rev=47223&view=rev
00047 # if defined _MSC_VER && _MSC_VER >= 1400 && defined __STDC_SECURE_LIB__
00048 #  include <stdlib.h> // For _set_invalid_parameter_handler(), etc.
00049 #  include <crtdbg.h> // For _CrtSetReportMode, etc.
00050 
00052 static void dummy_handler(const wchar_t*,
00053                           const wchar_t*,
00054                           const wchar_t*,
00055                           unsigned int,
00056                           uintptr_t)
00057 {
00058 }
00059 
00060 // Recent versions of MSVC call an "_invalid_parameter_handler" if a
00061 // CRT function receives an invalid parameter.  However, there are cases
00062 // where this is totally reasonable.  To avoid the application dying,
00063 // you just need to instantiate the MSVCIgnoreInvalidParameter class in
00064 // the scope where you want MSVC to ignore invalid parameters.
00065 class MSVCIgnoreInvalidParameter {
00066     _invalid_parameter_handler old_handler;
00067     int old_report_mode;
00068 
00069   public:
00070     MSVCIgnoreInvalidParameter() {
00071         // Install a dummy handler to avoid the program dying.
00072         old_handler = _set_invalid_parameter_handler(dummy_handler);
00073         // Make sure that no dialog boxes appear.
00074         old_report_mode = _CrtSetReportMode(_CRT_ASSERT, 0);
00075     }
00076 
00077     ~MSVCIgnoreInvalidParameter() {
00078         // Restore the previous settings.
00079         _set_invalid_parameter_handler(old_handler);
00080         _CrtSetReportMode(_CRT_ASSERT, old_report_mode);
00081     }
00082 };
00083 # else
00084 // Mingw seems to be free of this insanity, so for this and older MSVC versions
00085 // define a dummy class to allow MSVCIgnoreInvalidParameter to be used
00086 // unconditionally.
00087 struct MSVCIgnoreInvalidParameter {
00088     // Provide an explicit constructor so this isn't a POD struct - this seems
00089     // to prevent GCC warning about an unused variable whenever we instantiate
00090     // this class.
00091     MSVCIgnoreInvalidParameter() { }
00092 };
00093 # endif
00094 
00096 static HANDLE fd_to_handle(int fd) {
00097     MSVCIgnoreInvalidParameter invalid_handle_value_is_ok;
00098     HANDLE handle = (HANDLE)_get_osfhandle(fd);
00099     // On WIN32, a socket fd isn't the same as a non-socket fd - in fact
00100     // it's already a HANDLE!
00101     return (handle != INVALID_HANDLE_VALUE ? handle : (HANDLE)fd);
00102 }
00103 
00105 static void close_fd_or_socket(int fd) {
00106     MSVCIgnoreInvalidParameter invalid_fd_value_is_ok;
00107     if (close(fd) == -1 && errno == EBADF) {
00108         // Bad file descriptor - probably because the fd is actually
00109         // a socket.
00110         closesocket(fd);
00111     }
00112 }
00113 #else
00114 // There's no distinction between sockets and other fds on UNIX.
00115 inline void close_fd_or_socket(int fd) { close(fd); }
00116 #endif
00117 
00118 RemoteConnection::RemoteConnection(int fdin_, int fdout_,
00119                                    const string & context_)
00120     : fdin(fdin_), fdout(fdout_), context(context_)
00121 {
00122 #ifdef __WIN32__
00123     memset(&overlapped, 0, sizeof(overlapped));
00124     overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
00125     if (!overlapped.hEvent)
00126         throw Xapian::NetworkError("Failed to setup OVERLAPPED",
00127                                    context, -(int)GetLastError());
00128 
00129 #endif
00130 }
00131 
00132 RemoteConnection::~RemoteConnection()
00133 {
00134 #ifdef __WIN32__
00135     if (overlapped.hEvent)
00136         CloseHandle(overlapped.hEvent);
00137 #endif
00138 }
00139 
00140 void
00141 RemoteConnection::read_at_least(size_t min_len, const OmTime & end_time)
00142 {
00143     DEBUGCALL(REMOTE, string, "RemoteConnection::read_at_least",
00144               min_len << ", " << end_time);
00145 
00146     if (buffer.length() >= min_len) return;
00147 
00148 #ifdef __WIN32__
00149     HANDLE hin = fd_to_handle(fdin);
00150     do {
00151         char buf[4096];
00152         DWORD received;
00153         BOOL ok = ReadFile(hin, buf, sizeof(buf), &received, &overlapped);
00154         if (!ok) {
00155             int errcode = GetLastError();
00156             if (errcode != ERROR_IO_PENDING)
00157                 throw Xapian::NetworkError("read failed", context, -errcode);
00158             // Is asynch - just wait for the data to be received or a timeout.
00159             DWORD waitrc;
00160             waitrc = WaitForSingleObject(overlapped.hEvent, calc_read_wait_msecs(end_time));
00161             if (waitrc != WAIT_OBJECT_0) {
00162                 DEBUGLINE(REMOTE, "read: timeout has expired");
00163                 throw Xapian::NetworkTimeoutError("Timeout expired while trying to read", context);
00164             }
00165             // Get the final result of the read.
00166             if (!GetOverlappedResult(hin, &overlapped, &received, FALSE))
00167                 throw Xapian::NetworkError("Failed to get overlapped result",
00168                                            context, -(int)GetLastError());
00169         }
00170 
00171         if (received == 0)
00172             throw Xapian::NetworkError("Received EOF", context);
00173 
00174         buffer.append(buf, received);
00175     } while (buffer.length() < min_len);
00176 #else
00177     // If there's no end_time, just use blocking I/O.
00178     if (fcntl(fdin, F_SETFL, end_time.is_set() ? O_NONBLOCK : 0) < 0) {
00179         throw Xapian::NetworkError("Failed to set fdin non-blocking-ness",
00180                                    context, errno);
00181     }
00182 
00183     while (true) {
00184         char buf[4096];
00185         ssize_t received = read(fdin, buf, sizeof(buf));
00186 
00187         if (received > 0) {
00188             buffer.append(buf, received);
00189             if (buffer.length() >= min_len) return;
00190             continue;
00191         }
00192 
00193         if (received == 0)
00194             throw Xapian::NetworkError("Received EOF", context);
00195 
00196         DEBUGLINE(REMOTE, "read gave errno = " << strerror(errno));
00197         if (errno == EINTR) continue;
00198 
00199         if (errno != EAGAIN)
00200             throw Xapian::NetworkError("read failed", context, errno);
00201 
00202         Assert(end_time.is_set());
00203         while (true) {
00204             // Calculate how far in the future end_time is.
00205             OmTime time_diff = end_time - OmTime::now();
00206             // Check if the timeout has expired.
00207             if (time_diff.sec < 0) {
00208                 DEBUGLINE(REMOTE, "read: timeout has expired");
00209                 throw Xapian::NetworkTimeoutError("Timeout expired while trying to read", context);
00210             }
00211 
00212             struct timeval tv;
00213             tv.tv_sec = time_diff.sec;
00214             tv.tv_usec = time_diff.usec;
00215 
00216             // Use select to wait until there is data or the timeout is reached.
00217             fd_set fdset;
00218             FD_ZERO(&fdset);
00219             FD_SET(fdin, &fdset);
00220 
00221             int select_result = select(fdin + 1, &fdset, 0, &fdset, &tv);
00222             if (select_result > 0) break;
00223 
00224             if (select_result == 0)
00225                 throw Xapian::NetworkTimeoutError("Timeout expired while trying to read", context);
00226 
00227             // EINTR means select was interrupted by a signal.
00228             if (errno != EINTR)
00229                 throw Xapian::NetworkError("select failed during read", context, errno);
00230         }
00231     }
00232 #endif
00233 }
00234 
00235 bool
00236 RemoteConnection::ready_to_read() const
00237 {
00238     DEBUGCALL(REMOTE, bool, "RemoteConnection::ready_to_read", "");
00239 
00240     if (!buffer.empty()) RETURN(true);
00241 
00242     // Use select to see if there's data available to be read.
00243     fd_set fdset;
00244     FD_ZERO(&fdset);
00245     FD_SET(fdin, &fdset);
00246 
00247     // Set a 0.1 second timeout to avoid a busy loop.
00248     // FIXME: this would be much better done by exposing the fd so that the
00249     // matcher can call select on all the fds involved...
00250     struct timeval tv;
00251     tv.tv_sec = 0;
00252     tv.tv_usec = 100000;
00253     RETURN(select(fdin + 1, &fdset, 0, &fdset, &tv) > 0);
00254 }
00255 
00256 void
00257 RemoteConnection::send_message(char type, const string &message, const OmTime & end_time)
00258 {
00259     DEBUGCALL(REMOTE, void, "RemoteConnection::send_message",
00260               type << ", " << message << ", " << end_time);
00261 
00262     string header;
00263     header += type;
00264     header += encode_length(message.size());
00265 
00266 #ifdef __WIN32__
00267     HANDLE hout = fd_to_handle(fdout);
00268     const string * str = &header;
00269 
00270     size_t count = 0;
00271     while (true) {
00272         DWORD n;
00273         BOOL ok = WriteFile(hout, str->data() + count, str->size() - count, &n, &overlapped);
00274         if (!ok) {
00275             int errcode = GetLastError();
00276             if (errcode != ERROR_IO_PENDING)
00277                 throw Xapian::NetworkError("write failed", context, -errcode);
00278             // Just wait for the data to be received, or a timeout.
00279             DWORD waitrc;
00280             waitrc = WaitForSingleObject(overlapped.hEvent, calc_read_wait_msecs(end_time));
00281             if (waitrc != WAIT_OBJECT_0) {
00282                 DEBUGLINE(REMOTE, "write: timeout has expired");
00283                 throw Xapian::NetworkTimeoutError("Timeout expired while trying to write", context);
00284             }
00285             // Get the final result.
00286             if (!GetOverlappedResult(hout, &overlapped, &n, FALSE))
00287                 throw Xapian::NetworkError("Failed to get overlapped result",
00288                                            context, -(int)GetLastError());
00289         }
00290 
00291         count += n;
00292         if (count == str->size()) {
00293             if (str == &message || message.empty()) return;
00294             str = &message;
00295             count = 0;
00296         }
00297     }
00298 #else
00299     // If there's no end_time, just use blocking I/O.
00300     if (fcntl(fdout, F_SETFL, end_time.is_set() ? O_NONBLOCK : 0) < 0) {
00301         throw Xapian::NetworkError("Failed to set fdout non-blocking-ness",
00302                                    context, errno);
00303     }
00304 
00305     const string * str = &header;
00306 
00307     fd_set fdset;
00308     size_t count = 0;
00309     while (true) {
00310         // We've set write to non-blocking, so just try writing as there
00311         // will usually be space.
00312         ssize_t n = write(fdout, str->data() + count, str->size() - count);
00313 
00314         if (n >= 0) {
00315             count += n;
00316             if (count == str->size()) {
00317                 if (str == &message || message.empty()) return;
00318                 str = &message;
00319                 count = 0;
00320             }
00321             continue;
00322         }
00323 
00324         DEBUGLINE(REMOTE, "write gave errno = " << strerror(errno));
00325         if (errno == EINTR) continue;
00326 
00327         if (errno != EAGAIN)
00328             throw Xapian::NetworkError("write failed", context, errno);
00329 
00330         // Use select to wait until there is space or the timeout is reached.
00331         FD_ZERO(&fdset);
00332         FD_SET(fdout, &fdset);
00333 
00334         OmTime time_diff(end_time - OmTime::now());
00335         if (time_diff.sec < 0) {
00336             DEBUGLINE(REMOTE, "write: timeout has expired");
00337             throw Xapian::NetworkTimeoutError("Timeout expired while trying to write", context);
00338         }
00339 
00340         struct timeval tv;
00341         tv.tv_sec = time_diff.sec;
00342         tv.tv_usec = time_diff.usec;
00343 
00344         int select_result = select(fdout + 1, 0, &fdset, &fdset, &tv);
00345 
00346         if (select_result < 0) {
00347             if (errno == EINTR) {
00348                 // EINTR means select was interrupted by a signal.
00349                 // We could just retry the select, but it's easier to just
00350                 // retry the write.
00351                 continue;
00352             }
00353             throw Xapian::NetworkError("select failed during write", context, errno);
00354         }
00355 
00356         if (select_result == 0)
00357             throw Xapian::NetworkTimeoutError("Timeout expired while trying to write", context);
00358     }
00359 #endif
00360 }
00361 
00362 char
00363 RemoteConnection::get_message(string &result, const OmTime & end_time)
00364 {
00365     DEBUGCALL(REMOTE, char, "RemoteConnection::get_message",
00366               "[result], " << end_time);
00367 
00368     read_at_least(2, end_time);
00369     size_t len = static_cast<unsigned char>(buffer[1]);
00370     read_at_least(len + 2, end_time);
00371     if (len != 0xff) {
00372         result.assign(buffer.data() + 2, len);
00373         char type = buffer[0];
00374         buffer.erase(0, len + 2);
00375         RETURN(type);
00376     }
00377     len = 0;
00378     string::const_iterator i = buffer.begin() + 2;
00379     unsigned char ch;
00380     int shift = 0;
00381     do {
00382         if (i == buffer.end() || shift > 28) {
00383             // Something is very wrong...
00384             throw Xapian::NetworkError("Insane message length specified!");
00385         }
00386         ch = *i++;
00387         len |= size_t(ch & 0x7f) << shift;
00388         shift += 7;
00389     } while ((ch & 0x80) == 0);
00390     len += 255;
00391     size_t header_len = (i - buffer.begin());
00392     read_at_least(header_len + len, end_time);
00393     result.assign(buffer.data() + header_len, len);
00394     char type = buffer[0];
00395     buffer.erase(0, header_len + len);
00396     RETURN(type);
00397 }
00398 
00399 void
00400 RemoteConnection::do_close(bool wait)
00401 {
00402     DEBUGCALL(REMOTE, void, "RemoteConnection::do_close", wait);
00403 
00404     if (fdout == -1) return;
00405     // We can be called from a destructor, so we can't throw an exception.
00406     if (wait) {
00407         try {
00408             send_message(MSG_SHUTDOWN, string(), OmTime());
00409         } catch (...) {
00410         }
00411 #ifdef __WIN32__
00412         HANDLE hin = fd_to_handle(fdin);
00413         char dummy;
00414         DWORD received;
00415         BOOL ok = ReadFile(hin, &dummy, 1, &received, &overlapped);
00416         if (!ok && GetLastError() == ERROR_IO_PENDING) {
00417             // Wait for asynchronous read to complete.
00418             (void)WaitForSingleObject(overlapped.hEvent, INFINITE);
00419         }
00420 #else
00421         // Wait for the connection to be closed - when this happens
00422         // select() will report that a read won't block.
00423         fd_set fdset;
00424         FD_ZERO(&fdset);
00425         FD_SET(fdin, &fdset);
00426         int res;
00427         do {
00428             res = select(fdin + 1, &fdset, 0, &fdset, NULL);
00429         } while (res < 0 && errno == EINTR);
00430 #endif
00431     }
00432     close_fd_or_socket(fdin);
00433     if (fdin != fdout) close_fd_or_socket(fdout);
00434     fdout = -1;
00435 }
00436 
00437 #ifdef __WIN32__
00438 DWORD
00439 RemoteConnection::calc_read_wait_msecs(const OmTime & end_time)
00440 {
00441     if (!end_time.is_set())
00442         return INFINITE;
00443 
00444     // Calculate how far in the future end_time is.
00445     OmTime now(OmTime::now());
00446 
00447     DWORD msecs;
00448 
00449     // msecs is unsigned, so we mustn't try and return a negative value
00450     if (now > end_time) {
00451         throw Xapian::NetworkTimeoutError("Timeout expired before starting read", context);
00452     }
00453     OmTime time_diff = end_time - now;
00454     msecs = time_diff.sec * 1000 + time_diff.usec / 1000;
00455     return msecs;
00456 }
00457 #endif

Documentation for Xapian (version 1.0.10).
Generated on 24 Dec 2008 by Doxygen 1.5.2.