66
77use  cap_std_ext:: prelude:: CapStdExtCommandExt ; 
88use  cap_std_ext:: { cap_std,  cap_tempfile} ; 
9- use  futures_util:: Future ; 
9+ use  futures_util:: { Future ,   FutureExt } ; 
1010use  oci_spec:: image:: { Descriptor ,  Digest } ; 
1111use  serde:: { Deserialize ,  Serialize } ; 
1212use  std:: fs:: File ; 
13+ use  std:: num:: NonZeroU32 ; 
1314use  std:: ops:: Range ; 
1415use  std:: os:: fd:: OwnedFd ; 
1516use  std:: os:: unix:: prelude:: CommandExt ; 
@@ -64,6 +65,18 @@ impl Error {
6465    } 
6566} 
6667
68+ /// Errors returned by get_raw_blob 
69+ #[ derive( Error ,  Debug ) ]  
70+ #[ non_exhaustive]  
71+ pub  enum  GetBlobError  { 
72+     /// A client may reasonably retry on this type of error. 
73+ #[ error( "retryable error" ) ]  
74+     Retryable ( Box < str > ) , 
75+     #[ error( "error" ) ]  
76+     /// An unknown other error 
77+ Other ( Box < str > ) , 
78+ } 
79+ 
6780impl  From < rustix:: io:: Errno >  for  Error  { 
6881    fn  from ( value :  rustix:: io:: Errno )  -> Self  { 
6982        Self :: Io ( value. into ( ) ) 
@@ -318,6 +331,12 @@ pub struct ConvertedLayerInfo {
318331pub  media_type :  oci_spec:: image:: MediaType , 
319332} 
320333
334+ /// Maps the two types of return values from the proxy 
335+ enum  FileDescriptors  { 
336+     FinishPipe  {  pipeid :  NonZeroU32 ,  datafd :  OwnedFd  } , 
337+     DualFds  {  datafd :  OwnedFd ,  errfd :  OwnedFd  } , 
338+ } 
339+ 
321340impl  ImageProxy  { 
322341    /// Create an image proxy that fetches the target image, using default configuration. 
323342pub  async  fn  new ( )  -> Result < Self >  { 
@@ -373,7 +392,7 @@ impl ImageProxy {
373392    async  fn  impl_request_raw < T :  serde:: de:: DeserializeOwned  + Send  + ' static > ( 
374393        sockfd :  Arc < Mutex < OwnedFd > > , 
375394        req :  Request , 
376-     )  -> Result < ( T ,  Option < ( OwnedFd ,   u32 ) > ) >  { 
395+     )  -> Result < ( T ,  Option < FileDescriptors > ) >  { 
377396        tracing:: trace!( "sending request {}" ,  req. method. as_str( ) ) ; 
378397        // TODO: Investigate https://crates.io/crates/uds for SOCK_SEQPACKET tokio 
379398        let  r = tokio:: task:: spawn_blocking ( move  || { 
@@ -394,14 +413,14 @@ impl ImageProxy {
394413                rustix:: net:: RecvFlags :: CMSG_CLOEXEC , 
395414            ) ?
396415            . bytes ; 
397-             let  fdret = cmsg_buffer
416+             let  mut   fdret = cmsg_buffer
398417                . drain ( ) 
399418                . filter_map ( |m| match  m { 
400419                    rustix:: net:: RecvAncillaryMessage :: ScmRights ( f)  => Some ( f) , 
401420                    _ => None , 
402421                } ) 
403422                . flatten ( ) 
404-                 . next ( ) ; 
423+                 . fuse ( ) ; 
405424            let  buf = & buf[ ..nread] ; 
406425            let  reply:  Reply  = serde_json:: from_slice ( buf) ?; 
407426            if  !reply. success  { 
@@ -410,21 +429,42 @@ impl ImageProxy {
410429                    error :  reply. error . into ( ) , 
411430                } ) ; 
412431            } 
413-             let  fdret = match  ( fdret,  reply. pipeid )  { 
414-                 ( Some ( fd) ,  n)  => { 
415-                     if  n == 0  { 
416-                         return  Err ( Error :: Other ( "got fd but no pipeid" . into ( ) ) ) ; 
417-                     } 
418-                     Some ( ( fd,  n) ) 
432+             let  first_fd = fdret. next ( ) ; 
433+             let  second_fd = fdret. next ( ) ; 
434+             if  fdret. next ( ) . is_some ( )  { 
435+                 return  Err ( Error :: Other ( 
436+                     format ! ( "got more than two file descriptors" ) . into ( ) , 
437+                 ) ) ; 
438+             } 
439+             let  pipeid = NonZeroU32 :: new ( reply. pipeid ) ; 
440+             let  fdret = match  ( first_fd,  second_fd,  pipeid)  { 
441+                 // No fds, no pipeid 
442+                 ( None ,  None ,  None )  => None , 
443+                 // A FinishPipe instance 
444+                 ( Some ( datafd) ,  None ,  Some ( pipeid) )  => { 
445+                     Some ( FileDescriptors :: FinishPipe  {  pipeid,  datafd } ) 
446+                 } 
447+                 // A dualfd instance 
448+                 ( Some ( datafd) ,  Some ( errfd) ,  None )  => { 
449+                     Some ( FileDescriptors :: DualFds  {  datafd,  errfd } ) 
450+                 } 
451+                 // Everything after here is error cases 
452+                 ( Some ( _) ,  None ,  None )  => { 
453+                     return  Err ( Error :: Other ( format ! ( "got fd with zero pipeid" ) . into ( ) ) ) ; 
454+                 } 
455+                 ( None ,  Some ( _) ,  _)  => { 
456+                     return  Err ( Error :: Other ( format ! ( "got errfd with no datafd" ) . into ( ) ) ) ; 
457+                 } 
458+                 ( Some ( _) ,  Some ( _) ,  Some ( n) )  => { 
459+                     return  Err ( Error :: Other ( 
460+                         format ! ( "got pipeid {} with both datafd and errfd" ,  n) . into ( ) , 
461+                     ) ) ; 
419462                } 
420-                 ( None ,  n)  => { 
421-                     if  n != 0  { 
422-                         return  Err ( Error :: Other ( format ! ( "got no fd with pipeid {}" ,  n) . into ( ) ) ) ; 
423-                     } 
424-                     None 
463+                 ( None ,  _,  Some ( n) )  => { 
464+                     return  Err ( Error :: Other ( format ! ( "got no fd with pipeid {n}" ) . into ( ) ) ) ; 
425465                } 
426466            } ; 
427-             let  reply = serde_json:: from_value ( reply. value ) ?; 
467+             let  reply:   T  = serde_json:: from_value ( reply. value ) ?; 
428468            Ok ( ( reply,  fdret) ) 
429469        } ) 
430470        . await 
@@ -438,7 +478,7 @@ impl ImageProxy {
438478        & self , 
439479        method :  & str , 
440480        args :  T , 
441-     )  -> Result < ( R ,  Option < ( OwnedFd ,   u32 ) > ) > 
481+     )  -> Result < ( R ,  Option < FileDescriptors > ) > 
442482    where 
443483        T :  IntoIterator < Item  = I > , 
444484        I :  Into < serde_json:: Value > , 
@@ -456,9 +496,9 @@ impl ImageProxy {
456496    } 
457497
458498    #[ instrument]  
459-     async  fn  finish_pipe ( & self ,  pipeid :  u32 )  -> Result < ( ) >  { 
499+     async  fn  finish_pipe ( & self ,  pipeid :  NonZeroU32 )  -> Result < ( ) >  { 
460500        tracing:: debug!( "closing pipe" ) ; 
461-         let  ( r,  fd)  = self . impl_request ( "FinishPipe" ,  [ pipeid] ) . await ?; 
501+         let  ( r,  fd)  = self . impl_request ( "FinishPipe" ,  [ pipeid. get ( ) ] ) . await ?; 
462502        if  fd. is_some ( )  { 
463503            return  Err ( Error :: Other ( "Unexpected fd in finish_pipe reply" . into ( ) ) ) ; 
464504        } 
@@ -494,9 +534,12 @@ impl ImageProxy {
494534        Ok ( r) 
495535    } 
496536
497-     async  fn  read_all_fd ( & self ,  fd :  Option < ( OwnedFd ,  u32 ) > )  -> Result < Vec < u8 > >  { 
498-         let  ( fd,  pipeid)  = fd. ok_or_else ( || Error :: Other ( "Missing fd from reply" . into ( ) ) ) ?; 
499-         let  fd = tokio:: fs:: File :: from_std ( std:: fs:: File :: from ( fd) ) ; 
537+     async  fn  read_all_fd ( & self ,  fd :  Option < FileDescriptors > )  -> Result < Vec < u8 > >  { 
538+         let  fd = fd. ok_or_else ( || Error :: Other ( "Missing fd from reply" . into ( ) ) ) ?; 
539+         let  FileDescriptors :: FinishPipe  {  pipeid,  datafd }  = fd else  { 
540+             return  Err ( Error :: Other ( "got dualfds, expecting FinishPipe fd" . into ( ) ) ) ; 
541+         } ; 
542+         let  fd = tokio:: fs:: File :: from_std ( std:: fs:: File :: from ( datafd) ) ; 
500543        let  mut  fd = tokio:: io:: BufReader :: new ( fd) ; 
501544        let  mut  r = Vec :: new ( ) ; 
502545        let  reader = fd. read_to_end ( & mut  r) ; 
@@ -569,13 +612,67 @@ impl ImageProxy {
569612        let  args:  Vec < serde_json:: Value >  =
570613            vec ! [ img. 0 . into( ) ,  digest. to_string( ) . into( ) ,  size. into( ) ] ; 
571614        let  ( _bloblen,  fd)  = self . impl_request :: < i64 ,  _ ,  _ > ( "GetBlob" ,  args) . await ?; 
572-         let  ( fd,  pipeid)  = fd. ok_or_else ( || Error :: new_other ( "Missing fd from reply" ) ) ?; 
573-         let  fd = tokio:: fs:: File :: from_std ( std:: fs:: File :: from ( fd) ) ; 
615+         let  fd = fd. ok_or_else ( || Error :: Other ( "Missing fd from reply" . into ( ) ) ) ?; 
616+         let  FileDescriptors :: FinishPipe  {  pipeid,  datafd }  = fd else  { 
617+             return  Err ( Error :: Other ( "got dualfds, expecting FinishPipe fd" . into ( ) ) ) ; 
618+         } ; 
619+         let  fd = tokio:: fs:: File :: from_std ( std:: fs:: File :: from ( datafd) ) ; 
574620        let  fd = tokio:: io:: BufReader :: new ( fd) ; 
575621        let  finish = Box :: pin ( self . finish_pipe ( pipeid) ) ; 
576622        Ok ( ( fd,  finish) ) 
577623    } 
578624
625+     async  fn  read_blob_error ( fd :  OwnedFd )  -> std:: result:: Result < ( ) ,  GetBlobError >  { 
626+         let  fd = tokio:: fs:: File :: from_std ( std:: fs:: File :: from ( fd) ) ; 
627+         let  mut  errfd = tokio:: io:: BufReader :: new ( fd) ; 
628+         let  mut  buf = Vec :: new ( ) ; 
629+         errfd
630+             . read_to_end ( & mut  buf) 
631+             . await 
632+             . map_err ( |e| GetBlobError :: Other ( e. to_string ( ) . into_boxed_str ( ) ) ) ?; 
633+         if  buf. is_empty ( )  { 
634+             return  Ok ( ( ) ) ; 
635+         } 
636+         #[ derive( Deserialize ) ]  
637+         struct  RemoteError  { 
638+             code :  String , 
639+             message :  String , 
640+         } 
641+         let  e:  RemoteError  = serde_json:: from_slice ( & buf) 
642+             . map_err ( |e| GetBlobError :: Other ( e. to_string ( ) . into_boxed_str ( ) ) ) ?; 
643+         match  e. code . as_str ( )  { 
644+             // Actually this is OK 
645+             "EPIPE"  => Ok ( ( ) ) , 
646+             "retryable"  => Err ( GetBlobError :: Retryable ( e. message . into_boxed_str ( ) ) ) , 
647+             _ => Err ( GetBlobError :: Other ( e. message . into_boxed_str ( ) ) ) , 
648+         } 
649+     } 
650+ 
651+     /// Fetch a blob identified by e.g. `sha256:<digest>`; does not perform 
652+ /// any verification that the blob matches the digest. The size of the 
653+ /// blob and a pipe file descriptor are returned. 
654+ #[ instrument]  
655+     pub  async  fn  get_raw_blob ( 
656+         & self , 
657+         img :  & OpenedImage , 
658+         digest :  & Digest , 
659+     )  -> Result < ( 
660+         u64 , 
661+         tokio:: fs:: File , 
662+         impl  Future < Output  = std:: result:: Result < ( ) ,  GetBlobError > >  + Unpin  + ' _ , 
663+     ) >  { 
664+         tracing:: debug!( "fetching blob" ) ; 
665+         let  args:  Vec < serde_json:: Value >  = vec ! [ img. 0 . into( ) ,  digest. to_string( ) . into( ) ] ; 
666+         let  ( bloblen,  fd)  = self . impl_request :: < u64 ,  _ ,  _ > ( "GetRawBlob" ,  args) . await ?; 
667+         let  fd = fd. ok_or_else ( || Error :: new_other ( "Missing fd from reply" ) ) ?; 
668+         let  FileDescriptors :: DualFds  {  datafd,  errfd }  = fd else  { 
669+             return  Err ( Error :: Other ( "got single fd, expecting dual fds" . into ( ) ) ) ; 
670+         } ; 
671+         let  fd = tokio:: fs:: File :: from_std ( std:: fs:: File :: from ( datafd) ) ; 
672+         let  err = Self :: read_blob_error ( errfd) . boxed ( ) ; 
673+         Ok ( ( bloblen,  fd,  err) ) 
674+     } 
675+ 
579676    /// Fetch a descriptor. The requested size and digest are verified (by the proxy process). 
580677#[ instrument]  
581678    pub  async  fn  get_descriptor ( 
0 commit comments