- 
                Notifications
    You must be signed in to change notification settings 
- Fork 45
          Fix IO safety in unix.rs
          #101
        
          New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
49877d1    to
    40cffa2      
    Compare
  
    | So the behaviour of spawning a configured command after the (not-inherited) jobserver  
 | 
        
          
                src/unix.rs
              
                Outdated
          
        
      | return Err(err); | ||
| unsafe { | ||
| let mut pipes = [0; 2]; | ||
| match libc::syscall(libc::SYS_pipe2, pipes.as_mut_ptr(), libc::O_CLOEXEC) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could use cvt here and match on the error:
| match libc::syscall(libc::SYS_pipe2, pipes.as_mut_ptr(), libc::O_CLOEXEC) { | |
| match cvt(libc::syscall(libc::SYS_pipe2, pipes.as_mut_ptr(), libc::O_CLOEXEC)) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately libc::syscall returns a c_long whereas cvt takes a c_int.
        
          
                src/unix.rs
              
                Outdated
          
        
      | PIPE2_AVAILABLE.store(false, Ordering::SeqCst); | ||
| } else { | ||
| return Err(err); | ||
| unsafe { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My two cent is that smaller unsafe block is better, as it makes it easier to read
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've shrunk it further.
| // Keep a reference to the jobserver alive in the closure so that | ||
| // the pipe FDs aren't closed, otherwise `set_cloexec` might end up | ||
| // targetting a completely unrelated file descriptor. | ||
| let arc = self.clone(); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome, now it would always work once Client::configure is called
| let client = unsafe { | ||
| t!(match Client::from_env_ext(true).client { | ||
| // Its ok for a dropped jobservers path to no longer exist (e.g. on Windows). | ||
| Err(e) if matches!(e.kind(), FromEnvErrorKind::CannotOpenPath) => return, | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO it's kind of a bad behavior that it has different behavior on different platform.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately having the same behaviour would be difficult:
- On Windows, the child process doesn't have any OS-level reference to the jobserver until it opens the semaphore, so if the parent process Clientis dropped beforeClient::from_env_extis called thenClient::from_env_extwill fail as semaphores are automatically deleted when there are no handles referencing them.
- On Linux (when using pipes, which is what jobserver-rs currently does when creating it's own jobserver) the opposite is true: as long as the FDs are alive when the process is spawned (which this PR ensures) then Client::from_env_extwill succeed in the child process even if the parent processClientis dropped before the client process calls it.
The only possibility that comes to mind would be if on Windows the parent process lets the child process inherit a copy of the semaphore handle; this would mean that the semaphore would stick around for as long as that process was running. The difficult bit is ensuring only the desired processes inherit the handle and making sure the handle isn't closed before the command is spawned. This would require stdlib support, possibly involving PROC_THREAD_ATTRIBUTE_HANDLE_LIST and rust-lang/rust#114854.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChrisDenton, one more case to start supporting this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am happy to collaborate would @beetrees on this to stop processes from inheriting handles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check out rust-lang/rust#115501.
| (I've also fixed an unused import warning in the  | 
| 
 That's unfortunate since windows don't have  I have a fork of jobserver, which I workaround this issue by forcing user to pass in a closure and spawn the process there  pub fn configure_and_run<Cmd, F, R>(&self, cmd: Cmd, f: F) -> Result<R>
where
    Cmd: Command,
    F: FnOnce(&mut Cmd) -> Result<R>;
 I was planning to open a PR to optimize away the  Though that'd mean the jobserver would be inherited without  | 
As well as using
BorrowedFdas opposed toc_intin more places, this PR fixes two related bugs:Client::configure()wouldn't ensure the file descriptors passed toCommand::configureremained valid until the command was actually spawned. This meant that if theClientwas dropped before theCommandwas spawned, theset_cloexeccall could end up hitting an unrelated file descriptor (which the spawned process would then treat as a jobserver).string_arguses the FDs fromClientCreationArg::Fdsto put in the environment variables, but before this PRClient::configurewould use the FDs fromself.read/self.writeinstead. This would result in the wrong FDs getting passed toset_cloexec. This was basically harmless (apart from having the use-after-drop problem) as the only time the two sets of FDs differ is when the jobserver has been inherited from the environment, in which case the FDs are going to be not-CLOEXEC anyway.I've also removed the
unsafefrom the definitions ofClient::mkandClient::from_fdsas they don't appear to have any safety requirements.