Include <stdlib.h> in check for sys_errlist
[xapian.git] / xapian-core / net / tcpclient.cc
blob22d45613f0c71d04c93799153b1bcef1e1c887a0
1 /** @file
2 * @brief Open a TCP connection to a server.
3 */
4 /* Copyright 1999,2000,2001 BrightStation PLC
5 * Copyright 2002 Ananova Ltd
6 * Copyright 2004,2005,2006,2007,2008,2010,2012,2013,2015,2017 Olly Betts
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
24 #include <config.h>
26 #include "tcpclient.h"
28 #include "remoteconnection.h"
29 #include "resolver.h"
30 #include "str.h"
31 #include <xapian/error.h>
33 #include "realtime.h"
34 #include "safefcntl.h"
35 #include "safenetdb.h"
36 #include "safesyssocket.h"
37 #include "socket_utils.h"
39 #ifdef HAVE_POLL_H
40 # include <poll.h>
41 #else
42 # include "safesysselect.h"
43 #endif
45 #include <cerrno>
46 #include <cmath>
47 #include <cstring>
48 #ifndef __WIN32__
49 # include <netinet/in.h>
50 # include <netinet/tcp.h>
51 #endif
53 using namespace std;
55 int
56 TcpClient::open_socket(const std::string & hostname, int port,
57 double timeout_connect, bool tcp_nodelay)
59 int socketfd = -1;
60 int connect_errno = 0;
61 for (auto&& r : Resolver(hostname, port)) {
62 int socktype = r.ai_socktype | SOCK_CLOEXEC;
63 #ifdef SOCK_NONBLOCK
64 socktype |= SOCK_NONBLOCK;
65 #endif
66 int fd = socket(r.ai_family, socktype, r.ai_protocol);
67 if (fd == -1)
68 continue;
70 #if !defined __WIN32__ && defined F_SETFD && defined FD_CLOEXEC
71 // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as
72 // on Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC
73 // SOCK_CLOEXEC' to allow '#ifdef SOCK_CLOEXEC' to work.
74 if (SOCK_CLOEXEC == 0)
75 (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
76 #endif
78 #ifndef SOCK_NONBLOCK
79 #ifdef __WIN32__
80 ULONG enabled = 1;
81 int rc = ioctlsocket(fd, FIONBIO, &enabled);
82 #define FLAG_NAME "FIONBIO"
83 #elif defined O_NONBLOCK
84 int rc = fcntl(fd, F_SETFL, O_NONBLOCK);
85 #define FLAG_NAME "O_NONBLOCK"
86 #else
87 int rc = fcntl(fd, F_SETFL, O_NDELAY);
88 #define FLAG_NAME "O_NDELAY"
89 #endif
90 if (rc < 0) {
91 int saved_errno = socket_errno(); // note down in case close hits an error
92 CLOSESOCKET(fd);
93 throw Xapian::NetworkError("Couldn't set " FLAG_NAME, saved_errno);
94 #undef FLAG_NAME
96 #endif
98 if (tcp_nodelay) {
99 int optval = 1;
100 // 4th argument might need to be void* or char* - cast it to char*
101 // since C++ allows implicit conversion to void* but not from
102 // void*.
103 if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
104 reinterpret_cast<char *>(&optval),
105 sizeof(optval)) < 0) {
106 int saved_errno = socket_errno(); // note down in case close hits an error
107 CLOSESOCKET(fd);
108 throw Xapian::NetworkError("Couldn't set TCP_NODELAY", saved_errno);
112 int retval = connect(fd, r.ai_addr, r.ai_addrlen);
113 if (retval == 0) {
114 socketfd = fd;
115 break;
118 int err = socket_errno();
119 if (
120 #ifdef __WIN32__
121 WSAGetLastError() == WSAEWOULDBLOCK
122 #else
123 err == EINPROGRESS
124 #endif
126 // Wait for the socket to be writable or give an error, with a
127 // timeout.
128 #ifdef HAVE_POLL
129 struct pollfd fds;
130 fds.fd = fd;
131 fds.events = POLLOUT;
132 do {
133 retval = poll(&fds, 1, int(timeout_connect * 1000));
134 } while (retval < 0 && (errno == EINTR || errno == EAGAIN));
135 #else
136 fd_set fdset;
137 FD_ZERO(&fdset);
138 do {
139 FD_SET(fd, &fdset);
140 // FIXME: Reduce the timeout if we retry on EINTR.
141 struct timeval tv;
142 RealTime::to_timeval(timeout_connect, &tv);
143 retval = select(fd + 1, 0, &fdset, 0, &tv);
144 } while (retval < 0 && (errno == EINTR || errno == EAGAIN));
145 #endif
147 if (retval <= 0) {
148 int saved_errno = errno;
149 CLOSESOCKET(fd);
150 if (retval < 0)
151 throw Xapian::NetworkError("Couldn't connect (poll() or "
152 "select() on socket failed)",
153 saved_errno);
154 throw Xapian::NetworkTimeoutError("Timed out waiting to connect", ETIMEDOUT);
157 err = 0;
158 SOCKLEN_T len = sizeof(err);
160 // 4th argument might need to be void* or char* - cast it to char*
161 // since C++ allows implicit conversion to void* but not from void*.
162 retval = getsockopt(fd, SOL_SOCKET, SO_ERROR,
163 reinterpret_cast<char *>(&err), &len);
165 if (retval < 0) {
166 int saved_errno = socket_errno(); // note down in case close hits an error
167 CLOSESOCKET(fd);
168 throw Xapian::NetworkError("Couldn't get socket options", saved_errno);
170 if (err == 0) {
171 // Connected successfully.
172 socketfd = fd;
173 break;
177 // Note down the error code for the first address we try, which seems
178 // likely to be more helpful than the last in the case where they
179 // differ.
180 if (connect_errno == 0)
181 connect_errno = err;
183 // Failed to connect.
184 CLOSESOCKET(fd);
187 if (socketfd == -1) {
188 throw Xapian::NetworkError("Couldn't connect", connect_errno);
191 #ifdef __WIN32__
192 ULONG enabled = 0;
193 ioctlsocket(socketfd, FIONBIO, &enabled);
194 #else
195 fcntl(socketfd, F_SETFL, 0);
196 #endif
197 return socketfd;