@@ -8,7 +8,6 @@ pub use sandbox::*;
88
99use crate :: native;
1010use crate :: workspace:: Workspace ;
11- use failure:: { Error , Fail } ;
1211use futures_util:: {
1312 future:: { self , FutureExt } ,
1413 stream:: { self , TryStreamExt } ,
@@ -58,20 +57,79 @@ pub(crate) mod container_dirs {
5857}
5958
6059/// Error happened while executing a command.
61- #[ derive( Debug , Fail ) ]
60+ #[ derive( Debug , thiserror :: Error ) ]
6261#[ non_exhaustive]
6362pub enum CommandError {
6463 /// The command didn't output anything to stdout or stderr for more than the timeout, and it
6564 /// was killed. The timeout's value (in seconds) is the first value.
66- #[ fail ( display = "no output for {} seconds" , _0 ) ]
65+ #[ error ( "no output for {0 } seconds" ) ]
6766 NoOutputFor ( u64 ) ,
67+
6868 /// The command took more time than the timeout to end, and it was killed. The timeout's value
6969 /// (in seconds) is the first value.
70- #[ fail ( display = "command timed out after {} seconds" , _0 ) ]
70+ #[ error ( "command timed out after {0 } seconds" ) ]
7171 Timeout ( u64 ) ,
72+
73+ /// The command failed to execute.
74+ #[ error( "command failed: {0}" ) ]
75+ ExecutionFailed ( ExitStatus ) ,
76+
77+ /// Killing the underlying process after the timeout failed.
78+ #[ error( "{0}" ) ]
79+ KillAfterTimeoutFailed ( #[ source] KillFailedError ) ,
80+
7281 /// The sandbox ran out of memory and was killed.
73- #[ fail ( display = "container ran out of memory" ) ]
82+ #[ error ( "container ran out of memory" ) ]
7483 SandboxOOM ,
84+
85+ /// Pulling a sandbox image from the registry failed
86+ #[ error( "failed to pull the sandbox image from the registry: {0}" ) ]
87+ SandboxImagePullFailed ( #[ source] Box < CommandError > ) ,
88+
89+ /// The sandbox image is missing from the local system.
90+ #[ error( "sandbox image missing from the local system: {0}" ) ]
91+ SandboxImageMissing ( #[ source] Box < CommandError > ) ,
92+
93+ /// Running rustwide inside a Docker container requires the workspace directory to be mounted
94+ /// from the host system. This error happens if that's not true, for example if the workspace
95+ /// lives in a directory inside the container.
96+ #[ error( "the workspace is not mounted from outside the container" ) ]
97+ WorkspaceNotMountedCorrectly ,
98+
99+ /// The data received from the `docker inspect` command is not valid.
100+ #[ error( "invalid output of `docker inspect`: {0}" ) ]
101+ InvalidDockerInspectOutput ( #[ source] serde_json:: Error ) ,
102+
103+ /// An I/O error occured while executing the command.
104+ #[ error( transparent) ]
105+ IO ( #[ from] std:: io:: Error ) ,
106+ }
107+
108+ /// Error happened while trying to kill a process.
109+ #[ derive( Debug , thiserror:: Error ) ]
110+ #[ cfg_attr( unix, error(
111+ "failed to kill the process with PID {pid}{}" ,
112+ . errno. map( |e| format!( ": {}" , e. desc( ) ) ) . unwrap_or_else( String :: new)
113+ ) ) ]
114+ #[ cfg_attr( not( unix) , error( "failed to kill the process with PID {pid}" ) ) ]
115+ pub struct KillFailedError {
116+ pub ( crate ) pid : u32 ,
117+ #[ cfg( unix) ]
118+ pub ( crate ) errno : Option < nix:: errno:: Errno > ,
119+ }
120+
121+ impl KillFailedError {
122+ /// Return the PID of the process that couldn't be killed.
123+ pub fn pid ( & self ) -> u32 {
124+ self . pid
125+ }
126+
127+ /// Return the underlying error number provided by the operative system.
128+ #[ cfg( any( unix, doc) ) ]
129+ #[ cfg_attr( docs_rs, doc( cfg( unix) ) ) ]
130+ pub fn errno ( & self ) -> Option < i32 > {
131+ self . errno . map ( |errno| errno as i32 )
132+ }
75133}
76134
77135/// Name and kind of a binary executed by [`Command`](struct.Command.html).
@@ -285,7 +343,7 @@ impl<'w, 'pl> Command<'w, 'pl> {
285343
286344 /// Run the prepared command and return an error if it fails (for example with a non-zero exit
287345 /// code or a timeout).
288- pub fn run ( self ) -> Result < ( ) , Error > {
346+ pub fn run ( self ) -> Result < ( ) , CommandError > {
289347 self . run_inner ( false ) ?;
290348 Ok ( ( ) )
291349 }
@@ -296,11 +354,11 @@ impl<'w, 'pl> Command<'w, 'pl> {
296354 /// Even though the output will be captured and returned, if output logging is enabled (as it
297355 /// is by default) the output will be also logged. You can disable this behavior by calling the
298356 /// [`log_output`](struct.Command.html#method.log_output) method.
299- pub fn run_capture ( self ) -> Result < ProcessOutput , Error > {
357+ pub fn run_capture ( self ) -> Result < ProcessOutput , CommandError > {
300358 Ok ( self . run_inner ( true ) ?)
301359 }
302360
303- fn run_inner ( self , capture : bool ) -> Result < ProcessOutput , Error > {
361+ fn run_inner ( self , capture : bool ) -> Result < ProcessOutput , CommandError > {
304362 if let Some ( mut builder) = self . sandbox {
305363 let workspace = self
306364 . workspace
@@ -438,7 +496,7 @@ impl<'w, 'pl> Command<'w, 'pl> {
438496 if out. status . success ( ) {
439497 Ok ( out. into ( ) )
440498 } else {
441- failure :: bail! ( "command `{}` failed" , cmdstr ) ;
499+ Err ( CommandError :: ExecutionFailed ( out . status ) )
442500 }
443501 }
444502 }
@@ -499,7 +557,7 @@ async fn log_command(
499557 timeout : Option < Duration > ,
500558 no_output_timeout : Option < Duration > ,
501559 log_output : bool ,
502- ) -> Result < InnerProcessOutput , Error > {
560+ ) -> Result < InnerProcessOutput , CommandError > {
503561 let timeout = if let Some ( t) = timeout {
504562 t
505563 } else {
@@ -532,12 +590,12 @@ async fn log_command(
532590 . map ( move |result| match result {
533591 // If the timeout elapses, kill the process
534592 Err ( _timeout) => Err ( match native:: kill_process ( child_id) {
535- Ok ( ( ) ) => Error :: from ( CommandError :: NoOutputFor ( no_output_timeout. as_secs ( ) ) ) ,
536- Err ( err) => err,
593+ Ok ( ( ) ) => CommandError :: NoOutputFor ( no_output_timeout. as_secs ( ) ) ,
594+ Err ( err) => CommandError :: KillAfterTimeoutFailed ( err) ,
537595 } ) ,
538596
539597 // If an error occurred reading the line, flatten the error
540- Ok ( ( _, Err ( read_err) ) ) => Err ( Error :: from ( read_err) ) ,
598+ Ok ( ( _, Err ( read_err) ) ) => Err ( read_err. into ( ) ) ,
541599
542600 // If the read was successful, return the `OutputKind` and the read line
543601 Ok ( ( out_kind, Ok ( line) ) ) => Ok ( ( out_kind, line) ) ,
@@ -546,7 +604,7 @@ async fn log_command(
546604 // If the process is in a tight output loop the timeout on the process might fail to
547605 // be executed, so this extra check prevents the process to run without limits.
548606 if start. elapsed ( ) > timeout {
549- return future:: err ( Error :: from ( CommandError :: Timeout ( timeout. as_secs ( ) ) ) ) ;
607+ return future:: err ( CommandError :: Timeout ( timeout. as_secs ( ) ) ) ;
550608 }
551609
552610 if let Some ( f) = & mut process_lines {
@@ -587,12 +645,12 @@ async fn log_command(
587645 match result {
588646 // If the timeout elapses, kill the process
589647 Err ( _timeout) => Err ( match native:: kill_process ( child_id) {
590- Ok ( ( ) ) => Error :: from ( CommandError :: Timeout ( timeout. as_secs ( ) ) ) ,
591- Err ( err) => err,
648+ Ok ( ( ) ) => CommandError :: Timeout ( timeout. as_secs ( ) ) ,
649+ Err ( err) => CommandError :: KillAfterTimeoutFailed ( err) ,
592650 } ) ,
593651
594652 // If an error occurred with the child
595- Ok ( Err ( err) ) => Err ( Error :: from ( err) ) ,
653+ Ok ( Err ( err) ) => Err ( err. into ( ) ) ,
596654
597655 // If the read was successful, return the process's exit status
598656 Ok ( Ok ( exit_status) ) => Ok ( exit_status) ,
0 commit comments