1use libc::{c_char, c_int, c_uint, c_void, size_t};
4use std::ffi::{CStr, CString};
5use std::io;
6use std::io::prelude::*;
7use std::mem;
8use std::ptr;
9use std::slice;
10use std::str;
11
12use crate::util::Binding;
13use crate::{panic, raw, Error, Remote};
14
15#[allow(missing_copy_implementations)]
21pub struct Transport {
22 raw: *mut raw::git_transport,
23 owned: bool,
24}
25
26pub trait SmartSubtransport: Send + 'static {
36 fn action(&self, url: &str, action: Service)
43 -> Result<Box<dyn SmartSubtransportStream>, Error>;
44
45 fn close(&self) -> Result<(), Error>;
54}
55
56#[derive(Copy, Clone, PartialEq, Debug)]
58#[allow(missing_docs)]
59pub enum Service {
60 UploadPackLs,
61 UploadPack,
62 ReceivePackLs,
63 ReceivePack,
64}
65
66pub trait SmartSubtransportStream: Read + Write + Send + 'static {}
73
74impl<T: Read + Write + Send + 'static> SmartSubtransportStream for T {}
75
76type TransportFactory = dyn Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static;
77
78struct TransportData {
82 factory: Box<TransportFactory>,
83}
84
85#[repr(C)]
88struct 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#[repr(C)]
98struct RawSmartSubtransportStream {
99 raw: raw::git_smart_subtransport_stream,
100 obj: Box<dyn SmartSubtransportStream>,
101}
102
103pub unsafe fn register<F>(prefix: &str, factory: F) -> Result<(), Error>
109where
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
124impl Transport {
125 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 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); }
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
191impl Drop for Transport {
192 fn drop(&mut self) {
193 if self.owned {
194 unsafe { (*self.raw).free.unwrap()(self.raw) }
195 }
196 }
197}
198
199extern "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 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
232extern "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 transport = &mut *(raw_transport as *mut RawSmartSubtransport);
255 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) => return e.raw_set_git_error(),
263 };
264 *stream = mem::transmute(Box::new(RawSmartSubtransportStream {
265 raw: raw::git_smart_subtransport_stream {
266 subtransport: raw_transport,
267 read: Some(stream_read),
268 write: Some(stream_write),
269 free: Some(stream_free),
270 },
271 obj,
272 }));
273 transport.stream = Some(*stream);
274 } else {
275 if transport.stream.is_none() {
276 return -1;
277 }
278 *stream = transport.stream.unwrap();
279 }
280 0
281 })
282 .unwrap_or(-1)
283}
284
285extern "C" fn subtransport_close(transport: *mut raw::git_smart_subtransport) -> c_int {
288 let ret = panic::wrap(|| unsafe {
289 let transport = &mut *(transport as *mut RawSmartSubtransport);
290 transport.obj.close()
291 });
292 match ret {
293 Some(Ok(())) => 0,
294 Some(Err(e)) => e.raw_code() as c_int,
295 None => -1,
296 }
297}
298
299extern "C" fn subtransport_free(transport: *mut raw::git_smart_subtransport) {
302 let _ = panic::wrap(|| unsafe {
303 mem::transmute::<_, Box<RawSmartSubtransport>>(transport);
304 });
305}
306
307extern "C" fn stream_read(
310 stream: *mut raw::git_smart_subtransport_stream,
311 buffer: *mut c_char,
312 buf_size: size_t,
313 bytes_read: *mut size_t,
314) -> c_int {
315 let ret = panic::wrap(|| unsafe {
316 let transport = &mut *(stream as *mut RawSmartSubtransportStream);
317 let buf = slice::from_raw_parts_mut(buffer as *mut u8, buf_size as usize);
318 match transport.obj.read(buf) {
319 Ok(n) => {
320 *bytes_read = n as size_t;
321 Ok(n)
322 }
323 e => e,
324 }
325 });
326 match ret {
327 Some(Ok(_)) => 0,
328 Some(Err(e)) => unsafe {
329 set_err_io(&e);
330 -2
331 },
332 None => -1,
333 }
334}
335
336extern "C" fn stream_write(
339 stream: *mut raw::git_smart_subtransport_stream,
340 buffer: *const c_char,
341 len: size_t,
342) -> c_int {
343 let ret = panic::wrap(|| unsafe {
344 let transport = &mut *(stream as *mut RawSmartSubtransportStream);
345 let buf = slice::from_raw_parts(buffer as *const u8, len as usize);
346 transport.obj.write_all(buf)
347 });
348 match ret {
349 Some(Ok(())) => 0,
350 Some(Err(e)) => unsafe {
351 set_err_io(&e);
352 -2
353 },
354 None => -1,
355 }
356}
357
358unsafe fn set_err_io(e: &io::Error) {
359 let s = CString::new(e.to_string()).unwrap();
360 raw::git_error_set_str(raw::GIT_ERROR_NET as c_int, s.as_ptr());
361}
362
363extern "C" fn stream_free(stream: *mut raw::git_smart_subtransport_stream) {
366 let _ = panic::wrap(|| unsafe {
367 mem::transmute::<_, Box<RawSmartSubtransportStream>>(stream);
368 });
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use crate::{ErrorClass, ErrorCode};
375 use std::sync::Once;
376
377 struct DummyTransport;
378
379 fn dummy_error() -> Error {
381 Error::new(ErrorCode::Ambiguous, ErrorClass::Net, "bleh")
382 }
383
384 impl SmartSubtransport for DummyTransport {
385 fn action(
386 &self,
387 _url: &str,
388 _service: Service,
389 ) -> Result<Box<dyn SmartSubtransportStream>, Error> {
390 Err(dummy_error())
391 }
392
393 fn close(&self) -> Result<(), Error> {
394 Ok(())
395 }
396 }
397
398 #[test]
399 fn transport_error_propagates() {
400 static INIT: Once = Once::new();
401
402 unsafe {
403 INIT.call_once(|| {
404 register("dummy", move |remote| {
405 Transport::smart(&remote, true, DummyTransport)
406 })
407 .unwrap();
408 })
409 }
410
411 let (_td, repo) = crate::test::repo_init();
412 t!(repo.remote("origin", "dummy://ball"));
413
414 let mut origin = t!(repo.find_remote("origin"));
415
416 match origin.fetch(&["main"], None, None) {
417 Ok(()) => unreachable!(),
418 Err(e) => assert_eq!(e, dummy_error()),
419 }
420 }
421}