6
6
7
7
use cap_std_ext:: prelude:: CapStdExtCommandExt ;
8
8
use cap_std_ext:: { cap_std, cap_tempfile} ;
9
- use futures_util:: Future ;
9
+ use futures_util:: { Future , FutureExt } ;
10
10
use oci_spec:: image:: { Descriptor , Digest } ;
11
11
use serde:: { Deserialize , Serialize } ;
12
12
use std:: fs:: File ;
13
+ use std:: num:: NonZeroU32 ;
13
14
use std:: ops:: Range ;
14
15
use std:: os:: fd:: OwnedFd ;
15
16
use std:: os:: unix:: prelude:: CommandExt ;
@@ -64,6 +65,18 @@ impl Error {
64
65
}
65
66
}
66
67
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
+
67
80
impl From < rustix:: io:: Errno > for Error {
68
81
fn from ( value : rustix:: io:: Errno ) -> Self {
69
82
Self :: Io ( value. into ( ) )
@@ -318,6 +331,12 @@ pub struct ConvertedLayerInfo {
318
331
pub media_type : oci_spec:: image:: MediaType ,
319
332
}
320
333
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
+
321
340
impl ImageProxy {
322
341
/// Create an image proxy that fetches the target image, using default configuration.
323
342
pub async fn new ( ) -> Result < Self > {
@@ -373,7 +392,7 @@ impl ImageProxy {
373
392
async fn impl_request_raw < T : serde:: de:: DeserializeOwned + Send + ' static > (
374
393
sockfd : Arc < Mutex < OwnedFd > > ,
375
394
req : Request ,
376
- ) -> Result < ( T , Option < ( OwnedFd , u32 ) > ) > {
395
+ ) -> Result < ( T , Option < FileDescriptors > ) > {
377
396
tracing:: trace!( "sending request {}" , req. method. as_str( ) ) ;
378
397
// TODO: Investigate https://crates.io/crates/uds for SOCK_SEQPACKET tokio
379
398
let r = tokio:: task:: spawn_blocking ( move || {
@@ -394,14 +413,14 @@ impl ImageProxy {
394
413
rustix:: net:: RecvFlags :: CMSG_CLOEXEC ,
395
414
) ?
396
415
. bytes ;
397
- let fdret = cmsg_buffer
416
+ let mut fdret = cmsg_buffer
398
417
. drain ( )
399
418
. filter_map ( |m| match m {
400
419
rustix:: net:: RecvAncillaryMessage :: ScmRights ( f) => Some ( f) ,
401
420
_ => None ,
402
421
} )
403
422
. flatten ( )
404
- . next ( ) ;
423
+ . fuse ( ) ;
405
424
let buf = & buf[ ..nread] ;
406
425
let reply: Reply = serde_json:: from_slice ( buf) ?;
407
426
if !reply. success {
@@ -410,21 +429,42 @@ impl ImageProxy {
410
429
error : reply. error . into ( ) ,
411
430
} ) ;
412
431
}
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
+ ) ) ;
419
462
}
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 ( ) ) ) ;
425
465
}
426
466
} ;
427
- let reply = serde_json:: from_value ( reply. value ) ?;
467
+ let reply: T = serde_json:: from_value ( reply. value ) ?;
428
468
Ok ( ( reply, fdret) )
429
469
} )
430
470
. await
@@ -438,7 +478,7 @@ impl ImageProxy {
438
478
& self ,
439
479
method : & str ,
440
480
args : T ,
441
- ) -> Result < ( R , Option < ( OwnedFd , u32 ) > ) >
481
+ ) -> Result < ( R , Option < FileDescriptors > ) >
442
482
where
443
483
T : IntoIterator < Item = I > ,
444
484
I : Into < serde_json:: Value > ,
@@ -456,9 +496,9 @@ impl ImageProxy {
456
496
}
457
497
458
498
#[ instrument]
459
- async fn finish_pipe ( & self , pipeid : u32 ) -> Result < ( ) > {
499
+ async fn finish_pipe ( & self , pipeid : NonZeroU32 ) -> Result < ( ) > {
460
500
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 ?;
462
502
if fd. is_some ( ) {
463
503
return Err ( Error :: Other ( "Unexpected fd in finish_pipe reply" . into ( ) ) ) ;
464
504
}
@@ -494,9 +534,12 @@ impl ImageProxy {
494
534
Ok ( r)
495
535
}
496
536
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) ) ;
500
543
let mut fd = tokio:: io:: BufReader :: new ( fd) ;
501
544
let mut r = Vec :: new ( ) ;
502
545
let reader = fd. read_to_end ( & mut r) ;
@@ -569,13 +612,67 @@ impl ImageProxy {
569
612
let args: Vec < serde_json:: Value > =
570
613
vec ! [ img. 0 . into( ) , digest. to_string( ) . into( ) , size. into( ) ] ;
571
614
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) ) ;
574
620
let fd = tokio:: io:: BufReader :: new ( fd) ;
575
621
let finish = Box :: pin ( self . finish_pipe ( pipeid) ) ;
576
622
Ok ( ( fd, finish) )
577
623
}
578
624
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
+
579
676
/// Fetch a descriptor. The requested size and digest are verified (by the proxy process).
580
677
#[ instrument]
581
678
pub async fn get_descriptor (
0 commit comments