summaryrefslogtreecommitdiff
path: root/src/libgit2/streams/socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libgit2/streams/socket.c')
-rw-r--r--src/libgit2/streams/socket.c212
1 files changed, 189 insertions, 23 deletions
diff --git a/src/libgit2/streams/socket.c b/src/libgit2/streams/socket.c
index 8f23e746e..a463312fd 100644
--- a/src/libgit2/streams/socket.c
+++ b/src/libgit2/streams/socket.c
@@ -8,7 +8,6 @@
#include "streams/socket.h"
#include "posix.h"
-#include "netops.h"
#include "registry.h"
#include "runtime.h"
#include "stream.h"
@@ -29,6 +28,9 @@
# endif
#endif
+int git_socket_stream__connect_timeout = 0;
+int git_socket_stream__timeout = 0;
+
#ifdef GIT_WIN32
static void net_set_error(const char *str)
{
@@ -67,21 +69,119 @@ static int close_socket(GIT_SOCKET s)
}
+static int set_nonblocking(GIT_SOCKET s)
+{
+#ifdef GIT_WIN32
+ unsigned long nonblocking = 1;
+
+ if (ioctlsocket(s, FIONBIO, &nonblocking) != 0) {
+ net_set_error("could not set socket non-blocking");
+ return -1;
+ }
+#else
+ int flags;
+
+ if ((flags = fcntl(s, F_GETFL, 0)) == -1) {
+ net_set_error("could not query socket flags");
+ return -1;
+ }
+
+ flags |= O_NONBLOCK;
+
+ if (fcntl(s, F_SETFL, flags) != 0) {
+ net_set_error("could not set socket non-blocking");
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+/* Promote a sockerr to an errno for our error handling routines */
+static int handle_sockerr(GIT_SOCKET socket)
+{
+ int sockerr;
+ socklen_t errlen = sizeof(sockerr);
+
+ if (getsockopt(socket, SOL_SOCKET, SO_ERROR,
+ (void *)&sockerr, &errlen) < 0)
+ return -1;
+
+ if (sockerr == ETIMEDOUT)
+ return GIT_TIMEOUT;
+
+ errno = sockerr;
+ return -1;
+}
+
+GIT_INLINE(bool) connect_would_block(int error)
+{
+#ifdef GIT_WIN32
+ if (error == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
+ return true;
+#endif
+
+ if (error == -1 && errno == EINPROGRESS)
+ return true;
+
+ return false;
+}
+
+static int connect_with_timeout(
+ GIT_SOCKET socket,
+ const struct sockaddr *address,
+ socklen_t address_len,
+ int timeout)
+{
+ struct pollfd fd;
+ int error;
+
+ if (timeout && (error = set_nonblocking(socket)) < 0)
+ return error;
+
+ error = connect(socket, address, address_len);
+
+ if (error == 0 || !connect_would_block(error))
+ return error;
+
+ fd.fd = socket;
+ fd.events = POLLOUT;
+ fd.revents = 0;
+
+ error = p_poll(&fd, 1, timeout);
+
+ if (error == 0) {
+ return GIT_TIMEOUT;
+ } else if (error != 1) {
+ return -1;
+ } else if ((fd.revents & (POLLPRI | POLLHUP | POLLERR))) {
+ return handle_sockerr(socket);
+ } else if ((fd.revents & POLLOUT) != POLLOUT) {
+ git_error_set(GIT_ERROR_NET,
+ "unknown error while polling for connect: %d",
+ fd.revents);
+ return -1;
+ }
+
+ return 0;
+}
+
static int socket_connect(git_stream *stream)
{
- struct addrinfo *info = NULL, *p;
- struct addrinfo hints;
git_socket_stream *st = (git_socket_stream *) stream;
GIT_SOCKET s = INVALID_SOCKET;
- int ret;
+ struct addrinfo *info = NULL, *p;
+ struct addrinfo hints;
+ int error;
memset(&hints, 0x0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
- if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) {
+ if ((error = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) {
git_error_set(GIT_ERROR_NET,
- "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret));
+ "failed to resolve address for %s: %s",
+ st->host, p_gai_strerror(error));
return -1;
}
@@ -91,51 +191,115 @@ static int socket_connect(git_stream *stream)
if (s == INVALID_SOCKET)
continue;
- if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0)
+ error = connect_with_timeout(s, p->ai_addr,
+ (socklen_t)p->ai_addrlen,
+ st->parent.connect_timeout);
+
+ if (error == 0)
break;
/* If we can't connect, try the next one */
close_socket(s);
s = INVALID_SOCKET;
+
+ if (error == GIT_TIMEOUT)
+ break;
}
/* Oops, we couldn't connect to any address */
- if (s == INVALID_SOCKET && p == NULL) {
- git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host);
- p_freeaddrinfo(info);
- return -1;
+ if (s == INVALID_SOCKET) {
+ if (error == GIT_TIMEOUT)
+ git_error_set(GIT_ERROR_NET, "failed to connect to %s: Operation timed out", st->host);
+ else
+ git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host);
+ error = -1;
+ goto done;
}
+ if (st->parent.timeout && !st->parent.connect_timeout &&
+ (error = set_nonblocking(s)) < 0)
+ return error;
+
st->s = s;
+ error = 0;
+
+done:
p_freeaddrinfo(info);
- return 0;
+ return error;
}
-static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags)
+static ssize_t socket_write(
+ git_stream *stream,
+ const char *data,
+ size_t len,
+ int flags)
{
git_socket_stream *st = (git_socket_stream *) stream;
- ssize_t written;
+ struct pollfd fd;
+ ssize_t ret;
GIT_ASSERT(flags == 0);
GIT_UNUSED(flags);
- errno = 0;
+ ret = p_send(st->s, data, len, 0);
- if ((written = p_send(st->s, data, len, 0)) < 0) {
- net_set_error("error sending data");
+ if (st->parent.timeout && ret < 0 &&
+ (errno == EAGAIN || errno != EWOULDBLOCK)) {
+ fd.fd = st->s;
+ fd.events = POLLOUT;
+ fd.revents = 0;
+
+ ret = p_poll(&fd, 1, st->parent.timeout);
+
+ if (ret == 1) {
+ ret = p_send(st->s, data, len, 0);
+ } else if (ret == 0) {
+ git_error_set(GIT_ERROR_NET,
+ "could not write to socket: timed out");
+ return GIT_TIMEOUT;
+ }
+ }
+
+ if (ret < 0) {
+ net_set_error("error receiving data from socket");
return -1;
}
- return written;
+ return ret;
}
-static ssize_t socket_read(git_stream *stream, void *data, size_t len)
+static ssize_t socket_read(
+ git_stream *stream,
+ void *data,
+ size_t len)
{
- ssize_t ret;
git_socket_stream *st = (git_socket_stream *) stream;
+ struct pollfd fd;
+ ssize_t ret;
+
+ ret = p_recv(st->s, data, len, 0);
+
+ if (st->parent.timeout && ret < 0 &&
+ (errno == EAGAIN || errno != EWOULDBLOCK)) {
+ fd.fd = st->s;
+ fd.events = POLLIN;
+ fd.revents = 0;
+
+ ret = p_poll(&fd, 1, st->parent.timeout);
- if ((ret = p_recv(st->s, data, len, 0)) < 0)
- net_set_error("error receiving socket data");
+ if (ret == 1) {
+ ret = p_recv(st->s, data, len, 0);
+ } else if (ret == 0) {
+ git_error_set(GIT_ERROR_NET,
+ "could not read from socket: timed out");
+ return GIT_TIMEOUT;
+ }
+ }
+
+ if (ret < 0) {
+ net_set_error("error receiving data from socket");
+ return -1;
+ }
return ret;
}
@@ -183,6 +347,8 @@ static int default_socket_stream_new(
}
st->parent.version = GIT_STREAM_VERSION;
+ st->parent.timeout = git_socket_stream__timeout;
+ st->parent.connect_timeout = git_socket_stream__connect_timeout;
st->parent.connect = socket_connect;
st->parent.write = socket_write;
st->parent.read = socket_read;
@@ -249,7 +415,7 @@ int git_socket_stream_global_init(void)
return git_runtime_shutdown_register(socket_stream_global_shutdown);
}
-
+
#else
#include "stream.h"