]> git.proxmox.com Git - cargo.git/blob - vendor/git2/src/transport.rs
New upstream version 0.52.0
[cargo.git] / vendor / git2 / src / transport.rs
1 //! Interfaces for adding custom transports to libgit2
2
3 use libc::{c_char, c_int, c_uint, c_void, size_t};
4 use std::ffi::{CStr, CString};
5 use std::io;
6 use std::io::prelude::*;
7 use std::mem;
8 use std::ptr;
9 use std::slice;
10 use std::str;
11
12 use crate::util::Binding;
13 use crate::{panic, raw, Error, Remote};
14
15 /// A transport is a structure which knows how to transfer data to and from a
16 /// remote.
17 ///
18 /// This transport is a representation of the raw transport underneath it, which
19 /// is similar to a trait object in Rust.
20 #[allow(missing_copy_implementations)]
21 pub struct Transport {
22 raw: *mut raw::git_transport,
23 owned: bool,
24 }
25
26 /// Interface used by smart transports.
27 ///
28 /// The full-fledged definiton of transports has to deal with lots of
29 /// nitty-gritty details of the git protocol, but "smart transports" largely
30 /// only need to deal with read() and write() of data over a channel.
31 ///
32 /// A smart subtransport is contained within an instance of a smart transport
33 /// and is delegated to in order to actually conduct network activity to push or
34 /// pull data from a remote.
35 pub trait SmartSubtransport: Send + 'static {
36 /// Indicates that this subtransport will be performing the specified action
37 /// on the specified URL.
38 ///
39 /// This function is responsible for making any network connections and
40 /// returns a stream which can be read and written from in order to
41 /// negotiate the git protocol.
42 fn action(&self, url: &str, action: Service)
43 -> Result<Box<dyn SmartSubtransportStream>, Error>;
44
45 /// Terminates a connection with the remote.
46 ///
47 /// Each subtransport is guaranteed a call to close() between calls to
48 /// action(), except for the following two natural progressions of actions
49 /// against a constant URL.
50 ///
51 /// 1. UploadPackLs -> UploadPack
52 /// 2. ReceivePackLs -> ReceivePack
53 fn close(&self) -> Result<(), Error>;
54 }
55
56 /// Actions that a smart transport can ask a subtransport to perform
57 #[derive(Copy, Clone, PartialEq)]
58 #[allow(missing_docs)]
59 pub enum Service {
60 UploadPackLs,
61 UploadPack,
62 ReceivePackLs,
63 ReceivePack,
64 }
65
66 /// An instance of a stream over which a smart transport will communicate with a
67 /// remote.
68 ///
69 /// Currently this only requires the standard `Read` and `Write` traits. This
70 /// trait also does not need to be implemented manually as long as the `Read`
71 /// and `Write` traits are implemented.
72 pub trait SmartSubtransportStream: Read + Write + Send + 'static {}
73
74 impl<T: Read + Write + Send + 'static> SmartSubtransportStream for T {}
75
76 type TransportFactory = dyn Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static;
77
78 /// Boxed data payload used for registering new transports.
79 ///
80 /// Currently only contains a field which knows how to create transports.
81 struct TransportData {
82 factory: Box<TransportFactory>,
83 }
84
85 /// Instance of a `git_smart_subtransport`, must use `#[repr(C)]` to ensure that
86 /// the C fields come first.
87 #[repr(C)]
88 struct RawSmartSubtransport {
89 raw: raw::git_smart_subtransport,
90 stream: Option<*mut raw::git_smart_subtransport_stream>,
91 rpc: bool,
92 obj: Box<dyn SmartSubtransport>,
93 }
94
95 /// Instance of a `git_smart_subtransport_stream`, must use `#[repr(C)]` to
96 /// ensure that the C fields come first.
97 #[repr(C)]
98 struct RawSmartSubtransportStream {
99 raw: raw::git_smart_subtransport_stream,
100 obj: Box<dyn SmartSubtransportStream>,
101 }
102
103 /// Add a custom transport definition, to be used in addition to the built-in
104 /// set of transports that come with libgit2.
105 ///
106 /// This function is unsafe as it needs to be externally synchronized with calls
107 /// to creation of other transports.
108 pub unsafe fn register<F>(prefix: &str, factory: F) -> Result<(), Error>
109 where
110 F: Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static,
111 {
112 crate::init();
113 let mut data = Box::new(TransportData {
114 factory: Box::new(factory),
115 });
116 let prefix = CString::new(prefix)?;
117 let datap = (&mut *data) as *mut TransportData as *mut c_void;
118 let factory: raw::git_transport_cb = Some(transport_factory);
119 try_call!(raw::git_transport_register(prefix, factory, datap));
120 mem::forget(data);
121 Ok(())
122 }
123
124 impl Transport {
125 /// Creates a new transport which will use the "smart" transport protocol
126 /// for transferring data.
127 ///
128 /// A smart transport requires a *subtransport* over which data is actually
129 /// communicated, but this subtransport largely just needs to be able to
130 /// read() and write(). The subtransport provided will be used to make
131 /// connections which can then be read/written from.
132 ///
133 /// The `rpc` argument is `true` if the protocol is stateless, false
134 /// otherwise. For example `http://` is stateless but `git://` is not.
135 pub fn smart<S>(remote: &Remote<'_>, rpc: bool, subtransport: S) -> Result<Transport, Error>
136 where
137 S: SmartSubtransport,
138 {
139 let mut ret = ptr::null_mut();
140
141 let mut raw = Box::new(RawSmartSubtransport {
142 raw: raw::git_smart_subtransport {
143 action: Some(subtransport_action),
144 close: Some(subtransport_close),
145 free: Some(subtransport_free),
146 },
147 stream: None,
148 rpc,
149 obj: Box::new(subtransport),
150 });
151 let mut defn = raw::git_smart_subtransport_definition {
152 callback: Some(smart_factory),
153 rpc: rpc as c_uint,
154 param: &mut *raw as *mut _ as *mut _,
155 };
156
157 // Currently there's no way to pass a payload via the
158 // git_smart_subtransport_definition structure, but it's only used as a
159 // configuration for the initial creation of the smart transport (verified
160 // by reading the current code, hopefully it doesn't change!).
161 //
162 // We, however, need some state (gotta pass in our
163 // `RawSmartSubtransport`). This also means that this block must be
164 // entirely synchronized with a lock (boo!)
165 unsafe {
166 try_call!(raw::git_transport_smart(
167 &mut ret,
168 remote.raw(),
169 &mut defn as *mut _ as *mut _
170 ));
171 mem::forget(raw); // ownership transport to `ret`
172 }
173 return Ok(Transport {
174 raw: ret,
175 owned: true,
176 });
177
178 extern "C" fn smart_factory(
179 out: *mut *mut raw::git_smart_subtransport,
180 _owner: *mut raw::git_transport,
181 ptr: *mut c_void,
182 ) -> c_int {
183 unsafe {
184 *out = ptr as *mut raw::git_smart_subtransport;
185 0
186 }
187 }
188 }
189 }
190
191 impl Drop for Transport {
192 fn drop(&mut self) {
193 if self.owned {
194 unsafe { (*self.raw).free.unwrap()(self.raw) }
195 }
196 }
197 }
198
199 // callback used by register() to create new transports
200 extern "C" fn transport_factory(
201 out: *mut *mut raw::git_transport,
202 owner: *mut raw::git_remote,
203 param: *mut c_void,
204 ) -> c_int {
205 struct Bomb<'a> {
206 remote: Option<Remote<'a>>,
207 }
208 impl<'a> Drop for Bomb<'a> {
209 fn drop(&mut self) {
210 // TODO: maybe a method instead?
211 mem::forget(self.remote.take());
212 }
213 }
214
215 panic::wrap(|| unsafe {
216 let remote = Bomb {
217 remote: Some(Binding::from_raw(owner)),
218 };
219 let data = &mut *(param as *mut TransportData);
220 match (data.factory)(remote.remote.as_ref().unwrap()) {
221 Ok(mut transport) => {
222 *out = transport.raw;
223 transport.owned = false;
224 0
225 }
226 Err(e) => e.raw_code() as c_int,
227 }
228 })
229 .unwrap_or(-1)
230 }
231
232 // callback used by smart transports to delegate an action to a
233 // `SmartSubtransport` trait object.
234 extern "C" fn subtransport_action(
235 stream: *mut *mut raw::git_smart_subtransport_stream,
236 raw_transport: *mut raw::git_smart_subtransport,
237 url: *const c_char,
238 action: raw::git_smart_service_t,
239 ) -> c_int {
240 panic::wrap(|| unsafe {
241 let url = CStr::from_ptr(url).to_bytes();
242 let url = match str::from_utf8(url).ok() {
243 Some(s) => s,
244 None => return -1,
245 };
246 let action = match action {
247 raw::GIT_SERVICE_UPLOADPACK_LS => Service::UploadPackLs,
248 raw::GIT_SERVICE_UPLOADPACK => Service::UploadPack,
249 raw::GIT_SERVICE_RECEIVEPACK_LS => Service::ReceivePackLs,
250 raw::GIT_SERVICE_RECEIVEPACK => Service::ReceivePack,
251 n => panic!("unknown action: {}", n),
252 };
253
254 let mut transport = &mut *(raw_transport as *mut RawSmartSubtransport);
255 // Note: we only need to generate if rpc is on. Else, for receive-pack and upload-pack
256 // libgit2 reuses the stream generated for receive-pack-ls or upload-pack-ls.
257 let generate_stream =
258 transport.rpc || action == Service::UploadPackLs || action == Service::ReceivePackLs;
259 if generate_stream {
260 let obj = match transport.obj.action(url, action) {
261 Ok(s) => s,
262 Err(e) => {
263 set_err(&e);
264 return e.raw_code() as c_int;
265 }
266 };
267 *stream = mem::transmute(Box::new(RawSmartSubtransportStream {
268 raw: raw::git_smart_subtransport_stream {
269 subtransport: raw_transport,
270 read: Some(stream_read),
271 write: Some(stream_write),
272 free: Some(stream_free),
273 },
274 obj: obj,
275 }));
276 transport.stream = Some(*stream);
277 } else {
278 if transport.stream.is_none() {
279 return -1;
280 }
281 *stream = transport.stream.unwrap();
282 }
283 0
284 })
285 .unwrap_or(-1)
286 }
287
288 // callback used by smart transports to close a `SmartSubtransport` trait
289 // object.
290 extern "C" fn subtransport_close(transport: *mut raw::git_smart_subtransport) -> c_int {
291 let ret = panic::wrap(|| unsafe {
292 let transport = &mut *(transport as *mut RawSmartSubtransport);
293 transport.obj.close()
294 });
295 match ret {
296 Some(Ok(())) => 0,
297 Some(Err(e)) => e.raw_code() as c_int,
298 None => -1,
299 }
300 }
301
302 // callback used by smart transports to free a `SmartSubtransport` trait
303 // object.
304 extern "C" fn subtransport_free(transport: *mut raw::git_smart_subtransport) {
305 let _ = panic::wrap(|| unsafe {
306 mem::transmute::<_, Box<RawSmartSubtransport>>(transport);
307 });
308 }
309
310 // callback used by smart transports to read from a `SmartSubtransportStream`
311 // object.
312 extern "C" fn stream_read(
313 stream: *mut raw::git_smart_subtransport_stream,
314 buffer: *mut c_char,
315 buf_size: size_t,
316 bytes_read: *mut size_t,
317 ) -> c_int {
318 let ret = panic::wrap(|| unsafe {
319 let transport = &mut *(stream as *mut RawSmartSubtransportStream);
320 let buf = slice::from_raw_parts_mut(buffer as *mut u8, buf_size as usize);
321 match transport.obj.read(buf) {
322 Ok(n) => {
323 *bytes_read = n as size_t;
324 Ok(n)
325 }
326 e => e,
327 }
328 });
329 match ret {
330 Some(Ok(_)) => 0,
331 Some(Err(e)) => unsafe {
332 set_err_io(&e);
333 -2
334 },
335 None => -1,
336 }
337 }
338
339 // callback used by smart transports to write to a `SmartSubtransportStream`
340 // object.
341 extern "C" fn stream_write(
342 stream: *mut raw::git_smart_subtransport_stream,
343 buffer: *const c_char,
344 len: size_t,
345 ) -> c_int {
346 let ret = panic::wrap(|| unsafe {
347 let transport = &mut *(stream as *mut RawSmartSubtransportStream);
348 let buf = slice::from_raw_parts(buffer as *const u8, len as usize);
349 transport.obj.write_all(buf)
350 });
351 match ret {
352 Some(Ok(())) => 0,
353 Some(Err(e)) => unsafe {
354 set_err_io(&e);
355 -2
356 },
357 None => -1,
358 }
359 }
360
361 unsafe fn set_err_io(e: &io::Error) {
362 let s = CString::new(e.to_string()).unwrap();
363 raw::git_error_set_str(raw::GIT_ERROR_NET as c_int, s.as_ptr());
364 }
365
366 unsafe fn set_err(e: &Error) {
367 let s = CString::new(e.message()).unwrap();
368 raw::git_error_set_str(e.raw_class() as c_int, s.as_ptr());
369 }
370
371 // callback used by smart transports to free a `SmartSubtransportStream`
372 // object.
373 extern "C" fn stream_free(stream: *mut raw::git_smart_subtransport_stream) {
374 let _ = panic::wrap(|| unsafe {
375 mem::transmute::<_, Box<RawSmartSubtransportStream>>(stream);
376 });
377 }
378
379 #[cfg(test)]
380 mod tests {
381 use super::*;
382 use crate::{ErrorClass, ErrorCode};
383 use std::sync::Once;
384
385 struct DummyTransport;
386
387 // in lieu of lazy_static
388 fn dummy_error() -> Error {
389 Error::new(ErrorCode::Ambiguous, ErrorClass::Net, "bleh")
390 }
391
392 impl SmartSubtransport for DummyTransport {
393 fn action(
394 &self,
395 _url: &str,
396 _service: Service,
397 ) -> Result<Box<dyn SmartSubtransportStream>, Error> {
398 Err(dummy_error())
399 }
400
401 fn close(&self) -> Result<(), Error> {
402 Ok(())
403 }
404 }
405
406 #[test]
407 fn transport_error_propagates() {
408 static INIT: Once = Once::new();
409
410 unsafe {
411 INIT.call_once(|| {
412 register("dummy", move |remote| {
413 Transport::smart(&remote, true, DummyTransport)
414 })
415 .unwrap();
416 })
417 }
418
419 let (_td, repo) = crate::test::repo_init();
420 t!(repo.remote("origin", "dummy://ball"));
421
422 let mut origin = t!(repo.find_remote("origin"));
423
424 match origin.fetch(&["main"], None, None) {
425 Ok(()) => unreachable!(),
426 Err(e) => assert_eq!(e, dummy_error()),
427 }
428 }
429 }