]> git.proxmox.com Git - libgit2.git/blame - src/transport_git.c
Create netops and start moving git:// to it
[libgit2.git] / src / transport_git.c
CommitLineData
ecb6ca0e
CMN
1/*
2 * This file is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2,
4 * as published by the Free Software Foundation.
5 *
6 * In addition to the permissions in the GNU General Public License,
7 * the authors give you unlimited permission to link the compiled
8 * version of this file into combinations with other programs,
9 * and to distribute those combinations without any restriction
10 * coming from the use of this file. (The General Public License
11 * restrictions do apply in other respects; for example, they cover
12 * modification of the file, and distribution when not linked into
13 * a combined executable.)
14 *
15 * This file is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; see the file COPYING. If not, write to
22 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26#ifndef _MSC_VER
27# include <sys/types.h>
28# include <sys/socket.h>
29# include <netdb.h>
30#else
31# include <winsock2.h>
32# include <Ws2tcpip.h>
33# pragma comment(lib, "Ws2_32.lib")
34#endif
35
36#include "git2/net.h"
37#include "git2/pkt.h"
38#include "git2/common.h"
39#include "git2/types.h"
40#include "git2/errors.h"
41
42#include "vector.h"
43#include "transport.h"
44#include "common.h"
1b4f8140 45#include "netops.h"
ecb6ca0e
CMN
46
47typedef struct {
48 int socket;
49 git_vector refs;
50 git_remote_head **heads;
51} git_priv;
52
53/* The URL should already have been stripped of the protocol */
54static int extract_host_and_port(char **host, char **port, const char *url)
55{
56 char *colon, *slash, *delim;
57 int error = GIT_SUCCESS;
58
59 colon = strchr(url, ':');
60 slash = strchr(url, '/');
61
62 if (slash == NULL)
63 return git__throw(GIT_EOBJCORRUPTED, "Malformed URL: missing /");
64
65 if (colon == NULL) {
66 *port = git__strdup(GIT_DEFAULT_PORT);
67 } else {
68 *port = git__strndup(colon + 1, slash - colon - 1);
69 }
70 if (*port == NULL)
71 return GIT_ENOMEM;;
72
73
74 delim = colon == NULL ? slash : colon;
75 *host = git__strndup(url, delim - url);
76 if (*host == NULL) {
77 free(*port);
78 error = GIT_ENOMEM;
79 }
80
81 return error;
82}
83
84/*
85 * Parse the URL and connect to a server, storing the socket in
86 * out. For convenience this also takes care of asking for the remote
87 * refs
88 */
89static int do_connect(git_priv *priv, const char *url)
90{
91 int s = -1;
92 char *host, *port, *msg;
93 const char prefix[] = "git://";
94 int error, ret, msg_len, connected = 0;
ecb6ca0e
CMN
95
96 if (!git__prefixcmp(url, prefix))
97 url += STRLEN(prefix);
98
99 error = extract_host_and_port(&host, &port, url);
1b4f8140
CMN
100 s = gitno_connect(host, port);
101 connected = 1;
ecb6ca0e 102
1b4f8140
CMN
103 error = git_pkt_gen_proto(&msg, &msg_len, url);
104 if (error < GIT_SUCCESS)
ecb6ca0e 105 goto cleanup;
ecb6ca0e 106
1b4f8140
CMN
107 /* FIXME: Do this in a loop */
108 ret = send(s, msg, msg_len, 0);
109 free(msg);
110 if (ret < 0) {
111 error = git__throw(GIT_EOSERR, "Failed to send request");
112 goto cleanup;
ecb6ca0e
CMN
113 }
114
115 priv->socket = s;
116
117cleanup:
ecb6ca0e
CMN
118 free(host);
119 free(port);
120
121 if (error < GIT_SUCCESS && s > 0)
122 close(s);
123 if (!connected)
124 error = git__throw(GIT_EOSERR, "Failed to connect to any of the addresses");
125
126 return error;
127}
128
129/*
130 * Read from the socket and store the references in the vector
131 */
132static int store_refs(git_priv *priv)
133{
134 int s = priv->socket;
135 git_vector *refs = &priv->refs;
136 int error = GIT_SUCCESS;
7632e249 137 char buffer[1024];
ecb6ca0e
CMN
138 const char *line_end, *ptr;
139 int off = 0, ret;
7632e249 140 unsigned int bufflen = 0;
ecb6ca0e
CMN
141 git_pkt *pkt;
142
7632e249
CMN
143 memset(buffer, 0x0, sizeof(buffer));
144
ecb6ca0e 145 while (1) {
7632e249 146 ret = recv(s, buffer + off, sizeof(buffer) - off, 0);
ecb6ca0e
CMN
147 if (ret < 0)
148 return git__throw(GIT_EOSERR, "Failed to receive data");
7632e249
CMN
149 if (ret == 0) /* Orderly shutdown, so exit */
150 return GIT_SUCCESS;
ecb6ca0e 151
7632e249 152 bufflen += ret;
ecb6ca0e
CMN
153 ptr = buffer;
154 while (1) {
7632e249
CMN
155 if (bufflen == 0)
156 break;
157 error = git_pkt_parse_line(&pkt, ptr, &line_end, bufflen);
ecb6ca0e 158 /*
7632e249
CMN
159 * If the error is GIT_ESHORTBUFFER, it means the buffer
160 * isn't long enough to satisfy the request. Break out and
161 * wait for more input.
162 * On any other error, fail.
ecb6ca0e 163 */
7632e249
CMN
164 if (error == GIT_ESHORTBUFFER) {
165 line_end = ptr;
ecb6ca0e 166 break;
7632e249
CMN
167 }
168 if (error < GIT_SUCCESS) {
169 return error;
170 }
ecb6ca0e
CMN
171
172 error = git_vector_insert(refs, pkt);
173 if (error < GIT_SUCCESS)
174 return error;
175
7632e249 176 if (pkt->type == GIT_PKT_FLUSH)
ecb6ca0e
CMN
177 return GIT_SUCCESS;
178
7632e249 179 bufflen -= line_end - ptr;
ecb6ca0e
CMN
180 ptr = line_end;
181 }
182
183 /*
184 * Move the rest to the start of the buffer
185 */
7632e249
CMN
186 memmove(buffer, line_end, bufflen);
187 off = bufflen;
188 memset(buffer + off, 0x0, sizeof(buffer) - off);
ecb6ca0e
CMN
189 }
190
191 return error;
192}
193
194/*
195 * Since this is a network connection, we need to parse and store the
196 * pkt-lines at this stage and keep them there.
197 */
198static int git_connect(git_transport *transport, git_net_direction direction)
199{
200 git_priv *priv;
201 int error = GIT_SUCCESS;
202
203 if (direction == INTENT_PUSH)
204 return git__throw(GIT_EINVALIDARGS, "Pushing is not supported with the git protocol");
205
206 priv = git__malloc(sizeof(git_priv));
207 if (priv == NULL)
208 return GIT_ENOMEM;
209
210 memset(priv, 0x0, sizeof(git_priv));
211 transport->private = priv;
212 error = git_vector_init(&priv->refs, 16, NULL);
213 if (error < GIT_SUCCESS)
214 goto cleanup;
215
216 /* Connect and ask for the refs */
217 error = do_connect(priv, transport->url);
218 if (error < GIT_SUCCESS)
219 return error;
220
221 error = store_refs(priv);
222
223cleanup:
224 if (error < GIT_SUCCESS) {
225 git_vector_free(&priv->refs);
226 free(priv);
227 }
228
229 return error;
230}
231
232static int git_ls(git_transport *transport, git_headarray *array)
233{
234 git_priv *priv = transport->private;
235 git_vector *refs = &priv->refs;
236 int len = 0;
237 unsigned int i;
238
239 array->heads = git__calloc(refs->length, sizeof(git_remote_head *));
240 if (array->heads == NULL)
241 return GIT_ENOMEM;
242
243 for (i = 0; i < refs->length; ++i) {
244 git_pkt *p = git_vector_get(refs, i);
245 if (p->type != GIT_PKT_REF)
246 continue;
247
248 ++len;
249 array->heads[i] = &(((git_pkt_ref *) p)->head);
250 }
251 array->len = len;
252 priv->heads = array->heads;
253
254 return GIT_SUCCESS;
255}
256
257static int git_close(git_transport *transport)
258{
259 git_priv *priv = transport->private;
260 int s = priv->socket;
261 int error;
262
7632e249
CMN
263 /* FIXME: We probably want to send a flush pkt back */
264
ecb6ca0e
CMN
265 error = close(s);
266 if (error < 0)
267 error = git__throw(GIT_EOSERR, "Failed to close socket");
268
269 return error;
270}
271
272static void git_free(git_transport *transport)
273{
274 git_priv *priv = transport->private;
275 git_vector *refs = &priv->refs;
276 unsigned int i;
277
278 for (i = 0; i < refs->length; ++i) {
279 git_pkt *p = git_vector_get(refs, i);
be9fe679 280 git_pkt_free(p);
ecb6ca0e
CMN
281 }
282
283 git_vector_free(refs);
284 free(priv->heads);
285 free(priv);
286 free(transport->url);
287 free(transport);
288}
289
290int git_transport_git(git_transport *transport)
291{
292 transport->connect = git_connect;
293 transport->ls = git_ls;
294 transport->close = git_close;
295 transport->free = git_free;
296
297 return GIT_SUCCESS;
298}