@@ -152,6 +152,11 @@ impl Command {
152152 }
153153
154154 /// Set an auxiliary stream passed to the process, besides the stdio streams.
155+ ///
156+ /// Use with caution - ideally, only set one aux fd; if there are multiple,
157+ /// their old `fd` may overlap with another's `newfd`, and thus will break.
158+ /// If you need more than 1 auxiliary file descriptor, rewrite this function
159+ /// to be able to support that.
155160 #[ cfg( unix) ]
156161 pub fn set_aux_fd < F : Into < std:: os:: fd:: OwnedFd > > (
157162 & mut self ,
@@ -161,18 +166,28 @@ impl Command {
161166 use std:: os:: fd:: AsRawFd ;
162167 use std:: os:: unix:: process:: CommandExt ;
163168
169+ let cvt = |x| if x == -1 { Err ( std:: io:: Error :: last_os_error ( ) ) } else { Ok ( ( ) ) } ;
170+
164171 let fd = fd. into ( ) ;
165- unsafe {
166- self . cmd . pre_exec ( move || {
167- let fd = fd. as_raw_fd ( ) ;
168- let ret = if fd == newfd {
169- libc:: fcntl ( fd, libc:: F_SETFD , 0 )
170- } else {
171- libc:: dup2 ( fd, newfd)
172- } ;
173- if ret == -1 { Err ( std:: io:: Error :: last_os_error ( ) ) } else { Ok ( ( ) ) }
174- } ) ;
172+ if fd. as_raw_fd ( ) == newfd {
173+ // if fd is already where we want it, just turn off FD_CLOEXEC
174+ // SAFETY: io-safe: we have ownership over fd
175+ cvt ( unsafe { libc:: fcntl ( fd. as_raw_fd ( ) , libc:: F_SETFD , 0 ) } )
176+ . expect ( "disabling CLOEXEC failed" ) ;
177+ // we still unconditionally set the pre_exec function, since it captures
178+ // `fd`, and we want to ensure that it stays open until the fork
175179 }
180+ let pre_exec = move || {
181+ if fd. as_raw_fd ( ) != newfd {
182+ // set newfd to point to the same file as fd
183+ // SAFETY: io-"safe": newfd is. not necessarily an unused fd.
184+ // however, we're
185+ cvt ( unsafe { libc:: dup2 ( fd. as_raw_fd ( ) , newfd) } ) ?;
186+ }
187+ Ok ( ( ) )
188+ } ;
189+ // SAFETY: dup2 is pre-exec-safe
190+ unsafe { self . cmd . pre_exec ( pre_exec) } ;
176191 self
177192 }
178193
0 commit comments