[honey] Fix portability to systems without pread()
[xapian.git] / xapian-core / net / tcpclient.cc
blobf0a9d2cd4f7152d532b527d408fcda5a55d0f82d
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,2017 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 "resolver.h"
29 #include "str.h"
30 #include <xapian/error.h>
32 #include "realtime.h"
33 #include "safeerrno.h"
34 #include "safefcntl.h"
35 #include "safenetdb.h"
36 #include "safesysselect.h"
37 #include "safesyssocket.h"
38 #include "socket_utils.h"
40 #include <cmath>
41 #include <cstring>
42 #ifndef __WIN32__
43 # include <netinet/in.h>
44 # include <netinet/tcp.h>
45 #endif
47 using namespace std;
49 int
50 TcpClient::open_socket(const std::string & hostname, int port,
51 double timeout_connect, bool tcp_nodelay)
53 int socketfd = -1;
54 int connect_errno = 0;
55 for (auto&& r : Resolver(hostname, port)) {
56 int socktype = r.ai_socktype | SOCK_CLOEXEC;
57 int fd = socket(r.ai_family, socktype, r.ai_protocol);
58 if (fd == -1)
59 continue;
61 #if !defined __WIN32__ && defined F_SETFD && defined FD_CLOEXEC
62 // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as
63 // on Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC
64 // SOCK_CLOEXEC' to allow '#ifdef SOCK_CLOEXEC' to work.
65 if (SOCK_CLOEXEC == 0)
66 (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
67 #endif
69 #ifdef __WIN32__
70 ULONG enabled = 1;
71 int rc = ioctlsocket(fd, FIONBIO, &enabled);
72 #define FLAG_NAME "FIONBIO"
73 #elif defined O_NONBLOCK
74 int rc = fcntl(fd, F_SETFL, O_NONBLOCK);
75 #define FLAG_NAME "O_NONBLOCK"
76 #else
77 int rc = fcntl(fd, F_SETFL, O_NDELAY);
78 #define FLAG_NAME "O_NDELAY"
79 #endif
80 if (rc < 0) {
81 int saved_errno = socket_errno(); // note down in case close hits an error
82 close_fd_or_socket(fd);
83 throw Xapian::NetworkError("Couldn't set " FLAG_NAME, saved_errno);
84 #undef FLAG_NAME
87 if (tcp_nodelay) {
88 int optval = 1;
89 // 4th argument might need to be void* or char* - cast it to char*
90 // since C++ allows implicit conversion to void* but not from
91 // void*.
92 if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
93 reinterpret_cast<char *>(&optval),
94 sizeof(optval)) < 0) {
95 int saved_errno = socket_errno(); // note down in case close hits an error
96 close_fd_or_socket(fd);
97 throw Xapian::NetworkError("Couldn't set TCP_NODELAY", saved_errno);
101 int retval = connect(fd, r.ai_addr, r.ai_addrlen);
102 if (retval == 0) {
103 socketfd = fd;
104 break;
107 int err = socket_errno();
108 if (
109 #ifdef __WIN32__
110 WSAGetLastError() == WSAEWOULDBLOCK
111 #else
112 err == EINPROGRESS
113 #endif
115 // Wait for the socket to be writable, with a timeout.
116 fd_set fdset;
117 FD_ZERO(&fdset);
118 FD_SET(fd, &fdset);
120 do {
121 // FIXME: Reduce the timeout if we retry on EINTR.
122 struct timeval tv;
123 RealTime::to_timeval(timeout_connect, &tv);
124 retval = select(fd + 1, 0, &fdset, &fdset, &tv);
125 } while (retval < 0 && (errno == EINTR || errno == EAGAIN));
127 if (retval <= 0) {
128 int saved_errno = errno;
129 close_fd_or_socket(fd);
130 if (retval < 0)
131 throw Xapian::NetworkError("Couldn't connect (select() on socket failed)",
132 saved_errno);
133 throw Xapian::NetworkTimeoutError("Timed out waiting to connect", ETIMEDOUT);
136 err = 0;
137 SOCKLEN_T len = sizeof(err);
139 // 4th argument might need to be void* or char* - cast it to char*
140 // since C++ allows implicit conversion to void* but not from void*.
141 retval = getsockopt(fd, SOL_SOCKET, SO_ERROR,
142 reinterpret_cast<char *>(&err), &len);
144 if (retval < 0) {
145 int saved_errno = socket_errno(); // note down in case close hits an error
146 close_fd_or_socket(fd);
147 throw Xapian::NetworkError("Couldn't get socket options", saved_errno);
149 if (err == 0) {
150 // Connected successfully.
151 socketfd = fd;
152 break;
156 // Note down the error code for the first address we try, which seems
157 // likely to be more helpful than the last in the case where they
158 // differ.
159 if (connect_errno == 0)
160 connect_errno = err;
162 // Failed to connect.
163 CLOSESOCKET(fd);
166 if (socketfd == -1) {
167 throw Xapian::NetworkError("Couldn't connect", connect_errno);
170 #ifdef __WIN32__
171 ULONG enabled = 0;
172 ioctlsocket(socketfd, FIONBIO, &enabled);
173 #else
174 fcntl(socketfd, F_SETFL, 0);
175 #endif
176 return socketfd;
179 #ifdef DISABLE_GPL_LIBXAPIAN
180 # error GPL source we cannot relicense included in libxapian
181 #endif