socket.c 4.95 KB
Newer Older
1 2 3 4 5 6 7
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8
#include "streams/socket.h"
9

10 11
#include "posix.h"
#include "netops.h"
12
#include "registry.h"
13
#include "stream.h"
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

#ifndef _WIN32
#	include <sys/types.h>
#	include <sys/socket.h>
#	include <sys/select.h>
#	include <sys/time.h>
#	include <netdb.h>
#	include <netinet/in.h>
#       include <arpa/inet.h>
#else
#	include <winsock2.h>
#	include <ws2tcpip.h>
#	ifdef _MSC_VER
#		pragma comment(lib, "ws2_32")
#	endif
#endif

#ifdef GIT_WIN32
static void net_set_error(const char *str)
{
	int error = WSAGetLastError();
	char * win32_error = git_win32_get_error_message(error);

	if (win32_error) {
38
		git_error_set(GIT_ERROR_NET, "%s: %s", str, win32_error);
39 40
		git__free(win32_error);
	} else {
41
		git_error_set(GIT_ERROR_NET, "%s", str);
42 43 44 45 46
	}
}
#else
static void net_set_error(const char *str)
{
47
	git_error_set(GIT_ERROR_NET, "%s: %s", str, strerror(errno));
48 49 50 51 52 53 54 55 56 57 58 59 60
}
#endif

static int close_socket(GIT_SOCKET s)
{
	if (s == INVALID_SOCKET)
		return 0;

#ifdef GIT_WIN32
	if (SOCKET_ERROR == closesocket(s))
		return -1;

	if (0 != WSACleanup()) {
61
		git_error_set(GIT_ERROR_OS, "winsock cleanup failed");
62 63 64 65 66 67 68 69 70 71
		return -1;
	}

	return 0;
#else
	return close(s);
#endif

}

72
static int socket_connect(git_stream *stream)
73 74 75
{
	struct addrinfo *info = NULL, *p;
	struct addrinfo hints;
76
	git_socket_stream *st = (git_socket_stream *) stream;
77 78 79 80 81 82 83 84 85
	GIT_SOCKET s = INVALID_SOCKET;
	int ret;

#ifdef GIT_WIN32
	/* on win32, the WSA context needs to be initialized
	 * before any socket calls can be performed */
	WSADATA wsd;

	if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
86
		git_error_set(GIT_ERROR_OS, "winsock init failed");
87 88 89 90 91
		return -1;
	}

	if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
		WSACleanup();
92
		git_error_set(GIT_ERROR_OS, "winsock init failed");
93 94 95 96 97 98 99 100 101
		return -1;
	}
#endif

	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) {
102
		git_error_set(GIT_ERROR_NET,
103
			   "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret));
104 105 106 107
		return -1;
	}

	for (p = info; p != NULL; p = p->ai_next) {
108
		s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol);
109

110 111
		if (s == INVALID_SOCKET)
			continue;
112 113 114 115 116 117 118 119 120 121 122

		if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0)
			break;

		/* If we can't connect, try the next one */
		close_socket(s);
		s = INVALID_SOCKET;
	}

	/* Oops, we couldn't connect to any address */
	if (s == INVALID_SOCKET && p == NULL) {
123
		git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host);
124 125 126 127 128 129 130 131 132
		p_freeaddrinfo(info);
		return -1;
	}

	st->s = s;
	p_freeaddrinfo(info);
	return 0;
}

133
static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags)
134
{
135
	git_socket_stream *st = (git_socket_stream *) stream;
136
	ssize_t written;
137

138
	errno = 0;
139

140
	if ((written = p_send(st->s, data, len, flags)) < 0) {
Edward Thomson committed
141
		net_set_error("error sending data");
142
		return -1;
143 144
	}

145
	return written;
146 147
}

148
static ssize_t socket_read(git_stream *stream, void *data, size_t len)
149 150
{
	ssize_t ret;
151
	git_socket_stream *st = (git_socket_stream *) stream;
152 153

	if ((ret = p_recv(st->s, data, len, 0)) < 0)
Edward Thomson committed
154
		net_set_error("error receiving socket data");
155 156 157 158

	return ret;
}

159
static int socket_close(git_stream *stream)
160
{
161
	git_socket_stream *st = (git_socket_stream *) stream;
162 163 164 165 166 167 168 169
	int error;

	error = close_socket(st->s);
	st->s = INVALID_SOCKET;

	return error;
}

170
static void socket_free(git_stream *stream)
171
{
172
	git_socket_stream *st = (git_socket_stream *) stream;
173 174 175 176 177 178

	git__free(st->host);
	git__free(st->port);
	git__free(st);
}

179 180 181 182
static int default_socket_stream_new(
	git_stream **out,
	const char *host,
	const char *port)
183
{
184
	git_socket_stream *st;
185

186 187 188
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(host);
	GIT_ASSERT_ARG(port);
189

190
	st = git__calloc(1, sizeof(git_socket_stream));
191
	GIT_ERROR_CHECK_ALLOC(st);
192 193

	st->host = git__strdup(host);
194
	GIT_ERROR_CHECK_ALLOC(st->host);
195 196 197

	if (port) {
		st->port = git__strdup(port);
198
		GIT_ERROR_CHECK_ALLOC(st->port);
199 200 201 202 203 204 205 206 207 208 209 210 211
	}

	st->parent.version = GIT_STREAM_VERSION;
	st->parent.connect = socket_connect;
	st->parent.write = socket_write;
	st->parent.read = socket_read;
	st->parent.close = socket_close;
	st->parent.free = socket_free;
	st->s = INVALID_SOCKET;

	*out = (git_stream *) st;
	return 0;
}
212 213 214 215 216 217 218 219 220 221

int git_socket_stream_new(
	git_stream **out,
	const char *host,
	const char *port)
{
	int (*init)(git_stream **, const char *, const char *) = NULL;
	git_stream_registration custom = {0};
	int error;

222 223 224
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(host);
	GIT_ASSERT_ARG(port);
225

226
	if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0)
227 228 229 230 231 232 233
		init = custom.init;
	else if (error == GIT_ENOTFOUND)
		init = default_socket_stream_new;
	else
		return error;

	if (!init) {
234
		git_error_set(GIT_ERROR_NET, "there is no socket stream available");
235 236 237 238 239
		return -1;
	}

	return init(out, host, port);
}