Skip to main content

running_process/
environment.rs

1//! Process environment baselines.
2//!
3//! Reconstructing the logged-in user's environment is a host mechanic and
4//! lives in [`crate::platform::host`]: Windows builds it from machine and user
5//! settings, Unix rebuilds it from the passwd entry because no Unix API
6//! reconstructs a login environment. What this module owns is the *policy* --
7//! which baseline a spawn starts from, and how explicit entries are layered on
8//! top of it.
9
10use std::ffi::OsString;
11use std::io;
12
13/// Return the logged-in user's baseline environment.
14///
15/// This is the environment a fresh login would have, not a copy of this
16/// process's: variables that exist only here do not appear in it. See
17/// [`crate::platform::host::login_environment`] for what each host builds it
18/// from.
19pub fn user_baseline_environment() -> io::Result<Vec<(OsString, OsString)>> {
20    crate::platform::host::login_environment()
21}
22
23/// Return a `CreateProcessW`-compatible Unicode user environment block.
24///
25/// The returned buffer is sorted and double-NUL terminated by Windows. It is
26/// useful to callers that own a manual `CreateProcessW` path, and is exported
27/// only here because no other host has an API that consumes this shape.
28#[cfg(windows)]
29pub fn user_baseline_environment_block() -> io::Result<Vec<u16>> {
30    crate::platform::host::login_environment_block()
31}
32
33/// Materialize a string environment for backends whose native API accepts
34/// either an inherited environment (`None`) or one complete replacement
35/// block (`Some`). Ordered explicit entries are applied after the selected
36/// base and win ties, matching how this host compares variable names.
37#[cfg(any(feature = "daemon", test))]
38pub(crate) fn materialize_environment(
39    policy: crate::EnvironmentPolicy,
40    explicit: &[(String, String)],
41) -> io::Result<Option<Vec<(String, String)>>> {
42    if policy == crate::EnvironmentPolicy::Inherit && explicit.is_empty() {
43        return Ok(None);
44    }
45
46    let mut output: Vec<(String, String)> = match policy {
47        crate::EnvironmentPolicy::Inherit => std::env::vars().collect(),
48        crate::EnvironmentPolicy::UserBaseline => user_baseline_environment()?
49            .into_iter()
50            .map(|(key, value)| {
51                (
52                    key.to_string_lossy().into_owned(),
53                    value.to_string_lossy().into_owned(),
54                )
55            })
56            .collect(),
57        crate::EnvironmentPolicy::Clear => Vec::new(),
58        crate::EnvironmentPolicy::Auto => {
59            return Err(io::Error::new(
60                io::ErrorKind::InvalidInput,
61                "Auto environment policy must be resolved before materialization",
62            ));
63        }
64    };
65
66    for (key, value) in explicit {
67        // An explicit `Path=` must replace an inherited `PATH` on a host where
68        // those name the same variable, and must not on one where they do not.
69        // Asking the host settles it; guessing from the current OS is how the
70        // two spellings end up both present and one of them ignored.
71        let existing = output
72            .iter_mut()
73            .find(|(candidate, _)| environment_keys_match(candidate, key));
74        if let Some((existing_key, existing_value)) = existing {
75            *existing_key = key.clone();
76            *existing_value = value.clone();
77        } else {
78            output.push((key.clone(), value.clone()));
79        }
80    }
81    Ok(Some(output))
82}
83
84/// Whether two environment variable names refer to the same variable here.
85#[cfg(any(feature = "daemon", test))]
86fn environment_keys_match(left: &str, right: &str) -> bool {
87    if crate::platform::host::environment_keys_are_case_insensitive() {
88        left.eq_ignore_ascii_case(right)
89    } else {
90        left == right
91    }
92}
93
94#[cfg(test)]
95mod materialize_tests {
96    use super::*;
97
98    #[test]
99    fn clear_uses_only_explicit_entries() {
100        let env = materialize_environment(
101            crate::EnvironmentPolicy::Clear,
102            &[("CLIENT_ONLY".into(), "forwarded".into())],
103        )
104        .unwrap()
105        .unwrap();
106        assert_eq!(env, vec![("CLIENT_ONLY".into(), "forwarded".into())]);
107    }
108
109    #[test]
110    fn empty_inherit_uses_native_inheritance() {
111        assert_eq!(
112            materialize_environment(crate::EnvironmentPolicy::Inherit, &[]).unwrap(),
113            None
114        );
115    }
116
117    #[test]
118    fn unresolved_auto_is_rejected() {
119        assert!(materialize_environment(crate::EnvironmentPolicy::Auto, &[]).is_err());
120    }
121
122    /// Explicit entries replace an existing variable exactly when this host
123    /// says the two names are the same variable -- so the assertion is written
124    /// against that answer rather than against one host's rule.
125    #[test]
126    fn explicit_entries_replace_by_this_hosts_name_comparison() {
127        let env = materialize_environment(
128            crate::EnvironmentPolicy::Clear,
129            &[
130                ("ExampleVar".into(), "first".into()),
131                ("EXAMPLEVAR".into(), "second".into()),
132            ],
133        )
134        .unwrap()
135        .unwrap();
136
137        if crate::platform::host::environment_keys_are_case_insensitive() {
138            assert_eq!(
139                env,
140                vec![("EXAMPLEVAR".to_string(), "second".to_string())],
141                "one variable, last spelling and value win"
142            );
143        } else {
144            assert_eq!(
145                env,
146                vec![
147                    ("ExampleVar".to_string(), "first".to_string()),
148                    ("EXAMPLEVAR".to_string(), "second".to_string()),
149                ],
150                "two distinct variables"
151            );
152        }
153    }
154
155    /// The baseline is the user's, not this process's.
156    #[test]
157    fn user_baseline_excludes_process_only_variables() {
158        std::env::set_var("RUNNING_PROCESS_MATERIALIZE_CANARY", "1");
159        let env = materialize_environment(crate::EnvironmentPolicy::UserBaseline, &[]).unwrap();
160        std::env::remove_var("RUNNING_PROCESS_MATERIALIZE_CANARY");
161        let env = env.expect("UserBaseline always materializes a block");
162        assert!(
163            !env.iter()
164                .any(|(key, _)| key == "RUNNING_PROCESS_MATERIALIZE_CANARY"),
165            "a process-local variable must not reach the user baseline"
166        );
167    }
168}