summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
authorRuss Allbery <rra@stanford.edu>2013-08-14 13:11:50 -0700
committerRuss Allbery <rra@stanford.edu>2013-08-14 15:09:56 -0700
commit2eed8e45623b5db3d99a891d8c1ecb353097bc07 (patch)
treed16dcbc4d0b0cb68e972d0b22b8d190f6fea7063 /util
parent771b253fcd2276dfafde580abee93b17c5580804 (diff)
Restart the client nonblocking connect wait on EINTR
If the client specifies a timeout, restart the wait for a nonblocking connect when interrupted by a signal. This can mean that a connect can take longer than the timeout if interrupted; hopefully both timeouts and catching signals are rare enough that this won't pose a serious issue. Change-Id: Ice391360acc66602d31f2d1bd2fe9296b6d7ff45 Reviewed-on: https://gerrit.stanford.edu/1257 Reviewed-by: Russ Allbery <rra@stanford.edu> Tested-by: Russ Allbery <rra@stanford.edu>
Diffstat (limited to 'util')
-rw-r--r--util/network.c72
1 files changed, 50 insertions, 22 deletions
diff --git a/util/network.c b/util/network.c
index c131aa8..2264802 100644
--- a/util/network.c
+++ b/util/network.c
@@ -416,6 +416,53 @@ network_source(socket_type fd, int family, const char *source)
/*
+ * Internal helper function that waits for a non-blocking connect to complete
+ * on a socket. Takes the file descriptor and the timeout. Returns 0 on a
+ * successful completion of the connect within the timeout and -1 on failure.
+ * On failure, sets the socket errno.
+ */
+static int
+connect_wait(socket_type fd, time_t timeout)
+{
+ int status, err;
+ socklen_t length;
+ struct timeval tv;
+ fd_set set;
+
+ /*
+ * Use select to poll the file descriptor. Loop if interrupted by a
+ * caught signal. This means we could wait for longer than the timeout
+ * when interrupted, but there's no good way of recovering the elapsed
+ * time that's worth the hassle.
+ */
+ do {
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+ FD_ZERO(&set);
+ FD_SET(fd, &set);
+ status = select(fd + 1, NULL, &set, NULL, &tv);
+ } while (status < 0 && socket_errno == EINTR);
+
+ /*
+ * If we timed out, set errno appropriately. If the connection completes,
+ * retrieve the actual status from the socket.
+ */
+ if (status == 0 && !FD_ISSET(fd, &set)) {
+ status = -1;
+ socket_set_errno(ETIMEDOUT);
+ } else if (status > 0 && FD_ISSET(fd, &set)) {
+ length = sizeof(err);
+ status = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &length);
+ if (status == 0) {
+ status = (err == 0) ? 0 : -1;
+ socket_set_errno(err);
+ }
+ }
+ return status;
+}
+
+
+/*
* Given a linked list of addrinfo structs representing the remote service,
* try to create a local socket and connect to that service. Takes an
* optional source address. Try each address in turn until one of them
@@ -427,10 +474,7 @@ socket_type
network_connect(const struct addrinfo *ai, const char *source, time_t timeout)
{
socket_type fd = INVALID_SOCKET;
- int oerrno, status, err;
- socklen_t len;
- struct timeval tv;
- fd_set set;
+ int oerrno, status;
for (status = -1; status != 0 && ai != NULL; ai = ai->ai_next) {
if (fd != INVALID_SOCKET)
@@ -445,24 +489,8 @@ network_connect(const struct addrinfo *ai, const char *source, time_t timeout)
else {
fdflag_nonblocking(fd, true);
status = connect(fd, ai->ai_addr, ai->ai_addrlen);
- if (status < 0 && socket_errno == EINPROGRESS) {
- tv.tv_sec = timeout;
- tv.tv_usec = 0;
- FD_ZERO(&set);
- FD_SET(fd, &set);
- status = select(fd + 1, NULL, &set, NULL, &tv);
- if (status == 0 && !FD_ISSET(fd, &set)) {
- status = -1;
- socket_set_errno(ETIMEDOUT);
- } else if (status > 0 && FD_ISSET(fd, &set)) {
- len = sizeof(err);
- status = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
- if (status == 0) {
- status = (err == 0) ? 0 : -1;
- socket_set_errno(err);
- }
- }
- }
+ if (status < 0 && socket_errno == EINPROGRESS)
+ status = connect_wait(fd, timeout);
oerrno = socket_errno;
fdflag_nonblocking(fd, false);
socket_set_errno(oerrno);