Branch data Line data Source code
1 : : /* tcpclient.cc: Open a TCP connection to a server.
2 : : *
3 : : * Copyright 1999,2000,2001 BrightStation PLC
4 : : * Copyright 2002 Ananova Ltd
5 : : * Copyright 2004,2005,2006,2007,2008,2010 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 "remoteconnection.h"
26 : : #include "tcpclient.h"
27 : : #include <xapian/error.h>
28 : :
29 : : #include "safeerrno.h"
30 : : #include "safefcntl.h"
31 : : #include "socket_utils.h"
32 : :
33 : : #include <cmath>
34 : : #include <cstring>
35 : : #ifndef __WIN32__
36 : : # include <netdb.h>
37 : : # include <netinet/in.h>
38 : : # include <netinet/tcp.h>
39 : : # include <sys/socket.h>
40 : : # include "safesysselect.h"
41 : : #endif
42 : :
43 : : using namespace std;
44 : :
45 : : int
46 : 771 : TcpClient::open_socket(const std::string & hostname, int port,
47 : : double timeout_connect, bool tcp_nodelay)
48 : : {
49 : : // FIXME: timeout on gethostbyname() ?
50 : 771 : struct hostent *host = gethostbyname(hostname.c_str());
51 : :
52 [ - + ]: 771 : if (host == 0) {
53 : : throw Xapian::NetworkError(std::string("Couldn't resolve host ") + hostname,
54 : : #ifdef __WIN32__
55 : : socket_errno()
56 : : #else
57 : : // "socket_errno()" is just errno on UNIX which is
58 : : // inappropriate here - if gethostbyname() returns NULL an
59 : : // error code is available in h_errno (with values
60 : : // incompatible with errno). On Linux at least, if h_errno
61 : : // is < 0, then the error code *IS* in errno!
62 : : (h_errno < 0 ? errno : -h_errno)
63 : : #endif
64 [ # # ]: 0 : );
65 : : }
66 : :
67 : 771 : int socketfd = socket(PF_INET, SOCK_STREAM, 0);
68 : :
69 [ - + ]: 771 : if (socketfd < 0) {
70 : 0 : throw Xapian::NetworkError("Couldn't create socket", socket_errno());
71 : : }
72 : :
73 : : struct sockaddr_in remaddr;
74 : 771 : memset(&remaddr, 0, sizeof(remaddr));
75 : 771 : remaddr.sin_family = AF_INET;
76 : 771 : remaddr.sin_port = htons(port);
77 : 771 : memcpy(&remaddr.sin_addr, host->h_addr, host->h_length);
78 : :
79 : : #ifdef __WIN32__
80 : : ULONG enabled = 1;
81 : : int rc = ioctlsocket(socketfd, FIONBIO, &enabled);
82 : : #else
83 : 771 : int rc = fcntl(socketfd, F_SETFL, O_NDELAY);
84 : : #endif
85 [ - + ]: 771 : if (rc < 0) {
86 : 0 : int saved_errno = socket_errno(); // note down in case close hits an error
87 : 0 : close_fd_or_socket(socketfd);
88 : 0 : throw Xapian::NetworkError("Couldn't set O_NDELAY", saved_errno);
89 : : }
90 : :
91 [ + - ]: 771 : if (tcp_nodelay) {
92 : 771 : int optval = 1;
93 : : // 4th argument might need to be void* or char* - cast it to char*
94 : : // since C++ allows implicit conversion to void* but not from void*.
95 [ - + ]: 771 : if (setsockopt(socketfd, IPPROTO_TCP, TCP_NODELAY,
96 : : reinterpret_cast<char *>(&optval),
97 : : sizeof(optval)) < 0) {
98 : 0 : int saved_errno = socket_errno(); // note down in case close hits an error
99 : 0 : close_fd_or_socket(socketfd);
100 : 0 : throw Xapian::NetworkError("Couldn't set TCP_NODELAY", saved_errno);
101 : : }
102 : : }
103 : :
104 : : int retval = connect(socketfd, reinterpret_cast<sockaddr *>(&remaddr),
105 : 771 : sizeof(remaddr));
106 : :
107 [ + - ]: 771 : if (retval < 0) {
108 : : #ifdef __WIN32__
109 : : if (WSAGetLastError() != WSAEWOULDBLOCK) {
110 : : #else
111 [ - + ]: 771 : if (socket_errno() != EINPROGRESS) {
112 : : #endif
113 : 0 : int saved_errno = socket_errno(); // note down in case close hits an error
114 : 0 : close_fd_or_socket(socketfd);
115 : 0 : throw Xapian::NetworkError("Couldn't connect (1)", saved_errno);
116 : : }
117 : :
118 : : // wait for input to be available.
119 : : fd_set fdset;
120 : 771 : FD_ZERO(&fdset);
121 : 771 : FD_SET(socketfd, &fdset);
122 : :
123 [ - + # # ]: 771 : do {
[ - + ]
124 : : // FIXME: Reduce the timeout if we retry on EINTR.
125 : : struct timeval tv;
126 : 771 : tv.tv_sec = long(timeout_connect);
127 : 771 : tv.tv_usec = long(std::fmod(timeout_connect, 1.0) * 1e6);
128 : :
129 : 771 : retval = select(socketfd + 1, 0, &fdset, &fdset, &tv);
130 : : } while (retval < 0 && errno == EINTR);
131 : :
132 [ - + ]: 771 : if (retval < 0) {
133 : 0 : int saved_errno = errno;
134 : 0 : close_fd_or_socket(socketfd);
135 : 0 : throw Xapian::NetworkError("Couldn't connect (2)", saved_errno);
136 : : }
137 : :
138 [ - + ]: 771 : if (retval <= 0) {
139 : 0 : close_fd_or_socket(socketfd);
140 : 0 : throw Xapian::NetworkTimeoutError("Timed out waiting to connect", ETIMEDOUT);
141 : : }
142 : :
143 : 771 : int err = 0;
144 : 771 : SOCKLEN_T len = sizeof(err);
145 : :
146 : : // 4th argument might need to be void* or char* - cast it to char*
147 : : // since C++ allows implicit conversion to void* but not from void*.
148 : : retval = getsockopt(socketfd, SOL_SOCKET, SO_ERROR,
149 : 771 : reinterpret_cast<char *>(&err), &len);
150 : :
151 [ - + ]: 771 : if (retval < 0) {
152 : 0 : int saved_errno = socket_errno(); // note down in case close hits an error
153 : 0 : close_fd_or_socket(socketfd);
154 : 0 : throw Xapian::NetworkError("Couldn't get socket options", saved_errno);
155 : : }
156 [ - + ]: 771 : if (err) {
157 : 0 : close_fd_or_socket(socketfd);
158 : 0 : throw Xapian::NetworkError("Couldn't connect (3)", err);
159 : : }
160 : : }
161 : :
162 : : #ifdef __WIN32__
163 : : enabled = 0;
164 : : ioctlsocket(socketfd, FIONBIO, &enabled);
165 : : #else
166 : 771 : fcntl(socketfd, F_SETFL, 0);
167 : : #endif
168 : 771 : return socketfd;
169 : : }
|