@@ -10,6 +10,8 @@ use cap_std::io_lifetimes;
10
10
use cap_tempfile:: cap_std;
11
11
use io_lifetimes:: OwnedFd ;
12
12
use rustix:: fd:: { AsFd , FromRawFd , IntoRawFd } ;
13
+ use rustix:: io:: FdFlags ;
14
+ use std:: os:: fd:: AsRawFd ;
13
15
use std:: os:: unix:: process:: CommandExt ;
14
16
use std:: sync:: Arc ;
15
17
@@ -30,7 +32,15 @@ impl CapStdExtCommandExt for std::process::Command {
30
32
unsafe {
31
33
self . pre_exec ( move || {
32
34
let mut target = OwnedFd :: from_raw_fd ( target) ;
33
- rustix:: io:: dup2 ( & * fd, & mut target) ?;
35
+ // If the fd is already what we want, then just ensure that
36
+ // O_CLOEXEC is stripped off.
37
+ if target. as_raw_fd ( ) == fd. as_raw_fd ( ) {
38
+ let fl = rustix:: io:: fcntl_getfd ( & target) ?;
39
+ rustix:: io:: fcntl_setfd ( & mut target, fl. difference ( FdFlags :: CLOEXEC ) ) ?;
40
+ } else {
41
+ // Otherwise create a dup, which will also default to not setting O_CLOEXEC.
42
+ rustix:: io:: dup2 ( & * fd, & mut target) ?;
43
+ }
34
44
// Intentionally leak into the child.
35
45
let _ = target. into_raw_fd ( ) ;
36
46
Ok ( ( ) )
@@ -49,3 +59,25 @@ impl CapStdExtCommandExt for std::process::Command {
49
59
self
50
60
}
51
61
}
62
+
63
+ #[ cfg( test) ]
64
+ mod tests {
65
+ use super :: * ;
66
+ use std:: sync:: Arc ;
67
+
68
+ #[ test]
69
+ fn test_take_fdn ( ) -> anyhow:: Result < ( ) > {
70
+ // Pass srcfd == destfd and srcfd != destfd
71
+ for i in 0 ..1 {
72
+ let tempd = cap_tempfile:: TempDir :: new ( cap_std:: ambient_authority ( ) ) ?;
73
+ let tempd_fd = Arc :: new ( tempd. as_fd ( ) . try_clone_to_owned ( ) ?) ;
74
+ let n = tempd_fd. as_raw_fd ( ) + i;
75
+ let st = std:: process:: Command :: new ( "ls" )
76
+ . arg ( format ! ( "/proc/self/fd/{n}" ) )
77
+ . take_fd_n ( tempd_fd, n)
78
+ . status ( ) ?;
79
+ assert ! ( st. success( ) ) ;
80
+ }
81
+ Ok ( ( ) )
82
+ }
83
+ }
0 commit comments