| //! Interfaces for adding custom transports to libgit2 |
| |
| use libc::{c_char, c_int, c_uint, c_void, size_t}; |
| use std::ffi::{CStr, CString}; |
| use std::io; |
| use std::io::prelude::*; |
| use std::mem; |
| use std::ptr; |
| use std::slice; |
| use std::str; |
| |
| use crate::util::Binding; |
| use crate::{panic, raw, Error, Remote}; |
| |
| /// A transport is a structure which knows how to transfer data to and from a |
| /// remote. |
| /// |
| /// This transport is a representation of the raw transport underneath it, which |
| /// is similar to a trait object in Rust. |
| #[allow(missing_copy_implementations)] |
| pub struct Transport { |
| raw: *mut raw::git_transport, |
| owned: bool, |
| } |
| |
| /// Interface used by smart transports. |
| /// |
| /// The full-fledged definiton of transports has to deal with lots of |
| /// nitty-gritty details of the git protocol, but "smart transports" largely |
| /// only need to deal with read() and write() of data over a channel. |
| /// |
| /// A smart subtransport is contained within an instance of a smart transport |
| /// and is delegated to in order to actually conduct network activity to push or |
| /// pull data from a remote. |
| pub trait SmartSubtransport: Send + 'static { |
| /// Indicates that this subtransport will be performing the specified action |
| /// on the specified URL. |
| /// |
| /// This function is responsible for making any network connections and |
| /// returns a stream which can be read and written from in order to |
| /// negotiate the git protocol. |
| fn action(&self, url: &str, action: Service) |
| -> Result<Box<dyn SmartSubtransportStream>, Error>; |
| |
| /// Terminates a connection with the remote. |
| /// |
| /// Each subtransport is guaranteed a call to close() between calls to |
| /// action(), except for the following two natural progressions of actions |
| /// against a constant URL. |
| /// |
| /// 1. UploadPackLs -> UploadPack |
| /// 2. ReceivePackLs -> ReceivePack |
| fn close(&self) -> Result<(), Error>; |
| } |
| |
| /// Actions that a smart transport can ask a subtransport to perform |
| #[derive(Copy, Clone)] |
| #[allow(missing_docs)] |
| pub enum Service { |
| UploadPackLs, |
| UploadPack, |
| ReceivePackLs, |
| ReceivePack, |
| } |
| |
| /// An instance of a stream over which a smart transport will communicate with a |
| /// remote. |
| /// |
| /// Currently this only requires the standard `Read` and `Write` traits. This |
| /// trait also does not need to be implemented manually as long as the `Read` |
| /// and `Write` traits are implemented. |
| pub trait SmartSubtransportStream: Read + Write + Send + 'static {} |
| |
| impl<T: Read + Write + Send + 'static> SmartSubtransportStream for T {} |
| |
| type TransportFactory = dyn Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static; |
| |
| /// Boxed data payload used for registering new transports. |
| /// |
| /// Currently only contains a field which knows how to create transports. |
| struct TransportData { |
| factory: Box<TransportFactory>, |
| } |
| |
| /// Instance of a `git_smart_subtransport`, must use `#[repr(C)]` to ensure that |
| /// the C fields come first. |
| #[repr(C)] |
| struct RawSmartSubtransport { |
| raw: raw::git_smart_subtransport, |
| obj: Box<dyn SmartSubtransport>, |
| } |
| |
| /// Instance of a `git_smart_subtransport_stream`, must use `#[repr(C)]` to |
| /// ensure that the C fields come first. |
| #[repr(C)] |
| struct RawSmartSubtransportStream { |
| raw: raw::git_smart_subtransport_stream, |
| obj: Box<dyn SmartSubtransportStream>, |
| } |
| |
| /// Add a custom transport definition, to be used in addition to the built-in |
| /// set of transports that come with libgit2. |
| /// |
| /// This function is unsafe as it needs to be externally synchronized with calls |
| /// to creation of other transports. |
| pub unsafe fn register<F>(prefix: &str, factory: F) -> Result<(), Error> |
| where |
| F: Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static, |
| { |
| crate::init(); |
| let mut data = Box::new(TransportData { |
| factory: Box::new(factory), |
| }); |
| let prefix = CString::new(prefix)?; |
| let datap = (&mut *data) as *mut TransportData as *mut c_void; |
| try_call!(raw::git_transport_register( |
| prefix, |
| transport_factory, |
| datap |
| )); |
| mem::forget(data); |
| Ok(()) |
| } |
| |
| impl Transport { |
| /// Creates a new transport which will use the "smart" transport protocol |
| /// for transferring data. |
| /// |
| /// A smart transport requires a *subtransport* over which data is actually |
| /// communicated, but this subtransport largely just needs to be able to |
| /// read() and write(). The subtransport provided will be used to make |
| /// connections which can then be read/written from. |
| /// |
| /// The `rpc` argument is `true` if the protocol is stateless, false |
| /// otherwise. For example `http://` is stateless but `git://` is not. |
| pub fn smart<S>(remote: &Remote<'_>, rpc: bool, subtransport: S) -> Result<Transport, Error> |
| where |
| S: SmartSubtransport, |
| { |
| let mut ret = ptr::null_mut(); |
| |
| let mut raw = Box::new(RawSmartSubtransport { |
| raw: raw::git_smart_subtransport { |
| action: subtransport_action, |
| close: subtransport_close, |
| free: subtransport_free, |
| }, |
| obj: Box::new(subtransport), |
| }); |
| let mut defn = raw::git_smart_subtransport_definition { |
| callback: smart_factory, |
| rpc: rpc as c_uint, |
| param: &mut *raw as *mut _ as *mut _, |
| }; |
| |
| // Currently there's no way to pass a payload via the |
| // git_smart_subtransport_definition structure, but it's only used as a |
| // configuration for the initial creation of the smart transport (verified |
| // by reading the current code, hopefully it doesn't change!). |
| // |
| // We, however, need some state (gotta pass in our |
| // `RawSmartSubtransport`). This also means that this block must be |
| // entirely synchronized with a lock (boo!) |
| unsafe { |
| try_call!(raw::git_transport_smart( |
| &mut ret, |
| remote.raw(), |
| &mut defn as *mut _ as *mut _ |
| )); |
| mem::forget(raw); // ownership transport to `ret` |
| } |
| return Ok(Transport { |
| raw: ret, |
| owned: true, |
| }); |
| |
| extern "C" fn smart_factory( |
| out: *mut *mut raw::git_smart_subtransport, |
| _owner: *mut raw::git_transport, |
| ptr: *mut c_void, |
| ) -> c_int { |
| unsafe { |
| *out = ptr as *mut raw::git_smart_subtransport; |
| 0 |
| } |
| } |
| } |
| } |
| |
| impl Drop for Transport { |
| fn drop(&mut self) { |
| if self.owned { |
| unsafe { ((*self.raw).free)(self.raw) } |
| } |
| } |
| } |
| |
| // callback used by register() to create new transports |
| extern "C" fn transport_factory( |
| out: *mut *mut raw::git_transport, |
| owner: *mut raw::git_remote, |
| param: *mut c_void, |
| ) -> c_int { |
| struct Bomb<'a> { |
| remote: Option<Remote<'a>>, |
| } |
| impl<'a> Drop for Bomb<'a> { |
| fn drop(&mut self) { |
| // TODO: maybe a method instead? |
| mem::forget(self.remote.take()); |
| } |
| } |
| |
| panic::wrap(|| unsafe { |
| let remote = Bomb { |
| remote: Some(Binding::from_raw(owner)), |
| }; |
| let data = &mut *(param as *mut TransportData); |
| match (data.factory)(remote.remote.as_ref().unwrap()) { |
| Ok(mut transport) => { |
| *out = transport.raw; |
| transport.owned = false; |
| 0 |
| } |
| Err(e) => e.raw_code() as c_int, |
| } |
| }) |
| .unwrap_or(-1) |
| } |
| |
| // callback used by smart transports to delegate an action to a |
| // `SmartSubtransport` trait object. |
| extern "C" fn 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_int { |
| panic::wrap(|| unsafe { |
| let url = CStr::from_ptr(url).to_bytes(); |
| let url = match str::from_utf8(url).ok() { |
| Some(s) => s, |
| None => return -1, |
| }; |
| let action = match action { |
| raw::GIT_SERVICE_UPLOADPACK_LS => Service::UploadPackLs, |
| raw::GIT_SERVICE_UPLOADPACK => Service::UploadPack, |
| raw::GIT_SERVICE_RECEIVEPACK_LS => Service::ReceivePackLs, |
| raw::GIT_SERVICE_RECEIVEPACK => Service::ReceivePack, |
| n => panic!("unknown action: {}", n), |
| }; |
| let transport = &mut *(raw_transport as *mut RawSmartSubtransport); |
| let obj = match transport.obj.action(url, action) { |
| Ok(s) => s, |
| Err(e) => return e.raw_code() as c_int, |
| }; |
| *stream = mem::transmute(Box::new(RawSmartSubtransportStream { |
| raw: raw::git_smart_subtransport_stream { |
| subtransport: raw_transport, |
| read: stream_read, |
| write: stream_write, |
| free: stream_free, |
| }, |
| obj: obj, |
| })); |
| 0 |
| }) |
| .unwrap_or(-1) |
| } |
| |
| // callback used by smart transports to close a `SmartSubtransport` trait |
| // object. |
| extern "C" fn subtransport_close(transport: *mut raw::git_smart_subtransport) -> c_int { |
| let ret = panic::wrap(|| unsafe { |
| let transport = &mut *(transport as *mut RawSmartSubtransport); |
| transport.obj.close() |
| }); |
| match ret { |
| Some(Ok(())) => 0, |
| Some(Err(e)) => e.raw_code() as c_int, |
| None => -1, |
| } |
| } |
| |
| // callback used by smart transports to free a `SmartSubtransport` trait |
| // object. |
| extern "C" fn subtransport_free(transport: *mut raw::git_smart_subtransport) { |
| let _ = panic::wrap(|| unsafe { |
| mem::transmute::<_, Box<RawSmartSubtransport>>(transport); |
| }); |
| } |
| |
| // callback used by smart transports to read from a `SmartSubtransportStream` |
| // object. |
| extern "C" fn stream_read( |
| stream: *mut raw::git_smart_subtransport_stream, |
| buffer: *mut c_char, |
| buf_size: size_t, |
| bytes_read: *mut size_t, |
| ) -> c_int { |
| let ret = panic::wrap(|| unsafe { |
| let transport = &mut *(stream as *mut RawSmartSubtransportStream); |
| let buf = slice::from_raw_parts_mut(buffer as *mut u8, buf_size as usize); |
| match transport.obj.read(buf) { |
| Ok(n) => { |
| *bytes_read = n as size_t; |
| Ok(n) |
| } |
| e => e, |
| } |
| }); |
| match ret { |
| Some(Ok(_)) => 0, |
| Some(Err(e)) => unsafe { |
| set_err(&e); |
| -2 |
| }, |
| None => -1, |
| } |
| } |
| |
| // callback used by smart transports to write to a `SmartSubtransportStream` |
| // object. |
| extern "C" fn stream_write( |
| stream: *mut raw::git_smart_subtransport_stream, |
| buffer: *const c_char, |
| len: size_t, |
| ) -> c_int { |
| let ret = panic::wrap(|| unsafe { |
| let transport = &mut *(stream as *mut RawSmartSubtransportStream); |
| let buf = slice::from_raw_parts(buffer as *const u8, len as usize); |
| transport.obj.write_all(buf) |
| }); |
| match ret { |
| Some(Ok(())) => 0, |
| Some(Err(e)) => unsafe { |
| set_err(&e); |
| -2 |
| }, |
| None => -1, |
| } |
| } |
| |
| unsafe fn set_err(e: &io::Error) { |
| let s = CString::new(e.to_string()).unwrap(); |
| raw::git_error_set_str(raw::GIT_ERROR_NET as c_int, s.as_ptr()) |
| } |
| |
| // callback used by smart transports to free a `SmartSubtransportStream` |
| // object. |
| extern "C" fn stream_free(stream: *mut raw::git_smart_subtransport_stream) { |
| let _ = panic::wrap(|| unsafe { |
| mem::transmute::<_, Box<RawSmartSubtransportStream>>(stream); |
| }); |
| } |