1 //! Interfaces for adding custom transports to libgit2
3 use libc
::{c_char, c_int, c_uint, c_void, size_t}
;
4 use std
::ffi
::{CStr, CString}
;
6 use std
::io
::prelude
::*;
12 use crate::util
::Binding
;
13 use crate::{panic, raw, Error, Remote}
;
15 /// A transport is a structure which knows how to transfer data to and from a
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
,
26 /// Interface used by smart transports.
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.
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.
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
>;
45 /// Terminates a connection with the remote.
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.
51 /// 1. UploadPackLs -> UploadPack
52 /// 2. ReceivePackLs -> ReceivePack
53 fn close(&self) -> Result
<(), Error
>;
56 /// Actions that a smart transport can ask a subtransport to perform
57 #[derive(Copy, Clone, PartialEq)]
58 #[allow(missing_docs)]
66 /// An instance of a stream over which a smart transport will communicate with a
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 {}
74 impl<T
: Read
+ Write
+ Send
+ '
static> SmartSubtransportStream
for T {}
76 type TransportFactory
= dyn Fn(&Remote
<'_
>) -> Result
<Transport
, Error
> + Send
+ Sync
+ '
static;
78 /// Boxed data payload used for registering new transports.
80 /// Currently only contains a field which knows how to create transports.
81 struct TransportData
{
82 factory
: Box
<TransportFactory
>,
85 /// Instance of a `git_smart_subtransport`, must use `#[repr(C)]` to ensure that
86 /// the C fields come first.
88 struct RawSmartSubtransport
{
89 raw
: raw
::git_smart_subtransport
,
90 stream
: Option
<*mut raw
::git_smart_subtransport_stream
>,
92 obj
: Box
<dyn SmartSubtransport
>,
95 /// Instance of a `git_smart_subtransport_stream`, must use `#[repr(C)]` to
96 /// ensure that the C fields come first.
98 struct RawSmartSubtransportStream
{
99 raw
: raw
::git_smart_subtransport_stream
,
100 obj
: Box
<dyn SmartSubtransportStream
>,
103 /// Add a custom transport definition, to be used in addition to the built-in
104 /// set of transports that come with libgit2.
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
>
110 F
: Fn(&Remote
<'_
>) -> Result
<Transport
, Error
> + Send
+ Sync
+ '
static,
113 let mut data
= Box
::new(TransportData
{
114 factory
: Box
::new(factory
),
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
));
125 /// Creates a new transport which will use the "smart" transport protocol
126 /// for transferring data.
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.
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
>
137 S
: SmartSubtransport
,
139 let mut ret
= ptr
::null_mut();
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
),
149 obj
: Box
::new(subtransport
),
151 let mut defn
= raw
::git_smart_subtransport_definition
{
152 callback
: Some(smart_factory
),
154 param
: &mut *raw
as *mut _
as *mut _
,
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!).
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!)
166 try_call
!(raw
::git_transport_smart(
169 &mut defn
as *mut _
as *mut _
171 mem
::forget(raw
); // ownership transport to `ret`
173 return Ok(Transport
{
178 extern "C" fn smart_factory(
179 out
: *mut *mut raw
::git_smart_subtransport
,
180 _owner
: *mut raw
::git_transport
,
184 *out
= ptr
as *mut raw
::git_smart_subtransport
;
191 impl Drop
for Transport
{
194 unsafe { (*self.raw).free.unwrap()(self.raw) }
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
,
206 remote
: Option
<Remote
<'a
>>,
208 impl<'a
> Drop
for Bomb
<'a
> {
210 // TODO: maybe a method instead?
211 mem
::forget(self.remote
.take());
215 panic
::wrap(|| unsafe {
217 remote
: Some(Binding
::from_raw(owner
)),
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;
226 Err(e
) => e
.raw_code() as c_int
,
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
,
238 action
: raw
::git_smart_service_t
,
240 panic
::wrap(|| unsafe {
241 let url
= CStr
::from_ptr(url
).to_bytes();
242 let url
= match str::from_utf8(url
).ok() {
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
),
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
;
260 let obj
= match transport
.obj
.action(url
, action
) {
264 return e
.raw_code() as c_int
;
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
),
276 transport
.stream
= Some(*stream
);
278 if transport
.stream
.is_none() {
281 *stream
= transport
.stream
.unwrap();
288 // callback used by smart transports to close a `SmartSubtransport` trait
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()
297 Some(Err(e
)) => e
.raw_code() as c_int
,
302 // callback used by smart transports to free a `SmartSubtransport` trait
304 extern "C" fn subtransport_free(transport
: *mut raw
::git_smart_subtransport
) {
305 let _
= panic
::wrap(|| unsafe {
306 mem
::transmute
::<_
, Box
<RawSmartSubtransport
>>(transport
);
310 // callback used by smart transports to read from a `SmartSubtransportStream`
312 extern "C" fn stream_read(
313 stream
: *mut raw
::git_smart_subtransport_stream
,
316 bytes_read
: *mut size_t
,
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
) {
323 *bytes_read
= n
as size_t
;
331 Some(Err(e
)) => unsafe {
339 // callback used by smart transports to write to a `SmartSubtransportStream`
341 extern "C" fn stream_write(
342 stream
: *mut raw
::git_smart_subtransport_stream
,
343 buffer
: *const c_char
,
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
)
353 Some(Err(e
)) => unsafe {
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());
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());
371 // callback used by smart transports to free a `SmartSubtransportStream`
373 extern "C" fn stream_free(stream
: *mut raw
::git_smart_subtransport_stream
) {
374 let _
= panic
::wrap(|| unsafe {
375 mem
::transmute
::<_
, Box
<RawSmartSubtransportStream
>>(stream
);
382 use crate::{ErrorClass, ErrorCode}
;
385 struct DummyTransport
;
387 // in lieu of lazy_static
388 fn dummy_error() -> Error
{
389 Error
::new(ErrorCode
::Ambiguous
, ErrorClass
::Net
, "bleh")
392 impl SmartSubtransport
for DummyTransport
{
397 ) -> Result
<Box
<dyn SmartSubtransportStream
>, Error
> {
401 fn close(&self) -> Result
<(), Error
> {
407 fn transport_error_propagates() {
408 static INIT
: Once
= Once
::new();
412 register("dummy", move |remote
| {
413 Transport
::smart(&remote
, true, DummyTransport
)
419 let (_td
, repo
) = crate::test
::repo_init();
420 t
!(repo
.remote("origin", "dummy://ball"));
422 let mut origin
= t
!(repo
.find_remote("origin"));
424 match origin
.fetch(&["main"], None
, None
) {
425 Ok(()) => unreachable
!(),
426 Err(e
) => assert_eq
!(e
, dummy_error()),