git-tag-release: Push just the new tag; fetch before retry
[xapian.git] / xapian-core / net / tcpclient.cc
blob88cf7e0a5fdca463b6b68b1eeac8c0f39e771b8c
1 /* tcpclient.cc: Open a TCP connection to a server.
3 * Copyright 1999,2000,2001 BrightStation PLC
4 * Copyright 2002 Ananova Ltd
5 * Copyright 2004,2005,2006,2007,2008,2010,2012,2013,2015 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 "tcpclient.h"
27 #include "remoteconnection.h"
28 #include "str.h"
29 #include <xapian/error.h>
31 #include "realtime.h"
32 #include "safeerrno.h"
33 #include "safefcntl.h"
34 #include "safenetdb.h"
35 #include "safesysselect.h"
36 #include "safesyssocket.h"
37 #include "socket_utils.h"
39 #include <cmath>
40 #include <cstring>
41 #ifndef __WIN32__
42 # include <netinet/in.h>
43 # include <netinet/tcp.h>
44 #endif
46 using namespace std;
48 int
49 TcpClient::open_socket(const std::string & hostname, int port,
50 double timeout_connect, bool tcp_nodelay)
52 struct addrinfo hints;
53 memset(&hints, 0, sizeof(struct addrinfo));
54 hints.ai_family = AF_INET;
55 hints.ai_socktype = SOCK_STREAM;
56 hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
57 hints.ai_protocol = 0;
59 struct addrinfo *result;
60 int s = getaddrinfo(hostname.c_str(), str(port).c_str(), &hints, &result);
61 if (s != 0) {
62 throw Xapian::NetworkError("Couldn't resolve host " + hostname,
63 eai_to_xapian(s));
66 int connect_errno = 0;
67 int socketfd = -1;
68 for (struct addrinfo * rp = result; rp != NULL; rp = rp->ai_next) {
69 int socktype = rp->ai_socktype | SOCK_CLOEXEC;
70 int fd = socket(rp->ai_family, socktype, rp->ai_protocol);
71 if (fd == -1)
72 continue;
74 #if !defined __WIN32__ && defined F_SETFD && defined FD_CLOEXEC
75 // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as
76 // on Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC
77 // SOCK_CLOEXEC' to allow '#ifdef SOCK_CLOEXEC' to work.
78 if (SOCK_CLOEXEC == 0)
79 (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
80 #endif
82 #ifdef __WIN32__
83 ULONG enabled = 1;
84 int rc = ioctlsocket(fd, FIONBIO, &enabled);
85 #define FLAG_NAME "FIONBIO"
86 #elif defined O_NONBLOCK
87 int rc = fcntl(fd, F_SETFL, O_NONBLOCK);
88 #define FLAG_NAME "O_NONBLOCK"
89 #else
90 int rc = fcntl(fd, F_SETFL, O_NDELAY);
91 #define FLAG_NAME "O_NDELAY"
92 #endif
93 if (rc < 0) {
94 int saved_errno = socket_errno(); // note down in case close hits an error
95 close_fd_or_socket(fd);
96 freeaddrinfo(result);
97 throw Xapian::NetworkError("Couldn't set " FLAG_NAME, saved_errno);
98 #undef FLAG_NAME
101 if (tcp_nodelay) {
102 int optval = 1;
103 // 4th argument might need to be void* or char* - cast it to char*
104 // since C++ allows implicit conversion to void* but not from
105 // void*.
106 if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
107 reinterpret_cast<char *>(&optval),
108 sizeof(optval)) < 0) {
109 int saved_errno = socket_errno(); // note down in case close hits an error
110 close_fd_or_socket(fd);
111 freeaddrinfo(result);
112 throw Xapian::NetworkError("Couldn't set TCP_NODELAY", saved_errno);
116 int retval = connect(fd, rp->ai_addr, rp->ai_addrlen);
117 if (retval == 0) {
118 socketfd = fd;
119 break;
122 int err = socket_errno();
123 if (
124 #ifdef __WIN32__
125 WSAGetLastError() == WSAEWOULDBLOCK
126 #else
127 err == EINPROGRESS
128 #endif
130 // Wait for the socket to be writable, with a timeout.
131 fd_set fdset;
132 FD_ZERO(&fdset);
133 FD_SET(fd, &fdset);
135 do {
136 // FIXME: Reduce the timeout if we retry on EINTR.
137 struct timeval tv;
138 RealTime::to_timeval(timeout_connect, &tv);
139 retval = select(fd + 1, 0, &fdset, &fdset, &tv);
140 } while (retval < 0 && errno == EINTR);
142 if (retval <= 0) {
143 int saved_errno = errno;
144 close_fd_or_socket(fd);
145 freeaddrinfo(result);
146 if (retval < 0)
147 throw Xapian::NetworkError("Couldn't connect (select() on socket failed)",
148 saved_errno);
149 throw Xapian::NetworkTimeoutError("Timed out waiting to connect", ETIMEDOUT);
152 err = 0;
153 SOCKLEN_T len = sizeof(err);
155 // 4th argument might need to be void* or char* - cast it to char*
156 // since C++ allows implicit conversion to void* but not from void*.
157 retval = getsockopt(fd, SOL_SOCKET, SO_ERROR,
158 reinterpret_cast<char *>(&err), &len);
160 if (retval < 0) {
161 int saved_errno = socket_errno(); // note down in case close hits an error
162 close_fd_or_socket(fd);
163 freeaddrinfo(result);
164 throw Xapian::NetworkError("Couldn't get socket options", saved_errno);
166 if (err == 0) {
167 // Connected successfully.
168 socketfd = fd;
169 break;
173 // Note down the error code for the first address we try, which seems
174 // likely to be more helpful than the last in the case where they
175 // differ.
176 if (connect_errno == 0)
177 connect_errno = err;
179 // Failed to connect.
180 CLOSESOCKET(fd);
183 freeaddrinfo(result);
185 if (socketfd == -1) {
186 throw Xapian::NetworkError("Couldn't connect", connect_errno);
189 #ifdef __WIN32__
190 ULONG enabled = 0;
191 ioctlsocket(socketfd, FIONBIO, &enabled);
192 #else
193 fcntl(socketfd, F_SETFL, 0);
194 #endif
195 return socketfd;