@@ -10,23 +10,37 @@ mod imp {
1010 use nix:: unistd;
1111 use std:: os:: fd:: { AsRawFd , FromRawFd , IntoRawFd , OwnedFd } ;
1212 use std:: path:: Path ;
13+ use std:: sync:: { Mutex , MutexGuard } ;
14+
15+ /// Process-wide lock protecting fd 2 (stderr) manipulation.
16+ /// Held from `redirect_to_file` until `restore` to prevent concurrent
17+ /// VMs from racing on dup2.
18+ static STDERR_LOCK : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
1319
1420 pub struct Capture {
1521 original_stderr : OwnedFd ,
22+ _guard : MutexGuard < ' static , ( ) > ,
1623 }
1724
1825 impl Capture {
1926 pub fn redirect_to_file ( path : & Path ) -> Result < Self > {
27+ let guard = STDERR_LOCK
28+ . lock ( )
29+ . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) ) ;
2030 let capture_fd = std:: fs:: File :: create ( path) ?. into_raw_fd ( ) ;
2131 let original_stderr_raw = unistd:: dup ( 2 ) ?;
2232 unistd:: dup2 ( capture_fd, 2 ) ?;
2333 unistd:: close ( capture_fd) ?;
2434 let original_stderr = unsafe { OwnedFd :: from_raw_fd ( original_stderr_raw) } ;
25- Ok ( Self { original_stderr } )
35+ Ok ( Self {
36+ original_stderr,
37+ _guard : guard,
38+ } )
2639 }
2740
2841 pub fn restore ( self ) -> Result < ( ) > {
2942 unistd:: dup2 ( self . original_stderr . as_raw_fd ( ) , 2 ) ?;
43+ // _guard is dropped here, releasing STDERR_LOCK
3044 Ok ( ( ) )
3145 }
3246 }
0 commit comments