[ci] Fix clang-santisers job for GHA change
[xapian.git] / xapian-core / net / tcpclient.cc
blob3a2cf6050eacd44e140c42b7c3dc29f1e3c1104a
1 /** @file
2 * @brief Open a TCP connection to a server.
3 */
4 /* Copyright 2007-2024 Olly Betts
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <config.h>
23 #include "tcpclient.h"
25 #include <xapian/error.h>
27 #include "realtime.h"
28 #include "resolver.h"
29 #include "socket_utils.h"
31 #include "safefcntl.h"
32 #include "safesyssocket.h"
34 #ifdef HAVE_POLL_H
35 # include <poll.h>
36 #else
37 # include "safesysselect.h"
38 #endif
40 #include <cerrno>
41 #ifndef __WIN32__
42 # include <netinet/in.h>
43 # include <netinet/tcp.h>
44 #endif
46 #include <string_view>
48 using namespace std;
50 int
51 TcpClient::open_socket(std::string_view hostname, int port,
52 double timeout_connect, bool tcp_nodelay,
53 const string& context)
55 int socketfd = -1;
56 int connect_errno = 0;
57 for (auto&& r : Resolver(hostname, port)) {
58 int socktype = r.ai_socktype | SOCK_CLOEXEC;
59 #ifdef SOCK_NONBLOCK
60 // Set the socket as non-blocking so we can implement a timeout on
61 // the connection attempt by using select() or poll(). If
62 // SOCK_NONBLOCK is available we can get socket() to do this, but
63 // if not we call fcntl()/ioctlsocket() below to set it.
64 socktype |= SOCK_NONBLOCK;
65 #endif
66 int fd = socket(r.ai_family, socktype, r.ai_protocol);
67 if (fd < 0)
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 int rc = [&]() {
81 ULONG on = 1;
82 return ioctlsocket(fd, FIONBIO, &on);
83 }();
84 #define FLAG_NAME "FIONBIO"
85 #elif defined O_NONBLOCK
86 int rc = fcntl(fd, F_SETFL, O_NONBLOCK);
87 #define FLAG_NAME "O_NONBLOCK"
88 #else
89 int rc = fcntl(fd, F_SETFL, O_NDELAY);
90 #define FLAG_NAME "O_NDELAY"
91 #endif
92 if (rc < 0) {
93 int saved_errno = socket_errno();
94 CLOSESOCKET(fd);
95 throw Xapian::NetworkError("Couldn't set " FLAG_NAME,
96 context,
97 saved_errno);
98 #undef FLAG_NAME
100 #endif
102 if (tcp_nodelay) {
103 int on = 1;
104 // 4th argument might need to be void* or char* - cast it to char*
105 // since C++ allows implicit conversion to void* but not from
106 // void*.
107 if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
108 reinterpret_cast<char*>(&on),
109 sizeof(on)) < 0) {
110 int setsockopt_errno = socket_errno();
111 CLOSESOCKET(fd);
112 throw Xapian::NetworkError("Couldn't set TCP_NODELAY",
113 context,
114 setsockopt_errno);
118 int retval = connect(fd, r.ai_addr, r.ai_addrlen);
119 if (retval == 0) {
120 socketfd = fd;
121 break;
124 int err = socket_errno();
125 if (
126 #ifndef __WIN32__
127 err == EINPROGRESS
128 #else
129 err == WSAEWOULDBLOCK
130 #endif
132 // Wait for the socket to be writable or give an error, with a
133 // timeout. FIXME: Reduce the timeout if we retry.
134 #ifdef HAVE_POLL
135 struct pollfd fds;
136 fds.fd = fd;
137 fds.events = POLLOUT;
138 do {
139 retval = poll(&fds, 1, int(timeout_connect * 1000));
140 } while (retval < 0 && (errno == EINTR || errno == EAGAIN));
141 # define FUNC_NAME "poll()"
142 #else
143 fd_set fdset;
144 FD_ZERO(&fdset);
145 do {
146 FD_SET(fd, &fdset);
147 struct timeval tv;
148 RealTime::to_timeval(timeout_connect, &tv);
149 retval = select(fd + 1, 0, &fdset, 0, &tv);
150 } while (retval < 0 && (errno == EINTR || errno == EAGAIN));
151 # define FUNC_NAME "select()"
152 #endif
154 if (retval <= 0) {
155 int saved_errno = errno;
156 CLOSESOCKET(fd);
157 if (retval < 0) {
158 throw Xapian::NetworkError("Couldn't connect ("
159 FUNC_NAME " on socket failed)",
160 context,
161 saved_errno);
162 #undef FUNC_NAME
164 throw Xapian::NetworkTimeoutError("Timed out waiting to "
165 "connect",
166 context,
167 ETIMEDOUT);
170 err = 0;
171 SOCKLEN_T len = sizeof(err);
173 // 4th argument might need to be void* or char* - cast it to char*
174 // since C++ allows implicit conversion to void* but not from void*.
175 retval = getsockopt(fd, SOL_SOCKET, SO_ERROR,
176 reinterpret_cast<char*>(&err), &len);
178 if (retval < 0) {
179 int getsockopt_errno = socket_errno();
180 CLOSESOCKET(fd);
181 throw Xapian::NetworkError("getsockopt failed",
182 context,
183 getsockopt_errno);
185 if (err == 0) {
186 // Connected successfully.
187 socketfd = fd;
188 break;
192 // Note down the error code for the first address we try, which seems
193 // likely to be more helpful than the last in the case where they
194 // differ.
195 if (connect_errno == 0)
196 connect_errno = err;
198 // Failed to connect.
199 CLOSESOCKET(fd);
202 if (socketfd < 0) {
203 throw Xapian::NetworkError("connect failed",
204 context,
205 connect_errno);
208 // Set the socket to be blocking.
209 #ifndef __WIN32__
210 fcntl(socketfd, F_SETFL, 0);
211 #else
212 ULONG off = 0;
213 ioctlsocket(socketfd, FIONBIO, &off);
214 #endif
216 return socketfd;