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.
action(&self, url: &str, action: Service) -> Result<Box<dyn SmartSubtransportStream>, Error>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
close(&self) -> Result<(), Error>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.
register<F>(prefix: &str, factory: F) -> Result<(), Error> where F: Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static,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.
smart<S>(remote: &Remote<'_>, rpc: bool, subtransport: S) -> Result<Transport, Error> where S: SmartSubtransport,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 {
drop(&mut self)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
transport_factory( out: *mut *mut raw::git_transport, owner: *mut raw::git_remote, param: *mut c_void, ) -> c_int200 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.
subtransport_action( stream: *mut *mut raw::git_smart_subtransport_stream, raw_transport: *mut raw::git_smart_subtransport, url: *const c_char, action: raw::git_smart_service_t, ) -> c_int234 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,
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.
subtransport_close(transport: *mut raw::git_smart_subtransport) -> c_int290 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.
subtransport_free(transport: *mut raw::git_smart_subtransport)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.
stream_read( stream: *mut raw::git_smart_subtransport_stream, buffer: *mut c_char, buf_size: size_t, bytes_read: *mut size_t, ) -> c_int312 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.
stream_write( stream: *mut raw::git_smart_subtransport_stream, buffer: *const c_char, len: size_t, ) -> c_int341 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 
set_err_io(e: &io::Error)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 
set_err(e: &Error)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.
stream_free(stream: *mut raw::git_smart_subtransport_stream)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
dummy_error() -> Error388     fn dummy_error() -> Error {
389         Error::new(ErrorCode::Ambiguous, ErrorClass::Net, "bleh")
390     }
391 
392     impl SmartSubtransport for DummyTransport {
action( &self, _url: &str, _service: Service, ) -> Result<Box<dyn SmartSubtransportStream>, Error>393         fn action(
394             &self,
395             _url: &str,
396             _service: Service,
397         ) -> Result<Box<dyn SmartSubtransportStream>, Error> {
398             Err(dummy_error())
399         }
400 
close(&self) -> Result<(), Error>401         fn close(&self) -> Result<(), Error> {
402             Ok(())
403         }
404     }
405 
406     #[test]
transport_error_propagates()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 }
430