]> git.proxmox.com Git - libgit2.git/blob - src/transports/smart.c
Merge pull request #2745 from libgit2/cmn/pkg-config-ssh
[libgit2.git] / src / transports / smart.c
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 #include "git2.h"
8 #include "smart.h"
9 #include "refs.h"
10 #include "refspec.h"
11
12 static int git_smart__recv_cb(gitno_buffer *buf)
13 {
14 transport_smart *t = (transport_smart *) buf->cb_data;
15 size_t old_len, bytes_read;
16 int error;
17
18 assert(t->current_stream);
19
20 old_len = buf->offset;
21
22 if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
23 return error;
24
25 buf->offset += bytes_read;
26
27 if (t->packetsize_cb && !t->cancelled.val) {
28 error = t->packetsize_cb(bytes_read, t->packetsize_payload);
29 if (error) {
30 git_atomic_set(&t->cancelled, 1);
31 return GIT_EUSER;
32 }
33 }
34
35 return (int)(buf->offset - old_len);
36 }
37
38 GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
39 {
40 if (t->current_stream) {
41 t->current_stream->free(t->current_stream);
42 t->current_stream = NULL;
43 }
44
45 if (close_subtransport &&
46 t->wrapped->close(t->wrapped) < 0)
47 return -1;
48
49 return 0;
50 }
51
52 static int git_smart__set_callbacks(
53 git_transport *transport,
54 git_transport_message_cb progress_cb,
55 git_transport_message_cb error_cb,
56 git_transport_certificate_check_cb certificate_check_cb,
57 void *message_cb_payload)
58 {
59 transport_smart *t = (transport_smart *)transport;
60
61 t->progress_cb = progress_cb;
62 t->error_cb = error_cb;
63 t->certificate_check_cb = certificate_check_cb;
64 t->message_cb_payload = message_cb_payload;
65
66 return 0;
67 }
68
69 int git_smart__update_heads(transport_smart *t, git_vector *symrefs)
70 {
71 size_t i;
72 git_pkt *pkt;
73
74 git_vector_clear(&t->heads);
75 git_vector_foreach(&t->refs, i, pkt) {
76 git_pkt_ref *ref = (git_pkt_ref *) pkt;
77 if (pkt->type != GIT_PKT_REF)
78 continue;
79
80 if (symrefs) {
81 git_refspec *spec;
82 git_buf buf = GIT_BUF_INIT;
83 size_t j;
84 int error = 0;
85
86 git_vector_foreach(symrefs, j, spec) {
87 git_buf_clear(&buf);
88 if (git_refspec_src_matches(spec, ref->head.name) &&
89 !(error = git_refspec_transform(&buf, spec, ref->head.name)))
90 ref->head.symref_target = git_buf_detach(&buf);
91 }
92
93 git_buf_free(&buf);
94
95 if (error < 0)
96 return error;
97 }
98
99 if (git_vector_insert(&t->heads, &ref->head) < 0)
100 return -1;
101 }
102
103 return 0;
104 }
105
106 static void free_symrefs(git_vector *symrefs)
107 {
108 git_refspec *spec;
109 size_t i;
110
111 git_vector_foreach(symrefs, i, spec) {
112 git_refspec__free(spec);
113 git__free(spec);
114 }
115
116 git_vector_free(symrefs);
117 }
118
119 static int git_smart__connect(
120 git_transport *transport,
121 const char *url,
122 git_cred_acquire_cb cred_acquire_cb,
123 void *cred_acquire_payload,
124 int direction,
125 int flags)
126 {
127 transport_smart *t = (transport_smart *)transport;
128 git_smart_subtransport_stream *stream;
129 int error;
130 git_pkt *pkt;
131 git_pkt_ref *first;
132 git_vector symrefs;
133 git_smart_service_t service;
134
135 if (git_smart__reset_stream(t, true) < 0)
136 return -1;
137
138 t->url = git__strdup(url);
139 GITERR_CHECK_ALLOC(t->url);
140
141 t->direction = direction;
142 t->flags = flags;
143 t->cred_acquire_cb = cred_acquire_cb;
144 t->cred_acquire_payload = cred_acquire_payload;
145
146 if (GIT_DIRECTION_FETCH == t->direction)
147 service = GIT_SERVICE_UPLOADPACK_LS;
148 else if (GIT_DIRECTION_PUSH == t->direction)
149 service = GIT_SERVICE_RECEIVEPACK_LS;
150 else {
151 giterr_set(GITERR_NET, "Invalid direction");
152 return -1;
153 }
154
155 if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
156 return error;
157
158 /* Save off the current stream (i.e. socket) that we are working with */
159 t->current_stream = stream;
160
161 gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
162
163 /* 2 flushes for RPC; 1 for stateful */
164 if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
165 return error;
166
167 /* Strip the comment packet for RPC */
168 if (t->rpc) {
169 pkt = (git_pkt *)git_vector_get(&t->refs, 0);
170
171 if (!pkt || GIT_PKT_COMMENT != pkt->type) {
172 giterr_set(GITERR_NET, "Invalid response");
173 return -1;
174 } else {
175 /* Remove the comment pkt from the list */
176 git_vector_remove(&t->refs, 0);
177 git__free(pkt);
178 }
179 }
180
181 /* We now have loaded the refs. */
182 t->have_refs = 1;
183
184 first = (git_pkt_ref *)git_vector_get(&t->refs, 0);
185
186 if ((error = git_vector_init(&symrefs, 1, NULL)) < 0)
187 return error;
188
189 /* Detect capabilities */
190 if (git_smart__detect_caps(first, &t->caps, &symrefs) < 0)
191 return -1;
192
193 /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
194 if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
195 git_oid_iszero(&first->head.oid)) {
196 git_vector_clear(&t->refs);
197 git_pkt_free((git_pkt *)first);
198 }
199
200 /* Keep a list of heads for _ls */
201 git_smart__update_heads(t, &symrefs);
202
203 free_symrefs(&symrefs);
204
205 if (t->rpc && git_smart__reset_stream(t, false) < 0)
206 return -1;
207
208 /* We're now logically connected. */
209 t->connected = 1;
210
211 return 0;
212 }
213
214 static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
215 {
216 transport_smart *t = (transport_smart *)transport;
217
218 if (!t->have_refs) {
219 giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
220 return -1;
221 }
222
223 *out = (const git_remote_head **) t->heads.contents;
224 *size = t->heads.length;
225
226 return 0;
227 }
228
229 int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
230 {
231 transport_smart *t = (transport_smart *)transport;
232 git_smart_subtransport_stream *stream;
233 int error;
234
235 if (t->rpc && git_smart__reset_stream(t, false) < 0)
236 return -1;
237
238 if (GIT_DIRECTION_FETCH != t->direction) {
239 giterr_set(GITERR_NET, "This operation is only valid for fetch");
240 return -1;
241 }
242
243 if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
244 return error;
245
246 /* If this is a stateful implementation, the stream we get back should be the same */
247 assert(t->rpc || t->current_stream == stream);
248
249 /* Save off the current stream (i.e. socket) that we are working with */
250 t->current_stream = stream;
251
252 if ((error = stream->write(stream, (const char *)data, len)) < 0)
253 return error;
254
255 gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
256
257 return 0;
258 }
259
260 int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
261 {
262 int error;
263
264 if (t->rpc && git_smart__reset_stream(t, false) < 0)
265 return -1;
266
267 if (GIT_DIRECTION_PUSH != t->direction) {
268 giterr_set(GITERR_NET, "This operation is only valid for push");
269 return -1;
270 }
271
272 if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
273 return error;
274
275 /* If this is a stateful implementation, the stream we get back should be the same */
276 assert(t->rpc || t->current_stream == *stream);
277
278 /* Save off the current stream (i.e. socket) that we are working with */
279 t->current_stream = *stream;
280
281 gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
282
283 return 0;
284 }
285
286 static void git_smart__cancel(git_transport *transport)
287 {
288 transport_smart *t = (transport_smart *)transport;
289
290 git_atomic_set(&t->cancelled, 1);
291 }
292
293 static int git_smart__is_connected(git_transport *transport)
294 {
295 transport_smart *t = (transport_smart *)transport;
296
297 return t->connected;
298 }
299
300 static int git_smart__read_flags(git_transport *transport, int *flags)
301 {
302 transport_smart *t = (transport_smart *)transport;
303
304 *flags = t->flags;
305
306 return 0;
307 }
308
309 static int git_smart__close(git_transport *transport)
310 {
311 transport_smart *t = (transport_smart *)transport;
312 git_vector *common = &t->common;
313 unsigned int i;
314 git_pkt *p;
315 int ret;
316 git_smart_subtransport_stream *stream;
317 const char flush[] = "0000";
318
319 /*
320 * If we're still connected at this point and not using RPC,
321 * we should say goodbye by sending a flush, or git-daemon
322 * will complain that we disconnected unexpectedly.
323 */
324 if (t->connected && !t->rpc &&
325 !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) {
326 t->current_stream->write(t->current_stream, flush, 4);
327 }
328
329 ret = git_smart__reset_stream(t, true);
330
331 git_vector_foreach(common, i, p)
332 git_pkt_free(p);
333
334 git_vector_free(common);
335
336 if (t->url) {
337 git__free(t->url);
338 t->url = NULL;
339 }
340
341 t->connected = 0;
342
343 return ret;
344 }
345
346 static void git_smart__free(git_transport *transport)
347 {
348 transport_smart *t = (transport_smart *)transport;
349 git_vector *refs = &t->refs;
350 unsigned int i;
351 git_pkt *p;
352
353 /* Make sure that the current stream is closed, if we have one. */
354 git_smart__close(transport);
355
356 /* Free the subtransport */
357 t->wrapped->free(t->wrapped);
358
359 git_vector_free(&t->heads);
360 git_vector_foreach(refs, i, p)
361 git_pkt_free(p);
362
363 git_vector_free(refs);
364
365 git__free(t);
366 }
367
368 static int ref_name_cmp(const void *a, const void *b)
369 {
370 const git_pkt_ref *ref_a = a, *ref_b = b;
371
372 return strcmp(ref_a->head.name, ref_b->head.name);
373 }
374
375 int git_transport_smart(git_transport **out, git_remote *owner, void *param)
376 {
377 transport_smart *t;
378 git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
379
380 if (!param)
381 return -1;
382
383 t = git__calloc(sizeof(transport_smart), 1);
384 GITERR_CHECK_ALLOC(t);
385
386 t->parent.version = GIT_TRANSPORT_VERSION;
387 t->parent.set_callbacks = git_smart__set_callbacks;
388 t->parent.connect = git_smart__connect;
389 t->parent.close = git_smart__close;
390 t->parent.free = git_smart__free;
391 t->parent.negotiate_fetch = git_smart__negotiate_fetch;
392 t->parent.download_pack = git_smart__download_pack;
393 t->parent.push = git_smart__push;
394 t->parent.ls = git_smart__ls;
395 t->parent.is_connected = git_smart__is_connected;
396 t->parent.read_flags = git_smart__read_flags;
397 t->parent.cancel = git_smart__cancel;
398
399 t->owner = owner;
400 t->rpc = definition->rpc;
401
402 if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {
403 git__free(t);
404 return -1;
405 }
406
407 if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) {
408 git__free(t);
409 return -1;
410 }
411
412 if (definition->callback(&t->wrapped, &t->parent) < 0) {
413 git__free(t);
414 return -1;
415 }
416
417 *out = (git_transport *) t;
418 return 0;
419 }