]>
Commit | Line | Data |
---|---|---|
dd4ff2c9 CMN |
1 | /* |
2 | * Copyright (C) the libgit2 contributors. All rights reserved. | |
3 | * | |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
eae0bfdc PP |
8 | #include "streams/socket.h" |
9 | ||
dd4ff2c9 | 10 | #include "posix.h" |
dd4ff2c9 | 11 | #include "netops.h" |
ac3d33df | 12 | #include "registry.h" |
468d7b11 | 13 | #include "stream.h" |
dd4ff2c9 CMN |
14 | |
15 | #ifndef _WIN32 | |
16 | # include <sys/types.h> | |
17 | # include <sys/socket.h> | |
18 | # include <sys/select.h> | |
19 | # include <sys/time.h> | |
20 | # include <netdb.h> | |
21 | # include <netinet/in.h> | |
22 | # include <arpa/inet.h> | |
23 | #else | |
24 | # include <winsock2.h> | |
25 | # include <ws2tcpip.h> | |
26 | # ifdef _MSC_VER | |
27 | # pragma comment(lib, "ws2_32") | |
28 | # endif | |
29 | #endif | |
30 | ||
31 | #ifdef GIT_WIN32 | |
32 | static void net_set_error(const char *str) | |
33 | { | |
34 | int error = WSAGetLastError(); | |
35 | char * win32_error = git_win32_get_error_message(error); | |
36 | ||
37 | if (win32_error) { | |
ac3d33df | 38 | git_error_set(GIT_ERROR_NET, "%s: %s", str, win32_error); |
dd4ff2c9 CMN |
39 | git__free(win32_error); |
40 | } else { | |
ac3d33df | 41 | git_error_set(GIT_ERROR_NET, "%s", str); |
dd4ff2c9 CMN |
42 | } |
43 | } | |
44 | #else | |
45 | static void net_set_error(const char *str) | |
46 | { | |
ac3d33df | 47 | git_error_set(GIT_ERROR_NET, "%s: %s", str, strerror(errno)); |
dd4ff2c9 CMN |
48 | } |
49 | #endif | |
50 | ||
51 | static int close_socket(GIT_SOCKET s) | |
52 | { | |
53 | if (s == INVALID_SOCKET) | |
54 | return 0; | |
55 | ||
56 | #ifdef GIT_WIN32 | |
57 | if (SOCKET_ERROR == closesocket(s)) | |
58 | return -1; | |
59 | ||
60 | if (0 != WSACleanup()) { | |
ac3d33df | 61 | git_error_set(GIT_ERROR_OS, "winsock cleanup failed"); |
dd4ff2c9 CMN |
62 | return -1; |
63 | } | |
64 | ||
65 | return 0; | |
66 | #else | |
67 | return close(s); | |
68 | #endif | |
69 | ||
70 | } | |
71 | ||
ac3d33df | 72 | static int socket_connect(git_stream *stream) |
dd4ff2c9 CMN |
73 | { |
74 | struct addrinfo *info = NULL, *p; | |
75 | struct addrinfo hints; | |
468d7b11 | 76 | git_socket_stream *st = (git_socket_stream *) stream; |
dd4ff2c9 CMN |
77 | GIT_SOCKET s = INVALID_SOCKET; |
78 | int ret; | |
79 | ||
80 | #ifdef GIT_WIN32 | |
81 | /* on win32, the WSA context needs to be initialized | |
82 | * before any socket calls can be performed */ | |
83 | WSADATA wsd; | |
84 | ||
85 | if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { | |
ac3d33df | 86 | git_error_set(GIT_ERROR_OS, "winsock init failed"); |
dd4ff2c9 CMN |
87 | return -1; |
88 | } | |
89 | ||
90 | if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { | |
91 | WSACleanup(); | |
ac3d33df | 92 | git_error_set(GIT_ERROR_OS, "winsock init failed"); |
dd4ff2c9 CMN |
93 | return -1; |
94 | } | |
95 | #endif | |
96 | ||
97 | memset(&hints, 0x0, sizeof(struct addrinfo)); | |
98 | hints.ai_socktype = SOCK_STREAM; | |
99 | hints.ai_family = AF_UNSPEC; | |
100 | ||
101 | if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { | |
ac3d33df | 102 | git_error_set(GIT_ERROR_NET, |
909d5494 | 103 | "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret)); |
dd4ff2c9 CMN |
104 | return -1; |
105 | } | |
106 | ||
107 | for (p = info; p != NULL; p = p->ai_next) { | |
eae0bfdc | 108 | s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol); |
dd4ff2c9 | 109 | |
954e06a8 PS |
110 | if (s == INVALID_SOCKET) |
111 | continue; | |
dd4ff2c9 CMN |
112 | |
113 | if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) | |
114 | break; | |
115 | ||
116 | /* If we can't connect, try the next one */ | |
117 | close_socket(s); | |
118 | s = INVALID_SOCKET; | |
119 | } | |
120 | ||
121 | /* Oops, we couldn't connect to any address */ | |
122 | if (s == INVALID_SOCKET && p == NULL) { | |
ac3d33df | 123 | git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host); |
dd4ff2c9 CMN |
124 | p_freeaddrinfo(info); |
125 | return -1; | |
126 | } | |
127 | ||
128 | st->s = s; | |
129 | p_freeaddrinfo(info); | |
130 | return 0; | |
131 | } | |
132 | ||
ac3d33df | 133 | static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags) |
dd4ff2c9 | 134 | { |
468d7b11 | 135 | git_socket_stream *st = (git_socket_stream *) stream; |
ac3d33df | 136 | ssize_t written; |
dd4ff2c9 | 137 | |
ac3d33df | 138 | errno = 0; |
dd4ff2c9 | 139 | |
ac3d33df | 140 | if ((written = p_send(st->s, data, len, flags)) < 0) { |
22a2d3d5 | 141 | net_set_error("error sending data"); |
ac3d33df | 142 | return -1; |
dd4ff2c9 CMN |
143 | } |
144 | ||
ac3d33df | 145 | return written; |
dd4ff2c9 CMN |
146 | } |
147 | ||
ac3d33df | 148 | static ssize_t socket_read(git_stream *stream, void *data, size_t len) |
dd4ff2c9 CMN |
149 | { |
150 | ssize_t ret; | |
468d7b11 | 151 | git_socket_stream *st = (git_socket_stream *) stream; |
dd4ff2c9 CMN |
152 | |
153 | if ((ret = p_recv(st->s, data, len, 0)) < 0) | |
22a2d3d5 | 154 | net_set_error("error receiving socket data"); |
dd4ff2c9 CMN |
155 | |
156 | return ret; | |
157 | } | |
158 | ||
ac3d33df | 159 | static int socket_close(git_stream *stream) |
dd4ff2c9 | 160 | { |
468d7b11 | 161 | git_socket_stream *st = (git_socket_stream *) stream; |
dd4ff2c9 CMN |
162 | int error; |
163 | ||
164 | error = close_socket(st->s); | |
165 | st->s = INVALID_SOCKET; | |
166 | ||
167 | return error; | |
168 | } | |
169 | ||
ac3d33df | 170 | static void socket_free(git_stream *stream) |
dd4ff2c9 | 171 | { |
468d7b11 | 172 | git_socket_stream *st = (git_socket_stream *) stream; |
dd4ff2c9 CMN |
173 | |
174 | git__free(st->host); | |
175 | git__free(st->port); | |
176 | git__free(st); | |
177 | } | |
178 | ||
ac3d33df JK |
179 | static int default_socket_stream_new( |
180 | git_stream **out, | |
181 | const char *host, | |
182 | const char *port) | |
dd4ff2c9 | 183 | { |
468d7b11 | 184 | git_socket_stream *st; |
dd4ff2c9 | 185 | |
c25aa7cd PP |
186 | GIT_ASSERT_ARG(out); |
187 | GIT_ASSERT_ARG(host); | |
188 | GIT_ASSERT_ARG(port); | |
dd4ff2c9 | 189 | |
468d7b11 | 190 | st = git__calloc(1, sizeof(git_socket_stream)); |
ac3d33df | 191 | GIT_ERROR_CHECK_ALLOC(st); |
dd4ff2c9 CMN |
192 | |
193 | st->host = git__strdup(host); | |
ac3d33df | 194 | GIT_ERROR_CHECK_ALLOC(st->host); |
dd4ff2c9 CMN |
195 | |
196 | if (port) { | |
197 | st->port = git__strdup(port); | |
ac3d33df | 198 | GIT_ERROR_CHECK_ALLOC(st->port); |
dd4ff2c9 CMN |
199 | } |
200 | ||
201 | st->parent.version = GIT_STREAM_VERSION; | |
202 | st->parent.connect = socket_connect; | |
203 | st->parent.write = socket_write; | |
204 | st->parent.read = socket_read; | |
205 | st->parent.close = socket_close; | |
206 | st->parent.free = socket_free; | |
207 | st->s = INVALID_SOCKET; | |
208 | ||
209 | *out = (git_stream *) st; | |
210 | return 0; | |
211 | } | |
ac3d33df JK |
212 | |
213 | int git_socket_stream_new( | |
214 | git_stream **out, | |
215 | const char *host, | |
216 | const char *port) | |
217 | { | |
218 | int (*init)(git_stream **, const char *, const char *) = NULL; | |
219 | git_stream_registration custom = {0}; | |
220 | int error; | |
221 | ||
c25aa7cd PP |
222 | GIT_ASSERT_ARG(out); |
223 | GIT_ASSERT_ARG(host); | |
224 | GIT_ASSERT_ARG(port); | |
ac3d33df JK |
225 | |
226 | if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0) | |
227 | init = custom.init; | |
228 | else if (error == GIT_ENOTFOUND) | |
229 | init = default_socket_stream_new; | |
230 | else | |
231 | return error; | |
232 | ||
233 | if (!init) { | |
234 | git_error_set(GIT_ERROR_NET, "there is no socket stream available"); | |
235 | return -1; | |
236 | } | |
237 | ||
238 | return init(out, host, port); | |
239 | } |