@@ -3,15 +3,15 @@ use crate::io::{self, Error, ErrorKind};
3
3
use crate :: mem;
4
4
use crate :: num:: NonZeroI32 ;
5
5
use crate :: sys;
6
- use crate :: sys:: cvt;
7
6
use crate :: sys:: process:: process_common:: * ;
7
+ use crate :: sys:: { cvt, memchr} ;
8
8
use core:: ffi:: NonZero_c_int ;
9
9
10
10
#[ cfg( target_os = "linux" ) ]
11
11
use crate :: os:: linux:: process:: PidFd ;
12
12
#[ cfg( target_os = "linux" ) ]
13
13
use crate :: os:: unix:: io:: AsRawFd ;
14
-
14
+ use crate :: sys :: os :: { env_read_lock , environ } ;
15
15
#[ cfg( any(
16
16
target_os = "macos" ,
17
17
target_os = "watchos" ,
@@ -29,6 +29,8 @@ use libc::RTP_ID as pid_t;
29
29
#[ cfg( not( target_os = "vxworks" ) ) ]
30
30
use libc:: { c_int, pid_t} ;
31
31
32
+ use crate :: collections:: HashSet ;
33
+ use crate :: ffi:: { CStr , CString } ;
32
34
#[ cfg( not( any(
33
35
target_os = "vxworks" ,
34
36
target_os = "l4re" ,
@@ -68,6 +70,87 @@ cfg_if::cfg_if! {
68
70
// Command
69
71
////////////////////////////////////////////////////////////////////////////////
70
72
73
+ fn count_env_vars ( ) -> usize {
74
+ let mut count = 0 ;
75
+ unsafe {
76
+ let _guard = env_read_lock ( ) ;
77
+ let mut environ = * environ ( ) ;
78
+ while !( * environ) . is_null ( ) {
79
+ environ = environ. add ( 1 ) ;
80
+ count += 1 ;
81
+ }
82
+ }
83
+ count
84
+ }
85
+
86
+ /// Super-duper optimized version of capturing environment variables, that tries to avoid
87
+ /// unnecessary allocations and sorting.
88
+ fn capture_envp ( cmd : & mut Command ) -> CStringArray {
89
+ use crate :: os:: unix:: ffi:: OsStrExt ;
90
+
91
+ // Count the upper bound of environment variables (vars from the environ + vars coming from the
92
+ // command).
93
+ let env_count_upper_bound = count_env_vars ( ) + cmd. env . vars . len ( ) ;
94
+
95
+ let mut env_array = CStringArray :: with_capacity ( env_count_upper_bound) ;
96
+
97
+ // Remember which vars were already set by the user.
98
+ // If the user value is Some, we will add the variable to `env_array` and modify `visited`.
99
+ // If the user value is None, we will only modify `visited`.
100
+ // In either case, a variable with the same name from `environ` will not be added to `env_array`.
101
+ let mut visited: HashSet < & [ u8 ] > = HashSet :: with_capacity ( cmd. env . vars . len ( ) ) ;
102
+
103
+ // First, add user defined variables to `env_array`, and mark the visited ones.
104
+ for ( key, maybe_value) in cmd. env . vars . iter ( ) {
105
+ if let Some ( value) = maybe_value {
106
+ // One extra byte for '=', and one extra byte for the NULL terminator.
107
+ let mut env_var: Vec < u8 > =
108
+ Vec :: with_capacity ( key. as_bytes ( ) . len ( ) + value. as_bytes ( ) . len ( ) + 2 ) ;
109
+ env_var. extend_from_slice ( key. as_bytes ( ) ) ;
110
+ env_var. push ( b'=' ) ;
111
+ env_var. extend_from_slice ( value. as_bytes ( ) ) ;
112
+
113
+ if let Ok ( item) = CString :: new ( env_var) {
114
+ env_array. push ( item) ;
115
+ } else {
116
+ cmd. saw_nul = true ;
117
+ return env_array;
118
+ }
119
+ }
120
+ visited. insert ( key. as_bytes ( ) ) ;
121
+ }
122
+
123
+ // Then, if we're not clearing the original environment, go through it, and add each variable
124
+ // to env_array if we haven't seen it yet.
125
+ if !cmd. env . clear {
126
+ unsafe {
127
+ let _guard = env_read_lock ( ) ;
128
+ let mut environ = * environ ( ) ;
129
+ if !environ. is_null ( ) {
130
+ while !( * environ) . is_null ( ) {
131
+ let c_str = CStr :: from_ptr ( * environ) ;
132
+ let key_value = c_str. to_bytes ( ) ;
133
+ if !key_value. is_empty ( ) {
134
+ if let Some ( pos) = memchr:: memchr ( b'=' , & key_value[ 1 ..] ) . map ( |p| p + 1 ) {
135
+ let key = & key_value[ ..pos] ;
136
+ if !visited. contains ( & key) {
137
+ env_array. push ( CString :: from ( c_str) ) ;
138
+ }
139
+ }
140
+ }
141
+ environ = environ. add ( 1 ) ;
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ env_array
148
+ }
149
+
150
+ pub fn capture_env_linux ( cmd : & mut Command ) -> Option < CStringArray > {
151
+ if cmd. env . is_unchanged ( ) { None } else { Some ( capture_envp ( cmd) ) }
152
+ }
153
+
71
154
impl Command {
72
155
pub fn spawn (
73
156
& mut self ,
@@ -76,6 +159,9 @@ impl Command {
76
159
) -> io:: Result < ( Process , StdioPipes ) > {
77
160
const CLOEXEC_MSG_FOOTER : [ u8 ; 4 ] = * b"NOEX" ;
78
161
162
+ #[ cfg( target_os = "linux" ) ]
163
+ let envp = capture_env_linux ( self ) ;
164
+ #[ cfg( not( target_os = "linux" ) ) ]
79
165
let envp = self . capture_env ( ) ;
80
166
81
167
if self . saw_nul ( ) {
0 commit comments