diff options
Diffstat (limited to 'vendor/git2/src/transport.rs')
-rw-r--r-- | vendor/git2/src/transport.rs | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/vendor/git2/src/transport.rs b/vendor/git2/src/transport.rs new file mode 100644 index 000000000..5d4695948 --- /dev/null +++ b/vendor/git2/src/transport.rs @@ -0,0 +1,429 @@ +//! 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 definition 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, PartialEq)] +#[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, + stream: Option<*mut raw::git_smart_subtransport_stream>, + rpc: bool, + 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; + let factory: raw::git_transport_cb = Some(transport_factory); + try_call!(raw::git_transport_register(prefix, 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: Some(subtransport_action), + close: Some(subtransport_close), + free: Some(subtransport_free), + }, + stream: None, + rpc, + obj: Box::new(subtransport), + }); + let mut defn = raw::git_smart_subtransport_definition { + callback: Some(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.unwrap()(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 mut transport = &mut *(raw_transport as *mut RawSmartSubtransport); + // Note: we only need to generate if rpc is on. Else, for receive-pack and upload-pack + // libgit2 reuses the stream generated for receive-pack-ls or upload-pack-ls. + let generate_stream = + transport.rpc || action == Service::UploadPackLs || action == Service::ReceivePackLs; + if generate_stream { + let obj = match transport.obj.action(url, action) { + Ok(s) => s, + Err(e) => { + set_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: Some(stream_read), + write: Some(stream_write), + free: Some(stream_free), + }, + obj, + })); + transport.stream = Some(*stream); + } else { + if transport.stream.is_none() { + return -1; + } + *stream = transport.stream.unwrap(); + } + 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_io(&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_io(&e); + -2 + }, + None => -1, + } +} + +unsafe fn set_err_io(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()); +} + +unsafe fn set_err(e: &Error) { + let s = CString::new(e.message()).unwrap(); + raw::git_error_set_str(e.raw_class() 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); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ErrorClass, ErrorCode}; + use std::sync::Once; + + struct DummyTransport; + + // in lieu of lazy_static + fn dummy_error() -> Error { + Error::new(ErrorCode::Ambiguous, ErrorClass::Net, "bleh") + } + + impl SmartSubtransport for DummyTransport { + fn action( + &self, + _url: &str, + _service: Service, + ) -> Result<Box<dyn SmartSubtransportStream>, Error> { + Err(dummy_error()) + } + + fn close(&self) -> Result<(), Error> { + Ok(()) + } + } + + #[test] + fn transport_error_propagates() { + static INIT: Once = Once::new(); + + unsafe { + INIT.call_once(|| { + register("dummy", move |remote| { + Transport::smart(&remote, true, DummyTransport) + }) + .unwrap(); + }) + } + + let (_td, repo) = crate::test::repo_init(); + t!(repo.remote("origin", "dummy://ball")); + + let mut origin = t!(repo.find_remote("origin")); + + match origin.fetch(&["main"], None, None) { + Ok(()) => unreachable!(), + Err(e) => assert_eq!(e, dummy_error()), + } + } +} |