diff --git a/library/std/src/process.rs b/library/std/src/process.rs index ad29eeb6a0bed..7622052865661 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -116,6 +116,8 @@ use crate::path::Path; use crate::str; use crate::sys::pipe::{read2, AnonPipe}; use crate::sys::process as imp; +#[unstable(feature = "command_captured_envs_access", issue = "none")] +pub use crate::sys_common::process::CapturedEnvs; #[stable(feature = "command_access", since = "1.57.0")] pub use crate::sys_common::process::CommandEnvs; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; @@ -1048,8 +1050,9 @@ impl Command { /// Environment variables explicitly set using [`Command::env`], [`Command::envs`], and /// [`Command::env_remove`] can be retrieved with this method. /// - /// Note that this output does not include environment variables inherited from the parent - /// process. + /// Note that this output does not include environment variables inherited + /// from the parent process. To get the variables that will be set + /// when the child spawns use [`Command::capture_envs`]. /// /// Each element is a tuple key/value pair `(&OsStr, Option<&OsStr>)`. A [`None`] value /// indicates its key was explicitly removed via [`Command::env_remove`]. The associated key for @@ -1078,6 +1081,43 @@ impl Command { self.inner.get_envs() } + /// Captures a snapshot of all environment variables, inherited and explicitly set, that would + /// have been passed to the child process. + /// + /// Return value is an iterator where each element is a tuple `(OsString, OsString)`. The first + /// value is the environment variable key, and the second is its value. Unlike + /// [`Command::get_envs`], this method includes inherited and explicitly set environment + /// variables. + /// + /// Note that this method does not prevent time-of-check to time-of-use (TOCTOU) bugs. You + /// should only use it when those bugs are not an issue. If no changes occur between when this + /// method is called and when the command is spawned, the values returned will be the same as + /// those of the spawned child process. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsString; + /// use std::process::Command; + /// use std::collections::HashMap; + /// + /// let mut cmd = Command::new("ls"); + /// cmd.env("TERM", "dumb"); + /// cmd.env_remove("TZ"); + /// + /// let envs: HashMap = cmd.capture_envs().collect(); + /// // Inherited environment variable(s) + /// assert!(envs.contains_key(&OsString::from("PATH"))); + /// + /// // Explicitly set environment variable(s) + /// assert!(envs.contains_key(&OsString::from("TERM"))); + /// assert!(!envs.contains_key(&OsString::from("TZ"))); + /// ``` + #[unstable(feature = "command_captured_envs_access", issue = "none")] + pub fn capture_envs(&self) -> CapturedEnvs { + self.inner.capture_envs() + } + /// Returns the working directory for the child process. /// /// This returns [`None`] if the working directory will not be changed. diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index bac32d9e60e11..010c90e9e3364 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -12,7 +12,7 @@ use crate::ptr; use crate::sys::fd::FileDesc; use crate::sys::fs::File; use crate::sys::pipe::{self, AnonPipe}; -use crate::sys_common::process::{CommandEnv, CommandEnvs}; +use crate::sys_common::process::{CapturedEnvs, CommandEnv, CommandEnvs}; use crate::sys_common::{FromInner, IntoInner}; #[cfg(not(target_os = "fuchsia"))] @@ -302,6 +302,10 @@ impl Command { self.env.iter() } + pub fn capture_envs(&self) -> CapturedEnvs { + self.env.capture_iter() + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(|cs| Path::new(OsStr::from_bytes(cs.as_bytes()))) } diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index f4078d359448e..eefb0a482da8c 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -26,7 +26,7 @@ use crate::sys::handle::Handle; use crate::sys::path; use crate::sys::pipe::{self, AnonPipe}; use crate::sys::stdio; -use crate::sys_common::process::{CommandEnv, CommandEnvs}; +use crate::sys_common::process::{CapturedEnvs, CommandEnv, CommandEnvs}; use crate::sys_common::IntoInner; use core::ffi::c_void; @@ -244,6 +244,10 @@ impl Command { self.env.iter() } + pub fn capture_envs(&self) -> CapturedEnvs { + self.env.capture_iter() + } + pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_ref().map(|cwd| Path::new(cwd)) } diff --git a/library/std/src/sys_common/process.rs b/library/std/src/sys_common/process.rs index 4d295cf0f09d5..c7f5966bf21a6 100644 --- a/library/std/src/sys_common/process.rs +++ b/library/std/src/sys_common/process.rs @@ -98,9 +98,46 @@ impl CommandEnv { let iter = self.vars.iter(); CommandEnvs { iter } } + + pub fn capture_iter(&self) -> CapturedEnvs { + let iter = self.capture().into_iter(); + + CapturedEnvs { iter } + } +} + +/// An iterator over captured [`Command`][crate::process::Command] environment variables. +/// +/// This struct is created by +/// [`Command::capture_envs`][crate::process::Command::capture_envs]. It includes inherited and +/// explicitly set environment variables. +/// +/// See [`Command::capture_envs`][crate::process::Command::capture_envs] documentation for more. +pub struct CapturedEnvs { + iter: crate::collections::btree_map::IntoIter, } -/// An iterator over the command environment variables. +impl Iterator for CapturedEnvs { + type Item = (OsString, OsString); + + fn next(&mut self) -> Option { + self.iter.next().into() + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +/// An iterator over the explicitly set [`Command`][crate::process::Command] environment variables. +/// +/// Each element is a tuple key/value pair `(&OsStr, Option<&OsStr>)`. A [`None`] value +/// indicates its key was explicitly removed. The associated key for the [`None`] value will no +/// longer inherit from its parent process. +/// +/// Note that this output does not include environment variables inherited from the parent process. +/// To get the variables that will be set when the child spawns use +/// [`Command::captured_envs`][crate::process::Command::captured_envs]. /// /// This struct is created by /// [`Command::get_envs`][crate::process::Command::get_envs]. See its