Skip to main content

running_process/
environment.rs

1//! Process environment baselines.
2//!
3//! Windows exposes the logged-in user's machine + user environment through
4//! `CreateEnvironmentBlock`. Unix has no equivalent stable OS API, so the
5//! conservative fallback is a snapshot of the current process environment.
6
7#[cfg(windows)]
8use std::ffi::c_void;
9use std::ffi::OsString;
10use std::io;
11
12/// Return the logged-in user's baseline environment.
13///
14/// On Windows this is freshly constructed from machine and user settings and
15/// therefore excludes variables that exist only in the current process. On
16/// Unix this currently falls back to the current process environment.
17pub fn user_baseline_environment() -> io::Result<Vec<(OsString, OsString)>> {
18    #[cfg(windows)]
19    {
20        let block = user_baseline_environment_block()?;
21        Ok(parse_windows_environment_block(&block))
22    }
23    #[cfg(not(windows))]
24    {
25        Ok(std::env::vars_os().collect())
26    }
27}
28
29/// Return a CreateProcessW-compatible Unicode user environment block.
30///
31/// The returned buffer is sorted and double-NUL terminated by Windows. It is
32/// useful to callers that own a manual `CreateProcessW` path.
33#[cfg(windows)]
34pub fn user_baseline_environment_block() -> io::Result<Vec<u16>> {
35    use windows_sys::Win32::Foundation::CloseHandle;
36    use windows_sys::Win32::Security::TOKEN_QUERY;
37    use windows_sys::Win32::System::Environment::{
38        CreateEnvironmentBlock, DestroyEnvironmentBlock,
39    };
40    use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
41
42    let mut token = std::ptr::null_mut();
43    let opened = unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) };
44    if opened == 0 {
45        return Err(io::Error::last_os_error());
46    }
47
48    let mut raw_block: *mut c_void = std::ptr::null_mut();
49    let created = unsafe { CreateEnvironmentBlock(&mut raw_block, token, 0) };
50    let create_error = if created == 0 {
51        Some(io::Error::last_os_error())
52    } else {
53        None
54    };
55    unsafe {
56        CloseHandle(token);
57    }
58    if let Some(error) = create_error {
59        return Err(error);
60    }
61
62    let copied = unsafe { copy_windows_environment_block(raw_block.cast::<u16>()) };
63    unsafe {
64        DestroyEnvironmentBlock(raw_block);
65    }
66    Ok(copied)
67}
68
69#[cfg(windows)]
70unsafe fn copy_windows_environment_block(cursor: *const u16) -> Vec<u16> {
71    let mut len = 0usize;
72    loop {
73        if *cursor.add(len) == 0 && *cursor.add(len + 1) == 0 {
74            len += 2;
75            break;
76        }
77        len += 1;
78    }
79    std::slice::from_raw_parts(cursor, len).to_vec()
80}
81
82#[cfg(windows)]
83fn parse_windows_environment_block(block: &[u16]) -> Vec<(OsString, OsString)> {
84    use std::os::windows::ffi::OsStringExt;
85
86    let mut env = Vec::new();
87    let mut offset = 0usize;
88    while offset < block.len() && block[offset] != 0 {
89        let Some(relative_end) = block[offset..].iter().position(|value| *value == 0) else {
90            break;
91        };
92        let end = offset + relative_end;
93        let entry = &block[offset..end];
94        // Drive-current-directory pseudo variables have the shape
95        // `=C:=C:\path`; skip index zero so their second '=' is the
96        // key/value separator.
97        if let Some(separator) = entry
98            .iter()
99            .enumerate()
100            .skip(1)
101            .find_map(|(index, value)| (*value == b'=' as u16).then_some(index))
102        {
103            let key = OsString::from_wide(&entry[..separator]);
104            let value = OsString::from_wide(&entry[separator + 1..]);
105            env.push((key, value));
106        }
107        offset = end + 1;
108    }
109    env
110}
111
112#[cfg(all(test, windows))]
113mod tests {
114    use super::*;
115    use std::ffi::OsStr;
116    use std::os::windows::ffi::OsStrExt;
117
118    #[test]
119    fn parser_preserves_drive_current_directory_entries() {
120        let block: Vec<u16> = OsStr::new("=C:=C:\\work")
121            .encode_wide()
122            .chain(std::iter::once(0))
123            .chain(OsStr::new("Path=C:\\Windows").encode_wide())
124            .chain(std::iter::once(0))
125            .chain(std::iter::once(0))
126            .collect();
127        assert_eq!(
128            parse_windows_environment_block(&block),
129            vec![
130                (OsString::from("=C:"), OsString::from("C:\\work")),
131                (OsString::from("Path"), OsString::from("C:\\Windows")),
132            ]
133        );
134    }
135
136    #[test]
137    fn live_user_baseline_is_double_nul_terminated() {
138        let block = user_baseline_environment_block().unwrap();
139        assert!(block.len() >= 2);
140        assert_eq!(&block[block.len() - 2..], &[0, 0]);
141    }
142}