Skip to content

Commit c284f88

Browse files
committed
Auto merge of rust-lang#46789 - Diggsey:command-env-capture, r=dtolnay
Capture `Command` environment at spawn Fixes rust-lang#28975 This tracks a set of changes to the environment and then replays them at spawn time.
2 parents b9e4d34 + ccc91d7 commit c284f88

File tree

11 files changed

+321
-167
lines changed

11 files changed

+321
-167
lines changed

src/libstd/process.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ impl Command {
513513
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Command
514514
where K: AsRef<OsStr>, V: AsRef<OsStr>
515515
{
516-
self.inner.env(key.as_ref(), val.as_ref());
516+
self.inner.env_mut().set(key.as_ref(), val.as_ref());
517517
self
518518
}
519519

@@ -546,7 +546,7 @@ impl Command {
546546
where I: IntoIterator<Item=(K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>
547547
{
548548
for (ref key, ref val) in vars {
549-
self.inner.env(key.as_ref(), val.as_ref());
549+
self.inner.env_mut().set(key.as_ref(), val.as_ref());
550550
}
551551
self
552552
}
@@ -567,7 +567,7 @@ impl Command {
567567
/// ```
568568
#[stable(feature = "process", since = "1.0.0")]
569569
pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Command {
570-
self.inner.env_remove(key.as_ref());
570+
self.inner.env_mut().remove(key.as_ref());
571571
self
572572
}
573573

@@ -587,7 +587,7 @@ impl Command {
587587
/// ```
588588
#[stable(feature = "process", since = "1.0.0")]
589589
pub fn env_clear(&mut self) -> &mut Command {
590-
self.inner.env_clear();
590+
self.inner.env_mut().clear();
591591
self
592592
}
593593

@@ -1715,6 +1715,27 @@ mod tests {
17151715
"didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output);
17161716
}
17171717

1718+
#[test]
1719+
fn test_capture_env_at_spawn() {
1720+
use env;
1721+
1722+
let mut cmd = env_cmd();
1723+
cmd.env("RUN_TEST_NEW_ENV1", "123");
1724+
1725+
// This variable will not be present if the environment has already
1726+
// been captured above.
1727+
env::set_var("RUN_TEST_NEW_ENV2", "456");
1728+
let result = cmd.output().unwrap();
1729+
env::remove_var("RUN_TEST_NEW_ENV2");
1730+
1731+
let output = String::from_utf8_lossy(&result.stdout).to_string();
1732+
1733+
assert!(output.contains("RUN_TEST_NEW_ENV1=123"),
1734+
"didn't find RUN_TEST_NEW_ENV1 inside of:\n\n{}", output);
1735+
assert!(output.contains("RUN_TEST_NEW_ENV2=456"),
1736+
"didn't find RUN_TEST_NEW_ENV2 inside of:\n\n{}", output);
1737+
}
1738+
17181739
// Regression tests for #30858.
17191740
#[test]
17201741
fn test_interior_nul_in_progname_is_error() {

src/libstd/sys/redox/process.rs

+7-17
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use collections::hash_map::HashMap;
12-
use env::{self, split_paths};
11+
use env::{split_paths};
1312
use ffi::OsStr;
1413
use os::unix::ffi::OsStrExt;
1514
use fmt;
@@ -19,6 +18,7 @@ use sys::fd::FileDesc;
1918
use sys::fs::{File, OpenOptions};
2019
use sys::pipe::{self, AnonPipe};
2120
use sys::{cvt, syscall};
21+
use sys_common::process::{CommandEnv, DefaultEnvKey};
2222

2323
////////////////////////////////////////////////////////////////////////////////
2424
// Command
@@ -44,7 +44,7 @@ pub struct Command {
4444
// other keys.
4545
program: String,
4646
args: Vec<String>,
47-
env: HashMap<String, String>,
47+
env: CommandEnv<DefaultEnvKey>,
4848

4949
cwd: Option<String>,
5050
uid: Option<u32>,
@@ -90,7 +90,7 @@ impl Command {
9090
Command {
9191
program: program.to_str().unwrap().to_owned(),
9292
args: Vec::new(),
93-
env: HashMap::new(),
93+
env: Default::default(),
9494
cwd: None,
9595
uid: None,
9696
gid: None,
@@ -106,16 +106,8 @@ impl Command {
106106
self.args.push(arg.to_str().unwrap().to_owned());
107107
}
108108

109-
pub fn env(&mut self, key: &OsStr, val: &OsStr) {
110-
self.env.insert(key.to_str().unwrap().to_owned(), val.to_str().unwrap().to_owned());
111-
}
112-
113-
pub fn env_remove(&mut self, key: &OsStr) {
114-
self.env.remove(key.to_str().unwrap());
115-
}
116-
117-
pub fn env_clear(&mut self) {
118-
self.env.clear();
109+
pub fn env_mut(&mut self) -> &mut CommandEnv<DefaultEnvKey> {
110+
&mut self.env
119111
}
120112

121113
pub fn cwd(&mut self, dir: &OsStr) {
@@ -309,9 +301,7 @@ impl Command {
309301
args.push([arg.as_ptr() as usize, arg.len()]);
310302
}
311303

312-
for (key, val) in self.env.iter() {
313-
env::set_var(key, val);
314-
}
304+
self.env.apply();
315305

316306
let program = if self.program.contains(':') || self.program.contains('/') {
317307
Some(PathBuf::from(&self.program))

src/libstd/sys/unix/process/process_common.rs

+60-83
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
use os::unix::prelude::*;
1212

13-
use collections::hash_map::{HashMap, Entry};
14-
use env;
1513
use ffi::{OsString, OsStr, CString, CStr};
1614
use fmt;
1715
use io;
@@ -20,6 +18,8 @@ use ptr;
2018
use sys::fd::FileDesc;
2119
use sys::fs::{File, OpenOptions};
2220
use sys::pipe::{self, AnonPipe};
21+
use sys_common::process::{CommandEnv, DefaultEnvKey};
22+
use collections::BTreeMap;
2323

2424
////////////////////////////////////////////////////////////////////////////////
2525
// Command
@@ -45,9 +45,8 @@ pub struct Command {
4545
// other keys.
4646
program: CString,
4747
args: Vec<CString>,
48-
env: Option<HashMap<OsString, (usize, CString)>>,
4948
argv: Vec<*const c_char>,
50-
envp: Option<Vec<*const c_char>>,
49+
env: CommandEnv<DefaultEnvKey>,
5150

5251
cwd: Option<CString>,
5352
uid: Option<uid_t>,
@@ -96,8 +95,7 @@ impl Command {
9695
argv: vec![program.as_ptr(), ptr::null()],
9796
program,
9897
args: Vec::new(),
99-
env: None,
100-
envp: None,
98+
env: Default::default(),
10199
cwd: None,
102100
uid: None,
103101
gid: None,
@@ -121,68 +119,6 @@ impl Command {
121119
self.args.push(arg);
122120
}
123121

124-
fn init_env_map(&mut self) -> (&mut HashMap<OsString, (usize, CString)>,
125-
&mut Vec<*const c_char>) {
126-
if self.env.is_none() {
127-
let mut map = HashMap::new();
128-
let mut envp = Vec::new();
129-
for (k, v) in env::vars_os() {
130-
let s = pair_to_key(&k, &v, &mut self.saw_nul);
131-
envp.push(s.as_ptr());
132-
map.insert(k, (envp.len() - 1, s));
133-
}
134-
envp.push(ptr::null());
135-
self.env = Some(map);
136-
self.envp = Some(envp);
137-
}
138-
(self.env.as_mut().unwrap(), self.envp.as_mut().unwrap())
139-
}
140-
141-
pub fn env(&mut self, key: &OsStr, val: &OsStr) {
142-
let new_key = pair_to_key(key, val, &mut self.saw_nul);
143-
let (map, envp) = self.init_env_map();
144-
145-
// If `key` is already present then we just update `envp` in place
146-
// (and store the owned value), but if it's not there we override the
147-
// trailing NULL pointer, add a new NULL pointer, and store where we
148-
// were located.
149-
match map.entry(key.to_owned()) {
150-
Entry::Occupied(mut e) => {
151-
let (i, ref mut s) = *e.get_mut();
152-
envp[i] = new_key.as_ptr();
153-
*s = new_key;
154-
}
155-
Entry::Vacant(e) => {
156-
let len = envp.len();
157-
envp[len - 1] = new_key.as_ptr();
158-
envp.push(ptr::null());
159-
e.insert((len - 1, new_key));
160-
}
161-
}
162-
}
163-
164-
pub fn env_remove(&mut self, key: &OsStr) {
165-
let (map, envp) = self.init_env_map();
166-
167-
// If we actually ended up removing a key, then we need to update the
168-
// position of all keys that come after us in `envp` because they're all
169-
// one element sooner now.
170-
if let Some((i, _)) = map.remove(key) {
171-
envp.remove(i);
172-
173-
for (_, &mut (ref mut j, _)) in map.iter_mut() {
174-
if *j >= i {
175-
*j -= 1;
176-
}
177-
}
178-
}
179-
}
180-
181-
pub fn env_clear(&mut self) {
182-
self.env = Some(HashMap::new());
183-
self.envp = Some(vec![ptr::null()]);
184-
}
185-
186122
pub fn cwd(&mut self, dir: &OsStr) {
187123
self.cwd = Some(os2c(dir, &mut self.saw_nul));
188124
}
@@ -196,9 +132,6 @@ impl Command {
196132
pub fn saw_nul(&self) -> bool {
197133
self.saw_nul
198134
}
199-
pub fn get_envp(&self) -> &Option<Vec<*const c_char>> {
200-
&self.envp
201-
}
202135
pub fn get_argv(&self) -> &Vec<*const c_char> {
203136
&self.argv
204137
}
@@ -237,6 +170,15 @@ impl Command {
237170
self.stderr = Some(stderr);
238171
}
239172

173+
pub fn env_mut(&mut self) -> &mut CommandEnv<DefaultEnvKey> {
174+
&mut self.env
175+
}
176+
177+
pub fn capture_env(&mut self) -> Option<CStringArray> {
178+
let maybe_env = self.env.capture_if_changed();
179+
maybe_env.map(|env| construct_envp(env, &mut self.saw_nul))
180+
}
181+
240182
pub fn setup_io(&self, default: Stdio, needs_stdin: bool)
241183
-> io::Result<(StdioPipes, ChildPipes)> {
242184
let null = Stdio::Null;
@@ -268,6 +210,53 @@ fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
268210
})
269211
}
270212

213+
// Helper type to manage ownership of the strings within a C-style array.
214+
pub struct CStringArray {
215+
items: Vec<CString>,
216+
ptrs: Vec<*const c_char>
217+
}
218+
219+
impl CStringArray {
220+
pub fn with_capacity(capacity: usize) -> Self {
221+
let mut result = CStringArray {
222+
items: Vec::with_capacity(capacity),
223+
ptrs: Vec::with_capacity(capacity+1)
224+
};
225+
result.ptrs.push(ptr::null());
226+
result
227+
}
228+
pub fn push(&mut self, item: CString) {
229+
let l = self.ptrs.len();
230+
self.ptrs[l-1] = item.as_ptr();
231+
self.ptrs.push(ptr::null());
232+
self.items.push(item);
233+
}
234+
pub fn as_ptr(&self) -> *const *const c_char {
235+
self.ptrs.as_ptr()
236+
}
237+
}
238+
239+
fn construct_envp(env: BTreeMap<DefaultEnvKey, OsString>, saw_nul: &mut bool) -> CStringArray {
240+
let mut result = CStringArray::with_capacity(env.len());
241+
for (k, v) in env {
242+
let mut k: OsString = k.into();
243+
244+
// Reserve additional space for '=' and null terminator
245+
k.reserve_exact(v.len() + 2);
246+
k.push("=");
247+
k.push(&v);
248+
249+
// Add the new entry into the array
250+
if let Ok(item) = CString::new(k.into_vec()) {
251+
result.push(item);
252+
} else {
253+
*saw_nul = true;
254+
}
255+
}
256+
257+
result
258+
}
259+
271260
impl Stdio {
272261
pub fn to_child_stdio(&self, readable: bool)
273262
-> io::Result<(ChildStdio, Option<AnonPipe>)> {
@@ -337,18 +326,6 @@ impl ChildStdio {
337326
}
338327
}
339328

340-
fn pair_to_key(key: &OsStr, value: &OsStr, saw_nul: &mut bool) -> CString {
341-
let (key, value) = (key.as_bytes(), value.as_bytes());
342-
let mut v = Vec::with_capacity(key.len() + value.len() + 1);
343-
v.extend(key);
344-
v.push(b'=');
345-
v.extend(value);
346-
CString::new(v).unwrap_or_else(|_e| {
347-
*saw_nul = true;
348-
CString::new("foo=bar").unwrap()
349-
})
350-
}
351-
352329
impl fmt::Debug for Command {
353330
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
354331
write!(f, "{:?}", self.program)?;

src/libstd/sys/unix/process/process_fuchsia.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@ use sys::process::process_common::*;
2323
impl Command {
2424
pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
2525
-> io::Result<(Process, StdioPipes)> {
26+
let envp = self.capture_env();
27+
2628
if self.saw_nul() {
2729
return Err(io::Error::new(io::ErrorKind::InvalidInput,
2830
"nul byte found in provided data"));
2931
}
3032

3133
let (ours, theirs) = self.setup_io(default, needs_stdin)?;
3234

33-
let process_handle = unsafe { self.do_exec(theirs)? };
35+
let process_handle = unsafe { self.do_exec(theirs, envp.as_ref())? };
3436

3537
Ok((Process { handle: Handle::new(process_handle) }, ours))
3638
}
@@ -50,13 +52,13 @@ impl Command {
5052
}
5153
}
5254

53-
unsafe fn do_exec(&mut self, stdio: ChildPipes)
55+
unsafe fn do_exec(&mut self, stdio: ChildPipes, maybe_envp: Option<&CStringArray>)
5456
-> io::Result<zx_handle_t> {
5557
use sys::process::zircon::*;
5658

5759
let job_handle = zx_job_default();
58-
let envp = match *self.get_envp() {
59-
Some(ref envp) => envp.as_ptr(),
60+
let envp = match maybe_envp {
61+
Some(envp) => envp.as_ptr(),
6062
None => ptr::null(),
6163
};
6264

0 commit comments

Comments
 (0)