44//!
55//! More information: <https://github.com/containers/skopeo/pull/1476>
66
7- use anyhow:: { anyhow, Context , Result } ;
87use cap_std_ext:: prelude:: CapStdExtCommandExt ;
98use cap_std_ext:: { cap_std, cap_tempfile} ;
109use futures_util:: Future ;
@@ -18,11 +17,59 @@ use std::path::PathBuf;
1817use std:: pin:: Pin ;
1918use std:: process:: { Command , Stdio } ;
2019use std:: sync:: { Arc , Mutex , OnceLock } ;
20+ use thiserror:: Error ;
2121use tokio:: io:: { AsyncBufRead , AsyncReadExt } ;
2222use tokio:: sync:: Mutex as AsyncMutex ;
2323use tokio:: task:: JoinError ;
2424use tracing:: instrument;
2525
26+ /// Errors returned by this crate.
27+ #[ derive( Error , Debug ) ]
28+ #[ non_exhaustive]
29+ pub enum Error {
30+ #[ error( "i/o error" ) ]
31+ /// An input/output error
32+ Io ( #[ from] std:: io:: Error ) ,
33+ #[ error( "serialization error" ) ]
34+ /// Returned when serialization or deserialization fails
35+ SerDe ( #[ from] serde_json:: Error ) ,
36+ /// The proxy failed to initiate a request
37+ #[ error( "failed to invoke method {method}: {error}" ) ]
38+ RequestInitiationFailure { method : Box < str > , error : Box < str > } ,
39+ /// An error returned from the remote proxy
40+ #[ error( "proxy request returned error" ) ]
41+ RequestReturned ( Box < str > ) ,
42+ #[ error( "semantic version error" ) ]
43+ SemanticVersion ( #[ from] semver:: Error ) ,
44+ #[ error( "proxy too old (requested={requested_version} found={found_version}) error" ) ]
45+ /// The proxy doesn't support the requested semantic version
46+ ProxyTooOld {
47+ requested_version : Box < str > ,
48+ found_version : Box < str > ,
49+ } ,
50+ #[ error( "configuration error" ) ]
51+ /// Conflicting or missing configuration
52+ Configuration ( Box < str > ) ,
53+ #[ error( "error" ) ]
54+ /// An unknown other error
55+ Other ( Box < str > ) ,
56+ }
57+
58+ impl Error {
59+ pub ( crate ) fn new_other ( e : impl Into < Box < str > > ) -> Self {
60+ Self :: Other ( e. into ( ) )
61+ }
62+ }
63+
64+ impl From < rustix:: io:: Errno > for Error {
65+ fn from ( value : rustix:: io:: Errno ) -> Self {
66+ Self :: Io ( value. into ( ) )
67+ }
68+ }
69+
70+ /// The error type returned from this crate.
71+ pub type Result < T > = std:: result:: Result < T , Error > ;
72+
2673/// Re-export because we use this in our public APIs
2774pub use oci_spec;
2875
@@ -152,14 +199,14 @@ pub struct ImageProxyConfig {
152199}
153200
154201impl TryFrom < ImageProxyConfig > for Command {
155- type Error = anyhow :: Error ;
202+ type Error = Error ;
156203
157- fn try_from ( config : ImageProxyConfig ) -> Result < Self , Self :: Error > {
204+ fn try_from ( config : ImageProxyConfig ) -> Result < Self > {
158205 let mut allocated_fds = RESERVED_FD_RANGE . clone ( ) ;
159206 let mut alloc_fd = || {
160- allocated_fds
161- . next ( )
162- . ok_or_else ( || anyhow :: anyhow! ( "Ran out of reserved file descriptors for child" ) )
207+ allocated_fds. next ( ) . ok_or_else ( || {
208+ Error :: Other ( "Ran out of reserved file descriptors for child" . into ( ) )
209+ } )
163210 } ;
164211
165212 // By default, we set up pdeathsig to "lifecycle bind" the child process to us.
@@ -186,7 +233,9 @@ impl TryFrom<ImageProxyConfig> for Command {
186233 . count ( ) ;
187234 if auth_option_count > 1 {
188235 // This is a programmer error really
189- anyhow:: bail!( "Conflicting authentication options" ) ;
236+ return Err ( Error :: Configuration (
237+ "Conflicting authentication options" . into ( ) ,
238+ ) ) ;
190239 }
191240 if let Some ( authfile) = config. authfile {
192241 c. arg ( "--authfile" ) ;
@@ -197,11 +246,13 @@ impl TryFrom<ImageProxyConfig> for Command {
197246 // the file is only readable to privileged code.
198247 let target_fd = alloc_fd ( ) ?;
199248 let tmpd = & cap_std:: fs:: Dir :: open_ambient_dir ( "/tmp" , cap_std:: ambient_authority ( ) ) ?;
200- let mut tempfile = cap_tempfile:: TempFile :: new_anonymous ( tmpd)
201- . context ( "Creating temporary file for auth data" )
202- . map ( std:: io:: BufWriter :: new) ?;
249+ let mut tempfile =
250+ cap_tempfile:: TempFile :: new_anonymous ( tmpd) . map ( std:: io:: BufWriter :: new) ?;
203251 std:: io:: copy ( & mut auth_data, & mut tempfile) ?;
204- let tempfile = tempfile. into_inner ( ) ?. into_std ( ) ;
252+ let tempfile = tempfile
253+ . into_inner ( )
254+ . map_err ( |e| e. into_error ( ) ) ?
255+ . into_std ( ) ;
205256 let fd = std:: sync:: Arc :: new ( tempfile. into ( ) ) ;
206257 c. take_fd_n ( fd, target_fd) ;
207258 c. arg ( "--authfile" ) ;
@@ -261,7 +312,7 @@ impl ImageProxy {
261312 None ,
262313 ) ?;
263314 c. stdin ( Stdio :: from ( theirsock) ) ;
264- let child = c. spawn ( ) . context ( "Failed to spawn skopeo" ) ?;
315+ let child = c. spawn ( ) ?;
265316 tracing:: debug!( "Spawned skopeo pid={:?}" , child. id( ) ) ;
266317 // Here we use std sync API via thread because tokio installs
267318 // a SIGCHLD handler which can conflict with e.g. the glib one
@@ -283,11 +334,10 @@ impl ImageProxy {
283334 // Previously we had a feature to opt-in to requiring newer versions using `if cfg!()`.
284335 let supported = base_proto_version ( ) ;
285336 if !supported. matches ( & protover) {
286- return Err ( anyhow ! (
287- "Unsupported protocol version {} (compatible: {})" ,
288- protover,
289- supported
290- ) ) ;
337+ return Err ( Error :: ProxyTooOld {
338+ requested_version : protover. to_string ( ) . into ( ) ,
339+ found_version : supported. to_string ( ) . into ( ) ,
340+ } ) ;
291341 }
292342 r. protover = protover;
293343
@@ -327,28 +377,32 @@ impl ImageProxy {
327377 . flatten ( )
328378 . next ( ) ;
329379 let buf = & buf[ ..nread] ;
330- let reply: Reply = serde_json:: from_slice ( buf) . context ( "Deserializing reply" ) ?;
380+ let reply: Reply = serde_json:: from_slice ( buf) ?;
331381 if !reply. success {
332- return Err ( anyhow ! ( "remote error: {}" , reply. error) ) ;
382+ return Err ( Error :: RequestInitiationFailure {
383+ method : req. method . clone ( ) . into ( ) ,
384+ error : reply. error . into ( ) ,
385+ } ) ;
333386 }
334387 let fdret = match ( fdret, reply. pipeid ) {
335388 ( Some ( fd) , n) => {
336389 if n == 0 {
337- return Err ( anyhow ! ( "got fd but no pipeid" ) ) ;
390+ return Err ( Error :: Other ( "got fd but no pipeid" . into ( ) ) ) ;
338391 }
339392 Some ( ( fd, n) )
340393 }
341394 ( None , n) => {
342395 if n != 0 {
343- return Err ( anyhow ! ( "got no fd with pipeid {}" , n) ) ;
396+ return Err ( Error :: Other ( format ! ( "got no fd with pipeid {}" , n) . into ( ) ) ) ;
344397 }
345398 None
346399 }
347400 } ;
348- let reply = serde_json:: from_value ( reply. value ) . context ( "Deserializing value" ) ?;
401+ let reply = serde_json:: from_value ( reply. value ) ?;
349402 Ok ( ( reply, fdret) )
350403 } )
351- . await ??;
404+ . await
405+ . map_err ( |e| Error :: Other ( e. to_string ( ) . into ( ) ) ) ??;
352406 tracing:: trace!( "completed request" ) ;
353407 Ok ( r)
354408 }
@@ -367,12 +421,15 @@ impl ImageProxy {
367421 let mut childwait = self . childwait . lock ( ) . await ;
368422 tokio:: select! {
369423 r = req => {
370- Ok ( r. with_context( || format!( "Failed to invoke skopeo proxy method {method}" ) ) ?)
424+ r. map_err( |e| Error :: RequestInitiationFailure {
425+ method: method. to_string( ) . into( ) ,
426+ error: e. to_string( ) . into( )
427+ } )
371428 }
372429 r = childwait. as_mut( ) => {
373- let r = r??;
430+ let r = r. map_err ( |e| Error :: Other ( e . to_string ( ) . into ( ) ) ) ??;
374431 let stderr = String :: from_utf8_lossy( & r. stderr) ;
375- Err ( anyhow :: anyhow !( "skopeo proxy unexpectedly exited during request method {}: {}\n {}" , method, r. status, stderr) )
432+ Err ( Error :: Other ( format !( "skopeo proxy unexpectedly exited during request method {}: {}\n {}" , method, r. status, stderr) . into ( ) ) )
376433 }
377434 }
378435 }
@@ -382,7 +439,7 @@ impl ImageProxy {
382439 tracing:: debug!( "closing pipe" ) ;
383440 let ( r, fd) = self . impl_request ( "FinishPipe" , [ pipeid] ) . await ?;
384441 if fd. is_some ( ) {
385- return Err ( anyhow ! ( "Unexpected fd in finish_pipe reply" ) ) ;
442+ return Err ( Error :: Other ( "Unexpected fd in finish_pipe reply" . into ( ) ) ) ;
386443 }
387444 Ok ( r)
388445 }
@@ -417,7 +474,7 @@ impl ImageProxy {
417474 }
418475
419476 async fn read_all_fd ( & self , fd : Option < ( OwnedFd , u32 ) > ) -> Result < Vec < u8 > > {
420- let ( fd, pipeid) = fd. ok_or_else ( || anyhow ! ( "Missing fd from reply" ) ) ?;
477+ let ( fd, pipeid) = fd. ok_or_else ( || Error :: Other ( "Missing fd from reply" . into ( ) ) ) ?;
421478 let fd = tokio:: fs:: File :: from_std ( std:: fs:: File :: from ( fd) ) ;
422479 let mut fd = tokio:: io:: BufReader :: new ( fd) ;
423480 let mut r = Vec :: new ( ) ;
@@ -443,8 +500,7 @@ impl ImageProxy {
443500 img : & OpenedImage ,
444501 ) -> Result < ( String , oci_spec:: image:: ImageManifest ) > {
445502 let ( digest, raw) = self . fetch_manifest_raw_oci ( img) . await ?;
446- let manifest =
447- serde_json:: from_slice ( & raw ) . context ( "Deserializing manifest from skopeo" ) ?;
503+ let manifest = serde_json:: from_slice ( & raw ) ?;
448504 Ok ( ( digest, manifest) )
449505 }
450506
@@ -464,7 +520,7 @@ impl ImageProxy {
464520 img : & OpenedImage ,
465521 ) -> Result < oci_spec:: image:: ImageConfiguration > {
466522 let raw = self . fetch_config_raw ( img) . await ?;
467- serde_json:: from_slice ( & raw ) . context ( "Deserializing config from skopeo" )
523+ serde_json:: from_slice ( & raw ) . map_err ( Into :: into )
468524 }
469525
470526 /// Fetch a blob identified by e.g. `sha256:<digest>`.
@@ -487,7 +543,7 @@ impl ImageProxy {
487543 let args: Vec < serde_json:: Value > =
488544 vec ! [ img. 0 . into( ) , digest. to_string( ) . into( ) , size. into( ) ] ;
489545 let ( _bloblen, fd) = self . impl_request :: < i64 , _ , _ > ( "GetBlob" , args) . await ?;
490- let ( fd, pipeid) = fd. ok_or_else ( || anyhow ! ( "Missing fd from reply" ) ) ?;
546+ let ( fd, pipeid) = fd. ok_or_else ( || Error :: new_other ( "Missing fd from reply" ) ) ?;
491547 let fd = tokio:: fs:: File :: from_std ( std:: fs:: File :: from ( fd) ) ;
492548 let fd = tokio:: io:: BufReader :: new ( fd) ;
493549 let finish = Box :: pin ( self . finish_pipe ( pipeid) ) ;
@@ -535,10 +591,15 @@ impl ImageProxy {
535591 drop ( sendbuf) ;
536592 tracing:: debug!( "sent shutdown request" ) ;
537593 let mut childwait = self . childwait . lock ( ) . await ;
538- let output = childwait. as_mut ( ) . await ??;
594+ let output = childwait
595+ . as_mut ( )
596+ . await
597+ . map_err ( |e| Error :: new_other ( e. to_string ( ) ) ) ??;
539598 if !output. status . success ( ) {
540599 let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
541- anyhow:: bail!( "proxy failed: {}\n {}" , output. status, stderr)
600+ return Err ( Error :: RequestReturned (
601+ format ! ( "proxy failed: {}\n {}" , output. status, stderr) . into ( ) ,
602+ ) ) ;
542603 }
543604 tracing:: debug!( "proxy exited successfully" ) ;
544605 Ok ( ( ) )
0 commit comments